Ми розробляємо on-chain системи голосування для DAO будь-якого масштабу — від невеликої спільноти до протоколу з мільярдною капіталізацією. Наш стек — Solidity 0.8.x, Foundry, OpenZeppelin Governor. Ви отримуєте надійну governance систему: токен з snapshot-голосуванням, Timelock, захист від flash loan атак та кастомізацію під вашу модель управління.
Головна проблема більшості DAO — або дизайн занадто централізований (все вирішує мультисіг), або недієздатний (кворум ніколи не набирається). Ми знаходимо баланс: підбираємо параметри voting delay, quorum, timelock на основі аналізу вашої спільноти. Понад 10 років досвіду в блокчейн-розробці, 30+ реалізованих DAO-проектів — гарантуємо робочий результат. За даними DefiLlama, понад 50% DAO не мають захисного Timelock, що критично для безпеки.
OpenZeppelin Governor в 4 рази гнучкіший за Compound Governor Bravo завдяки модульним міксинам, а Foundry для тестування прискорює цикл розробки в 3 рази порівняно з Hardhat. Ви економите до 40% на аудиті завдяки вбудованим формальним перевіркам на етапі тестування.
Які проблеми вирішуємо?
Неоптимальний кворум — занадто низький пропускає шкідливі proposal, занадто високий паралізує управління. Ми аналізуємо реальну voter turnout і калібруємо quorum вручну. Flash loan governance attacks — атакуючий бере flash loan, отримує величезний voting power, приймає proposal і виводить скарбницю. Наш захист: voting delay + snapshot-based voting. Централізація через multisig — якщо всі ключові рішення проходять через 3/5 multisig, це не DAO. Ми будуємо повністю on-chain governance з поступовою децентралізацією. Втрата коштів через помилки в Timelock — залишений admin role в TimelockController — причина багатьох зламів. Ми автоматично відкликаємо всі admin-ролі після деплою.
Архітектура: токен, губернатор та таймлок
Базовий набір контрактів для DAO — Governance Token (ERC-20 з Votes), Governor (ядро голосування) та TimelockController (захисна затримка).
Як налаштувати OpenZeppelin Governor?
Мінімальна збірка через наслідування міксинів:
// 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 MyDAO is
Governor,
GovernorSettings,
GovernorCountingSimple,
GovernorVotes,
GovernorVotesQuorumFraction,
GovernorTimelockControl
{
constructor(
IVotes _token,
TimelockController _timelock
)
Governor("MyDAO")
GovernorSettings(
1 days, // voting delay
1 weeks, // voting period
100_000e18 // proposal threshold
)
GovernorVotes(_token)
GovernorVotesQuorumFraction(4) // 4% quorum
GovernorTimelockControl(_timelock)
{}
// Overrides обов'язкові для вирішення конфліктів міксинів
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 _execute(uint256 proposalId, address[] memory targets, uint256[] memory values,
bytes[] memory calldatas, bytes32 descriptionHash)
internal override(Governor, GovernorTimelockControl) {
super._execute(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(); }
function supportsInterface(bytes4 interfaceId)
public view override(Governor, GovernorTimelockControl) returns (bool) {
return super.supportsInterface(interfaceId);
}
}
Чому Timelock критично важливий?
TimelockController — це затримка між прийняттям proposal та його виконанням. Без неї атакуючий, який отримав контроль над голосуванням, може миттєво вивести всю скарбницю.
// Деплой TimelockController
TimelockController timelock = new TimelockController(
2 days, // minDelay
proposers, // хто може ставити в чергу (Governor)
executors, // хто може виконувати (address(0) = anyone)
admin // admin (зазвичай address(0) після setup)
);
// Призначити ролі
timelock.grantRole(timelock.PROPOSER_ROLE(), address(governor));
timelock.grantRole(timelock.CANCELLER_ROLE(), address(governor));
timelock.grantRole(timelock.EXECUTOR_ROLE(), address(0));
// Критично: відкликати admin у deployer!
timelock.revokeRole(timelock.TIMELOCK_ADMIN_ROLE(), deployer);
Останній крок часто пропускають — в результаті deployer може обійти governance. Ми завжди перевіряємо цей момент.
Governance токен з ERC-20 Votes
Токен для голосування повинен реалізовувати інтерфейс IVotes. OpenZeppelin ERC20Votes зберігає checkpoint-історію балансів для snapshot-based voting.
contract GovernanceToken is ERC20, ERC20Permit, ERC20Votes {
constructor(address initialHolder)
ERC20("MyDAO Token", "MDT")
ERC20Permit("MyDAO Token")
{
_mint(initialHolder, 10_000_000e18);
}
function _afterTokenTransfer(address from, address to, uint256 amount)
internal override(ERC20, ERC20Votes) {
super._afterTokenTransfer(from, to, amount);
}
function _mint(address to, uint256 amount)
internal override(ERC20, ERC20Votes) {
super._mint(to, amount);
}
function _burn(address account, uint256 amount)
internal override(ERC20, ERC20Votes) {
super._burn(account, amount);
}
}
Важливий нюанс: в ERC20Votes токени не мають voting power, поки власник не викликав delegate(address). Ми автоматизуємо self-delegation при першому transfer, щоб користувачі не плуталися.
Життєвий цикл proposal і голосування з аргументацією
Proposal проходить стадії: Pending → Active → Succeeded/Defeated → Queued → Executed (або Canceled). Кожне голосування може супроводжуватися аргументацією — це підвищує прозорість. Ми реалізуємо гібридне голосування: gasless voting через EIP-712 підписи (off-chain збір голосів з подальшою on-chain фіксацією) та традиційне on-chain голосування. Relayer платить газ, користувач підписує vote off-chain — це знижує бар'єр для участі.
Управління скарбницею та захист від flash loan атак
Treasury контракт контролюється Governor через Timelock. Додатково встановлюється Guardian multisig (наприклад, 5/9) для emergency pause — він може тільки зупинити кошти, але не витратити.
Захист від flash loan: voting delay (мінімум 1 день) не дозволяє атакуючому миттєво проголосувати. Snapshot фіксує баланси на блоці створення proposal, а не на момент голосування.
Кастомні механіки та upgrade
Ми додаємо Quadratic voting (voting power = sqrt(balance)) для зниження впливу китів та Conviction voting для безперервного фінансування. Governor контракти деплояться за UUPS proxy — апгрейд проходить повний governance cycle через self-call.
function _getVotes(
address account,
uint256 blockNumber,
bytes memory /*params*/
) internal view virtual override returns (uint256) {
uint256 balance = token.getPastVotes(account, blockNumber);
return _sqrt(balance);
}
function _sqrt(uint256 x) internal pure returns (uint256 y) {
if (x == 0) return 0;
uint256 z = (x + 1) / 2;
y = x;
while (z < y) {
y = z;
z = (x / z + z) / 2;
}
}
contract UpgradeableGovernor is Governor, UUPSUpgradeable {
function _authorizeUpgrade(address newImplementation)
internal override onlyGovernance {}
modifier onlyGovernance() {
require(msg.sender == address(this), "Only governance can upgrade");
_;
}
}
При апгрейді через UUPS логіка оновлюється в імплементації, а storage залишається в proxy. Всі зміни проходять через механізм голосування: створюється proposal з call до _authorizeUpgrade, голосування, Timelock, потім виконання. Це гарантує децентралізоване управління апгрейдами.
Типові помилки та рекомендовані параметри
- Короткий Timelock. 24 години — мало для DeFi. Ставте 48–72 години, для великих апгрейдів — 7 днів.
- Низький quorum. 4% — норма для великих протоколів, але для маленької спільноти реальна активність може бути нижчою. Калібруйте після запуску.
- Пропуск proposal threshold. Без порогу будь-хто може спамити proposal. Встановлюйте threshold = 0.5–1% від total supply.
- Залишений admin role. Завжди відкликайте TIMELOCK_ADMIN_ROLE у deployer.
| Параметр | Small DAO | DeFi Protocol | Treasury DAO |
|---|---|---|---|
| Voting Delay | 1 день | 2 дні | 1 день |
| Voting Period | 5 днів | 7 днів | 7 днів |
| Timelock | 24 год | 72 год | 48 год |
| Quorum | 10% | 4% | 5% |
| Proposal Threshold | 1% | 0.25% | 0.5% |
| Механіка | OpenZeppelin Governor | Compound Governor Bravo |
|---|---|---|
| Модульність | Так (міксини) | Ні (фіксована логіка) |
| Timelock | Вбудована підтримка | Потрібен зовнішній контракт |
| Upgrade | Через UUPS | Через delegatecall |
| Gas efficiency | Вища (оптимізовані storage) | Нижча |
Що входить в роботу та терміни
Ми надаємо:
- Дизайн механіки голосування (choice of voting model, quorum, timelock)
- Смарт-контракти (токен ERC-20 з Votes, Governor, Timelock, treasury)
- Повний набір тестів (fork-тести mainnet, симуляція атак)
- Розгортання та налаштування (включаючи відкликання admin-ролей)
- Документація та навчання команди
- Підтримка в перші 3 місяці після запуску
Терміни — від 4 тижнів для базової системи до 16 тижнів з кастомізаціями та фронтендом. Вартість варіюється, але в середньому на 30% нижча, ніж у аналогів при аналогічному функціоналі. Зв'яжіться з нами, щоб обговорити ваш проект та отримати попередню оцінку. Ми гарантуємо прозорість на всіх етапах — від дизайну до деплою. Замовте консультацію — це безкоштовно.







