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: 63/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
rOUSG
In the rOUSG.sol
contract, pausing functionality is not initialized by calling __Pausable_init()
inside the initialize
function. Thus, the contract cannot be paused
.
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L58
contract ROUSG is ... @> PausableUpgradeable, ... {
As can be seen, the initialize
function lacks initialization of the pausing mechanism:
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L102-L110
function initialize( address _kycRegistry, uint256 requirementGroup, address _ousg, address guardian, address _oracle ) public virtual initializer { __rOUSG_init(_kycRegistry, requirementGroup, _ousg, guardian, _oracle); // @doesn't init pausable }
Pausing mechanism should be setup like so:
function initialize( address _kycRegistry, uint256 requirementGroup, address _ousg, address guardian, address _oracle ) public virtual initializer { __rOUSG_init(_kycRegistry, requirementGroup, _ousg, guardian, _oracle); + __Pausable_init(); }
burn()
function is in the wrong orderIn the rOUSG.sol
contract, the burn()
function emits the Transfer
event topics in the wrong order.
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L638
function burn( address _account, uint256 _amount ) external onlyRole(BURNER_ROLE) { ... _burnShares(_account, ousgSharesAmount); ousg.transfer( msg.sender, ousgSharesAmount / OUSG_TO_ROUSG_SHARES_MULTIPLIER ); @> emit Transfer(address(0), msg.sender, getROUSGByShares(ousgSharesAmount)); // @audit-low wrong order of topics emit TransferShares(_account, address(0), ousgSharesAmount); }
As can be seen in the EIP20, the Transfer
event should follow the below order:
https://eips.ethereum.org/EIPS/eip-20
event Transfer(address indexed _from, address indexed _to, uint256 _value)
A note from the EIP 20 specification:
A token contract which creates new tokens SHOULD trigger a Transfer event with the _from address set to 0x0 when tokens are created.
But as we can see above in the Transfer
event implementation of the burn()
function, the topics are in the wrong order as we are not creating or minting new tokens to be setting the from
address as the 0x0
address. Instead, the from
address should be the address the rOUSG
is being burnt from and the to
address should be the 0x0
address.
Hence, the order of topics should be as follows:
function burn( address _account, uint256 _amount ) external onlyRole(BURNER_ROLE) { ... _burnShares(_account, ousgSharesAmount); ousg.transfer( msg.sender, ousgSharesAmount / OUSG_TO_ROUSG_SHARES_MULTIPLIER ); + emit Transfer(_account, address(0), getROUSGByShares(ousgSharesAmount)); - emit Transfer(address(0), msg.sender, getROUSGByShares(ousgSharesAmount)); emit TransferShares(_account, address(0), ousgSharesAmount); }
BURNER_ROLE
In the rOUSG.sol
contract, roles are not uniformly named and hashed into their bytes32
equivalent.
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L94
bytes32 public constant BURNER_ROLE = keccak256("BURN_ROLE"); <--
A better naming to follow similarly how the PAUSER_ROLE
was setup should be:
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); - bytes32 public constant BURNER_ROLE = keccak256("BURN_ROLE"); + bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
_mintShares
can do without the whenNotPaused
modifier as the wrap()
function already enforces itIn the rOUSG.sol
contract, minting of shares are possible when the contract is not paused. When two functions (public/external & private/internal) reference the same whenNotPaused
modifier, the inner function can do without the modifier in the case that such inner function won't be reached any other way other than the outer function.
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L415
function wrap(uint256 _OUSGAmount) external whenNotPaused { // note: pause mechanism protection ... @> _mintShares(msg.sender, ousgSharesAmount); ... }
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L532
function _mintShares( address _recipient, uint256 _sharesAmount ) internal whenNotPaused returns (uint256) { <-----@ ... }
Get rid the inner whenNotPaused
modifier in the _mintShares
function:
function _mintShares( address _recipient, uint256 _sharesAmount + ) internal returns (uint256) { - ) internal whenNotPaused returns (uint256) { ... }
#0 - c4-pre-sort
2024-04-05T04:12:29Z
0xRobocop marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-04-05T20:18:50Z
0xRobocop marked the issue as insufficient quality report
#2 - c4-judge
2024-04-09T15:57:02Z
3docSec marked the issue as grade-c
#3 - rexjoseph
2024-04-11T21:10:58Z
@3docSec thanks for the quick judging turnaround on this.
This was a terrible performance for DarkTower no doubt. Considering #23, #59 have been concluded as QAs and L-2 of DarkTower QA being quite the same as point number 2 in #139, could the overall QA report be regarded as grade-a with those 3 more downgraded issues counted towards it?
Just following other facts, for example, #287 is marked grade-a with 19 points from my calculation of 3 Ls and 4 NCs (each low being 5 points and NC 1 point)
DarkTower QA has 3 Lows, 3 NCs (L-01 is an NC) so that would be 18 points. 1 point shy of #287
I think that would qualify DarkTower QA report for a grade-a all things considered as it is currently marked grade-c so I'm not so sure those issues I pointed out were taken into consideration.
Btw, if because for the fact it's 1 point shy of #287 & for that reason, grade-a is not reasonable, then I think we can do with a grade-b.
Thank you!
#4 - 3docSec
2024-04-12T07:51:54Z
Hi, you are right, this report is at least as valuable as many downgraded HMs, and certainly more valuable than many bot-like reports with no project-tailored addition.
It however does not have the critical mass it needs for grade-a, so yes grade-b is fair indeed.
#5 - c4-judge
2024-04-12T07:52:06Z
3docSec marked the issue as grade-b
#6 - shaflow01
2024-04-12T08:03:44Z
Could you please take another look at #168 Is it also sufficient to be rated as B? According to this calculation, it have at least 10 points. @3docSec