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
Rank: 2/72
Findings: 3
Award: $4,541.99
π Selected for report: 1
π Solo Findings: 0
3937.119 USDC - $3,937.12
Any User can use mint
function in ousgInstantManager
contract to mint OUSG
tokens by providing USDC
token.
It calls internal function _mint
where the main logic resides.
function _mint(uint256 usdcAmountIn, address to) internal returns (uint256 ousgAmountOut) { // SNIP: Validation uint256 usdcfees = _getInstantMintFees(usdcAmountIn); uint256 usdcAmountAfterFee = usdcAmountIn - usdcfees; // Calculate the mint amount based on mint fees and usdc quantity uint256 ousgPrice = getOUSGPrice(); ousgAmountOut = _getMintAmount(usdcAmountAfterFee, ousgPrice); require(ousgAmountOut > 0, "OUSGInstantManager::_mint: net mint amount can't be zero"); // SNIP: Transfering USDC ousg.mint(to, ousgAmountOut); }
2 important Points to understand here:
The contract depends on the OUSG price obtained from an oracle, which is heavily constrained (As per Readme) to ensure stability.
OUSG Price - The OUSG price tracks an off chain portfolio of cash equivalents and treasury bills, price changes are heavily constrained in the OUSG Oracle, which uses the change in the price of SHV to set the allowable OUSG price in between updates. We are aware that the SHV price could differ from the OUSG portfolio, so any findings related to this price discrepancy is out of scope. Also, scenarios where the OUSG price increases by many orders of magnitudes are not realistic and consequently not considered valid.
As per RWAOracleRateCheck
Oracle, Constraints includes:
23 hours
.1%
.function setPrice(int256 newPrice) external onlyRole(SETTER_ROLE) { if (newPrice <= 0) { revert InvalidPrice(); } @-> if (block.timestamp - priceTimestamp < MIN_PRICE_UPDATE_WINDOW) { revert PriceUpdateWindowViolation(); } @-> if (_getPriceChangeBps(rwaPrice, newPrice) > MAX_CHANGE_DIFF_BPS) { revert DeltaDifferenceConstraintViolation(); } // Set new price int256 oldPrice = rwaPrice; rwaPrice = newPrice; priceTimestamp = block.timestamp; emit RWAPriceSet(oldPrice, newPrice, block.timestamp); }
These constraints ensure relative stability of the OUSG price.
The calculation of the amount of OUSG tokens to mint assumes a fixed conversion rate of 1 USDC = 1 USD
.
Key point:
_getMintAmount
function calculates the OUSG amount based on the provided USDC amount and the OUSG price obtained from the oracle (By just upscaling and dividing).function _getMintAmount( uint256 usdcAmountIn, uint256 price ) internal view returns (uint256 ousgAmountOut) { uint256 amountE36 = _scaleUp(usdcAmountIn) * 1e18; ousgAmountOut = amountE36 / price; }
Here, There are No validation checks implemented regarding the current USDC price.
Consider Alice's attempt to mint OUSG tokens by providing 100,000 USDC, assuming no minting fees and OUSG price of 105e18
USD. The calculation yields: 100_000e36 / 105e18
which is approximately 95_000e18
or 95_000
OUSG tokens for the 100_000
USDC provided.
However, in the event of a USDC depeg, where USDC's value deviates from 1 USD:
This scenario leads to Alice getting close to 95_000
OUSG tokens again for 100_000 USDC
provided But this time 100_000 USDC
can be worth as low as 87_000
USD if we take recent depeg event in March 2023, where USDC price went as low as 87 cents (Reference).
This way, contract will allow Users to mint excessive OUSG tokens during Depeg event.
Minting of Excessive Token in case of USDC Depeg.
VS Code
Ideally, there needs to be an additional oracle to check current Price of USDC and take it's price into the consideration when calculation OUSG tokens to mint.
Context
#0 - c4-pre-sort
2024-04-04T04:22:29Z
0xRobocop marked the issue as duplicate of #297
#1 - c4-judge
2024-04-09T10:03:49Z
3docSec changed the severity to 3 (High Risk)
#2 - c4-judge
2024-04-09T10:04:19Z
3docSec marked the issue as satisfactory
#3 - c4-judge
2024-04-09T10:04:37Z
3docSec marked the issue as selected for report
#4 - 3docSec
2024-04-09T10:05:10Z
Upgraded as High because there is risk of value extraction from the protocol under conditions that can be monitored by an attacker.
#5 - cameronclifton
2024-04-10T19:00:13Z
Ondo has the ability to convert USDC to USD at a 1:1 rate and USD to BUIDL at a 1:1 rate, and this ability can be assumed. Should these abilities be removed this contract will be immediately paused. If someone is able to purchase USDC at a lower price to their benefit that should not negatively affect the protocol.
For the above reasons we do not believe this is a high severity issue.
#6 - c4-sponsor
2024-04-10T20:00:37Z
cameronclifton marked the issue as disagree with severity
#7 - notbozho
2024-04-12T07:32:52Z
Hey @3docSec
This is a really really rare event. For proof, let's look at the USDC price chart.
Over the past 1 year, the price of USDC has moved the most by $0.001 (the difference between the highest and lowest value) and the price has been consistently maintained at $1 over the past year.
For the all time chart, the USDC low is $0.97 and the high is $1.034 (only $0.064 movement all time!).
Source: coinmarketcap
Also, as the sponsor says above:
Should these abilities be removed this contract will be immediately paused. If someone is able to purchase USDC at a lower price to their benefit that should not negatively affect the protocol.
That being said, this issue should be considered as medium severity.
#8 - 3docSec
2024-04-12T07:53:56Z
Noted, I'll think about it.
#9 - cameronclifton
2024-04-12T23:07:01Z
After further review, we will be mitigating this by adding a Chainlink USDC/USD oracle to the OUSGInsatntManager contract. If the price is lower than what we are comfortable with, all mints and redemptions will be blocked. While we think it is unlikely that we won't be able to convert USDC->USD 1:1 in our backend systems, we decided to do this out of extreme caution.
#10 - c4-sponsor
2024-04-21T16:39:45Z
cameronclifton (sponsor) confirmed
490.6256 USDC - $490.63
Judge has assessed an item in Issue #279 as 2 risk. The relevant finding follows:
[L-04] Risk of Funds Getting Stuck due to Minimum Redemption Amount Update
#0 - c4-judge
2024-04-11T09:15:03Z
3docSec marked the issue as duplicate of #142
#1 - c4-judge
2024-04-11T09:15:07Z
3docSec marked the issue as satisfactory
π Selected for report: immeas
Also found by: 0xAkira, 0xCiphky, 0xGreyWolf, 0xJaeger, 0xMosh, 0xabhay, 0xlemon, 0xmystery, 0xweb3boy, Aamir, Abdessamed, Aymen0909, Breeje, DanielArmstrong, DarkTower, Dots, EaglesSecurity, FastChecker, HChang26, Honour, IceBear, JC, K42, Krace, MaslarovK, Omik, OxTenma, SAQ, Shubham, Stormreckson, Tigerfrake, Tychai0s, VAD37, ZanyBonzy, albahaca, arnie, ast3ros, asui, b0g0, bareli, baz1ka, btk, caglankaan, carrotsmuggler, cheatc0d3, dd0x7e8, grearlake, igbinosuneric, jaydhales, kaden, kartik_giri_47538, m4ttm, ni8mare, niser93, nonn_ac, oualidpro, pfapostol, pkqs90, popeye, radev_sw, samuraii77, slvDev, zabihullahazadzoi
114.2409 USDC - $114.24
Count | Explanation |
---|---|
[L-01] | TransparentUpgradeableProxy clashing selector calls may not be delegated |
[L-02] | Potential DoS in redeem functionality in case USDC enables Fees on transfer |
[L-03] | Lack of Enforced Timelock on Fee Updates |
[L-04] | Risk of Funds Getting Stuck due to Minimum Redemption Amount Update |
[L-05] | Precision loss from minting and redeeming rOUSG can be reduced |
[L-06] | Some functions can be vulnerable to Slippage |
Total Low Risk Issues | 6 |
---|
rOUSGFactory
contract, the deployRebasingOUSG
function utilizes the TransparentUpgradeableProxy
pattern to deploy the rOUSGProxy
contract.@-> rOUSGProxy = new TokenProxy(address(rOUSGImplementation), address(rOUSGProxyAdmin), "");
However, the TransparentUpgradeableProxy
contract imported from the external/openzeppelin
directory is based on an outdated version (4.4.1) of the OpenZeppelin library (Link).
There is a known Vulnerability in this version of TransparentUpgradableProxy
pattern which you can see here.
Impact of this vulnerability as stated officially:
A function in the implementation contract may be inaccessible if its selector clashes with one of the proxy's own selectors. Specifically, if the clashing function has a different signature with incompatible ABI encoding, the proxy could revert while attempting to decode the arguments from calldata.
Use latest Version of Openzeppelin Library to mitigate this risk.
The _redeemBUIDL
function in the contract OUSGInstantManager
helps the redemption of BUIDL tokens.
Within _redeemBUIDL
, a require statement (marked with @->) verifies the balance of USDC tokens after redemption to ensure a 1:1 ratio with BUIDL tokens redeemed.
function _redeemBUIDL(uint256 buidlAmountToRedeem) internal { require(buidl.balanceOf(address(this)) >= minBUIDLRedeemAmount,"OUSGInstantManager::_redeemBUIDL: Insufficient BUIDL balance"); uint256 usdcBalanceBefore = usdc.balanceOf(address(this)); buidl.approve(address(buidlRedeemer), buidlAmountToRedeem); buidlRedeemer.redeem(buidlAmountToRedeem); @-> require(usdc.balanceOf(address(this)) == usdcBalanceBefore + buidlAmountToRedeem, "OUSGInstantManager::_redeemBUIDL: BUIDL:USDC not 1:1"); }
Currently, USDC does not impose fees on token transfers. However, there is a possibility of enabling such fees in the future.
If USDC introduces fees on transfers, the require statement validating the balance of USDC tokens post-redemption against the pre-redemption balance will always fail.
Consequently, any attempt to redeem BUIDL tokens would result in a reverting transaction, leading to DoS Scenario.
OUSGInstantManager
contract contains functions such as setMintFee
and setRedeemFee
, which enable the adjustment of fees associated with minting and redeeming operations.function setMintFee( uint256 _mintFee ) external override onlyRole(CONFIGURER_ROLE) { require(mintFee < 200, "OUSGInstantManager::setMintFee: Fee too high"); emit MintFeeSet(mintFee, _mintFee); mintFee = _mintFee; }
These fee adjustment functions do not currently incorporate a timelock mechanism, allowing privileged roles to update fees instantly without prior notice to users.
The absence of a timelock mechanism means that fee updates can take effect immediately upon execution of the corresponding function by a privileged role.
Users who initiate minting or redemption transactions during or shortly after a fee update may experience discrepancies in the expected fee structure, leading to confusion and potentially financial loss.
Consider the following Scenario:
The contract currently sets the setMinimumRedemptionAmount
to 50_000e6
.
For instance, consider Alice, who has deposited 120,000 USDC
to mint X
OUSG tokens.
Alice then initiates a redemption of 60,000 USDC
worth of OUSG, assuming she can redeem 60,000 USDC
more anytime given the current setMinimumRedemptionAmount
of 50_000e6
.
At some time in future, CONFIGURE_ROLE
decides to increase this minimum redemption amount to 75_000e6
which is completely normal behavour on his behalf.
However, now as the setMinimumRedemptionAmount
is updated to 75_000e6
, Alice's funds will get stuck as she cannot redeem her amount, falling below the new minimum redemption threshold.
Recommended Mitigation:
Use a Timelock for this call which allows users enough time to make necessary changes to their portfolio.
In the OUSGInstantManager
contract, the mintRebasingOUSG
function helps minting of rOUSG
tokens in exchange for USDC
.
However, the current implementation may result in precision loss due to rounding when transferring rOUSG
tokens to the user (As Acknowledged in Readme as well).
In mintRebasingOUSG
function, ROUSG Amount is calculated from shares
and then that amount is transferred using transfer
function. This can result in loss of dust amount in the contract because of rounding.
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); }
rousg.transferShares
function instead of rousg.transfer
function, this issue can be easily mitigated.Given the possibility of rebasing prices based on updates from the Oracle, certain functions within the contract may be vulnerable to slippage or lack of deadline enforcement.
Whether it's transfer
, transferShares
or even approve
function, Not having slippage or deadline can lead to issues.
Issue Detail:
For such Scenario, Having a Slippage control and Deadline helps protecting user from unexpected Issues.
#0 - c4-pre-sort
2024-04-05T04:00:42Z
0xRobocop marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-04-05T20:06:52Z
0xRobocop marked the issue as high quality report
#2 - cameronclifton
2024-04-05T23:28:20Z
[L-01] TransparentUpgradeableProxy clashing selector calls may not be delegated This isn't a problem because there are no clashing selector calls right? I think this would be valid only if there was an existing problem. [L-02] Potential DoS in redeem functionality in case USDC enables Fees on transfer It is not realistic to assume this contract will work perfectly should the USDC or BUIDL token change in such a drastic manner [L-03] Lack of Enforced Timelock on Fee Updates This seems like a feature suggestion rather than a vulnerability. I believe the code is working as intended here [L-04] Risk of Funds Getting Stuck due to Minimum Redemption Amount Update See other response related to this issue (it is not fair to assume that this is the only way users can perform redemptions) [L-05] Precision loss from minting and redeeming rOUSG can be reduced Interesting, please provide a concrete example [L-06] Some functions can be vulnerable to Slippage See other response regarding slippage
#3 - c4-sponsor
2024-04-05T23:28:23Z
cameronclifton marked the issue as disagree with severity
#4 - c4-sponsor
2024-04-05T23:29:33Z
cameronclifton (sponsor) acknowledged
#5 - c4-judge
2024-04-10T07:08:53Z
3docSec marked the issue as grade-a
#6 - Breeje16
2024-04-11T08:52:15Z
Hi @3docSec,
L-04 is a dup of #44 and falls under #142 umbrella.
#7 - 3docSec
2024-04-11T09:15:44Z
L-04 is a dup of https://github.com/code-423n4/2024-03-ondo-finance-findings/issues/44 and falls under https://github.com/code-423n4/2024-03-ondo-finance-findings/issues/142 umbrella.
Thanks for flagging β