Challenge 1 - Unstoppable
合约
- ReceiverUnstoppable:继承IERC3156FlashBorrower合约,用于发起闪电贷,执行闪电贷后的回调
- UnstoppableVault:金库合约,继承IERC3156FlashLender、ERC4626,支持闪电贷
脚本
- 依次部署DamnValuableToken、UnstoppableVault合约
- 存入TOKENS_IN_VAULT数量的token到金库中,转入player用户INITIAL_PLAYER_TOKEN_BALANCE数目的token
- 部署ReceiverUnstoppable合约
- 执行攻击脚本
- 期望ReceiverUnstoppable执行闪电贷的交易被revert
题解
攻击目标是使得通过ReceiverUnstoppable合约发起的executeFlashLoan方法被revert,首先分析executeFlashLoan的调用流程

重点在UnstoppableVault.flashLoan方法,分别会进行以下操作:
- 计算闪电贷开始前的余额:totalAssets()
- 计算当前的share:convertToShares(totalSupply)是否与前面计算出来的余额一致
- 计算闪电贷手续费:flashFee
- 转移amount个token到receiver,再调用receiver的onFlashLoan方法执行回调
- 从receiver方转回amount+fee数目的token
- 将fee转移给feeRecipient账户,完成本次闪电贷
若要使交易revert,关键的校验点在于使得:convertToShares(totalSupply) != totalAssets()
这两个函数都是ERC4626中的定义,关于此协议可参考下面的文章: WTF-Solidity/51_ERC4626/readme.md at main · WTFAcademy/WTF-Solidity
简单来说就是ERC20的组合:资产代币asset和份额代币share,存入资产或提取资产时都会相对应的铸造或销毁对应数目的share代币
totalAssets():计算的是当前金库中的资产代币数目convertToShares(totalSupply):totalSupply是总的share代币数目(只有deposit或mint时才会产生),convertToShares就是计算:assets * totalSupply / totalAssets()
要想使得两者不一致,只要不通过depost或mint方法向UnstoppableVault中转入token即可,因此攻击脚本内容如下:
it('Execution', async function () {
/** CODE YOUR SOLUTION HERE */
const dvtForPlayer = token.connect(player);
await dvtForPlayer.transfer(vault.address,1);
});