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

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Розробка системи грантів DAO
Середній
~1-2 тижні
Часті запитання

Напрямки блокчейн-розробки

Етапи блокчейн-розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1306
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1218
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    920
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1147
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    610
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    885

Розробка системи пропозицій та голосування

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

Тут — детально про механіку пропозицій та голосування, включаючи паттерни не очевидні з документації.

Життєвий цикл пропозиції

Створення та снапшот

Пропозиція створюється викликом governor.propose(). При створенні фіксується proposalSnapshot — номер блоку, де буде враховуватися голосувальна сила. Це критично: якщо снапшот збігається з поточним блоком, зловмисник може купити токени в одному блоці й проголосувати.

Тому votingDelay — блоки/секунди між створенням пропозиції й початком голосування — повинен бути ненульовим. 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. Пропозиція проходить якщо: (1) кворум набраний (голосів не менше порога), (2) FOR > AGAINST.

Дробове голосування — більш продвинуте: делегат може розподілити голосувальну силу дробово між варіантами. Корисна коли делегат хоче виразити позиції виборців, де вони не погоджуються. Реалізується через користувацький GovernorCountingFractional (форк від a16z існує).

Квадратичне голосування — голосувальна сила = sqrt(токени). Вирівнює вплив великих і малих емітентів. Важко реалізувати on-chain чесно через Sybil: адреса з 10000 токенів може розділитися на 100 адрес по 100 токенів кожна, отримавши в 10 разів більше впливу. Вимагає верифікації особистості (Worldcoin, Gitcoin Passport) для Sybil-стійкості.

Кворум та розрахунок

Кворум — мінімум голосів (FOR + AGAINST + ABSTAIN) для валідності. GovernorVotesQuorumFraction рахує кворум як відсоток від загального у момент снапшоту.

Проблема: якщо вестинг поступово розблоковує токени, загальна кількість росте — кворум в абсолютних числах теж. При високому зростанні пропозиції ранні пройшли з нижчим кворумом. getPastTotalSupply(proposalSnapshot) вирішує це — кворум рахується від пропозиції на знапшоту, не поточної.

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

Механізм делегування

Рідке делегування

ERC20Votes дозволяє змінювати делегата в будь-який час. Зміна вступає в силу одразу для майбутніх голосувань, але не ретроактивно — для відкритих пропозицій снапшот вже фіксований.

Це створює цікаву динаміку: перед важливим голосуванням активні учасники агресивно збирають делегування. Компанії-делегати (Gauntlet, команда a16z governance, Blockchain@UMich) публічно декларують позиції щодо кожного питання, привертаючи пасивних емітентів.

Піддельгування

Стандартний ERC20Votes не підтримує піддельгування: якщо A делегував B, B не може далі делегувати C (B використовує власні токени + делеговані разом, але не піддельгування). Compound v3 Governor запровадив часткове делегування та піддельгування через окремий механізм.

Для складного управління з ієрархіями делегатів — потрібно користувацьке розширення поверх ERC20Votes.

Типи пропозицій та механіка

Однодійні пропозиції

Найпростіше: одна дія — змінити параметр. Приклад: змінити процентну ставку в протоколі лендингу:

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

Мультидійні пропозиції (групові)

Governor підтримує масиви targets/values/calldatas — всі дії виконуються атомарно. Корисна для пов'язаних змін: оновити реалізацію контракту І оновити параметри в одній пропозиції. Якщо будь-яка дія реверт — всі реверт.

Скасування пропозиції

Творець може скасувати пропозицію перед початком голосування. Захищає від помилок (неправильні calldatas). Після початку голосування — тільки 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);
}

Розширення запобігання пізньому кворуму

Класична атака: великий емітент чекає останніх хвилин голосування, коли результат здається вирішеним, потім змінює результат одним голосом. Опоненти не мають часу реагувати.

GovernorPreventLateQuorum — розширення розширює період голосування, якщо кворум набраний в останніх N блоках перед дедлайном:

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

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

Фронтенд та UX

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

Tally.xyz та Boardroom.io — готові UI управління. Підтримують OpenZeppelin Governor та Governor Bravo. Безкоштовні для публічних DAO. Надають: списки пропозицій, інтерфейс голосування, директорію делегатів, перегляд казни.

Мінус: брендинг та налаштування обмежені. Для вбудованого управління у власному dApp — потрібен користувацький UI.

Користувацький UI управління

Ключові дані фронтенду:

  • Список пропозицій: ProposalCreated подій через The Graph subgraph
  • Голосувальна сила користувача: token.getVotes(address) для поточної, token.getPastVotes(address, blockNumber) для снапшоту
  • Інформація делегата: Tally API або власний індексер

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

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'],
  })
}

Графік і обсяг

Базова система пропозицій та голосування на OpenZeppelin Governor — 1-2 тижні розробки контракту плюс 1 тиждень тестування. Користувацька механіка голосування (дробова, квадратична) — +1-2 тижні. Фронтенд з повним UI управління — 3-4 тижні.

Аудит обов'язковий, якщо Governor керує реальними коштами. Типовий обсяг аудиту контракту управління — 1-2 тижні від кваліфікованої команди.