Ondo Finance - pkqs90's results

Institutional-Grade Finance, Now Onchain.

General Information

Platform: Code4rena

Start Date: 29/03/2024

Pot Size: $36,500 USDC

Total HM: 5

Participants: 72

Period: 5 days

Judge: 3docSec

Total Solo HM: 1

Id: 357

League: ETH

Ondo Finance

Findings Distribution

Researcher Performance

Rank: 43/72

Findings: 1

Award: $8.28

🌟 Selected for report: 0

🚀 Solo Findings: 0

Lines of code

https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/ousgInstantManager.sol#L269

Vulnerability details

Impact

When users call ousgInstantManager.sol#mintRebasingOUSG(), some of the rOUSG shares that are supposed to send to user is accmulated in ousgInstantManager.sol contract instead.

Bug Description

When performing mintRebasingOUSG() there are three steps:

  1. Call internal _mint() to exchange USDC for OUSG
  2. Wrap OUSG for rOUSG
  3. Send rOUSG to user

The issue happens in step 3. Since rOUSG is a rebasing token, to avoid precision error, transferShare() should be used instead of transfer(). The original implementation would suffer from 2 precision errors during calculation, first is rOUSGShares -> rOUSG, second is rOUSG -> rOUSGShares (during rousg.transfer()). Both precision errors can be simply avoided if we use transferShare() in the first place.

  function mintRebasingOUSG(
    uint256 usdcAmountIn
  )
    external
    override
    nonReentrant
    whenMintNotPaused
    returns (uint256 rousgAmountOut)
  {
    uint256 ousgAmountOut = _mint(usdcAmountIn, address(this));
    ousg.approve(address(rousg), ousgAmountOut);
    rousg.wrap(ousgAmountOut);
@>  rousgAmountOut = rousg.getROUSGByShares(
@>    ousgAmountOut * OUSG_TO_ROUSG_SHARES_MULTIPLIER
@>  );
@>  rousg.transfer(msg.sender, rousgAmountOut);
    emit InstantMintRebasingOUSG(
      msg.sender,
      usdcAmountIn,
      ousgAmountOut,
      rousgAmountOut
    );
  }
  function getROUSGByShares(uint256 _shares) public view returns (uint256) {
    return
      (_shares * getOUSGPrice()) / (1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER);
  }

Proof of Concept

Add the following code to mint.t.sol and run unit test. We can see after Alice calls mintRebasingOUSG(), there are still some shares of rOUSG left in ousgInstantManager.

  function test_mintRebasingOUSG() public {
    // Set oracle price to a 123.45e18.
    oracleCheckHarnessOUSG.setPrice(123456789012345678900);
    deal(address(USDC), alice, 100_000e6);
    vm.startPrank(alice);
    USDC.approve(address(ousgInstantManager), 100_000e6);
    ousgInstantManager.mintRebasingOUSG(100_000e6);
    vm.stopPrank();
    // rOUSGToken.sharesOf(address(ousgInstantManager))=47
    console.log("rOUSGToken.sharesOf(address(ousgInstantManager))=", rOUSGToken.sharesOf(address(ousgInstantManager)));
  }

Tools Used

Foundry

-    rousg.transfer(msg.sender, rousgAmountOut);
+    rousg.transferShares(msg.sender, ousgAmountOut * OUSG_TO_ROUSG_SHARES_MULTIPLIER);

Assessed type

Token-Transfer

#0 - 0xRobocop

2024-04-05T03:25:05Z

Report does not provide evidence for a non-trivial imprecision loss, it will take a lot of deposits to get a loss of at least 1 USD. Direct usage of transferShare seems to be better tough.

#1 - c4-pre-sort

2024-04-05T03:25:08Z

0xRobocop marked the issue as insufficient quality report

#2 - 3docSec

2024-04-09T09:45:42Z

Agree with presort

#3 - c4-judge

2024-04-09T09:45:45Z

3docSec changed the severity to QA (Quality Assurance)

#4 - c4-judge

2024-04-09T09:45:50Z

3docSec marked the issue as grade-b

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter