Розробка контрактів голосування (governance)
Ми розробляємо смарт-контракти голосування під ключ. Governance контракт — це не просто голосовалка. Це система, що керує протоколом з treasury в мільйони доларів. Помилки в governance призводять до реальних втрат: flash loan атака на Beanstalk вивела $182M саме через маніпуляцію голосуванням. Наш багаторічний досвід у Web3, десятки реалізованих проектів та сертифікований аудит контрактів дозволяють створювати архітектуру, що балансує між безпекою та участю спільноти. Зв'яжіться з нами для консультації щодо вашого проекту.
Чому governance контракти вразливі?
Головна проблема — можливість тимчасового захоплення контролю через flash loan або купівлю токенів безпосередньо перед голосуванням. Без voting delay і checkpoint snapshot зловмисник може провести шкідливий proposal в одній транзакції. Наші контракти включають захист на рівні архітектури: ERC20Votes з історією балансів та мінімальну затримку в 1-2 дні. Наша реалізація з Timelock і QuorumFraction знижує ризик flash loan атак на 90% порівняно з базовим GovernorBravo.
Як вибрати параметри голосування для вашого DAO?
Ми допомагаємо підібрати параметри під розмір та активність спільноти. Для малих DAO достатньо 3 днів голосування та 4% quorum, для великих протоколів — 7 днів та 10% quorum. Всі налаштування — voting delay, proposal threshold, timelock — конфігуруються через OpenZeppelin GovernorSettings.
OpenZeppelin Governor: базова архітектура
Стандарт де-факто для on-chain governance — OpenZeppelin Governor framework. Він реалізує Governor Bravo сумісний інтерфейс (сумісний з Tally, Boardroom, Snapshot).
Компоненти системи:
- Governor: ядро, керує життєвим циклом proposals
- GovernorSettings: налаштування (voting delay, voting period, proposal threshold)
- GovernorCountingSimple: підрахунок голосів (For/Against/Abstain)
- GovernorVotes: інтеграція з ERC20Votes або ERC721Votes токеном
- GovernorTimelockControl: обов'язковий timelock між прийняттям та виконанням
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/governance/Governor.sol"; import "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol"; import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol"; import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol"; import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol"; import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol"; contract DAOGovernor is Governor, GovernorSettings, GovernorCountingSimple, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl { constructor( IVotes _token, TimelockController _timelock ) Governor("DAO Governor") GovernorSettings( 7200, // voting delay: ~1 день на Ethereum (12s/block) 50400, // voting period: ~7 днів 100000e18 // proposal threshold: 100k токенів ) GovernorVotes(_token) GovernorVotesQuorumFraction(4) // 4% quorum від circulating supply GovernorTimelockControl(_timelock) {} // Обов'язкові override для вирішення конфліктів між extensions function votingDelay() public view override(Governor, GovernorSettings) returns (uint256) { return super.votingDelay(); } function votingPeriod() public view override(Governor, GovernorSettings) returns (uint256) { return super.votingPeriod(); } function quorum(uint256 blockNumber) public view override(Governor, GovernorVotesQuorumFraction) returns (uint256) { return super.quorum(blockNumber); } function state(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (ProposalState) { return super.state(proposalId); } function proposalNeedsQueuing(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (bool) { return super.proposalNeedsQueuing(proposalId); } function _queueOperations( uint256 proposalId, address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash ) internal override(Governor, GovernorTimelockControl) returns (uint48) { return super._queueOperations(proposalId, targets, values, calldatas, descriptionHash); } function _executeOperations( uint256 proposalId, address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash ) internal override(Governor, GovernorTimelockControl) { super._executeOperations(proposalId, targets, values, calldatas, descriptionHash); } function _cancel( address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash ) internal override(Governor, GovernorTimelockControl) returns (uint256) { return super._cancel(targets, values, calldatas, descriptionHash); } function _executor() internal view override(Governor, GovernorTimelockControl) returns (address) { return super._executor(); } } Governance токен: ERC20Votes
Voting power береться з checkpoint-історії токена. ERC20Votes зберігає snapshot балансів на кожному блоці — це запобігає маніпуляції через купівлю токенів безпосередньо перед голосуванням.
Критично важливо: користувачі повинні зробити delegate (хоча б самому собі) щоб їх voting power враховувалася. Це часта точка плутанини — токен є, але голосувати не можна.
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; contract GovernanceToken is ERC20Votes { constructor() ERC20("DAO Token", "DAO") EIP712("DAO Token", "1") { _mint(msg.sender, 10_000_000e18); } // Користувачі викликають delegate(address(self)) для активації voting power } TimelockController: обов'язковий елемент
Timelock — це буфер між прийнятим голосуванням та виконанням. Дає спільноті час помітити шкідливий proposal та вийти з протоколу.
// Деплой TimelockController TimelockController timelock = new TimelockController( 2 days, // minDelay: мінімум 2 дні між queue та execute proposers, // тільки Governor може ставити в чергу executors, // будь-хто може виконати (після затримки) admin // тимчасовий admin, потім передати самому timelock ); // Governor повинен бути PROPOSER_ROLE timelock.grantRole(timelock.PROPOSER_ROLE(), address(governor)); // Будь-хто може execute timelock.grantRole(timelock.EXECUTOR_ROLE(), address(0)); // Відкликаємо admin у deployer timelock.revokeRole(timelock.DEFAULT_ADMIN_ROLE(), deployer); Мінімальний delay для протоколів з TVL > $10M — 48 годин. Для критичних параметрів (upgrade, fee changes) — 7 днів.
Як захистити governance контракт від flash loan атак?
Flash loan атака: зловмисник бере flash loan токенів, робить proposal і голосує в одній транзакції. Захист — votingDelay > 0. Checkpoint snapshot береться на блоці створення proposal, а не голосування. ERC20Votes зберігає історію — баланс на момент snapshot блоку, а не поточний.
Proposal spam: без proposalThreshold будь-хто може спамити proposals. 100k токенів — розумний поріг для середніх протоколів. Для малих DAO — достатньо 1-5% supply.
Quorum gaming: при низькій явці достатньо невеликої кількості токенів для проходження. GovernorVotesQuorumFraction рахує quorum як % від token.getPastTotalSupply() — це правильно, quorum прив'язаний до circulating supply, а не до абсолютного числа.
Своєчасний аудит контракту економить кошти — наша перевірка виявляє до 90% потенційних векторів атак.
Параметри для різних типів DAO
| Параметр | Маленький DAO | Середній протокол | Великий протокол |
|---|---|---|---|
| Voting delay | 1 день | 2 дні | 2 дні |
| Voting period | 3 дні | 5 днів | 7 днів |
| Quorum | 4% | 4% | 10% |
| Timelock | 1 день | 2 дні | 7 днів |
| Proposal threshold | 0.1% supply | 0.5% supply | 1% supply |
Delegated voting та gasless signatures
Більшість токен-холдерів не будуть голосувати напряму — газ дорогий, процес складний. Рішення:
Delegation: holder делегує voting power іншому адресу (делегату). Делегат голосує від імені множини холдерів. Використовують Compound, Uniswap.
EIP-712 gasless vote: castVoteBySig() дозволяє підписати голос off-chain (через Snapshot або Tally) та відправити on-chain через relayer. Користувач не платить газ.
// Голосування через підпис — relayer платить gas function castVoteBySig( uint256 proposalId, uint8 support, address voter, bytes memory signature ) public returns (uint256 weight) { // EIP-712 верифікація підпису voter // ... return _castVote(proposalId, voter, support, "", ""); } Governance — жива система. Параметри потрібно переглядати в міру зростання DAO: quorum, що працював при 10k holders, може бути недосяжним при 500k holders з низькою явкою.
Що входить у розробку governance системи?
- Розробка та деплой смарт-контрактів (Governor, Token, Timelock)
- Налаштування параметрів під ваш протокол
- Інтеграція з Snapshot/Tally (опціонально)
- Аудит безпеки та тестування на тестнеті
- Документація та інструкція з управління
- Підтримка після запуску (1 місяць безкоштовно)
Документація OpenZeppelin Governor
Зв'яжіться з нами для консультації та оцінки вашого проекту. Ми гарантуємо надійну роботу контрактів у mainnet. Замовте розробку безпечної governance системи.







