Lucene search

K
code423n4Code4renaCODE423N4:2023-09-ONDO-FINDINGS-ISSUES-481
HistorySep 07, 2023 - 12:00 a.m.

Incorrect calculation of totalSupply(), balanceOf() in rUSDY.sol if the rate is unlinked from $1

2023-09-0700:00:00
Code4rena
github.com
7
vulnerability
impact
calculation
totalsupply
balanceof
oracle
price
usdy
stability
accuracy
documentation
competition
ust
stablecoins
deviation
variables
mitigation
coefficient
peg
mitigation
math

Lines of code
<https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/usdy/rUSDY.sol#L226-L228&gt;

Vulnerability details

Impact

In rUSDY.sol, the functions totalSupply(), balanceOf() are calculated.

totalSupply() (<https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/usdy/rUSDY.sol#L216-L218&gt;):

function totalSupply() public view returns (uint256) {
return (totalShares * oracle.getPrice()) / (1e18 * BPS_DENOMINATOR);
}

balanceOf() (<https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/usdy/rUSDY.sol#L226-L228&gt;):

function balanceOf(address _account) public view returns (uint256) {
return (_sharesOf(_account) * oracle.getPrice()) / (1e18 * BPS_DENOMINATOR);
}

The calculated value of these two functions depends on oracle.getPrice(). The oracle.getPrice() function determines the USDY price. However, the documentation for the competition states:

β€œWhere as the price of a single USDY token varies over time, the price of a single rUSDY token is fixed at a price of 1 Dollar, with yield being accrued in the form of additional rUSDY tokens.”

That is, it is assumed that the price of the USDY token may change. Given the collapse of UST, the price of stablecoins can change both for an increase and for a decrease.

The calculation of the totalSupply() and balanceOf() functions may be incorrect in case of low values or decoupling of the USDY rate from $1 when it goes down.

Proof of Concept

  1. Low values

Suppose:
totalShares = 9000
USDY = 1 * 1e18
_sharesOf(_account) = 9000

Means:
totalSupply = (totalShares * oracle.getPrice()) / (1e18 * BPS_DENOMINATOR) = (9000 * 1e18) / (1e18 * 10000) = 0
balanceOf = (_sharesOf(_account) * oracle.getPrice()) / (1e18 * BPS_DENOMINATOR) = (9000 * 1e18) / (1e18 * 10000) = 0

  1. At low values, totalSupply and balanceOf can be equal to 0, although this is not true. Also, when the price of USDY deviates, these parameters can change up and down. This will display incorrect information about the totalSupply and balanceOf variables.

Suppose:
Option 1:
totalShares = 9 * 1e18
USDY = 1.02 * 1e18
_sharesOf(_account) = 3 * 1e18

Option 2:
totalShares = 9 * 1e18
USDY = 0.99 * 1e18
_sharesOf(_account) = 3 * 1e18

Means:
Option 1:
totalSupply = (totalShares * oracle.getPrice()) / (1e18 * BPS_DENOMINATOR) = (9 * 1e18 * 1.02 * 1e18) / (1e18 * 10000) = 9.18 * 1e14
balanceOf = (_sharesOf(_account) * oracle.getPrice()) / (1e18 * BPS_DENOMINATOR) = (3 * 1e18 * 1.02 * 1e18) / (1e18 * 10000) = 3.06 * 1e14

Option 2:
totalSupply = (totalShares * oracle.getPrice()) / (1e18 * BPS_DENOMINATOR) = (9 * 1e18 * 0.99 * 1e18) / (1e18 * 10000) = 8.91 * 1e14
balanceOf = (_sharesOf(_account) * oracle.getPrice()) / (1e18 * BPS_DENOMINATOR) = (3 * 1e18 * 0.99 * 1e18) / (1e18 * 10000) = 2.97 * 1e14

This shows that the value of totalSupply and balanceOf depends on the USDY price. This contradicts the claim that rUSDY is worth one dollar.

Tools Used

Manual review

Recommended Mitigation Steps

Remove BPS_DENOMINATOR coefficient. Make a peg to one dollar.

Assessed type

Math


The text was updated successfully, but these errors were encountered:

All reactions