Розробка контрактів голосування (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 системи.







