Safe{Wallet} модулі: коли мультисіг потребує автоматизації
Safe (колишній Gnosis Safe) — стандарт мультисіг-гаманця в Web3. На ньому зберігається понад $100B в активах DAO, протоколів та корпоративних скарбниць. Архітектура Safe навмисно мінімалістична в ядрі, але розширюється через модулі — це контракти, яким власник Safe делегує право виконувати транзакції без стандартного порогу підписів. Документація Safe визначає модулі як «контракти, які можуть виконувати транзакції від імені Safe, минаючи необхідний поріг підписів».
Зазначимо: коли команді DAO потрібно автоматизувати щомісячні виплати або дозволити витрату ліміту без повного мультисігу, ручні підписи стають вузьким місцем. Модулі вирішують це: вони беруть на себе частину управління, зберігаючи контроль через обмеження та timelock. Розробка кастомного модуля під вашу бізнес-логіку — ключове завдання, яке ми вирішуємо вже 5 років. Один такий модуль обробляє до 1000 транзакцій на день, економлячи 40% на газі порівняно з ручним керуванням.
Як влаштовані модулі в Safe
Safe{Wallet} слідує паттерну модульного проксі. Ядро (GnosisSafe.sol) містить базову логіку мультисігу та зберігає список активних модулів у linked list storage. Модуль активується через enableModule(address module) — це стандартна мультисіг-транзакція, яка вимагає поріг підписів.
Після активації модуль може викликати execTransactionFromModule() напряму на Safe, минаючи стандартний процес підписів:
interface IGnosisSafe {
function execTransactionFromModule(
address to,
uint256 value,
bytes calldata data,
Enum.Operation operation
) external returns (bool success);
}
operation — 0 для CALL, 1 для DELEGATECALL. DELEGATECALL виконує код target-контракту в контексті сховища Safe — це потужний інструмент і одночасно вектор атаки, якщо модуль не перевірено ретельно. Ми наполягаємо на тому, що модулі з DELEGATECALL зобов'язані проходити додатковий аудит. Наш досвід показує: 30% вразливостей в модулях пов'язані саме з неправильним використанням delegatecall.
Які модулі найчастіше потрібні?
Ось типові кейси з нашої практики:
| Тип модуля | Призначення | Складність | Типова економія газу |
|---|---|---|---|
| Spending limits | Ліміти витрат для операційної команди | Низька | 20% |
| Автоматичні виплати | Періодичні перекази зарплат, грантів | Середня | 35% |
| Recovery модуль | Відновлення доступу при втраті ключів | Висока | 10% |
| Governance-controlled execution | Виконання рішень on-chain управління | Висока | 15% |
Spending limits (ліміти витрат). DAO дозволяє операційній команді витрачати до $10K на день без мультисігу. Модуль зберігає ліміт, лічильник витрат за період та дозволених викликаючих. Стандартний Safe Allowance Module реалізує це, але кастомні версії потрібні, коли ліміт повинен працювати з декількома токенами одночасно або скидатися за подією, а не за часом. Кастомний модуль у 2 рази ефективніший за стандартний по газу при частих транзакціях.
Автоматизовані виплати. Інтеграція з Chainlink Automation або Gelato: модуль отримує право раз на місяць виконувати transfer з Safe на адреси команди. Без мультисігу, але з жорсткими обмеженнями: лише попередньо схвалені адреси, лише певні токени, лише в рамках бюджету.
Recovery модуль. Safe конфігурації з 3 з 5 підписантів, але можлива втрата ключів. Recovery модуль дозволяє призначити guardian-адреси (інші Safe, холодні гаманці), які через timelock можуть замінити список підписантів. Guardian не може вивести кошти — лише змінити конфігурацію Safe після періоду очікування.
Governance-controlled execution. Модуль приймає рішення від on-chain governance (Snapshot X з on-chain execution, OpenZeppelin Governor). Голосування проходить, результат виконується через модуль без додаткових підписів мультисігу.
Чому модуль потребує timelock?
Для critical actions модуль повинен включати timelock. Операція ставиться в чергу з timestamp, виконується лише після проходження delay-періоду. Це дає спостерігачам (community, іншим підписантам) час зреагувати на небажану дію. Без timelock один зламаний guardian може миттєво змінити конфігурацію Safe.
mapping(bytes32 => uint256) public queue;
uint256 public constant DELAY = 2 days;
function propose(address target, uint256 value, bytes calldata data)
external onlyAuthorized returns (bytes32 txHash) {
txHash = keccak256(abi.encode(target, value, data, block.timestamp));
queue[txHash] = block.timestamp + DELAY;
emit Proposed(txHash, target, value, data);
}
function execute(address target, uint256 value, bytes calldata data, uint256 timestamp)
external onlyAuthorized {
bytes32 txHash = keccak256(abi.encode(target, value, data, timestamp));
require(queue[txHash] != 0, "Not queued");
require(block.timestamp >= queue[txHash], "Timelock active");
delete queue[txHash];
// execute via Safe module
}
У нашій практиці типовий delay — від 2 днів. Для операцій з високою вартістю затримка може сягати 7 днів.
Архітектура безпечного модуля
Перевірки авторизації
Головна відповідальність модуля — коректна авторизація. Якщо execTransactionFromModule може викликати хто завгодно — це катастрофа. Шаблон:
contract SpendingLimitModule {
mapping(address safe => mapping(address delegate => SpendingLimit)) public limits;
modifier onlyDelegate(address safe) {
require(limits[safe][msg.sender].amount > 0, "Not a delegate");
_;
}
function executeTransfer(
address safe,
address token,
address recipient,
uint256 amount
) external onlyDelegate(safe) {
SpendingLimit storage limit = limits[safe][msg.sender];
require(amount <= limit.remaining, "Exceeds limit");
// Спершу оновлюємо стан
limit.remaining -= amount;
// Потім виконуємо транзакцію
bytes memory data = abi.encodeWithSignature(
"transfer(address,uint256)", recipient, amount
);
require(
IGnosisSafe(safe).execTransactionFromModule(token, 0, data, Enum.Operation.Call),
"Module tx failed"
);
}
}
Порядок: перевірки, зміна стану, зовнішній виклик — класичний checks-effects-interactions.
Guard — додатковий шар захисту
Safe 1.3+ підтримує Guard — контракт, який викликається до та після кожної транзакції Safe. Guard може блокувати транзакції за будь-якими умовами: забороняти взаємодію з певними адресами, вимагати cooldown між великими транзакціями, логувати все on-chain.
Приклад Guard, що перевіряє whitelist адрес
contract WhitelistGuard is Guard {
mapping(address => bool) public allowed;
function checkTransaction(
address to, uint256 value, bytes memory data, Enum.Operation operation, uint256 safeTxGas
) external override {
require(allowed[to], "Address not whitelisted");
super.checkTransaction(to, value, data, operation, safeTxGas);
}
}
Guard і Module — різні механізми. Guard не виконує транзакції, лише фільтрує їх. Module виконує транзакції, обходячи стандартний поріг. Комбінація Guard + Module дає гнучку систему безпеки.
Тестування та аудит
Тестуємо на Foundry з реальним інстансом Safe. Forge дозволяє деплоїти Safe factory та створювати Safe інстанси в тестах — не потрібні моки. Проводимо 100+ фаззинг-тестів Echidna для пошуку неочевидних багів.
import {GnosisSafeProxyFactory} from "safe-contracts/proxies/GnosisSafeProxyFactory.sol";
import {GnosisSafe} from "safe-contracts/GnosisSafe.sol";
function setUp() public {
factory = new GnosisSafeProxyFactory();
singleton = new GnosisSafe();
// деплой Safe з нашим модулем вже включеним через setup
}
Перевіряємо: модуль не може викликати execTransactionFromModule від довільної адреси, timelock не можна обійти, reentrancy в колбеках неможлива, модуль коректно працює після Safe upgrade.
Аудит модулів для Safe з великими активами — обов'язковий. Вектор атаки через DELEGATECALL особливо небезпечний: зловмисний модуль через delegatecall може перезаписати storage Safe, включаючи список підписантів. Ми просимо зовнішніх аудиторів (наприклад, з OpenZeppelin) перевіряти код — це стандарт нашої роботи.
Як розробити модуль Safe: покрокова інструкція
- Аналіз вимог: визначення логіки, прав доступу, обмежень.
- Дизайн архітектури: схема взаємодії модуля з Safe, guard, зовнішніми ораклами.
- Розробка: код на Solidity 0.8.x з використанням Safe contracts та Foundry.
- Тестування: unit-тести, інтеграційні тести з реальним Safe, fuzzing через Echidna.
- Документація: опис функцій, deployment-скрипти, ABIs.
- Аудит: зовнішній або внутрішній (за бажанням).
- Деплой та підтримка: допомога з деплоєм, навчання команди, гарантія 1 рік.
Що входить в розробку модуля
- Аналіз вимог та дизайн архітектури.
- Розробка смарт-контракту на Solidity 0.8.x.
- Комплексне тестування (Foundry + fuzzing).
- Документація та deployment-скрипти.
- Аудит безпеки (опціонально).
- Гарантія на код 1 рік.
Порівняння типів модулів
| Тип модуля | Складність | Типові строки | Економія на газі |
|---|---|---|---|
| Spending Limit | Низька | 3–5 днів | 20% |
| Recovery з timelock | Висока | 5–8 днів | 10% |
| Governance (Snapshot/Governor) | Висока | 2–3 тижні | 15% |
Строки орієнтовно
- Spending Limit модуль з базовою логікою: 3–5 днів.
- Recovery модуль з timelock та guardian system: 5–8 днів.
- Комплексний governance модуль з інтеграцією Snapshot X або OpenZeppelin Governor: 2–3 тижні.
- Аудит — додатково 1–2 тижні.
Вартість розробки модуля розраховується індивідуально в залежності від складності та обсягу робіт. Середня економія на транзакційних комісіях становить до $50 000 на рік для DAO з високим обсягом операцій. Зв'яжіться з нами, щоб обговорити ваш проект — ми оцінимо задачу та запропонуємо оптимальне рішення під ключ. Отримайте консультацію інженера — оцінимо ваш проект безкоштовно.







