Разработка протокола флеш-займов
Flash loan — это атомарный кредит: берёшь любую сумму, делаешь что угодно, возвращаешь в той же транзакции. Если возврат не произошёл — вся транзакция реверсируется. Никакого залога, никакой кредитной истории. Aave V3 держит в пулах флеш-займов сотни миллионов долларов — и это работает, потому что атомарность EVM является абсолютной гарантией.
Но та же атомарность делает flash loans главным инструментом атак на DeFi. Из 10 крупнейших взломов последних трёх лет — минимум 7 использовали flash loans как стартовый капитал для oracle manipulation, price manipulation или governance attacks.
Архитектура flash loan протокола
Механика исполнения
Классическая схема Aave V2: пул вызывает executeOperation() на адресе receiver, передаёт активы, receiver делает что нужно, возвращает assets + premium. Весь сценарий — один вызов flashLoan(), одна транзакция, один блок.
Aave V3 добавил flashLoanSimple() для одного актива (дешевле по газу) и переработал интерфейс receiver на IFlashLoanSimpleReceiver. EIP-3156 формализовал стандарт: flashLoan(receiver, token, amount, data) и обязательный коллбэк onFlashLoan(). Если строим протокол с расчётом на интеграцию — имеет смысл поддерживать оба интерфейса.
Типы реализаций
Single-asset flash loans (EIP-3156 compliant): самый простой вариант. Один токен, один receiver. Газ минимален. Подходит для протоколов, которые хотят монетизировать простаивающую ликвидность.
Multi-asset batch loans (Aave-style): несколько токенов в одной транзакции. Сложнее имплементация, зато открывает сценарии типа «занять ETH + USDC одновременно для арбитража на пуле». Реализуется через массивы assets/amounts в одном вызове.
Callback-less flash loans (Uniswap V2-style): пул отправляет токены сначала, receiver возвращает в конце транзакции через отдельный вызов. Менее безопасная конструкция — receiver должен сам проверять, что его не вызывают повторно.
Ключевые проблемы безопасности
Reentrancy через флеш-займ коллбэк
Стандартная ловушка: receiver контракт вызывает другую функцию пула внутри executeOperation(). Если пул не защищён reentrancy guard на уровне хранилища — атакующий может изменить состояние пула до завершения оригинальной транзакции.
// НЕПРАВИЛЬНО: reentrancy возможна
function flashLoan(address receiver, uint256 amount) external {
uint256 balanceBefore = token.balanceOf(address(this));
token.transfer(receiver, amount);
IFlashLoanReceiver(receiver).executeOperation(amount, fee, msg.sender);
// receiver мог вызвать deposit() и изменить balanceBefore-логику
require(token.balanceOf(address(this)) >= balanceBefore + fee);
}
Решение: nonReentrant modifier от OpenZeppelin на flashLoan() и на все функции, меняющие state пула (deposit, withdraw, borrow).
Проверка caller в receiver контракте
Receiver должен проверять, что executeOperation() вызывает именно доверенный пул, а не произвольный адрес. Без этой проверки атакующий может напрямую вызвать executeOperation() на receiver, имитируя флеш-займ.
function executeOperation(
address asset,
uint256 amount,
uint256 premium,
address initiator,
bytes calldata params
) external override returns (bool) {
require(msg.sender == address(LENDING_POOL), "Invalid caller");
require(initiator == address(this), "Invalid initiator");
// логика
}
Fee accounting и precision issues
Типичная ошибка: fee считается как amount * FEE_BPS / 10000, где FEE_BPS = 9 (0.09%). При маленьких суммах результат может округлиться до 0 из-за целочисленного деления. Атакующий дробит один большой займ на тысячи маленьких и платит нулевую комиссию.
Защита: минимальный fee в абсолютном значении (например, 1 wei) и проверка amountOwed > amount — не просто amountOwed >= amount + fee_calculated.
Как строим флеш-займ протокол
Стек: Solidity 0.8.x, OpenZeppelin 5.x для ReentrancyGuard и SafeERC20, Foundry для тестирования.
Пул контракт хранит ликвидность провайдеров. Поддерживаем несколько токенов через маппинг token => PoolState. PoolState включает: totalLiquidity, totalBorrowed (для учёта одновременных займов), feeAccumulator для LP-наград.
Fee distribution: комиссия с флеш-займов накапливается в пуле и повышает exchange rate LP-токенов (как Compound cTokens). LP получают yield без отдельного claim — просто при выводе получают больше базового токена.
Access control: через OpenZeppelin AccessControl. Роли: PAUSER_ROLE (circuit breaker при атаке), FEE_SETTER_ROLE (под timelock governance), ASSET_MANAGER_ROLE (добавление новых токенов).
Circuit breaker: если за один блок из пула ушло более X% ликвидности — автоматическая пауза. Реализуется через _blockLoanVolume маппинг и проверку в начале flashLoan(). Ложные срабатывания возможны при легитимном использовании — порог выставляем через governance.
Тестирование
Fork-тесты на Ethereum mainnet критичны. Гоняем через Foundry:
- Стандартный займ и возврат с комиссией
- Попытка невозврата — транзакция должна реверснуться
- Reentrancy атака на
flashLoan()через вредоносный receiver - Нулевой fee при минимальной сумме (проверяем anti-dust логику)
- Паника: 100 последовательных займов максимального объёма
Fuzzing в Echidna с инвариантом: totalLiquidity после любой последовательности операций не меньше суммы депозитов минус снятия.
Легитимные use cases — почему это нужно строить
Flash loans — это не только атаки. Протокол открывает:
Арбитраж без капитала: трейдер видит разницу цен между Uniswap и Curve, берёт займ, исполняет арбитраж, возвращает. Прибыль — spread минус gas. DEX-арбитраж выравнивает цены и делает рынки эффективнее.
Self-liquidation: пользователь с позицией в Aave, близкой к liquidation, берёт flash loan, погашает долг, выводит залог, возвращает займ. Избегает penalty от внешних ликвидаторов.
Collateral swap: заменить ETH залог на WBTC без закрытия позиции. Одна транзакция вместо четырёх.
Leverage unwinding: закрыть leveraged position в одной транзакции.
Ориентиры по срокам
Базовый EIP-3156 compliant протокол для одного токена — 3-5 дней включая тесты. Multi-asset пул с LP-токенами и fee distribution — 1-1.5 недели. Интеграция с существующим lending протоколом (добавление flash loans поверх) — 1-2 недели в зависимости от архитектуры базового протокола. Стоимость рассчитывается после анализа требований.







