// Step 1: Now the pool has less tokenA, so the price of tokenA (in terms of tokenB) is higher
// Step 2: Swap tokenA for tokenB at this manipulated rate
uint256 swapAmount = 100 * 1e18;
tokenA.transferFrom(attacker, address(this), swapAmount);
tokenA.approve(address(pool), swapAmount);
uint256 receivedB = pool.swap(tokenA, swapAmount);
// Step 3: Repay the flash loan
tokenA.transfer(address(pool), loanAmount);
// Step 4: Send profits to attacker
uint256 remainingBalanceA = tokenA.balanceOf(address(this));
uint256 remainingBalanceB = tokenB.balanceOf(address(this));
if (remainingBalanceA > 0) {
tokenA.transfer(attacker, remainingBalanceA);
}
if (remainingBalanceB > 0) {
tokenB.transfer(attacker, remainingBalanceB);
}
}
}
В этом сценарии функция getPrice контракта пула рассчитывает обменный курс на основе текущих остатков tokenA и tokenB, хранящихся в пуле. Эта зависимость от внутреннего, легко изменяемого состояния является основной уязвимостью.
Как работает атака?
1. Настройка: злоумышленник финансирует контракт Exploit небольшой суммой токена A (swapAmount), которую он будет использовать для свопа.
2. Flashloan: злоумышленник вызывает атаку на контракт Exploit, инициируя крупный флэш-кредит токена A из самого контракта Pool. Этот кредит выполняется до того, как функция receiveFlashLoan возвращает результат.
3. Манипулирование ценой (внутри receiveFlashLoan): пока флэш-заем активен, баланс токена A в пуле искусственно занижен. Когда контракт Exploit вызывает pool.swap(tokenA, swapAmount), функция getPrice пула рассчитывает цену на основе этих временно искаженных низких резервов токена A. Это приводит к манипулированию ценой, в результате чего токен A выглядит гораздо более ценным, чем в нормальных условиях.
5. Своп (внутри receiveFlashLoan): контракт Exploit обменивает небольшую сумму собственного предварительно профинансированного tokenA на tokenB с помощью функции pool.swap. Из-за манипулируемой цены этот своп приносит непропорционально большую сумму tokenB.
6. Погашение кредита (внутри receiveFlashLoan): контракт Exploit погашает крупный флэш-кредит tokenA контракту Pool. Вызов receiveFlashLoan должен завершиться успешно, что означает погашение кредита.
7. Прибыль (после возврата receiveFlashLoan): Контракт Exploit теперь содержит исходное количество tokenA (или немного меньше из-за комиссий, хотя в этом упрощенном примере комиссии опущены) плюс избыточное количество tokenB, полученное в результате манипулирования свопом. Оставшиеся токены отправляются обратно на адрес злоумышленника, что приносит прибыль в tokenB.
Исправление: использование надежных источников цен (оракулов)
Надежным решением является отказ от использования соотношений балансов в цепочке для расчета цен и интеграция внешних оракулов цен, таких как Chainlink. Оракулы предоставляют надежные, защищенные от подделки данные о ценах вне цепочки, что значительно упрочняет контракты против манипуляций, основанных на внутреннем состоянии.
Получая цены из фида Chainlink, контракт отделяет свою ценовую логику от внутреннего, поддающегося манипуляциям состояния и внешних, нестабильных спотовых рынков, что значительно сокращает поверхность атаки.
#price