Разработка оптимистичного governance (optimistic approval)
Optimistic governance — это инверсия классической модели. Стандартный Governor: proposal создаётся → сообщество голосует → при наборе quorum и majority исполняется. Optimistic: proposal создаётся → автоматически исполняется после задержки → если сообщество против, они должны активно заблокировать его.
Идея заимствована из optimistic rollup-ов: по умолчанию считаем валидным, оспаривание требует действия. Для DAO это решает главную проблему большинства governance систем — voter apathy (апатия избирателей). Когда quorum не набирается месяцами из-за низкой явки, протокол замирает. Optimistic governance работает даже при 90% пассивных держателей.
Когда оптимистичный подход оправдан
Не каждой DAO нужен optimistic approval. Подходит когда:
- Операционные решения принимаются командой с делегированным мандатом (SubDAO, grants committee, security council)
- Высокочастотные административные действия (обновление параметров риска, whitelist новых активов)
- Сообщество доверяет исполнительной команде, но хочет право veto
- Стандартный governance слишком медленен для реагирования на market events
Не подходит для: изменений tokenomics, обновлений core смарт-контрактов, решений с необратимыми последствиями для treasury. Для этого — стандартный Governor с полным голосованием.
Архитектурные паттерны
Паттерн 1: Optimistic Multisig
Самая простая модель. Доверенный мультисиг (core team, 5/9 подписей) исполняет решения. Timelock даёт сообществу окно для veto через emergency governance.
Multisig submits tx → TimelockController queues → [7 days window] → execute
↑
Community can invoke Guardian veto
Проблема: это не настоящий optimistic governance, это просто multisig с задержкой. Нет механизма for community veto без централизованного Guardian.
Паттерн 2: Veto-based Governor
Более децентрализованная модель. Proposal автоматически принимается после задержки, если не набрал veto-quorum (например, 10% токенов проголосовали ПРОТИВ).
contract OptimisticGovernor is Governor, GovernorTimelockControl {
uint256 public vetoThreshold; // % токенов необходимых для veto (e.g. 10%)
function _voteSucceeded(uint256 proposalId) internal view override returns (bool) {
ProposalVote storage proposalVote = _proposalVotes[proposalId];
uint256 totalSupplyAtSnapshot = token.getPastTotalSupply(proposalSnapshot(proposalId));
uint256 vetoQuorum = totalSupplyAtSnapshot * vetoThreshold / 100;
// Proposal провалился только если набрано достаточно AGAINST голосов
return proposalVote.againstVotes < vetoQuorum;
}
function _quorumReached(uint256 proposalId) internal view override returns (bool) {
return true; // quorum всегда считается достигнутым — proposal проходит по умолчанию
}
}
Механизм: proposal создаётся → votingPeriod (например, 7 дней для сбора veto голосов) → если AGAINST < vetoThreshold — proposal passes → queue в timelock → execute.
Кто может создавать proposals важен для безопасности: если threshold низкий — спамеры. Решение: proposals создают только члены Security Council или SubDAO через отдельный approval контракт.
Паттерн 3: Optimistic SubDAO (Optimism модель)
Optimism использует двухпалатную систему. Token House (все держатели OP) — полноценное голосование. Citizens' House (badgeholders) — контрбаланс. Для некоторых типов решений: Citizens' House имеет право veto против Token House majority.
Для протоколов меньшего масштаба: SubDAO модель. Core team = SubDAO с optimistic-правами в рамках лимитов. За пределами лимитов — полный governance.
SubDAO Action Categories:
├── Tier 1 (< $50k, params) → Optimistic approval, 48h veto window
├── Tier 2 ($50k - $500k, integrations) → Standard multisig vote, 7d veto window
└── Tier 3 (> $500k, code upgrades) → Full token voting, no optimistic
Детальная реализация veto механизма
Veto Registry Contract
Отдельный контракт регистрирует veto голоса и взаимодействует с TimelockController:
contract VetoRegistry {
TimelockController public immutable timelock;
IVotes public immutable votingToken;
uint256 public vetoQuorumBps; // e.g. 1000 = 10%
mapping(bytes32 => uint256) public vetoVotes; // operationId => total veto weight
mapping(bytes32 => mapping(address => bool)) public hasVetoed;
mapping(bytes32 => uint256) public vetoSnapshotBlock; // блок для расчёта voting power
event VetoCast(bytes32 indexed operationId, address indexed voter, uint256 weight);
event OperationVetoed(bytes32 indexed operationId);
function castVeto(bytes32 operationId) external {
require(timelock.isOperationPending(operationId), "Not pending");
require(!hasVetoed[operationId][msg.sender], "Already vetoed");
uint256 snapshot = vetoSnapshotBlock[operationId];
require(snapshot != 0, "Snapshot not set");
uint256 weight = votingToken.getPastVotes(msg.sender, snapshot);
require(weight > 0, "No voting power");
hasVetoed[operationId][msg.sender] = true;
vetoVotes[operationId] += weight;
emit VetoCast(operationId, msg.sender, weight);
// Проверяем достигнут ли veto threshold
uint256 totalSupply = votingToken.getPastTotalSupply(snapshot);
if (vetoVotes[operationId] * 10000 / totalSupply >= vetoQuorumBps) {
timelock.cancel(operationId);
emit OperationVetoed(operationId);
}
}
}
Snapshot для veto голосов
При постановке operation в очередь TimelockController — нужно зафиксировать snapshot блок для veto расчётов. Это делается через хук onQueue или через отдельный вызов:
function onOperationQueued(bytes32 operationId) external onlyTimelock {
vetoSnapshotBlock[operationId] = block.number;
}
Важно: snapshot должен быть на момент постановки в очередь, не на момент создания proposal. Иначе злоумышленник может накопить токены после создания proposal, воспользоваться ими для blokировки veto (если хочет пропустить harmful proposal) или обратно.
Canceller role management
В TimelockController нужно выдать VetoRegistry CANCELLER_ROLE. Это позволяет контракту отменять операции при достижении veto threshold.
// При деплое
timelock.grantRole(timelock.CANCELLER_ROLE(), address(vetoRegistry));
// Убрать CANCELLER у мультисига после запуска (или оставить как emergency)
// timelock.revokeRole(timelock.CANCELLER_ROLE(), multisig);
Экономика участия в veto
Проблема veto-based систем: gas cost голосования. Если veto голос стоит $5-50 в газе, а держатель имеет небольшой стейк — рационально не голосовать. Это означает что только крупные держатели реально участвуют в veto.
Решения:
- Газовый рефанд. Протокол возмещает gas для veto голосования если proposal был в итоге заблокирован. Реализуется через VetoRegistry, который отправляет refund при cancellation.
- Gasless voting через EIP-712 + relayer. Пользователь подписывает veto off-chain, relayer публикует on-chain, оплачивая газ. Используется стандарт meta-transactions (ERC-2771 с Forwarder).
- Optimism: L2 для veto. Если протокол на L2 — gas уже дешёвый (cent-уровень), проблема менее актуальна.
Security considerations
Harmful proposal через optimistic path
Атака: злоумышленник создаёт harmful proposal (drain treasury через malicious calldata), рассчитывает что сообщество не заметит в window veto. Защиты:
- Мониторинг proposals. Автоматические alerts на новые proposals в timelock. Defender (OpenZeppelin Defender), custom keeper, Tenderly Web3 Actions.
- Высокий veto threshold не помогает против атаки: злоумышленник может иметь > (100% - threshold) голосов, блокируя veto. Threshold должен быть реально достижим для активного сообщества.
- Multisig как backstop CANCELLER. Помимо VetoRegistry — команда имеет право экстренной отмены. Centralizing, но необходимо на раннем этапе.
Timelock bypass через delegatecall
Если proposal включает delegatecall в TimelockController — это критическая уязвимость: delegatecall исполняется в контексте Timelock, может изменить его storage, включая roles. OpenZeppelin TimelockController имеет защиту: _execute() не принимает delegatecall. Любая кастомная реализация должна воспроизвести эту защиту.
Replay attack при перезапуске
Если протокол деплоится заново или TimelockController заменяется — старые salt/operationId могут совпасть с новыми. Убедиться что новый деплой не наследует состояние старого timelock.
Реальные примеры
Optimism Foundation. Security Council может предпринимать экстренные действия (upgrade, pause) с 24-часовым timelock. Стандартные proposals — полноценное голосование Token House.
Gnosis DAO. GnosisDAO использует optimistic governance для routine operational decisions через специализированные SubDAO (карго-модуль).
Aragon. Aragon OSx поддерживает optimistic plugin через официальный OptimisticTokenVotingPlugin — один из стандартных плагинов фреймворка.
Стек и процесс
Контракты: Solidity 0.8.x + OZ TimelockController + кастомный VetoRegistry. Foundry тесты: fuzz тесты для veto threshold расчётов, invariant тесты (если proposal vetoed — исполнение невозможно).
Мониторинг: OpenZeppelin Defender Sentinels или Tenderly Alerts на CallScheduled events TimelockController — уведомления в Discord/Telegram сразу при постановке proposal в очередь.
Frontend: отдельная страница активных timelock операций с veto интерфейсом. Показывать countdown до исполнения и текущее количество veto голосов.
| Компонент | Технология | Сложность |
|---|---|---|
| Optimistic Governor | OZ Governor + custom vote counting | Средняя |
| VetoRegistry | Кастомный Solidity | Средняя |
| Gasless veto | ERC-2771 + Relayer | Высокая |
| Monitoring | OZ Defender / Tenderly | Низкая |
| Frontend | wagmi + viem + React | Средняя |
Сроки: базовая реализация (OptimisticGovernor + VetoRegistry + monitoring) — 3-4 недели разработки. С gasless voting и полноценным frontend — 6-8 недель. Аудит обязателен — 2-4 недели у специализированной команды.
Стоимость рассчитывается после архитектурного анализа конкретного use case.







