有一个以百万DVT代币抵押的代币化保险库。在宽限期结束之前,它提供免费的闪电贷款服务。
为了完成挑战,使该保险库停止提供闪电贷款服务。
你的初始余额为10个DVT代币。
https://www.damnvulnerabledefi.xyz/challenges/unstoppable/
挑战合约代码分为两部分,分别是提供闪电贷的金库合约(UnstoppableVault.sol)和执行闪电贷的合约(ReceiverUnstoppable.sol)。
通过阅读合约代码就可以了解到 Flashloan原理,俗称闪电贷,原理简单来说就是你的合约先调用金库合约,例如aave、uniswap等等这些合约提供闪电贷功能,金库合约先转币给你的智能合约,并且回调你的智能合约,你的智能合约收到币和被回调后执行合约逻辑去套利,执行完你的合约逻辑后要还币加手续费给金库合约。如果金库合约检查收到的币足够就交易完成,否则就触发revert中断交易。
闪电贷的金库合约(UnstoppableVault.sol)
其中金库合约继承了ERC4626协议。简单来说,存入100ETH,协议mint 一定量 xETH。xETH就是存入计数的,更多细节查关于ERC4626资料吧。
金库代码中还有一段Solidity的内联汇编:
/**
* @inheritdoc ERC4626
*/
function totalAssets() public view override returns (uint256) {
assembly {
// better safe than sorry
if eq(sload(0), 2) {
mstore(0x00, 0xed3ba6a6)
revert(0x1c, 0x04)
}
}
return asset.balanceOf(address(this));
}
如果不熟悉Solidity的内联汇编,通过chatGPT翻译得到:

sload(0)表示从存储器中加载位置0的值。 这里涉及到Ethereum Virtual Machine (EVM)储存布局。简单来说就是合约中的全局变量是按顺序标记的。这是ERC20合约代码例子:

同样constant修饰因为是确定值也是直接将值写死在代码中,不会占用插槽。 回头看ReceiverUnstoppable.sol中代码,发现ReetrancyGuard.sol中是第一个占用插槽。

sload(0)指的就是全局变量locked。

if eq(sload(0), 2) {//其实这句代码本意是防止重入攻击
mstore(0x00, 0xed3ba6a6)
revert(0x1c, 0x04)
}
通过挑战的关键就是UnstoppableVault.sol的104行的断言
if (convertToShares(totalSupply) != balanceBefore)
revert InvalidBalance(); // enforce ERC4626 requirement
unstoppable.challenge.js,给金库转币token,上面的断言就无法通过,金库的闪电贷功能就废了。
it('Execution', async function () {
/** CODE YOUR SOLUTION HERE */
await token.transfer(vault.address, 1);
});
