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: 45/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
PausableUpgradable
not initialized.When initializing the rOUSG
the PausableUpgradeable's __Pausable_init
is not called.
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L128
function __rOUSG_init_unchained( address _kycRegistry, uint256 _requirementGroup, address _ousg, address guardian, address _oracle ) internal onlyInitializing { __KYCRegistryClientInitializable_init(_kycRegistry, _requirementGroup); ousg = IERC20(_ousg); oracle = IRWAOracle(_oracle); _grantRole(DEFAULT_ADMIN_ROLE, guardian); _grantRole(PAUSER_ROLE, guardian); _grantRole(BURNER_ROLE, guardian); _grantRole(CONFIGURER_ROLE, guardian); }
If your contract inherits from PausableUpgradeable, you can still use the functions provided, even if you don't call Pausable_init() in your initialize function. However, it's important to understand that while you can technically use the functions provided by PausableUpgradeable without explicitly initializing the pausable functionality, the behavior might not be as expected. The pausable functionality won't be properly set up, so the contract might not behave as intended when you attempt to pause or unpause it, as it may not have initialized the storage slots properly.
Please modify like below:
function __rOUSG_init_unchained( address _kycRegistry, uint256 _requirementGroup, address _ousg, address guardian, address _oracle ) internal onlyInitializing { __KYCRegistryClientInitializable_init(_kycRegistry, _requirementGroup); + __Pausable_init() ousg = IERC20(_ousg); oracle = IRWAOracle(_oracle); _grantRole(DEFAULT_ADMIN_ROLE, guardian); _grantRole(PAUSER_ROLE, guardian); _grantRole(BURNER_ROLE, guardian); _grantRole(CONFIGURER_ROLE, guardian); }
The whenNotPaused
modifier should be relocated to the external rOUSG::burn
, rOUSG::approve
, rOUSG::transferFrom
, rOUSG::transfer
, rOUSG::increaseAllowance
, and rOUSG::decreaseAllowance
functions instead of the internal rOUSG::_burn
, rOUSG::_approve
, and rOUSG::_transferShares
functions. This adjustment enhances code readability and maintains consistency, aligning with the placement of other whenNotPaused
modifiers on external functions. By advancing the modifier, gas savings can also be achieved.
There is no need for a require statement to check if there are sufficient allowances for the contract, as it will revert during transfer if there is insufficient allowance.
function _mint( uint256 usdcAmountIn, address to ) internal returns (uint256 ousgAmountOut) { // rest of function - require( - usdc.allowance(msg.sender, address(this)) >= usdcAmountIn, - "OUSGInstantManager::_mint: Allowance must be given to OUSGInstantManager" - ); // rest of function }
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L378-L380
+ /** + * @notice Returns the current price of OUSG in USDC with 18 decimals of precision + */ function getOUSGPrice() public view returns (uint256 price) { (price, ) = oracle.getPriceData(); }
rOUSG::_transferShares
, rOUSG::_mintShares
, rOUSG::_burnShares
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L529
function _mintShares( address _recipient, uint256 _sharesAmount ) internal whenNotPaused returns (uint256) { require(_recipient != address(0), "MINT_TO_THE_ZERO_ADDRESS"); _beforeTokenTransfer(address(0), _recipient, _sharesAmount); totalShares += _sharesAmount; - shares[_recipient] = shares[_recipient] + _sharesAmount; + shares[_recipient] += _sharesAmount; return totalShares; }
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L501
function _transferShares( address _sender, address _recipient, uint256 _sharesAmount ) internal whenNotPaused { require(_sender != address(0), "TRANSFER_FROM_THE_ZERO_ADDRESS"); require(_recipient != address(0), "TRANSFER_TO_THE_ZERO_ADDRESS"); _beforeTokenTransfer(_sender, _recipient, _sharesAmount); uint256 currentSenderShares = shares[_sender]; require( _sharesAmount <= currentSenderShares, "TRANSFER_AMOUNT_EXCEEDS_BALANCE" ); shares[_sender] = currentSenderShares - _sharesAmount; - shares[_recipient] = shares[_recipient] + _sharesAmount; + shares[_recipient] += _sharesAmount; }
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L554
function _burnShares( address _account, uint256 _sharesAmount ) internal whenNotPaused returns (uint256) { require(_account != address(0), "BURN_FROM_THE_ZERO_ADDRESS"); _beforeTokenTransfer(_account, address(0), _sharesAmount); uint256 accountShares = shares[_account]; require(_sharesAmount <= accountShares, "BURN_AMOUNT_EXCEEDS_BALANCE"); totalShares -= _sharesAmount; - shares[_account] = accountShares - _sharesAmount; + shares[_account] -= _sharesAmount; return totalShares; }
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L28-L53
* rOUSG balances are dynamic and represent the holder's share of the underlying OUSG * controlled by the protocol. To calculate each account's balance, we do * * shares[account] * ousgPrice * * For example, assume that we have: * * ousgPrice = 100.505 * sharesOf(user1) -> 100 * sharesOf(user2) -> 400 * * Therefore: * - * balanceOf(user1) -> 105 tokens which corresponds 105 rOUSG + * balanceOf(user1) -> (_sharesOf(_account) * getOUSGPrice()) / (1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER); + * + * (100e18 * 10000 * 100.505) / (1e18 * 10000) = 10050 + * + * balanceOf(user1) -> 10050 rOUSG which corresponds to 100 shares - * balanceOf(user2) -> 420 tokens which corresponds 420 rOUSG + * balanceOf(user2) -> 40202 rOUSG which corresponds to 400 shares *
ousgInstantManager::getOUSGPrice()
notice/** - * @notice Returns the current price of OUSG in USDC + * @notice Returns the current price of OUSG in USDC with 18 decimal precision * * @dev Sanity check: this function will revert if the price is unexpectedly low * - * @return price The current price of OUSG in USDC + * @return price The current price of OUSG in USDC with 18 decimal precision */
ousgInstantManager::increaseAllowance()
notice/** * @notice Atomically increases the allowance granted to `_spender` by the caller by `_addedValue`. * * This is an alternative to `approve` that can be used as a mitigation for * problems described in: - * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol#L42 + * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.7/contracts/token/ERC20/IERC20.sol#L57 * Emits an `Approval` event indicating the updated allowance. * * Requirements: * * - `_spender` cannot be the the zero address. * - the contract must not be paused. */
#0 - c4-pre-sort
2024-04-05T05:32:42Z
0xRobocop marked the issue as insufficient quality report
#1 - 3docSec
2024-04-11T09:39:51Z
Nice quality, all NC's however, because L-01 does nothing.
#2 - c4-judge
2024-04-11T09:39:58Z
3docSec marked the issue as grade-b