داشبورد QA نظارت بر وضعیت قرارداد هوشمند  پست قبلی یک پیاده‌سازی کامل را بررسی کرد: یک قرارداد توکن حداقلی، بازسازی وضعیت خارج از زنجیرهداشبورد QA نظارت بر وضعیت قرارداد هوشمند  پست قبلی یک پیاده‌سازی کامل را بررسی کرد: یک قرارداد توکن حداقلی، بازسازی وضعیت خارج از زنجیره

وضعیت حساب اتریوم: خط لوله تضمین کیفیت برای یک توکن حداقلی

2026/04/09 13:48
مدت مطالعه: 9 دقیقه
برای ارائه بازخورد یا طرح هرگونه نگرانی درباره این محتوا، لطفاً با ما از طریق crypto.news@mexc.com تماس بگیرید.
داشبورد QA نظارت بر وضعیت قرارداد هوشمند

پست قبلی یک پیاده‌سازی انتها به انتها را شرح داد: یک قرارداد توکن حداقلی، بازسازی وضعیت خارج از زنجیره، و یک رابط کاربری React - تمام مسیر از `mint()` تا MetaMask. این پست از جایی که آن متوقف شد ادامه می‌دهد: چگونه چیزی مانند این را QA می‌کنید؟

من یک مهندس بلاکچین نیستم (هنوز)، اما الگوهای QA به خوبی در حوزه‌های مختلف قابل انتقال هستند، و قرض گرفتن آنچه در جای دیگری کار می‌کند، روشی است که من سریع‌ترین یادگیری را دارم.

قرارداد فقط سه کار انجام می‌دهد: `mint`، `transfer`، و `burn`، اما حتی این برای تمرین کامل زنجیره ابزار QA کافی است: تحلیل استاتیک، تست جهش، پروفایلینگ گس، تایید رسمی.

کد در `egpivo/ethereum-account-state` است.

هرم QA بلاک چین: از تحلیل استاتیک در پایه تا تایید رسمی در بالا

چیزی که با آن شروع کردیم

قبل از اضافه کردن هر چیز جدیدی، پروژه قبلاً داشت:

  • 21 تست واحد Foundry که هر انتقال وضعیت را پوشش می‌دهد (موفقیت، برگشت در ورودی غیرقانونی، انتشار رویداد)
  • 3 تست ثابت از طریق یک `TokenHandler` که دنباله‌های تصادفی `mint`/`transfer`/`burn` را روی 10 بازیگر اجرا می‌کند (هر کدام 128 هزار فراخوانی)
  • تست‌های Fuzz که `sum(balances) == totalSupply` را برای مقادیر تصادفی بررسی می‌کنند
  • تست‌های دامنه TypeScript (Vitest) که ماشین وضعیت درون زنجیره را منعکس می‌کند
  • CI: کامپایل، تست، لینت (Prettier + solhint)

همه تست‌ها قبول شدند. پوشش خوب به نظر می‌رسید. پس چرا زحمت بیشتر؟

زیرا "همه تست‌ها قبول شدند" به معنای "همه باگ‌ها گرفته شدند" نیست. پوشش 100% خط هنوز هم می‌تواند یک باگ واقعی را از دست بدهد اگر هیچ assertion چیز درستی را بررسی نکند.

فاز 1: تحلیل استاتیک قرارداد هوشمند و پوشش

Slither

Slither(Trail of Bits) مشکلاتی را می‌گیرد که برای تست‌ها نامرئی هستند: reentrancy، مقادیر برگشتی بررسی نشده، عدم تطابق رابط.

./scripts/run-qa.sh slither

نتیجه: 1 یافته متوسط: `erc20-interface`: `transfer()` مقدار `bool` برنمی‌گرداند.

این مورد انتظار می‌رود. قرارداد عمداً یک ERC20 کامل نیست: این یک ماشین وضعیت آموزشی است. اما این یافته آکادمیک نیست:

اگر بعداً کسی این توکن را در یک پروتکل وارد کند که انتظار ERC20 دارد، عدم تطابق رابط به صورت خاموش شکست می‌خورد. Slither اکنون آن را علامت‌گذاری می‌کند تا تصمیم آگاهانه باشد.

