Разработка квадратичного финансирования (quadratic funding)

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка квадратичного финансирования (quadratic funding)
Сложная
~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

Разработка квадратичного финансирования (quadratic funding)

Quadratic funding — это механизм распределения matching funds, который математически усиливает широкую народную поддержку над концентрированными донациями. Придуман Глен Вейл и Виталик Бутерин. Gitcoin Grants использует его для распределения $50M+ среди публичных благ Web3. Реализация технически нетривиальна: нужно собирать индивидуальные вклады, считать квадратные корни, агрегировать и распределять matching — всё с защитой от Sybil атак.

Математика: как работает QF

Классическая формула для matching суммы проекта:

matching = (Σ √contributionᵢ)² - Σ contributionᵢ

Где сумма берётся по всем донаторам проекта. Важное следствие: 100 донаций по $1 дают matching больше, чем одна донация $100.

Пример:

Проект Донации Сумма донаций Matching расчёт Matching
A 1 × $100 $100 (√100)² - 100 = 100 - 100 = 0 $0
B 100 × $1 $100 (100 × √1)² - 100 = 10000 - 100 $9900
C 10 × $10 $100 (10 × √10)² - 100 ≈ 10000 - 100 ~$900

Итоговый matching нормализуется по matching pool: если total matching > pool, все суммы пропорционально уменьшаются.

Архитектура системы

QF система состоит из четырёх компонентов:

1. Grant Registry: хранит список проектов с метаданными
2. Round Contract: управляет раундом — период приёма донаций, matching pool
3. Donation Collector: принимает взносы, эмитит события
4. Distribution Engine: рассчитывает matching и выплачивает

Расчёт matching делается off-chain (слишком дорого on-chain) и верифицируется through merkle proof или ZK proof перед выплатой.

Смарт-контракты

Round Controller

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

contract QFRound is Ownable {
    using SafeERC20 for IERC20;

    IERC20 public immutable donationToken;  // USDC или DAI
    uint256 public immutable matchingPool;  // фиксируется при создании раунда
    uint256 public immutable roundStart;
    uint256 public immutable roundEnd;

    // project ID => donor => amount
    mapping(uint256 => mapping(address => uint256)) public donations;
    // project ID => total donations
    mapping(uint256 => uint256) public projectTotalDonations;
    // project ID => список донаторов (для off-chain расчёта)
    mapping(uint256 => address[]) public projectDonors;

    bytes32 public matchingMerkleRoot;  // устанавливается после round end
    mapping(uint256 => bool) public matchingClaimed;

    event DonationMade(
        uint256 indexed projectId,
        address indexed donor,
        uint256 amount
    );
    event MatchingDistributed(uint256 indexed projectId, uint256 amount);

    constructor(
        address _token,
        uint256 _matchingPool,
        uint256 _start,
        uint256 _end
    ) Ownable(msg.sender) {
        donationToken = IERC20(_token);
        matchingPool = _matchingPool;
        roundStart = _start;
        roundEnd = _end;
    }

    function donate(uint256 projectId, uint256 amount) external {
        require(block.timestamp >= roundStart, "Round not started");
        require(block.timestamp <= roundEnd, "Round ended");
        require(amount > 0, "Zero amount");

        // Если первая донация — добавляем в список донаторов
        if (donations[projectId][msg.sender] == 0) {
            projectDonors[projectId].push(msg.sender);
        }

        donations[projectId][msg.sender] += amount;
        projectTotalDonations[projectId] += amount;

        donationToken.safeTransferFrom(msg.sender, address(this), amount);
        emit DonationMade(projectId, msg.sender, amount);
    }

    // Устанавливается после off-chain расчёта
    function setMatchingRoot(bytes32 _root) external onlyOwner {
        require(block.timestamp > roundEnd, "Round not ended");
        matchingMerkleRoot = _root;
    }

    // Проект клеймит свой matching через merkle proof
    function claimMatching(
        uint256 projectId,
        address recipient,
        uint256 matchingAmount,
        bytes32[] calldata proof
    ) external {
        require(!matchingClaimed[projectId], "Already claimed");
        require(matchingMerkleRoot != bytes32(0), "Root not set");

        bytes32 leaf = keccak256(bytes.concat(
            keccak256(abi.encode(projectId, recipient, matchingAmount))
        ));

        require(
            MerkleProof.verify(proof, matchingMerkleRoot, leaf),
            "Invalid proof"
        );

        matchingClaimed[projectId] = true;
        donationToken.safeTransfer(recipient, matchingAmount);

        emit MatchingDistributed(projectId, matchingAmount);
    }
}

Off-chain расчёт matching

interface Donation {
  projectId: number;
  donor: string;
  amount: bigint;
}

interface ProjectMatching {
  projectId: number;
  recipient: string;
  matchingAmount: bigint;
}

