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: 61/72
Findings: 1
Award: $8.28
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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
8.2807 USDC - $8.28
https://github.com/code-423n4/2024-03-ondo-finance/blob/be2e9ebca6fca460c5b0253970ab280701a15ca1/contracts/ousg/rOUSG.sol#L431-L443 https://github.com/code-423n4/2024-03-ondo-finance/blob/be2e9ebca6fca460c5b0253970ab280701a15ca1/contracts/ousg/ousgInstantManager.sol#L362-L386
Ondo Finance with 50_000 USD minimum withdrawal amount. Minimum 100_000 USD deposit. KYC user might be institution/company. Which very likely to use GnosisSafe or Timelock to manage their fund.
Signing/Executing pending transaction for 1-2 days is normal for this kind of user.
The problem with rOUSG.sol
is it depend on oracle price to convert USD value into underlying investment OUSG for all operations.
If oracle move while transaction is pending, it might revert.
It is possible for some special user like GnosisSafe fail to withdraw/redeem 100% of their fund. Due to oracle price movement.
rOUSG.sol
is stable coin ERC20 token pegged to USD value.
But rOUSG.sol
only store underlying OUSG token underneath.
/** * @dev rOUSG balances are dynamic and are calculated based on the accounts' shares (OUSG) * and the the price of OUSG. Account shares aren't * normalized, so the contract also stores the sum of all shares to calculate * each account's token balance which equals to: * * shares[account] * ousgPrice */ mapping(address => uint256) private shares; // ERC20 balanceOf convert user share into USD value function balanceOf(address _account) public view returns (uint256) { return (_sharesOf(_account) * getOUSGPrice()) / (1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER); }
So all ERC20 function transfer
, approve
, transferFrom
, must convert value from USD into OUSG token.
Through the use of oracle.
The oracle update every 23 hours minimum.
When user want to convert their share into USD, they call rOUSG.unwarp(USD amount)
. This use oracle convert USD value into OUSG share then burn it.
function unwrap(uint256 _rOUSGAmount) external whenNotPaused { require(_rOUSGAmount > 0, "rOUSG: can't unwrap zero rOUSG tokens");//@oracle Price ~100e18 in test file uint256 ousgSharesAmount = getSharesByROUSG(_rOUSGAmount);//@share = amount * 1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER) / oraclePrice if (ousgSharesAmount < OUSG_TO_ROUSG_SHARES_MULTIPLIER) revert UnwrapTooSmall(); _burnShares(msg.sender, ousgSharesAmount); ousg.transfer( msg.sender, ousgSharesAmount / OUSG_TO_ROUSG_SHARES_MULTIPLIER ); emit Transfer(msg.sender, address(0), _rOUSGAmount); emit TransferShares(msg.sender, address(0), ousgSharesAmount); }
If user call unwarp 100_000 USD to convert their entire share of 1000 token and oracle price move down while transaction is pending.
100_000 USD now worth 1001 token. And _burnShares
will revert due to not enough OUSG share.
But oracle price updated really rarely. So it must be user with transaction pending for long period of time.
This is a problem for institution user.
There is only one function to withdraw funds and it revert when over withdraw.
manual
Include unstake()
function that use share value to withdraw funds. For accessiblity and usability.
function unstake(uint share) external whenNotPaused { _burnShares(msg.sender, share); ousg.transfer( msg.sender, share / OUSG_TO_ROUSG_SHARES_MULTIPLIER ); emit Transfer(msg.sender, address(0), getROUSGByShares(share)); emit TransferShares(msg.sender, address(0), share); }
Timing
#0 - c4-pre-sort
2024-04-04T05:35:11Z
0xRobocop marked the issue as insufficient quality report
#1 - 3docSec
2024-04-09T07:39:48Z
UX improvement -> QA
#2 - c4-judge
2024-04-09T07:39:58Z
3docSec changed the severity to QA (Quality Assurance)
#3 - c4-judge
2024-04-09T07:40:03Z
3docSec marked the issue as grade-b