Разработка контрактов ликвидити-майнинга

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

Разработка контрактов ликвидити-майнинга

Liquidity mining — механизм, при котором пользователи предоставляют ликвидность и получают вознаграждение в токенах протокола. SushiSwap в 2020 году провёл «vampire attack» на Uniswap именно через это: задеплоил аналогичный AMM с агрессивными SUSHI-вознаграждениями и в течение 24 часов переманил $1B TVL. Механика работает.

Но за внешней простотой «стейкаешь LP-токен → получаешь вознаграждение» скрывается нетривиальная математика и несколько классов уязвимостей, которые регулярно встречаются в production.

Математика распределения наград

Алгоритм Masterchef (Synthetix StakingRewards)

Базовый алгоритм, используемый большинством протоколов, основан на накопленной стоимости вознаграждения на единицу стейка (rewardPerTokenStored).

// Накопленное вознаграждение на токен
uint256 public rewardPerTokenStored;

function rewardPerToken() public view returns (uint256) {
    if (totalSupply == 0) return rewardPerTokenStored;
    return rewardPerTokenStored + (
        (block.timestamp - lastUpdateTime) 
        * rewardRate 
        * 1e18 
        / totalSupply
    );
}

// Вознаграждение конкретного пользователя
function earned(address account) public view returns (uint256) {
    return (balanceOf[account] * 
        (rewardPerToken() - userRewardPerTokenPaid[account]) 
        / 1e18) 
        + rewards[account];
}

Ключевое свойство: обновление rewardPerTokenStored происходит при каждом stake/withdraw/getReward, а не при каждом блоке. Это O(1) операция независимо от количества участников.

При каждом взаимодействии пользователя сохраняем userRewardPerTokenPaid[user] = rewardPerToken(). Разница между текущим rewardPerToken() и сохранённым значением — накопленное вознаграждение с момента последнего взаимодействия.

Эта математика корректна при непрерывном времени. На практике дискретные блоки создают погрешность округления, которую нужно учитывать.

Boosted Rewards (ve-токены)

Convex/Curve-модель: вознаграждение зависит не только от суммы стейка, но и от количества заблокированного governance-токена (veToken). Формула:

effective_balance = min(
    0.4 * balance + 0.6 * (totalSupply * veBal / veTotalSupply),
    balance
)

Это создаёт incentive холдить governance-токен, снижает sell pressure на reward-токен. Математически сложнее, требует точного тестирования edge cases при нулевом veBalance.

Multi-reward распределение

Когда нужно раздавать несколько токенов одновременно (например, протокольный токен + USDC fee sharing), архитектура усложняется. Каждый reward-токен требует собственный rewardPerTokenStored. OpenZeppelin не предоставляет готовую реализацию для multi-reward; используем Synthetix StakingMultiRewards как референс.

Уязвимости, которые мы видели в аудитах

Inflation attack при первом депозите

При пустом стейкинг-контракте (totalSupply = 0) первый пользователь может манипулировать rewardPerToken(). Атака: задеплоили контракт, атакующий делает микро-депозит (1 wei), контракт накапливает все вознаграждения для 1 wei, атакующий получает непропорционально большую долю.

Решение: виртуальный начальный баланс (VIRTUAL_TOTAL_SUPPLY = 1e18), который никогда не снимается, или проверка минимального депозита.

Flash loan attack на rewards

Атакующий берёт flash loan на LP-токены, стейкает огромную сумму, получает вознаграждение за один блок, выводит. При высоком rewardRate это может быть прибыльно.

Защита: минимальный период стейка (lockup period, 1-7 дней). Альтернатива — vesting вознаграждений: не выдавать сразу, а линейно в течение N дней. Даже 24-часовой vesting делает flash loan атаку нерентабельной.

Griefing через updateReward

Функции stake, withdraw, getReward должны вызывать _updateReward(msg.sender). Если это modifier — каждый вызов обновляет состояние для msg.sender. Но если _updateReward дорогостоящий (например, итерирует по массиву), внешний пользователь может вызывать функции в цикле и делать контракт недоступным через gas griefing.

Решение: _updateReward должен быть O(1). Не итерировать по массивам участников.

Precision loss при малых суммах

При делении rewardRate * dt / totalSupply, если totalSupply очень большой, а dt маленький, результат может быть 0 из-за целочисленного деления. Вознаграждения «испаряются».

Решение: масштабирование через 1e18 (или 1e27 для Aave-like precision), накопление remainder'ов.

Экономика и параметры

rewardRate — количество reward-токенов в секунду. Рассчитывается как rewardAmount / duration. При notifyRewardAmount() пересчитывается с учётом остатка текущего периода.

function notifyRewardAmount(uint256 reward) external onlyOwner {
    if (block.timestamp >= periodFinish) {
        rewardRate = reward / rewardsDuration;
    } else {
        uint256 remaining = periodFinish - block.timestamp;
        uint256 leftover = remaining * rewardRate;
        rewardRate = (reward + leftover) / rewardsDuration;
    }
    // ...
}

Emergency withdrawal — пользователь должен иметь возможность вывести stake даже если contarct на паузе. Награды при этом можно не выдавать, но основной стейк — обязательно. Этого требует не только здравый смысл, но и некоторые юрисдикции.

Процесс работы

Проектирование (0.5-1 день). Определяем: один reward-токен или несколько, нужен ли boosting, минимальный lockup, механизм пополнения наград (manual vs автоматизированный).

Разработка (2-3 дня). Основной контракт, события для indexing (The Graph субграф строится по events), тесты с fuzz-тестированием арифметики.

Тесты (1-2 дня). Покрываем: нормальный стейкинг/вывод, flash loan сценарий с минимальным lockup, precision tests с граничными значениями, multi-user scenarios с разными пропорциями.

Деплой. Отдельный deployer-скрипт с конфигурацией первоначального rewardRate, transferOwnership на multisig.

Для большинства проектов полный цикл — 3-5 рабочих дней. Сложные схемы с veToken boosting и multiple reward tokens — 8-12 дней.