Інтеграція з OpenZeppelin Governor
OpenZeppelin Governor — це модульний фреймворк для on-chain управління, що реалізує стандарт, сумісний з Compound Governor Bravo. Більшість DeFi протоколів з on-chain управління побудовані на цій базі або сумісні з нею: Uniswap, Compound, Gitcoin, ENS використовують контракти, сумісні з Governor. Тому інтеграція з Governor — це не просто додавання управління до вашого протоколу, це досягнення сумісності з більш широкою екосистемою інструментів: Tally, Boardroom, Snapshot (для off-chain сигналізації).
Архітектура Governor
Governor — це не один контракт, а набір модулів через множинне спадкування Solidity.
contract MyGovernor is
Governor,
GovernorSettings, // voting delay, voting period, proposal threshold
GovernorCountingSimple, // For, Against, Abstain підрахунок
GovernorVotes, // інтеграція з токеном ERC-20Votes
GovernorVotesQuorumFraction, // кворум як % від загального пропозиції
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% від циркуляцією = кворум
GovernorTimelockControl(_timelock)
{}
}
Кожен модуль вирішує окрему задачу. Якщо потрібна користувацька логіка підрахунку голосів (наприклад, квадратичне голосування), замініть GovernorCountingSimple своєю реалізацією.
Життєвий цикл пропозиції
Пропозиція проходить через суворо визначені стани:
Очікування → Активна → Успішна/Поражена/Скасована → В черзі → Виконана
Створення → [voting delay] → Голосування → [voting period] → Підрахунок →
[timelock delay] → Виконання
votingDelay захищає від атак flash loan. Якщо голосування починається одразу після створення, зловмисник може купити великі токени в одному блоці, проголосувати й продати. З затримкою голосування в 1-2 дні це неможливо: токени повинні утримуватися до блоку снапшоту.
GovernorVotes фіксує голосувальну силу кожної адреси на блоці початку голосування через механізм чекпоїнту ERC-20Votes. Зміни баланса після цього блоку не впливають на вагу голосу.
Інтеграція Timelock
TimelockController — окремий контракт, який є власником ваших контрактів протоколу. Governor відправляє транзакції в чергу Timelock; після затримки (зазвичай 48–72 години) виконання стає можливим.
// Деплой TimelockController
TimelockController timelock = new TimelockController(
2 days, // minDelay
proposers, // тільки Governor може ставити в чергу
executors, // будь-хто може виконати (або конкретна адреса)
admin // після налаштування admin = address(0), видалити права admin
);
// Governor отримує PROPOSER_ROLE
timelock.grantRole(timelock.PROPOSER_ROLE(), address(governor));
// Executor — відкритий (address(0)) або конкретна адреса
timelock.grantRole(timelock.EXECUTOR_ROLE(), address(0));
// Відкликати admin (важливо!)
timelock.revokeRole(timelock.DEFAULT_ADMIN_ROLE(), deployer);
Чому відкликання admin важливо: якщо deployer зберігає DEFAULT_ADMIN_ROLE, він може будь-коли обійти управління й безпосередньо надати собі PROPOSER_ROLE. Останній крок — revokeRole admin від deployer.
Propose, Vote, Execute
Створення пропозиції
Пропозиція — це один або більше викликів функцій, виконаних, якщо голосування пройде.
// Приклад: пропозиція на змінення параметра комісії протоколу
address[] memory targets = new address[](1);
targets[0] = address(protocolContract);
uint256[] memory values = new uint256[](1); // Значення ETH, зазвичай 0
values[0] = 0;
bytes[] memory calldatas = new bytes[](1);
calldatas[0] = abi.encodeWithSignature("setFee(uint256)", 30); // 0.3%
string memory description = "Пропозиція #1: встановити комісію протоколу на 0.3%";
uint256 proposalId = governor.propose(targets, values, calldatas, description);
proposalId обчислюється як keccak256(abi.encode(targets, values, calldatas, keccak256(bytes(description)))). Це має значення: опис є частиною id, тому зміна її створює іншу пропозицію.
Голосування
// 0 = Проти, 1 = За, 2 = Утримаюсь
governor.castVote(proposalId, 1);
// З обгрунтуванням (on-chain, дорогий газ)
governor.castVoteWithReason(proposalId, 1, "Підвищує конкурентоспроможність протоколу");
// Підпис EIP-712 для безгазового голосування через relayer
governor.castVoteBySig(proposalId, 1, v, r, s);
castVoteBySig критичний для UX. Більшість емітентів не будуть платити $5–20 за on-chain голос. Безгазове голосування: користувач підписує голос off-chain, relayer (або сам протокол) відправляє транзакцію й платить газ.
Виконання
// Після успішного голосування та затримки timelock
governor.execute(targets, values, calldatas, keccak256(bytes(description)));
Налаштування: що ви можете змінити
Джерело голосувальної сили. За замовчуванням — токен ERC-20Votes. Можна замінити на: ERC-721Votes (управління NFT), користувацьке зважування (заблоковані токени важать більше), модель veToken (стиль Curve).
Кворум. GovernorVotesQuorumFraction підраховує кворум від загального пропозиції. Проблема: якщо велика частина токенів не делегована, кворум ніколи не досягається. Рішення — підраховуйте від делегованої пропозиції, не загальної. Вимагає перевизначення функції quorum().
Поріг пропозиції. Високий поріг (наприклад, 1% пропозиції) захищає від спам-пропозицій, але виключає малих емітентів. Альтернатива: поріг делегування — будь-який емітент може створити пропозицію, якщо збирає достатньо делегатів.
Tally та екосистема інструментів
Tally — это web3 дашборд управління, який працює з будь-яким контрактом, сумісним з Governor, з коробки. Після розгортання Governor на Tally він з'являється автоматично через їхній індексер. Це має значення: не потрібно будувати користувацький UI голосування з нуля.
Snapshot для off-chain сигналізації використовує підписи EIP-712 без газу. Інтеграція Governor + Snapshot: результати Snapshot служать сигналом перед on-chain пропозицією, або через модуль Snapshot у стилі Governor дозволяють binding голосування з on-chain виконанням. ENS використовує цей паттерн.
Часті помилки інтеграції
Забуття transferOwnership до Timelock. Governor + Timelock марні, якщо ваші контракти протоколу все ще належать гаманцю deployer. Повинні явно передати власність.
Неправильний кворум. 4% від загального при 30% циркуляційній ставці означає 13% від циркуляції — важко досягти. Тестуйте проти реальних даних розподілу токенів.
Відсутність ERC-20Votes у токені. Стандартний ERC-20 не підтримує чекпоїнти. Або токен спадкує ERC20Votes, або використовуйте обгортку (як wToken). Модифікуйте існуючий токен через ERC20VotesWrapper.
Графік інтеграції: стандартна інтеграція Governor з існуючим токеном — 1–2 тижні розробки плюс 1 тиждень тестування. Користувацька механіка голосування — 3–4 тижні додаткових.