پوشش

./scripts/run-qa.sh coverage نتیجه پوشش.

یک تابع پوشش داده نشده: `BalanceLib.gt()`. ما به این بازمی‌گردیم.

خروجی forge coverage: 24 تست قبول شد، جدول پوشش Token.sol

عکس‌های فوری گس

./scripts/run-qa.sh gas

هزینه‌های گس پایه برای سه عملیات:

گس بر حسب عملیات

در اجراهای بعدی، `forge snapshot — diff` با خط پایه مقایسه می‌کند. یک رگرسیون 20% گس در `transfer()` هزینه واقعی برای هر کاربر است - گرفتن آن قبل از merge ارزان است.

فاز 2: تست جهش و تایید رسمی

تست جهش (Gambit)

اینجا جایی است که موضوع جالب شد. Gambit(Certora) جهش‌یافته‌ها تولید می‌کند: نسخه‌هایی از `Token.sol` با باگ‌های کوچک عمدی (`+=` به `-=`، `>=` به `>`، شرایط نفی شده). خط لوله مجموعه کامل تست را در برابر هر جهش‌یافته اجرا می‌کند. اگر یک جهش‌یافته زنده بماند (همه تست‌ها هنوز قبول می‌شوند)، آن یک شکاف تست مشخص است.

./scripts/run-qa.sh mutation

نتیجه: امتیاز جهش 97.0% - 32 کشته شده، 1 زنده مانده از 33 جهش‌یافته.

لاگ خروجی Gambit هر جهش‌یافته و آنچه تغییر داده را نشان می‌دهد. چند مثال:

Generated mutant #7: BinaryOpMutation — Token.sol:168
totalSupply = totalSupply.add(amountBalance) → totalSupply = totalSupply.sub(amountBalance)
KILLED by test_Mint_Success
Generated mutant #19: RelationalOpMutation — Token.sol:196
if (!fromBalance.gte(amountBalance)) → if (fromBalance.gte(amountBalance))
KILLED by test_Transfer_Success
Generated mutant #28: SwapArgumentsMutation — Token.sol:81
return Balance.unwrap(a) > Balance.unwrap(b) → return Balance.unwrap(b) > Balance.unwrap(a)
SURVIVED ← هیچ تستی این را نگرفت تست جهش Gambit: 32 کشته شده، 1 زنده مانده، امتیاز جهش 97.0%

جهش‌یافته زنده مانده `a > b` را به `b > a` در `BalanceLib.gt()` تعویض کرد. هیچ تستی آن را نگرفت زیرا `gt()` کد مرده است. هرگز در هیچ‌جای `Token.sol` فراخوانی نمی‌شود.

پوشش 91.67% توابع را علامت‌گذاری کرد اما نتوانست شکاف را توضیح دهد. تست جهش این کار را کرد: `gt()` کد مرده است، هیچ چیز آن را فراخوانی نمی‌کند، و هیچ‌کس متوجه نمی‌شود اگر اشتباه باشد.

کد مرده یا محافظت نشده در قراردادهای هوشمند سابقه واقعی دارد.

تابع قرار نبود قابل فراخوانی باشد، اما هیچ‌کس آن فرض را تست نکرد. `gt()` ما در مقایسه بی‌ضرر است، اما الگو یکسان است: کدی که وجود دارد اما هرگز اجرا نمی‌شود، کدی است که هیچ‌کس نظارت نمی‌کند.

تایید رسمی (Halmos)

Halmos(a16z) در مورد همه ورودی‌های ممکن به صورت نمادین استدلال می‌کند. جایی که تست‌های fuzz مقادیر تصادفی را نمونه‌برداری می‌کنند و امیدوارند به موارد لبه برخورد کنند، Halmos ویژگی‌ها را به طور کامل اثبات می‌کند.

./scripts/run-qa.sh halmos

نتیجه: 9/9 تست نمادین قبول شد - همه ویژگی‌ها برای همه ورودی‌ها اثبات شد.

ویژگی‌های تایید شده:

ویژگی‌های تایید شده