function calculateQFMatching(
  donations: Donation[],
  matchingPool: bigint
): ProjectMatching[] {
  // Группируем донации по проектам
  const projectDonations = new Map<number, Map<string, bigint>>();

  for (const d of donations) {
    if (!projectDonations.has(d.projectId)) {
      projectDonations.set(d.projectId, new Map());
    }
    const donors = projectDonations.get(d.projectId)!;
    donors.set(d.donor, (donors.get(d.donor) ?? 0n) + d.amount);
  }

  // Рассчитываем QF score для каждого проекта
  const projectScores = new Map<number, bigint>();
  let totalScore = 0n;

  for (const [projectId, donors] of projectDonations) {
    // Σ √contributionᵢ с fixed-point арифметикой
    // Используем 1e9 scale для точности при работе с bigint
    const SCALE = 1_000_000_000n;
    let sumSqrt = 0n;

    for (const amount of donors.values()) {
      // Целочисленный квадратный корень с масштабированием
      const scaledAmount = amount * SCALE * SCALE;
      const sqrt = isqrt(scaledAmount);
      sumSqrt += sqrt;
    }

    // QF score = (Σ √contributionᵢ)²
    const score = (sumSqrt * sumSqrt) / SCALE / SCALE;
    projectScores.set(projectId, score);
    totalScore += score;
  }

  if (totalScore === 0n) return [];

  // Нормализуем: matching = score / totalScore * matchingPool
  const result: ProjectMatching[] = [];
  for (const [projectId, score] of projectScores) {
    const matchingAmount = (score * matchingPool) / totalScore;
    result.push({
      projectId,
      recipient: getProjectRecipient(projectId),
      matchingAmount
    });
  }

  return result;
}

// Целочисленный квадратный корень (метод Ньютона)
function isqrt(n: bigint): bigint {
  if (n < 0n) throw new Error("Negative input");
  if (n < 2n) return n;
  let x = n;
  let y = (x + 1n) / 2n;
  while (y < x) {
    x = y;
    y = (x + n / x) / 2n;
  }
  return x;
}

Sybil resistance

Quadratic funding без Sybil защиты — это просто равномерное распределение, только дороже. Злоумышленник создаёт 100 кошельков, делает минимальные донации с каждого и получает максимальный matching.

Методы защиты

Gitcoin Passport: децентрализованный identity score. Пользователь верифицируется через GitHub, Twitter, ENS, Lens, BrightID и другие провайдеры. Каждый stamp даёт score. Порог для участия в QF: обычно 15-20 баллов.

// Проверка Gitcoin Passport score перед донацией
async function checkPassportScore(address: string): Promise<boolean> {
  const response = await fetch(
    `https://api.scorer.gitcoin.co/registry/score/${SCORER_ID}/${address}`,
    { headers: { "X-API-Key": PASSPORT_API_KEY } }
  );
  const { score } = await response.json();
  return parseFloat(score) >= MINIMUM_PASSPORT_SCORE; // 15.0
}

Proof of Humanity / WorldID: биометрическая верификация уникального человека. WorldID использует ZK-proof для подтверждения "я — уникальный человек" без раскрытия личности.

Connection-weighted QF (COCM): алгоритм Gitcoin Grants 19+. Учитывает связи между донаторами в социальном графе. Пожертвования от кластеров связанных кошельков получают меньший вес — это снижает эффективность Sybil атаки даже при реальных верифицированных аккаунтах.

Pairwise coordination subsidy (PCS)

Расширение QF, которое снижает matching для пожертвований от коррелированных доноров:

adjustedMatching = originalMatching × (1 - correlationFactor)

Если два донора часто жертвуют в одни и те же проекты — их совместный вклад получает штраф. Это снижает влияние координированных групп без полного исключения.

Верификация расчётов через ZK proof

Проблема merkle подхода: оператор теоретически может выставить неверный merkle root. ZK-proof позволяет верифицировать корректность расчёта on-chain без раскрытия всех данных.

Используется Circom + SnarkJS для построения ZK circuit, который доказывает:

  • Все донации из определённого набора commitment-ов
  • Расчёт QF формулы применён корректно
  • Итоговые matching суммы соответствуют merkle листьям

Это активно развивающаяся область — Gitcoin Allo Protocol движется в этом направлении. Для production 2024-2025 — merkle подход с доверенным оператором (DAO multisig) остаётся практичным вариантом.

Интеграция с Gitcoin Allo Protocol

Gitcoin Allo Protocol v2 — open-source фреймворк для capital allocation с QF стратегиями. Вместо написания с нуля:

import { IAllo } from "@gitcoin/allo-v2/contracts/core/interfaces/IAllo.sol";

// Создание QF пула через Allo
allo.createPool(
    profileId,           // Gitcoin registry profile
    QF_STRATEGY,         // адрес QFVotingStrategy контракта
    initData,            // параметры раунда
    token,               // USDC
    matchingAmount,      // matching pool
    metadata,            // IPFS metadata
    managers             // кто управляет раундом
);

Allo Protocol уже имеет QF стратегию, Sybil защиту через Passport и интерфейс для проектов. Кастомная реализация имеет смысл при специфических требованиях — например, нативный токен вместо stablecoins или нестандартная формула взвешивания.