Ondo Finance - DarkTower's results

Institutional-Grade Finance, Now Onchain.

General Information

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

Ondo Finance

Findings Distribution

Researcher Performance

Rank: 63/72

Findings: 1

Award: $8.28

🌟 Selected for report: 0

🚀 Solo Findings: 0

[L-1] PausableUpgradeable is not initialized in 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();
  }

[L-2] Transfer event topics of the burn() function is in the wrong order

In 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);
  }

[N-1] Employ a more uniform naming convention for 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");

[N-2] _mintShares can do without the whenNotPaused modifier as the wrap() function already enforces it

In 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

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter