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 с высоким объёмом операций. Свяжитесь с нами, чтобы обсудить ваш проект — мы оценим задачу и предложим оптимальное решение под ключ. Получите консультацию инженера — оценим ваш проект бесплатно.







