美文网首页
DEFI COVER 被黑过程分析(storage赋值给memo

DEFI COVER 被黑过程分析(storage赋值给memo

作者: PERMANENT_REDIR | 来源:发表于2021-01-18 17:55 被阅读0次

    有漏洞的合约代码:etherscan需要翻墙

    /// @notice update pool's rewards & bonus per staked token till current block timestamp

      function updatePool(address _lpToken) public override {

        Pool storage pool = pools[_lpToken];

        if (block.timestamp <= pool.lastUpdatedAt) return;

        uint256 lpTotal = IERC20(_lpToken).balanceOf(address(this));

        if (lpTotal == 0) {

          pool.lastUpdatedAt = block.timestamp;

          return;

        }

        // update COVER rewards for pool

        uint256 coverRewards = _calculateCoverRewardsForPeriod(pool);

        pool.accRewardsPerToken = pool.accRewardsPerToken.add(coverRewards.div(lpTotal));

        pool.lastUpdatedAt = block.timestamp;

        // update bonus token rewards if exist for pool

        BonusToken storage bonusToken = bonusTokens[_lpToken];

        if (bonusToken.lastUpdatedAt < bonusToken.endTime && bonusToken.startTime < block.timestamp) {

          uint256 bonus = _calculateBonusForPeriod(bonusToken);

          bonusToken.accBonusPerToken = bonusToken.accBonusPerToken.add(bonus.div(lpTotal));

          bonusToken.lastUpdatedAt = block.timestamp <= bonusToken.endTime ? block.timestamp : bonusToken.endTime;

        }

      }

      function claimRewards(address _lpToken) public override {

        updatePool(_lpToken);

        Pool memory pool = pools[_lpToken];

        Miner storage miner = miners[_lpToken][msg.sender];

        BonusToken memory bonusToken = bonusTokens[_lpToken];

        _claimCoverRewards(pool, miner);

        _claimBonus(bonusToken, miner);

        // update writeoff to match current acc rewards & bonus per token

        miner.rewardWriteoff = miner.amount.mul(pool.accRewardsPerToken).div(CAL_MULTIPLIER);

        miner.bonusWriteoff = miner.amount.mul(bonusToken.accBonusPerToken).div(CAL_MULTIPLIER);

      }

      function claimRewardsForPools(address[] calldata _lpTokens) external override {

        for (uint256 i = 0; i < _lpTokens.length; i++) {

          claimRewards(_lpTokens[i]);

        }

      }

      function deposit(address _lpToken, uint256 _amount) external override {

        require(block.timestamp >= START_TIME , "Blacksmith: not started");

        require(_amount > 0, "Blacksmith: amount is 0");

    //主要原因就是先将storage赋值给memory,再改变storage的值,然而最后pool.accRewardsPerToken却用的是memory的值

     Pool memory pool = pools[_lpToken];

        require(pool.lastUpdatedAt > 0, "Blacksmith: pool does not exists");

        require(IERC20(_lpToken).balanceOf(msg.sender) >= _amount, "Blacksmith: insufficient balance");

        updatePool(_lpToken);

        Miner storage miner = miners[_lpToken][msg.sender];

        BonusToken memory bonusToken = bonusTokens[_lpToken];

        _claimCoverRewards(pool, miner);

        _claimBonus(bonusToken, miner);

        miner.amount = miner.amount.add(_amount);

        // update writeoff to match current acc rewards/bonus per token

        miner.rewardWriteoff = miner.amount.mul(pool.accRewardsPerToken).div(CAL_MULTIPLIER);

        miner.bonusWriteoff = miner.amount.mul(bonusToken.accBonusPerToken).div(CAL_MULTIPLIER);

        IERC20(_lpToken).safeTransferFrom(msg.sender, address(this), _amount);

        emit Deposit(msg.sender, _lpToken, _amount);

      }

      function withdraw(address _lpToken, uint256 _amount) external override {

        require(_amount > 0, "Blacksmith: amount is 0");

        Miner storage miner = miners[_lpToken][msg.sender];

        require(miner.amount >= _amount, "Blacksmith: insufficient balance");

        updatePool(_lpToken);

        Pool memory pool = pools[_lpToken];

        BonusToken memory bonusToken = bonusTokens[_lpToken];

        _claimCoverRewards(pool, miner);

        _claimBonus(bonusToken, miner);

        miner.amount = miner.amount.sub(_amount);

        // update writeoff to match current acc rewards/bonus per token

        miner.rewardWriteoff = miner.amount.mul(pool.accRewardsPerToken).div(CAL_MULTIPLIER);

        miner.bonusWriteoff = miner.amount.mul(bonusToken.accBonusPerToken).div(CAL_MULTIPLIER);

        _safeTransfer(_lpToken, _amount);

        emit Withdraw(msg.sender, _lpToken, _amount);

      }

    整个攻击过程如下图所示:

    攻击者首先发起一笔交易,调用deposit函数向Blacksmith存入1525.5552810089260015362LP代币。接着攻击者发起另一笔交易,调用withdraw函数取回1525.5552810089260015361LP代币。这样攻击者在Blacksmith中只剩下1e-18LP代币。但由于此时还有别的用户存入这种LP代币,此时的lpTotal还是很大。

    接着,攻击者等待别的用户把他们的LP代币都取走。攻击者很快就等到了一个攻击机会:一位用户通过交易取走了自己的LP代币,此时lpTotal的值为1,即代表1e-18LP代币。

    攻击者立即发起了一笔关键的攻击交易,调用deposit函数。根据前面说的攻击原理,此时pool.accRewardsPerToken的旧值为3.37e9,新值为2.67e27。而用旧值错误计算出的miner.rewardWriteoff为5.14e19,远小于正确值4.08e37。

    攻击者最后简单的发起一笔交易,调用claimRewards函数,获得了4.08e19COVER代币

    相关文章

      网友评论

          本文标题:DEFI COVER 被黑过程分析(storage赋值给memo

          本文链接:https://www.haomeiwen.com/subject/xxbxzktx.html