Интеграция с OpenZeppelin Governor
OpenZeppelin Governor — это модульный фреймворк для on-chain governance, реализующий стандарт совместимый с Compound Governor Bravo. Большинство DeFi-протоколов с on-chain governance построены на этой базе или совместимы с ней: Uniswap, Compound, Gitcoin, ENS используют Governor-совместимые контракты. Поэтому интеграция с Governor — это не просто добавление governance к вашему протоколу, но и совместимость с экосистемой инструментов: Tally, Boardroom, Snapshot (для off-chain signaling).
Архитектура Governor
Governor — это не один контракт, а набор модулей через Solidity multiple inheritance.
contract MyGovernor is
Governor,
GovernorSettings, // voting delay, voting period, proposal threshold
GovernorCountingSimple, // For, Against, Abstain подсчёт
GovernorVotes, // интеграция с ERC-20Votes токеном
GovernorVotesQuorumFraction, // кворум как % от total supply
GovernorTimelockControl // исполнение через Timelock
{
constructor(IVotes _token, TimelockController _timelock)
Governor("MyProtocol Governor")
GovernorSettings(
1 days, // voting delay: сколько ждать после создания пропозала
1 weeks, // voting period: сколько длится голосование
10_000e18 // proposal threshold: минимум токенов для создания пропозала
)
GovernorVotes(_token)
GovernorVotesQuorumFraction(4) // 4% от circulating supply = кворум
GovernorTimelockControl(_timelock)
{}
}
Каждый модуль решает отдельную задачу. Если нужна кастомная логика подсчёта голосов (например, квадратичное голосование) — заменяете GovernorCountingSimple своей реализацией.
Жизненный цикл пропозала
Пропозал проходит через строго определённые состояния:
Pending → Active → Succeeded/Defeated/Canceled → Queued → Executed
Создание → [voting delay] → Голосование → [voting period] → Подсчёт →
[timelock delay] → Исполнение
votingDelay — защита от flash loan атак. Если голосование начинается сразу после создания пропозала, атакующий может купить большой объём токенов в тот же блок, проголосовать, и продать. С voting delay в 1–2 дня это невозможно: нужно удерживать токены до snapshot-блока.
GovernorVotes фиксирует voting power каждого адреса на блоке начала голосования через ERC-20Votes checkpoint механизм. Изменения баланса после этого блока не влияют на vote weight.
Timelock интеграция
TimelockController — отдельный контракт, который является владельцем ваших протокольных контрактов. Governor отправляет транзакции в Timelock очередь; после delay (обычно 48–72 часа) исполнение становится возможным.
// Деплой TimelockController
TimelockController timelock = new TimelockController(
2 days, // minDelay
proposers, // только Governor может ставить в очередь
executors, // любой может исполнить (или конкретный адрес)
admin // после setup admin = address(0), убираем admin права
);
// Governor получает PROPOSER_ROLE
timelock.grantRole(timelock.PROPOSER_ROLE(), address(governor));
// Executor — open (address(0)) или конкретный address
timelock.grantRole(timelock.EXECUTOR_ROLE(), address(0));
// Отзываем admin (важно!)
timelock.revokeRole(timelock.DEFAULT_ADMIN_ROLE(), deployer);
Почему важно отозвать admin-роль: если у deployer остаётся DEFAULT_ADMIN_ROLE, он может в любой момент обойти governance и напрямую выдать себе PROPOSER_ROLE. Финальный шаг — revokeRole admin от deployer.
Propose, Vote, Execute
Создание пропозала
Пропозал — это один или несколько вызовов функций, которые будут исполнены если голосование пройдёт.
// Пример: пропозал на изменение fee параметра протокола
address[] memory targets = new address[](1);
targets[0] = address(protocolContract);
uint256[] memory values = new uint256[](1); // ETH value, обычно 0
values[0] = 0;
bytes[] memory calldatas = new bytes[](1);
calldatas[0] = abi.encodeWithSignature("setFee(uint256)", 30); // 0.3%
string memory description = "Proposal #1: Set protocol fee to 0.3%";
uint256 proposalId = governor.propose(targets, values, calldatas, description);
proposalId вычисляется как keccak256(abi.encode(targets, values, calldatas, keccak256(bytes(description)))). Это важно: description входит в id, поэтому изменение описания создаёт другой пропозал.
Голосование
// 0 = Against, 1 = For, 2 = Abstain
governor.castVote(proposalId, 1);
// С обоснованием (on-chain, дорого по газу)
governor.castVoteWithReason(proposalId, 1, "Increases protocol competitiveness");
// EIP-712 подпись для gasless голосования через relayer
governor.castVoteBySig(proposalId, 1, v, r, s);
castVoteBySig — критичная функция для UX. Большинство holders не будут платить $5–20 за on-chain vote. Gasless voting: пользователь подписывает vote off-chain, relayer (или сам протокол) отправляет транзакцию и платит газ.
Исполнение
// После успешного голосования и timelock delay
governor.execute(targets, values, calldatas, keccak256(bytes(description)));
Кастомизация: что можно менять
Voting power источник. По умолчанию — ERC-20Votes токен. Можно заменить на: ERC-721Votes (NFT governance), custom weighting (locked tokens весят больше), veToken модель (Curve style).
Quorum. GovernorVotesQuorumFraction считает кворум от total supply. Проблема: если большая часть токенов не делегирована, кворум никогда не достигается. Решение — считать от delegated supply, а не total. Требует override quorum() функции.
Proposal threshold. Высокий порог (например, 1% supply) защищает от спам-пропозалов, но исключает мелких holders. Альтернатива: delegation threshold — любой holder может создать пропозал если соберёт достаточно делегатов.
Tally и экосистема инструментов
Tally — web3 governance дашборд, который работает с любым Governor-совместимым контрактом out of the box. После деплоя Governor на Tally появляется автоматически через их indexer. Это важно: не нужно строить custom UI для голосования с нуля.
Snapshot для off-chain signaling использует EIP-712 signatures без газа. Интеграция Governor + Snapshot: Snapshot результаты могут использоваться как signal перед on-chain пропозалом, или через Governor-style модуль Snapshot можно делать binding голосования с on-chain исполнением.
Распространённые ошибки при интеграции
Забыть transferOwnership на Timelock. Governor + Timelock бесполезны, если ваши протокольные контракты по-прежнему принадлежат deployer-кошельку. Нужно явно передать ownership.
Неправильный quorum. 4% от total supply при 30% circulation rate означает реальный порог в 13% от circulating — трудно достижимо. Тестируйте на реальных данных распределения токенов.
Отсутствие ERC-20Votes в токене. Стандартный ERC-20 не поддерживает чекпоинты. Либо токен должен наследовать ERC20Votes, либо нужен wrapper (как wToken). Retrofit существующего токена — через ERC20VotesWrapper.
Сроки интеграции: стандартная Governor интеграция с существующим токеном — 1–2 недели разработки + 1 неделя тестирования. Кастомная voting механика — 3–4 недели.







