אובדן של 10 מיליון דולר כתוצאה מפרצת כניסה חוזרת (reentrancy) יכול היה להימנע באמצעות אימות פורמלי. אנו מיישמים הוכחה מתמטית לנכונות עבור פרוטוקולי DeFi קריטיים. ההבדל הוא קריטי: בדיקות מוצאות נוכחות של באגים, אימות מוכיח את היעדרם. MakerDAO, Aave ו-Compound משתמשות באימות פורמלי עבור רכיבים קריטיים. בואו נראה איך זה עובד בפועל.
אימות פורמלי הוא לא רק ביקורת—זו הוכחה מתמטית. הוא מבטיח שעבור כל נתוני קלט, החוזה מתנהג כראוי. לפי מחקר Certora, אימות פורמלי מכסה 100% מנתיבי הביצוע האפשריים, בעוד שבדיקות fuzz מכסות רק 60%—מה שהופך אותו ליעיל פי 1.67 בכיסוי נתיבים. למציאת פרצות כניסה חוזרת, הוא יעיל פי 5 מביקורת סטנדרטית.
למה אימות פורמלי אינו בדיקות
בדיקות בודקות תרחישים ספציפיים; אימות בודק את כל הקלטים האפשריים. Certora Prover מחפש דוגמה נגדית—קבוצת נתונים שבה טענה מופרת. אם לא נמצאה דוגמה נגדית בתוך זמן נתון (למשל, 20 שניות), המאפיין נחשב מוכח. זה מספק ערובה שבדיקות fuzz לא יכולות להשיג.
Certora Prover
Certora Prover הוא הכלי הנפוץ ביותר לחוזי EVM. הוא משתמש בשפת מפרט ייעודית, CVL (Certora Verification Language). הוא פועל כ-SaaS—העלו את החוזה והמפרט שלכם, קבלו תוצאות.
המפרט נכתב ב-CVL:
// Спецификация для ERC-20 transfer methods
{
function transfer(address, uint256) external returns (bool) envfree;
function balanceOf(address) external returns (uint256) envfree;
function totalSupply() external returns (uint256) envfree;
}
// Инвариант: сумма всех балансов = totalSupply
invariant totalSupplyIsSum(address a, address b)
a != b => balanceOf(a) + balanceOf(b) <= totalSupply();
// Правило: transfer уменьшает баланс отправителя
rule transferDecreasesBalance(address sender, address recipient, uint256 amount) {
require sender != recipient;
require balanceOf(sender) >= amount;
uint256 balanceBefore = balanceOf(sender);
env e;
require e.msg.sender == sender;
transfer(e, recipient, amount);
assert balanceOf(sender) == balanceBefore - amount;
}
// Правило: transfer никогда не создаёт токены из воздуха
rule noTokenCreation(method f, address a) {
uint256 totalBefore = totalSupply();
env e;
calldataarg args;
f(e, args);
assert totalSupply() <= totalBefore;
}Prover מנסה למצוא דוגמה נגדית. אם לא נמצאה, המפרט נחשב מוכח.
Solidity SMTChecker
מובנה במהדר Solidity, מבוסס על SMT (Satisfiability Modulo Theories). מופעל באמצעות pragma או דגלי מהדר:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// Включаем SMT проверку
/// @custom:smtchecker abstract-function-nondet
contract VaultVerified {
mapping(address => uint256) public balances;
uint256 public totalDeposited;
function deposit(uint256 amount) external {
require(amount > 0, "Zero amount");
balances[msg.sender] += amount;
totalDeposited += amount;
}
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
totalDeposited -= amount;
}
}הרצה באמצעות // Спецификация для ERC-20 transfer methods { function transfer(address, uint256) external returns (bool) envfree; function balanceOf(address) external returns (uint256) envfree; function totalSupply() external returns (uint256) envfree; } // Инвариант: сумма всех балансов = totalSupply invariant totalSupplyIsSum(address a, address b) a != b => balanceOf(a) + balanceOf(b) <= totalSupply(); // Правило: transfer уменьшает баланс отправителя rule transferDecreasesBalance(address sender, address recipient, uint256 amount) { require sender != recipient; require balanceOf(sender) >= amount; uint256 balanceBefore = balanceOf(sender); env e; require e.msg.sender == sender; transfer(e, recipient, amount); assert balanceOf(sender) == balanceBefore - amount; } // Правило: transfer никогда не создаёт токены из воздуха rule noTokenCreation(method f, address a) { uint256 totalBefore = totalSupply(); env e; calldataarg args; f(e, args); assert totalSupply() <= totalBefore; } בהגדרות Hardhat. SMTChecker בודק אוטומטית גלישות, גלישות תחתונות ואינווריאנטים.
Halmos — ביצוע סמלי עבור Foundry
Halmos מבצע ביצוע סמלי של בדיקות Foundry קיימות עבור כל נתוני הקלט האפשריים:
contract TestVault is Test {
Vault vault;
function setUp() public {
vault = new Vault(address(token));
}
function testFormal_depositWithdraw(
uint256 amount,
address caller
) public {
vm.assume(amount > 0 && amount < type(uint128).max);
vm.assume(caller != address(0));
deal(address(token), caller, amount);
vm.prank(caller);
token.approve(address(vault), amount);
vm.prank(caller);
vault.deposit(amount);
uint256 shares = vault.balanceOf(caller);
vm.prank(caller);
vault.withdraw(shares);
assertGe(token.balanceOf(caller), amount * 99 / 100);
}
} איך מפרט מונע פרצות
כלים הם רק אמצעים. העבודה העיקרית היא כתיבת המפרט. מפרט גרוע יוכיח שהחוזה נכון לפי דרישות שגויות. לכן אנו מתמקדים בפורמליזציה של הלוגיקה העסקית.
סוגי מאפיינים לאימות
מאפייני בטיחות ("דבר רע אף פעם לא קורה"):
- היתרה לעולם לא הופכת לשלילית
-
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; // Включаем SMT проверку /// @custom:smtchecker abstract-function-nondet contract VaultVerified { mapping(address => uint256) public balances; uint256 public totalDeposited; function deposit(uint256 amount) external { require(amount > 0, "Zero amount"); balances[msg.sender] += amount; totalDeposited += amount; } function withdraw(uint256 amount) external { require(balances[msg.sender] >= amount, "Insufficient balance"); balances[msg.sender] -= amount; totalDeposited -= amount; } }לעולם לא חורג מ-modelChecker - רק הבעלים יכול לקרוא ל-
contract TestVault is Test { Vault vault; function setUp() public { vault = new Vault(address(token)); } function testFormal_depositWithdraw( uint256 amount, address caller ) public { vm.assume(amount > 0 && amount < type(uint128).max); vm.assume(caller != address(0)); deal(address(token), caller, amount); vm.prank(caller); token.approve(address(vault), amount); vm.prank(caller); vault.deposit(amount); uint256 shares = vault.balanceOf(caller); vm.prank(caller); vault.withdraw(shares); assertGe(token.balanceOf(caller), amount * 99 / 100); } } - מנגנון הגנת הכניסה החוזרת פועל כראוי
מאפייני חיוניות ("דבר טוב בסופו של דבר קורה"):
- אם משתמש מפקיד כספים, הוא יכול למשוך אותם
- הצעות בסופו של דבר מבוצעות או נדחות
- מחזיק מניות מקבל בסופו של דבר תגמולים
אינווריאנטים ("תמיד נכון"):
- Σ יתרות = totalSupply (שימור טוקנים)
- lockedAmount <= totalDeposited
- מחיר האורקל תמיד > 0
דוגמת מפרט לפרוטוקול הלוואות
methods {
function deposit(uint256) external envfree;
function borrow(uint256) external envfree;
function repay(uint256) external envfree;
function liquidate(address) external;
function getHealthFactor(address) external returns (uint256) envfree;
function collateral(address) external returns (uint256) envfree;
function debt(address) external returns (uint256) envfree;
}
// Инвариант: нельзя ликвидировать здорового заёмщика
rule noLiquidationOfHealthyBorrower(address borrower) {
require getHealthFactor(borrower) >= 1e18;
env e;
liquidate@withrevert(e, borrower);
assert lastReverted, "Healthy borrower should not be liquidatable";
}
// Инвариант: сумма долгов не превышает сумму залогов
invariant solvencyInvariant(address user)
debt(user) * 100 <= collateral(user) * MAX_LTV_PERCENT
filtered { f -> !f.isView }
// Reentrancy: state не может измениться дважды в одной транзакции
rule noReentrancy(method f) {
uint256 collateralBefore = collateral(currentContract);
env e;
calldataarg args;
f(e, args);
uint256 collateralAfter = collateral(currentContract);
assert collateralAfter >= collateralBefore || collateralAfter <= collateralBefore;
} מה כלול בשירות שלנו
אנו מציעים מחזור מלא של אימות פורמלי לחוזה שלכם. התהליך שלנו מורכב מארבעה שלבים: (1) הגדרת דרישות, (2) כתיבת כללי CVL, (3) אימות איטרטיבי, ו-(4) הכנת דוח. כתוצאה מכך, אתם מקבלים:
- תיעוד בצורת מפרט CVL עבור כל המאפיינים הקריטיים
- ביצוע Certora Prover (או Halmos) עם דוח מפורט
- רשימת מאפיינים מאומתים וכל הפרות שנמצאו
- תמיכה בתיקון דוגמאות נגדיות
- ערובה שהמאפיינים מוכחים מתמטית
הניסיון שלנו: 5+ שנים בפיתוח בלוקצ'יין, 15+ ביקורות חוזים חכמים, 3 פרוטוקולים מאומתים עם TVL > 200 מיליון דולר. צרו קשר כדי להעריך את הפרויקט שלכם. עלות טיפוסית: 10,000–50,000 דולר בהתאם למורכבות, ולעתים קרובות חוסכת מיליונים בהפסדים פוטנציאליים. הזמינו אימות פורמלי תוך 4–8 שבועות.
מגבלות של אימות פורמלי
אימות פורמלי אינו פתרון קסם:
- פער שלמות: רק מה שמוגדר מאומת. אם תוקף מוצא וקטור שאינו מכוסה על ידי המפרט, האימות לא יתפוס אותו.
- מדרגיות: חוזים גדולים (>1000 שורות) קשים לאימות מלא. פתרון: אמתו רכיבים קריטיים בנפרד.
- הנחות אורקל: אם החוזה משתמש באורקל, האימות מניח שהאורקל מחזיר נתונים נכונים.
- קריאות חיצוניות: אינטראקציות עם חוזים חיצוניים קשות להגדרה מלאה.
השוואת שיטות אימות
| סוג בדיקה | מה היא מוצאת | עלות | זמן |
|---|---|---|---|
| בדיקות יחידה | תרחישים ספציפיים | נמוכה | 1–2 שבועות |
| בדיקות fuzz | נתוני קלט אקראיים | נמוכה | שבוע אחד |
| ביקורת ידנית | שגיאות לוגיות | בינונית | 2–4 שבועות |
| אימות פורמלי | הוכחה מתמטית | גבוהה (10k–50k דולר) | 4–8 שבועות |
השוואת כלי אימות פורמלי
| כלי | סוג | שפת מפרט | קלות אימוץ | כיסוי |
|---|---|---|---|---|
| Certora Prover | בדיקת מודלים | CVL | בינונית | מלא עבור EVM |
| SMTChecker | פתרון SMT | הערות Solidity | נמוכה | אוטומטי |
| Halmos | ביצוע סמלי | בדיקות Foundry | בינונית | תלוי בבדיקות |
אימות פורמלי אינו מחליף ביקורת ידנית—הם משלימים זה את זה. ביקורת ידנית מוצאת שגיאות לוגיות בלוגיקה העסקית; אימות מוכיח נכונות של מאפיינים מתמטיים.
שאלות נפוצות (לחצו להרחבה)
- אימות פורמלי שונה מביקורת רגילה בכך שהוא מוכיח היעדר באגים, לא רק מוצא אותם. עבור חוזים קריטיים (למשל, פרוטוקולי הלוואות), זו הדרך היחידה להבטיח שאין פרצות נסתרות עבור כל נתוני קלט.
- אימות מלא אורך בדרך כלל 4 עד 8 שבועות בהתאם למורכבות הפרוטוקול, כולל הגדרת דרישות, כתיבת כללי CVL, אימות איטרטיבי והכנת דוח.
- הכלים בהם משתמשים כוללים Certora Prover לחוזי EVM, Halmos לביצוע סמלי, ו-SMTChecker המובנה ב-Solidity. הבחירה תלויה בגודל החוזה ובעומק הנדרש.
- חוזים שכבר נפרסו יכולים להיות מאומתים אם קוד המקור זמין; עם זאת, תיקון שגיאות לאחר פריסה דורש שדרוג פרוקסי או העברה, ולכן מומלץ לאמת לפני הפריסה.
- הערבויות כוללות הוכחה מתמטית עבור כל המאפיינים המוגדרים. אם נמצאה שגיאה לאחר האימות, המפרט מתוקן והנכונות מוכחת מחדש ללא עלות נוספת. לצוות יש 5+ שנות ניסיון בפיתוח בלוקצ'יין ו-15+ ביקורות מוצלחות.
צרו קשר כדי לדון בפרויקט שלכם. עם רקורד של 15+ ביקורות מוצלחות ו-5+ שנים באבטחת בלוקצ'יין, אנו מספקים הוכחה מתמטית לאבטחת החוזה החכם שלכם.







