hats-finance / Origami-0x998f1b716a5022be026ca6b919c0ddf45ca31abd

GNU Affero General Public License v3.0
2 stars 0 forks source link

In some cases, users may be unable to invest in the OvUSDC vault. #14

Open hats-bug-reporter[bot] opened 8 months ago

hats-bug-reporter[bot] commented 8 months ago

Github username: -- Twitter username: -- Submission hash (on-chain): 0x62e91209f206313db4ba5e094cc4307656c0e164e8a5c6796109b6ffb2e0acf2 Severity: medium

Description: Description\ There are several investment options available to users:

Attack Scenario\ User A invests USDC tokens and receives oUSDC tokens in OrigamiOToken.

function investWithToken(InvestQuoteData calldata quoteData) external virtual override nonReentrant returns (uint256 investmentAmount) {
    IOrigamiOTokenManager _manager = manager;
    IERC20(quoteData.fromToken).safeTransferFrom(msg.sender, address(_manager), quoteData.fromTokenAmount);
    investmentAmount = _manager.investWithToken(msg.sender, quoteData);

    if (investmentAmount != 0) {
        _mint(msg.sender, investmentAmount);
    }
}

The invested USDC tokens earn some interest, and these interests are added to the OvUSDC vault.

function addPendingReserves(uint256 amount) external override onlyElevatedAccess {
    IERC20(reserveToken).safeTransferFrom(msg.sender, address(this), amount);
}

User A invests exactly 1 oUSDC token into the OvUSDC vault.

function _issueSharesFromReserves(
    uint256 reserveTokenAmount, 
    address recipient, 
    uint256 minSharesAmount
) internal returns (uint256 sharesAmount) {
    sharesAmount = reservesToShares(reserveTokenAmount);
    _mint(recipient, sharesAmount);
}

He will receive 1 share because the totalSupply is 0 at this point.

function reservesToShares(uint256 reserves) public view override returns (uint256) {
    uint256 _totalSupply = totalSupply();

    return (_totalSupply == 0)
        ? reserves
        : reserves.mulDiv(_totalSupply, totalReserves(), OrigamiMath.Rounding.ROUND_DOWN);
}

(Maybe the interest can be added after User A deposits 1 oUSDC token. i.e. the order is not important)

User B wants to invest to OvUSDC vault. If User B's investment amount is smaller than the current total reserves in the OvUSDC vault, User B will receive 0 shares.

function reservesToShares(uint256 reserves) public view override returns (uint256) {
    uint256 _totalSupply = totalSupply();  // @audit, totalSupply = 1
    return (_totalSupply == 0)
        ? reserves
        : reserves.mulDiv(_totalSupply, totalReserves(), OrigamiMath.Rounding.ROUND_DOWN);  // @audit, reserves * 1 / totalReserves = 0 due to rounding
}

And this transaction will be reverted.

function _issueSharesFromReserves(
    uint256 reserveTokenAmount, 
    address recipient, 
    uint256 minSharesAmount
) internal returns (uint256 sharesAmount) {
    sharesAmount = reservesToShares(reserveTokenAmount);
    if (sharesAmount == 0) revert CommonEventsAndErrors.ExpectedNonZero();
}

Attachments

  1. Proof of Concept (PoC) File

  2. Revised Code File (Optional)

    Limit the deposit size for the initial depositor.

frontier159 commented 8 months ago

This is not a bug, it's a feature - by design.

Simple answer here is that User B investing with 1 wei is not enough to meet the minimum criteria