Skip to content

Latest commit

 

History

History
86 lines (58 loc) · 4.33 KB

File metadata and controls

86 lines (58 loc) · 4.33 KB

Unstoppable

首先第一题名字叫不可阻挡的。看了一下他的题目介绍

有一个代币化金库,存入了100万个DVT代币。在宽限期结束之前,它免费提供快速贷款。

为了在100%无权限之前发现任何错误,开发人员决定在测试网中运行实时测试版。有一个监控合同来检查flashloan功能的运行状况。

从10个DVT代币的余额开始,表明可以停止金库。它必须停止提供快速贷款。

解释: 看题目给我们的提示似乎可以让金库宕机导致其无法运行。

合约分析

一共有两个合约文件 UnstoppableVault.sol和 UnstoppableMonitor.sol。UnstoppableVault 合约是提供闪电贷服务的金库,而 UnstoppableMonitor 合约用于监控合约的闪电贷功能。使用了ERC4262的金库协议。那么这个ERC4262协议了其实存在一个通货膨胀攻击漏洞,通货膨胀攻击是一个针对ERC4262协议的普遍问题,简单看了一下两个合约我们其实可以发现所有整个合约的入口其实就是flashLoan函数是对外公开并且可以进行外部调用的没有任何限制。

     function flashLoan(IERC3156FlashBorrower receiver, address _token, uint256 amount, bytes calldata data)
        external
        returns (bool)
    {
        if (amount == 0) revert InvalidAmount(0); // fail early
        if (address(asset) != _token) revert UnsupportedCurrency(); // enforce ERC3156 requirement
        uint256 balanceBefore = totalAssets();
        if (convertToShares(totalSupply) != balanceBefore) revert InvalidBalance(); // enforce ERC4626 requirement

        // transfer tokens out + execute callback on receiver
        ERC20(_token).safeTransfer(address(receiver), amount);

        // callback must return magic value, otherwise assume it failed
        uint256 fee = flashFee(_token, amount);
        if (
            receiver.onFlashLoan(msg.sender, address(asset), amount, fee, data)
                != keccak256("IERC3156FlashBorrower.onFlashLoan")
        ) {
            revert CallbackFailed();
        }

        // pull amount + fee from receiver, then pay the fee to the recipient
        ERC20(_token).safeTransferFrom(address(receiver), address(this), amount + fee);
        ERC20(_token).safeTransfer(feeRecipient, fee);

        return true;
    }

这个闪电贷函数检查了四个条件,根据前面题目介绍中所提到的这个破坏协议让它不可运行的提示,

我们可以看到他检查了哪些四个条件,那么肯定其中有一个条件可以对他进行破坏导致其无法运行。

1.检查数量是否为0。

2.检查闪电贷的代币是不是该协议的资产。

3.总供应量的这个转换和当前协议地址余额是否所匹配

4.接收这地址是否实现了接口。

最后闪电贷结束以后是协议调用transferfrom把资产从接收者转移到协议里面。

那么显而易见,下面的这个判断就是我们的出发点让他不匹配就可以让整个协议无法运行。

if (convertToShares(totalSupply) != balanceBefore) revert InvalidBalance(); // enforce ERC4626 requirement

他用当前地址余额和供应量之间的转换进行比较,看下面的代码:

   function convertToShares(uint256 assets) public view virtual returns (uint256) {
        uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
        return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets());
    }

如果 supply 为 0,表示当前合约没有任何股份,那么资产数量就等于股份数量,直接返回 assets

第二种情况是 (assets * supply) / totalAssets()。其中 mulDivDown 是一个内部函数,用于安全地执行乘法和除法运算,防止溢出。

解题过程

我们其实可以看见整个合约的问题就出现在这个地方。totalAssets()是当前地址余额我们可以进行操作,那么当我们操作过后这个函数在进行对比的时候就会出现不一致的情况。导致整个合约抛出revert InvalidBalance(); 错误。解题思路就是我们手动转入token到这个合约里面就可以破坏这个合约。我们之间转移一个token或多个到合约里面就可以完成攻击。

token.transfer(address(vault), 1);