یک نکته عملی: Halmos 0.3.3 از `vm.expectRevert()` پشتیبانی نمی‌کند، بنابراین نتوانستم تست‌های revert را به روش معمولی Foundry بنویسم. راه‌حل یک الگوی try/catch است - اگر فراخوانی موفق شود وقتی باید برگردد، `assert(false)` اثبات را شکست می‌دهد:

function check_mint_reverts_on_zero_address(uint256 amount) public {
vm.assume(amount > 0);
try token.mint(address(0), amount) {
assert(false); // نباید به اینجا برسد
} catch {
// برگشت مورد انتظار - Halmos اثبات می‌کند این مسیر همیشه گرفته می‌شود
}
}

زیباترین نیست، اما کار می‌کند - Halmos هنوز ویژگی را برای همه ورودی‌ها اثبات می‌کند. این نوع چیزی است که فقط با اجرای واقعی ابزار پیدا می‌کنید.

برای زمینه اینکه چرا تایید رسمی مهم است:

آسیب‌پذیری در کد بود، توسط هرکسی قابل بررسی، اما هیچ ابزار یا تستی قبل از استقرار آن را نگرفت. اثبات‌کننده‌های نمادین مانند Halmos دقیقاً برای بستن آن شکاف وجود دارند - آن‌ها نمونه‌برداری نمی‌کنند؛ فضای ورودی را تخلیه می‌کنند.

خروجی Halmos: 9 تست قبول شد، 0 شکست خورد، نتایج تست نمادین

فایل تست `contracts/test/Token.halmos.t.sol` است.

فاز 3: تست ویژگی بین لایه‌ای

معماری پست اول یک لایه دامنه TypeScript دارد که ماشین وضعیت درون زنجیره را منعکس می‌کند. این فاز تست می‌کند که آیا این دو واقعاً موافق هستند.

تست مبتنی بر ویژگی با fast-check

من تست‌های ویژگی fast-check را برای لایه دامنه TypeScript اضافه کردم، که آنچه فازر Foundry برای Solidity انجام می‌دهد را منعکس می‌کند:

npm test - tests/unit/property.test.ts

نتیجه: 9/9 تست ویژگی قبول شد پس از رفع یک باگ واقعی.

ویژگی‌های تست شده:

  • `Balance`: تعویض‌پذیری، شرکت‌پذیری، هویت، معکوس، سازگاری مقایسه
  • `Token`: ثابت `sum(balances) == totalSupply` تحت دنباله‌های عملیات تصادفی (200 اجرا، هر کدام 50 عملیات)
  • `Token`: `totalSupply` غیر منفی پس از دنباله‌های تصادفی
  • `mint` همیشه برای ورودی‌های معتبر موفق می‌شود
  • `transfer` حفظ می‌کند `totalSupply`

باگی که fast-check پیدا کرد

fast-check یک باگ سازگاری بین لایه‌ای واقعی در `Token.ts` `transfer()` پیدا کرد. مثال متقابل کوچک شده بلافاصله واضح بود:

Property failed after 3 tests
Shrunk 2 time(s)
Counterexample: transfer(from=0xaaa…, to=0xaaa…, amount=1n)
→ from == to (انتقال خودکار)
→ verifyInvariant() برگرداند false

انتقال خودکار (`from == to`) ثابت `sum(balances) == totalSupply` را شکست. `toBalance` قبل از به‌روزرسانی `fromBalance` خوانده شد، بنابراین وقتی `from == to`، مقدار قدیمی کسر را بازنویسی کرد:

// قبل (باگ‌دار)
const fromBalance = this.getBalance(from);
const toBalance = this.getBalance(to); // ← قدیمی وقتی from == to
this.accounts.set(from.getValue(), fromBalance.subtract(amount));
this.accounts.set(to.getValue(), toBalance.add(amount)); // ← تفریق را بازنویسی می‌کند

رفع: خواندن `toBalance` پس از نوشتن `fromBalance`، مطابق با معنایی ذخیره‌سازی Solidity:

