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

2021-01-18  本文已影响0人  PERMANENT_REDIR

有漏洞的合约代码: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代币

上一篇下一篇

猜你喜欢

热点阅读