Featured image of post 1219 GoldFinch旧版本Pool合约漏洞

1219 GoldFinch旧版本Pool合约漏洞

相关信息

交易日志:

0x3a8bde0a17e04f6a119ae2f28e6b56ac736feb70761e2fa97cac25f816f751c2

代理合约

0x0689aa2234d06Ac0d04cdac874331d287aFA4B43

实际漏洞合约

0x7dbE30FDE1Eca6856806fF14fc456ec6791c73FE

Pool.sol


// SPDX-License-Identifier: MIT

pragma solidity ^0.6.8;

import "@openzeppelin/contracts-ethereum-package/contracts/access/Ownable.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/Initializable.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol";
import "./OwnerPausable.sol";

contract Pool is Initializable, OwnableUpgradeSafe, OwnerPausable {
  using SafeMath for uint256;
  uint public sharePrice;
  uint mantissa;
  uint public totalShares;
  mapping(address => uint) public capitalProviders;
  address public erc20address;
  string name;
  uint public totalFundsLimit = 0;
  uint public transactionLimit = 0;

  event DepositMade(address indexed capitalProvider, uint amount);
  event WithdrawalMade(address indexed capitalProvider, uint amount);
  event TransferMade(address indexed from, address indexed to, uint amount);
  event InterestCollected(address indexed payer, uint amount);
  event PrincipalCollected(address indexed payer, uint amount);
  event LimitChanged(address indexed owner, string limitType, uint amount);

  function initialize(address _erc20address, string memory _name, uint _mantissa) public initializer {
    __Context_init_unchained();
    __Ownable_init_unchained();
    __OwnerPausable__init();
    name = _name;
    erc20address = _erc20address;
    mantissa = _mantissa;
    sharePrice = _mantissa;

    // Sanity check the address
    ERC20UpgradeSafe(erc20address).totalSupply();

    // Unlock self for infinite amount
    ERC20UpgradeSafe(erc20address).approve(address(this), uint(-1));
  }

  function deposit(uint amount) external payable whenNotPaused {
    require(transactionWithinLimit(amount), "Amount is over the per-transaction limit.");
    // Determine current shares the address has, and the amount of new shares to be added
    uint currentShares = capitalProviders[msg.sender];
    uint depositShares = getNumShares(amount, mantissa, sharePrice);
    uint potentialNewTotalShares = totalShares.add(depositShares);
    require(poolWithinLimit(potentialNewTotalShares), "Deposit would put the Pool over the total limit.");

    doERC20Transfer(msg.sender, address(this), amount);

    // Add the new shares to both the pool and the address
    totalShares = totalShares.add(depositShares);
    capitalProviders[msg.sender] = currentShares.add(depositShares);

    emit DepositMade(msg.sender, amount);
  }

  function withdraw(uint amount) external whenNotPaused {
    // Determine current shares the address has and the shares requested to withdraw
    require(transactionWithinLimit(amount), "Amount is over the per-transaction limit");
    uint currentShares = capitalProviders[msg.sender];
    uint withdrawShares = getNumShares(amount, mantissa, sharePrice);

    // Ensure the address has enough value in the pool
    require(withdrawShares <= currentShares, "Amount requested is greater than what this address owns");

    // Remove the new shares from both the pool and the address
    totalShares = totalShares.sub(withdrawShares);
    capitalProviders[msg.sender] = currentShares.sub(withdrawShares);

    // Send the amount to the address
    doERC20Transfer(address(this), msg.sender, amount);
    emit WithdrawalMade(msg.sender, amount);
  }

  function collectInterestRepayment(address from, uint amount) external whenNotPaused {
    doERC20Transfer(from, address(this), amount);
    uint increment = amount.mul(mantissa).div(totalShares);
    sharePrice = sharePrice + increment;
    emit InterestCollected(from, amount);
  }

  function collectPrincipalRepayment(address from, uint amount) external whenNotPaused {
    // Purposefully does nothing except receive money. No share price updates for principal.
    doERC20Transfer(from, address(this), amount);
    emit PrincipalCollected(from, amount);
  }

  function setTotalFundsLimit(uint amount) public onlyOwner whenNotPaused {
    totalFundsLimit = amount;
    emit LimitChanged(msg.sender, "totalFundsLimit", amount);
  }

  function setTransactionLimit(uint amount) public onlyOwner whenNotPaused {
    transactionLimit = amount;
    emit LimitChanged(msg.sender, "transactionLimit", amount);
  }

  function transferFrom(address from, address to, uint amount) public onlyOwner whenNotPaused returns (bool) {
    bool result = doERC20Transfer(from, to, amount);
    emit TransferMade(from, to, amount);
    return result;
  }

  function enoughBalance(address user, uint amount) public view whenNotPaused returns(bool) {
    return ERC20UpgradeSafe(erc20address).balanceOf(user) >= amount;
  }

  /* Internal Functions */

  function poolWithinLimit(uint _totalShares) internal view returns (bool) {
    return _totalShares.mul(sharePrice).div(mantissa) <= totalFundsLimit;
  }

  function transactionWithinLimit(uint amount) internal view returns (bool) {
    return amount <= transactionLimit;
  }

  function getNumShares(uint amount, uint multiplier, uint price) internal pure returns (uint) {
    return amount.mul(multiplier).div(price);
  }

  function doERC20Transfer(address from, address to, uint amount) internal returns (bool) {
    ERC20UpgradeSafe erc20 = ERC20UpgradeSafe(erc20address);
    uint balanceBefore = erc20.balanceOf(to);

    bool success = erc20.transferFrom(from, to, amount);

    // Calculate the amount that was *actually* transferred
    uint balanceAfter = erc20.balanceOf(to);
    require(balanceAfter >= balanceBefore, "Token Transfer Overflow Error");
    return success;
  }

  function doERC20Withdraw(address payable to, uint amount) internal returns (bool) {
    ERC20UpgradeSafe erc20 = ERC20UpgradeSafe(erc20address);
    bool success = erc20.transfer(to, amount);

    require(success, "Token Withdraw Failed");
    return success;
  }
}

漏洞原因

概括:调用collectInteresPayment时提高了sharePrice,紧接着立即提现时仅需消耗少量share即可提出实际价值更多的asset. asset由全部授权给Pool合约的用户提供。 同时存在逻辑漏洞,允许任意调用偿还函数,导致了SharePrice操纵。 且大户无限授权了该合约,但未存入任何份额,导致成了提款机。

漏洞还需要一个条件就是当前Pool合约的总份额极低,否则攻击者投入成本不足以覆盖收益。此处攻击投入成本10000U,获利350000.

使用 Hugo 构建
主题 StackJimmy 设计