// بعد (رفع شده)
const fromBalance = this.getBalance(from);
this.accounts.set(from.getValue(), fromBalance.subtract(amount));
const toBalance = this.getBalance(to); // ← اکنون مقدار به‌روز شده را می‌خواند
this.accounts.set(to.getValue(), toBalance.add(amount));

قرارداد Solidity تحت تاثیر نبود: پس از هر نوشتن، ذخیره‌سازی را دوباره می‌خواند. اما آینه TypeScript یک وابستگی ترتیب ظریف داشت که هیچ تست واحد موجودی پوشش نمی‌داد.

عدم تطابق بین لایه‌ای در مقیاس بزرگتر فاجعه‌بار بوده است.

باگ انتقال خودکار ما پول کسی را از دست نمی‌داد، اما حالت شکست از نظر ساختاری یکسان است: دو لایه که قرار است موافق باشند، نیستند.

مشکلاتی که در طول مسیر برخورد کردیم

اجرای ابزارهای QA روی یک پروژه موجود هرگز فقط "نصب و اجرا" نیست. چند چیز قبل از اینکه کار کنند شکستند:

  • پوشش 0% چون `foundry.toml` مسیر تست نداشت: اولین اجرای `forge coverage` در همه جا 0% برگرداند. معلوم شد `foundry.toml` مشخص نکرده بود `test = "contracts/test"` یا `script = "contracts/script"`، بنابراین Forge هیچ تستی را کشف نمی‌کرد. دستور پوشش به صورت خاموش موفق شد - فقط چیزی برای پوشش نداشت. این گمراه‌کننده‌ترین شکست بود: یک اجرای سبز بدون خروجی مفید.
  • ایمپورت `InvariantTest` در forge-std v1.14.0 حذف شد: `Invariant.t.sol` مورد `InvariantTest` را از `forge-std` ایمپورت کرد، که در یک انتشار اخیر حذف شد. کامپایل با خطای مبهم "نماد پیدا نشد" شکست خورد. رفع حذف ایمپورت بود - `Test` به تنهایی برای تست ثابت Foundry اکنون کافی است.
  • `uint256(token.totalSupply())` در مقابل `Balance.unwrap()`: تست‌ها از یک cast صریح برای استخراج `uint256` زیربنایی از نوع `Balance` تعریف شده توسط کاربر استفاده می‌کردند. کامپایل شد، اما اصطلاح اشتباه است - `Balance.unwrap(token.totalSupply())` همان چیزی است که سیستم UDVT برای آن طراحی شده است. در `Token.t.sol`، `Invariant.t.sol`، و `DeploySepolia.s.sol` اعمال شد.

طراحی خط لوله

