Разработка системы грантов DAO

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка системы грантов DAO
Средняя
~1-2 недели
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1258
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1170
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    873
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1092
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    563
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    830

Разработка системы предложений и голосований DAO

Если DAO уже существует, а система голосований должна быть доработана или построена с нуля — значит нужно чётко понимать что именно строим: полноценный on-chain Governor, легковесный off-chain механизм, или гибрид. Выбор зависит от числа активных участников, размера treasury и требований к decentralization.

Здесь — детально про механику proposals и voting, включая паттерны, которые не очевидны из документации.

Жизненный цикл proposal

Создание и snapshot

Proposal создаётся вызовом governor.propose(). В момент создания фиксируется proposalSnapshot — номер блока, на котором будет считаться voting power. Это критично: если snapshot совпадает с текущим блоком, злоумышленник может в том же блоке купить токены и проголосовать ими.

Поэтому votingDelay — количество блоков/секунд между созданием proposal и началом голосования — должен быть ненулевым. Compound использует 1 день (6570 блоков на Ethereum mainnet), Aave — 1 день.

// GovernorSettings параметры
uint48 public constant VOTING_DELAY = 1 days;    // задержка до начала голосования
uint32 public constant VOTING_PERIOD = 7 days;   // длительность голосования
uint256 public constant PROPOSAL_THRESHOLD = 100_000e18; // минимум токенов для создания

Голосование: простое vs взвешенное

GovernorCountingSimple — стандарт: FOR, AGAINST, ABSTAIN. Proposal проходит если: (1) quorum набран (голосов не меньше порога), (2) FOR > AGAINST.

Fractional voting — более продвинутый паттерн: делегат может распределить voting power между вариантами дробно. Полезно когда делегат хочет выразить позицию своих доверителей, а они расходятся во мнениях. Реализуется через custom GovernorCountingFractional (есть fork от a16z).

Quadratic voting — voting power = sqrt(токенов). Выравнивает влияние крупных и мелких держателей. Сложно реализовать on-chain честно из-за Sybil: адрес с 10000 токенами может разбить их на 100 адресов по 100 токенов, получив в 10 раз больше влияния. Требует identity verification (Worldcoin, Gitcoin Passport) для Sybil resistance.

Quorum и его расчёт

Quorum — минимальное количество голосов (FOR + AGAINST + ABSTAIN) для валидности голосования. GovernorVotesQuorumFraction считает quorum как процент от total supply на момент snapshot.

Проблема: если вестинг постепенно разблокирует токены, total supply растёт — quorum в абсолютных числах тоже растёт. При высоком росте supply ранние proposals проходили с меньшим quorum. getPastTotalSupply(proposalSnapshot) решает это — quorum считается от supply на момент snapshot, не текущего.

function quorum(uint256 timepoint) public view override returns (uint256) {
    return token.getPastTotalSupply(timepoint) * quorumNumerator(timepoint) / quorumDenominator();
}

Delegation механизм

Fluid delegation

ERC20Votes позволяет менять делегата в любой момент. Изменение вступает в силу сразу для будущих голосований, но не retroactively — для открытых proposals уже зафиксирован snapshot.

Это создаёт интересную динамику: перед важным голосованием активные участники агрессивно собирают делегирования. Компании-делегаты (Gauntlet, a16z governance team, Blockchain@UMich) публично декларируют позицию по каждому вопросу, привлекая пассивных держателей.

Subdelegation

Стандартный ERC20Votes не поддерживает subdelegation: если A делегировал B, B не может делегировать дальше C (B использует свои собственные токены + делегированные вместе, но не может subdelegation). Compound v3 Governor ввёл partial delegation и subdelegation через отдельный механизм.

Для сложных governance систем с delegate hierarchies — нужен кастомный extension поверх ERC20Votes.

Типы proposals и их механика

Single-action proposals

Простейший случай: одно действие — изменить параметр. Например, поменять процентную ставку в lending протоколе:

targets = [address(lendingPool)];
values = [0];
calldatas = [abi.encodeWithSelector(ILendingPool.setInterestRate.selector, newRate)];

Multi-action proposals (batched)

Governor поддерживает массивы targets/values/calldatas — все действия исполняются атомарно. Полезно для связанных изменений: например, обновить реализацию контракта И обновить параметры в одном proposal. Если любое действие ревертится — всё ревертится.

Proposal cancellation

Создатель proposal может отменить его до начала голосования. Это защита от ошибок (неверный calldata). После начала голосования — только Guardian с CANCELLER ролью в TimelockController.

function cancel(
    address[] memory targets,
    uint256[] memory values,
    bytes[] memory calldatas,
    bytes32 descriptionHash
) public returns (uint256) {
    uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash);
    require(
        _msgSender() == proposalProposer(proposalId),
        "Only proposer can cancel"
    );
    return _cancel(targets, values, calldatas, descriptionHash);
}

Prevent Late Quorum extension

Классическая атака: большой держатель ждёт последних минут голосования, когда результат кажется предрешённым, и меняет исход одним голосом. У оппонентов нет времени среагировать.

GovernorPreventLateQuorum — расширение, которое продлевает voting period если quorum набран в последние N блоков перед дедлайном:

function _castVote(...) internal override returns (uint256) {
    uint256 result = super._castVote(...);
    
    uint256 deadline = proposalDeadline(proposalId);
    if (deadline - block.number < voteExtension && _quorumReached(proposalId)) {
        // продлить deadline
        _extendedDeadlines[proposalId] = block.number + voteExtension;
        emit ProposalExtended(proposalId, block.number + voteExtension);
    }
    
    return result;
}

Compound Governance использует аналогичный механизм. Это важно для fairness, особенно на ранней стадии с малым числом активных участников.

Proposal description и metadata

Description в proposal — произвольная строка. Стандарт: первая строка — заголовок, остальное — Markdown описание. Индексаторы (Tally, Boardroom) парсят это для отображения.

Deskription hash: keccak256(bytes(description)) входит в proposalId расчёт. Это позволяет верифицировать что описание не менялось после создания proposal.

IPFS для больших описаний. Если proposal включает длинный текст (specification, audit report ссылки) — description хранит только CID: ipfs://Qm.... IPFS контент immutable — гарантия что условия proposal не изменятся.

Frontend и UX

Tally и Boardroom как готовые решения

Tally.xyz и Boardroom.io — готовые governance UI. Поддерживают OpenZeppelin Governor и Governor Bravo. Бесплатны для public DAO. Дают: список proposals, voting interface, delegate directory, treasury view.

Минус: брендинг и кастомизация ограничены. Для embedded governance в собственном dApp — нужен custom UI.

Custom governance UI

Ключевые данные для frontend:

  • Список proposals: ProposalCreated события через The Graph субграф
  • Voting power пользователя: token.getVotes(address) для текущей, token.getPastVotes(address, blockNumber) для snapshot
  • Delegate информация: Tally API или собственный индексер

wagmi hooks для голосования:

const { writeContract } = useWriteContract()

function castVote(proposalId: bigint, support: 0 | 1 | 2) {
  writeContract({
    address: GOVERNOR_ADDRESS,
    abi: governorAbi,
    functionName: 'castVoteWithReason',
    args: [proposalId, support, 'My reasoning here'],
  })
}

Сроки и scope

Базовая система proposals и голосований на OpenZeppelin Governor — 1-2 недели разработки контракта + 1 неделя тестов. Custom voting механика (fractional, quadratic) — +1-2 недели. Frontend с полным governance UI — 3-4 недели.

Аудит обязателен если Governor управляет реальными средствами. Типичный объём аудита governance контракта — 1-2 недели у квалифицированной команды.