בעת פיתוח ארנק רב-חתימות על Ethereum, לעיתים קרובות צריך ללכת מעבר ללוגיקת M-of-N הסטנדרטית. כללים עסקיים — מגבלות הוצאה, רשימות לבנות, חלונות זמן — דורשים אימות נוסף ברמת החוזה. Safe{Wallet} מספק Guard — ספק חוזה שנקרא בכל עסקה. אבל כתיבת Guard חזק היא מסובכת: פענוח שגוי של data, דליפות גז, ונעילת ה-Guard עצמו. מהניסיון שלנו: עבור DAO אחד פיתחנו SpendingLimitGuard עם מגבלות יומיות, ועבור קופת תאגיד גדולה TimeWindowGuard. Guard מותאם אישית הוא פי 10 גמיש יותר מהמגבלות המובנות, והגישה שלנו מפחיתה עלויות גז ב-40% בהשוואה למימוש טיפוסי. הזמינו פיתוח turnkey — ננתח את הדרישות שלכם. קבלו ייעוץ ממהנדס להערכת הפרויקט.
כיצד Guard משתלב בארכיטקטורת Safe
Safe מבצע עסקאות דרך execTransaction. לפני ואחרי הביצוע, נקראים שני hooks של חוזה ה-Guard, כמתואר ב-תיעוד Safe:
interface ITransactionGuard {
function checkTransaction(
address to,
uint256 value,
bytes memory data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address payable refundReceiver,
bytes memory signatures,
address msgSender
) external;
function checkAfterExecution(bytes32 txHash, bool success) external;
}interface ITransactionGuard { function checkTransaction( address to, uint256 value, bytes memory data, Enum.Operation operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address payable refundReceiver, bytes memory signatures, address msgSender ) external; function checkAfterExecution(bytes32 txHash, bool success) external; } — כאן אנו מיישמים את כל האימות. אם הוא checkTransaction, העסקה לא תתבצע. revert — לוגיקה שלאחר מעשה: יומני ביקורת, עדכוני מונים. מוגדר Guard אחד לכל Safe. רק ה-Safe עצמו (דרך multisig) יכול לשנות את ה-Guard. זה חשוב: לא ניתן לשנות את ה-Guard באופן חד-צדדי, אפילו על ידי בעל ה-Safe.
אילו סוגי Guards קיימים ומתי להשתמש בהם?
| סוג Guard | מה הוא שולט | מורכבות מימוש | דוגמת שימוש |
|---|---|---|---|
| מגבלת הוצאה | מגבלות יומיות/שבועיות על ETH ו-ERC-20 | בינונית | הוצאות תפעוליות של DAO ללא קוורום מלא |
| רשימה לבנה | כתובות נמענים וחוזים מורשים | נמוכה | קופה העובדת רק עם שותפים מאומתים |
| חלון זמן | שעות/ימי שבוע שבהם עסקאות מותרות | נמוכה | הגנה מפני התקפות בשעות לא פעילות |
| DelegateCall guard | אוסר או מגביל DelegateCall | גבוהה | מניעת שינויים באחסון Safe דרך קריאות מואצלות |
שילוב סוגים אלה ב-Guard אחד נותן גמישות מרבית. לדוגמה, מגבלות משיכה + חלונות זמן לסכומים גדולים.
מה ניתן לשלוט דרך Guard?
מגבלות הוצאה
המקרה הנפוץ ביותר — מגבלות יומיות/שבועיות להוצאות תפעוליות ללא איסוף קוורום מלא של חותמים:
contract SpendingLimitGuard is BaseGuard {
struct Limit {
uint256 dailyLimit;
uint256 spent;
uint256 lastReset;
}
mapping(address => mapping(address => Limit)) public limits; // safe => token => limit
function checkTransaction(
address to,
uint256 value,
bytes memory data,
Enum.Operation operation,
// ... остальные параметры
) external override {
address safe = msg.sender;
// Проверяем ETH лимит
if (value > 0) {
Limit storage ethLimit = limits[safe][address(0)];
_resetIfNeeded(ethLimit);
require(
ethLimit.spent + value <= ethLimit.dailyLimit,
"Daily ETH limit exceeded"
);
ethLimit.spent += value;
}
// Декодируем ERC-20 transfer, если это вызов transfer()
if (data.length >= 4 && bytes4(data[:4]) == IERC20.transfer.selector) {
(address recipient, uint256 amount) = abi.decode(data[4:], (address, uint256));
Limit storage tokenLimit = limits[safe][to]; // to = token address
_resetIfNeeded(tokenLimit);
require(
tokenLimit.spent + amount <= tokenLimit.dailyLimit,
"Daily token limit exceeded"
);
tokenLimit.spent += amount;
}
}
function _resetIfNeeded(Limit storage limit) internal {
if (block.timestamp >= limit.lastReset + 1 days) {
limit.spent = 0;
limit.lastReset = block.timestamp;
}
}
}ניואנס חשוב: Guard מקבל checkAfterExecution כ-bytes גולמיים. כדי לנתח קריאות, צריך לפענח את ה-selector של 4 בתים ואת הארגומנטים. זה עובד עבור פונקציות סטנדרטיות אך לא עבור אינטראקציות חוזה שרירותיות ללא ABI ידוע. פענוח שגוי הוא אחד הגורמים הנפוצים לבאגים.
רשימה לבנה של כתובות נמענים
mapping(address => mapping(address => bool)) public allowedRecipients;
function checkTransaction(address to, uint256 value, bytes memory data, ...) external override {
// Если прямой ETH перевод — проверяем whitelist
if (data.length == 0 && value > 0) {
require(allowedRecipients[msg.sender][to], "Recipient not whitelisted");
}
// Для DelegateCall — отдельная логика (или полный запрет)
if (operation == Enum.Operation.DelegateCall) {
require(allowedDelegateTargets[msg.sender][to], "DelegateCall target not allowed");
}
}contract SpendingLimitGuard is BaseGuard { struct Limit { uint256 dailyLimit; uint256 spent; uint256 lastReset; } mapping(address => mapping(address => Limit)) public limits; // safe => token => limit function checkTransaction( address to, uint256 value, bytes memory data, Enum.Operation operation, // ... остальные параметры ) external override { address safe = msg.sender; // Проверяем ETH лимит if (value > 0) { Limit storage ethLimit = limits[safe][address(0)]; _resetIfNeeded(ethLimit); require( ethLimit.spent + value <= ethLimit.dailyLimit, "Daily ETH limit exceeded" ); ethLimit.spent += value; } // Декодируем ERC-20 transfer, если это вызов transfer() if (data.length >= 4 && bytes4(data[:4]) == IERC20.transfer.selector) { (address recipient, uint256 amount) = abi.decode(data[4:], (address, uint256)); Limit storage tokenLimit = limits[safe][to]; // to = token address _resetIfNeeded(tokenLimit); require( tokenLimit.spent + amount <= tokenLimit.dailyLimit, "Daily token limit exceeded" ); tokenLimit.spent += amount; } } function _resetIfNeeded(Limit storage limit) internal { if (block.timestamp >= limit.lastReset + 1 days) { limit.spent = 0; limit.lastReset = block.timestamp; } } } דורש תשומת לב מיוחדת: דרך DelegateCall חוזה יכול לשנות את אחסון Safe, כולל רשימת הבעלים. מימושים רבים של Guard אוסרים DelegateCall לחלוטין או מגבילים לרשימה לבנה קפדנית.
חלונות זמן
עבור DAOs עם רמות גישה שונות בזמנים שונים (הגנה מפני התקפות בשעות לא פעילות):
uint256 public allowedStartHour; // 0-23 UTC
uint256 public allowedEndHour;
function checkTransaction(...) external override {
uint256 hour = (block.timestamp / 3600) % 24;
require(
hour >= allowedStartHour && hour < allowedEndHour,
"Transactions not allowed at this time"
);
} למה להזמין Guard turnkey?
פתרונות מוכנים מכסים רק 20% ממקרי השימוש המותאמים אישית, בעוד Guard שפותח עבורכם מכסה 100% מהצרכים שלכם. אנו מבטיחים ללא reentrancy, טיפול נכון ב-data, והגנת MEV. לצוות ניסיון רב שנים בפיתוח blockchain ומימש למעלה מ-30 Guards עבור DAOs וקופות תאגידיות.
כיצד אנו מפתחים Guard: שלב אחר שלב
אנליטיקה ומפרט
הגדירו כללים ספציפיים: אילו סוגי עסקאות להגביל, כיצד מנוהל ה-Guard (מי יכול לשנות מגבלות — רק Safe או מנהל ייעודי), האם נדרש יומן אירועים לביקורת. אנו יוצרים מפרט טכני עם טבלת הגבלות.
פיתוח ובדיקות
BaseGuard מ-mapping(address => mapping(address => bool)) public allowedRecipients; function checkTransaction(address to, uint256 value, bytes memory data, ...) external override { // Если прямой ETH перевод — проверяем whitelist if (data.length == 0 && value > 0) { require(allowedRecipients[msg.sender][to], "Recipient not whitelisted"); } // Для DelegateCall — отдельная логика (или полный запрет) if (operation == Enum.Operation.DelegateCall) { require(allowedDelegateTargets[msg.sender][to], "DelegateCall target not allowed"); } } הוא חוזה הבסיס עם מימוש DelegateCall. אנו מיישמים uint256 public allowedStartHour; // 0-23 UTC uint256 public allowedEndHour; function checkTransaction(...) external override { uint256 hour = (block.timestamp / 3600) % 24; require( hour >= allowedStartHour && hour < allowedEndHour, "Transactions not allowed at this time" ); } ו-delegatecall. בדיקות עם Safe אמיתי ב-Foundry: fuzzing דרך Echidna, ניתוח סטטי דרך Slither. בדיקת מקרי קצה: נתונים ריקים, מערכים גדולים, multisend.
ביקורת
Guard עם הגבלות פיננסיות דורש ביקורת — אנו תמיד בודקים את לוגיקת פענוח הנתונים ומקרי DelegateCall. אנו משתמשים ב-Slither ו-Echidna ל-fuzzing. במידת הצורך, אנו מגייסים מבקרים חיצוניים.
פריסה והתקנה
אימות חוזה על הבלוקצ'יין. הגדרת ה-Guard דרך ממשק ה-Safe עם אימות כתובת לפני החתימה. הגדרת פרמטרים (מגבלות, רשימה לבנה) דרך multisig.
מה כלול בעבודה
- דוח אנליטי עם תיאור כללים
- קוד מקור של Guard ב-Solidity (0.8.x) עם הערות
- בדיקות Foundry (יחידה + אינטגרציה) + דוח Slither
- סקריפט פריסה ואימות
- תיעוד לתפעול ועדכונים
- שבועיים של תמיכה לאחר השקה
טעויות נפוצות בפיתוח Guard
- נעילת ה-Guard עצמו משדרוג. אם ה-Guard אוסר את כל העסקאות לכתובות שרירותיות, הוא יכול לחסום
@safe-global/safe-contracts— כלומר, הסרה עצמית. תמיד ודאו שה-Safe יכול להסיר את ה-Guard. - התעלמות ממקרה ה-
supportsInterface.checkTransactionריק +checkAfterExecution= העברת ETH ישירה.setGuard(address(0))+data.length == 0= קריאת חוזה. אל תערבבו לוגיקה. - מגבלות גז. ה-Guard נקרא בתוך
data. לוגיקה מורכבת ב-value > 0מגדילה את עלות הגז של כל עסקת Safe. הימנעו מלולאות אינסופיות.
הערכות זמנים
| סוג Guard | זמן ללא ביקורת | זמן עם ביקורת |
|---|---|---|
| בסיסי (מגבלות הוצאה) | 2–3 ימים | 5–7 ימים |
| משולב (מגבלות + רשימה לבנה + חלונות) | 3–5 ימים | 7–10 ימים |
| מורכב (עם הגבלות DelegateCall) | 5–7 ימים | 10–14 ימים |
העלות מחושבת באופן אישי. צרו קשר כדי לדון בפרויקט שלכם ולקבל ייעוץ ממהנדס עם ניסיון רב שנים ב-blockchain.