همه چیز از طریق دو اسکریپت اجرا می‌شود:

  • scripts/setup-qa-tools.sh`: نصب Slither، Halmos، Gambit (idempotent)
  • `scripts/run-qa.sh`: بررسی‌ها را اجرا می‌کند، نتایج با برچسب زمانی را در `qa-results/` ذخیره می‌کند

./scripts/run-qa.sh slither gas # فقط تحلیل استاتیک + گس
./scripts/run-qa.sh mutation # فقط تست جهش
./scripts/run-qa.sh all # همه چیز

هر بررسی سریع نیست. Slither و پوشش در هر commit اجرا می‌شوند. تست جهش و Halmos کندتر هستند - برای اجراهای هفتگی یا قبل از انتشار مناسب‌تر هستند.

خلاصه

زنجیره ابزار QA بلاکچین: آنچه هر لایه می‌گیرد - از تحلیل استاتیک تا تست ویژگی بین لایه‌ای

پنج لایه QA، هر کدام یک کلاس مختلف از مشکل را می‌گیرند.

توضیح لایه

Gambit و fast-check در این دور قابل اقدام‌ترین نتایج را دادند.

خط لوله CI

بررسی‌های QA اکنون به GitHub Actions به عنوان یک خط لوله شش مرحله‌ای متصل شده‌اند:

خط لوله CI: Build و Lint به مراحل Test، Coverage، Gas، Slither، و Audit منتشر می‌شود

خط لوله GitHub Actions: Build و Lint همه مراحل پایین‌دست را دروازه می‌کند.

توضیح مرحله

منابع

  • منبع Ethereum Account State: [github.com/egpivo/ethereum-account-state](https://github.com/egpivo/ethereum-account-state)
  • پست قبلی: Ethereum Account State
  • Slither: github.com/crytic/slither
  • Gambit: github.com/Certora/gambit
  • Halmos: github.com/a16z/halmos
  • fast-check: github.com/dubzzz/fast-check
  • Foundry: getfoundry.sh

یادداشت‌ها

  • این پست از پست وبلاگ اصلی من اقتباس شده است.

Ethereum Account State: QA Pipeline for a Minimal Token در ابتدا در Coinmonks در Medium منتشر شد، جایی که مردم با برجسته کردن و پاسخ دادن به این داستان به گفتگو ادامه می‌دهند.

سلب مسئولیت: مطالب بازنشرشده در این وب‌ سایت از منابع عمومی گردآوری شده‌ اند و صرفاً به‌ منظور اطلاع‌ رسانی ارائه می‌ شوند. این مطالب لزوماً بازتاب‌ دهنده دیدگاه‌ ها یا مواضع MEXC نیستند. کلیه حقوق مادی و معنوی آثار متعلق به نویسندگان اصلی است. در صورت مشاهده هرگونه محتوای ناقض حقوق اشخاص ثالث، لطفاً از طریق آدرس ایمیل crypto.news@mexc.com با ما تماس بگیرید تا مورد بررسی و حذف قرار گیرد.MEXC هیچ‌ گونه تضمینی نسبت به دقت، جامعیت یا به‌ روزبودن اطلاعات ارائه‌ شده ندارد و مسئولیتی در قبال هرگونه اقدام یا تصمیم‌ گیری مبتنی بر این اطلاعات نمی‌ پذیرد. همچنین، محتوای منتشرشده نباید به‌عنوان توصیه مالی، حقوقی یا حرفه‌ ای تلقی شود و به منزله پیشنهاد یا تأیید رسمی از سوی MEXC نیست.

محتوای پیشنهادی

عصر هوش مصنوعی عاملی نیازمند نقش‌های ورودی جدید است. آیا شرکت‌های SA در حال ایجاد آن‌ها هستند؟

عصر هوش مصنوعی عاملی نیازمند نقش‌های ورودی جدید است. آیا شرکت‌های SA در حال ایجاد آن‌ها هستند؟

با توجه به تمام حدس و گمان‌ها درباره حذف مشاغل سطح ابتدایی توسط هوش مصنوعی، جای تعجب نیست که جوانان آفریقای جنوبی نسبت به شروع حرفه خود نگران هستند. این اضطراب
اشتراک
TechFinancials2026/04/09 16:00
تجارت کشاورزی آفریقای جنوبی با خطرات خاورمیانه روبرو است

تجارت کشاورزی آفریقای جنوبی با خطرات خاورمیانه روبرو است

تجارت کشاورزی آفریقای جنوبی با خطرات فزاینده درگیری خاورمیانه روبرو است که بر صادرات منطقه‌ای به ارزش ۱.۳ میلیارد دلار تأثیر می‌گذارد تجارت کشاورزی آفریقای جنوبی با خاورمیانه روبرو است
اشتراک
Furtherafrica2026/04/09 15:28
سهام SoundHound AI (SOUN) در میان رشد گسترده‌تر فناوری به دلیل تحولات ژئوپلیتیکی افزایش می‌یابد

سهام SoundHound AI (SOUN) در میان رشد گسترده‌تر فناوری به دلیل تحولات ژئوپلیتیکی افزایش می‌یابد

سهام SoundHound AI (SOUN) روز چهارشنبه در میان بازگشت فناوری 1.3% افزایش یافت و به 6.79 دلار رسید. درآمد 59% نسبت به سال قبل افزایش یافت، اما فروش سهام توسط اشخاص داخلی و خروج مدیر مالی احساسات را تعدیل کرد. این پست
اشتراک
Blockonomi2026/04/09 16:25

$30,000 در PRL و 15,000 USDT

$30,000 در PRL و 15,000 USDT$30,000 در PRL و 15,000 USDT

واریز و معامله PRL برای افزایش جوایز خود!