Ondo Finance contest - IllIllI's results

Institutional-Grade Finance. On-Chain. For Everyone.

General Information

Platform: Code4rena

Start Date: 11/01/2023

Pot Size: $60,500 USDC

Total HM: 6

Participants: 69

Period: 6 days

Judge: Trust

Total Solo HM: 2

Id: 204

League: ETH

Ondo Finance

Findings Distribution

Researcher Performance

Rank: 15/69

Findings: 2

Award: $336.94

QA:
grade-a
Gas:
grade-b

🌟 Selected for report: 0

šŸš€ Solo Findings: 0

Summary

Low Risk Issues

IssueInstances
[L‑01]Upgradeable contract not initialized1
[L‑02]Loss of precision1
[L‑03]Owner can renounce while system is paused1
[L‑04]require() should be used instead of assert()3

Total: 6 instances over 4 issues

Non-critical Issues

IssueInstances
[N‑01]Upgradeable contract is missing a __gap[50] storage variable to allow for new storage variables in later versions4
[N‑02]Import declarations should import specific identifiers, rather than the whole file45
[N‑03]Duplicate import statements1
[N‑04]The nonReentrant modifier should occur before all other modifiers3
[N‑05]Adding a return statement when the function defines a named return variable, is redundant1
[N‑06]constants should be defined rather than using magic numbers3
[N‑07]Numeric values having to do with time should use time units for readability1
[N‑08]Events that mark critical parameter changes should contain both the old and the new value2
[N‑09]Expressions for constant values such as a call to keccak256(), should use immutable rather than constant14
[N‑10]Constant redefined elsewhere7
[N‑11]Typos5
[N‑12]Constructor visibility is ignored3
[N‑13]File is missing NatSpec1
[N‑14]NatSpec is incomplete16
[N‑15]Consider using delete rather than assigning zero to clear values3
[N‑16]Contracts should have full test coverage1
[N‑17]Large or complicated code bases should implement fuzzing tests1
[N‑18]Function ordering does not follow the Solidity style guide8
[N‑19]Contract does not follow the Solidity style guide's suggested layout ordering14
[N‑20]Interfaces should be indicated with an I prefix in the contract name6

Total: 139 instances over 20 issues

Low Risk Issues

[L‑01] Upgradeable contract not initialized

Upgradeable contracts are initialized via an initializer function rather than by a constructor. Leaving such a contract uninitialized may lead to it being taken over by a malicious user

There is 1 instance of this issue:

File: contracts/cash/token/Cash.sol

/// @audit missing __ERC20PresetMinterPauser_init()
21:   contract Cash is ERC20PresetMinterPauserUpgradeable {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/Cash.sol#L21

[L‑02] Loss of precision

Division by large numbers may result in the result being zero, due to solidity not supporting fractions. Consider requiring a minimum amount for the numerator to ensure that it is always larger than the denominator

There is 1 instance of this issue:

File: contracts/cash/CashManager.sol

755         uint256 collateralAmountDue = (amountToDist * cashAmountReturned) /
756:          quantityBurned;

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L755-L756

[L‑03] Owner can renounce while system is paused

The contract owner or single user with a role is not prevented from renouncing the role/ownership while the contract is paused, which would cause any user assets stored in the protocol, to be locked indefinitely

There is 1 instance of this issue:

File: contracts/cash/CashManager.sol

526     function pause() external onlyRole(PAUSER_ADMIN) {
527       _pause();
528:    }

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L526-L528

[L‑04] require() should be used instead of assert()

Prior to solidity version 0.8.0, hitting an assert consumes the remainder of the transaction's available gas rather than returning it, as require()/revert() do. assert() should be avoided even past solidity version 0.8.0 as its documentation states that "The assert function creates an error of type Panic(uint256). ... Properly functioning code should never create a Panic, not even on invalid external input. If this happens, then there is a bug in your contract which you should fix".

There are 3 instances of this issue:

File: contracts/cash/factory/CashFactory.sol

97:       assert(cashProxyAdmin.owner() == guardian);

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashFactory.sol#L97

File: contracts/cash/factory/CashKYCSenderFactory.sol

106:      assert(cashKYCSenderProxyAdmin.owner() == guardian);

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderFactory.sol#L106

File: contracts/cash/factory/CashKYCSenderReceiverFactory.sol

106:      assert(cashKYCSenderReceiverProxyAdmin.owner() == guardian);

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderReceiverFactory.sol#L106

Non-critical Issues

[N‑01] Upgradeable contract is missing a __gap[50] storage variable to allow for new storage variables in later versions

See this link for a description of this storage variable. While some contracts may not currently be sub-classed, adding the variable now protects against forgetting to add it in the future.

There are 4 instances of this issue:

File: contracts/cash/Proxy.sol

20:   contract TokenProxy is TransparentUpgradeableProxy {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/Proxy.sol#L20

File: contracts/cash/token/CashKYCSenderReceiver.sol

22    contract CashKYCSenderReceiver is
23      ERC20PresetMinterPauserUpgradeable,
24:     KYCRegistryClientInitializable

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/CashKYCSenderReceiver.sol#L22-L24

File: contracts/cash/token/CashKYCSender.sol

22    contract CashKYCSender is
23      ERC20PresetMinterPauserUpgradeable,
24:     KYCRegistryClientInitializable

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/CashKYCSender.sol#L22-L24

File: contracts/cash/token/Cash.sol

21:   contract Cash is ERC20PresetMinterPauserUpgradeable {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/Cash.sol#L21

[N‑02] Import declarations should import specific identifiers, rather than the whole file

Using import declarations of the form import {<identifier_name>} from "some/file.sol" avoids polluting the symbol namespace making flattened files smaller, and speeds up compilation

There are 45 instances of this issue:

File: contracts/cash/CashManager.sol

17:   import "contracts/cash/interfaces/ICashManager.sol";

18:   import "contracts/cash/interfaces/IMulticall.sol";

19:   import "contracts/cash/token/Cash.sol";

20:   import "contracts/cash/kyc/KYCRegistryClientConstructable.sol";

21:   import "contracts/cash/external/openzeppelin/contracts/security/Pausable.sol";

22:   import "contracts/cash/external/openzeppelin/contracts/token/IERC20.sol";

23:   import "contracts/cash/external/openzeppelin/contracts/token/IERC20Metadata.sol";

24:   import "contracts/cash/external/openzeppelin/contracts/token/SafeERC20.sol";

25:   import "contracts/cash/external/openzeppelin/contracts/access/AccessControlEnumerable.sol";

26:   import "contracts/cash/external/openzeppelin/contracts/security/ReentrancyGuard.sol";

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L17

File: contracts/cash/factory/CashFactory.sol

19:   import "contracts/cash/external/openzeppelin/contracts/proxy/ProxyAdmin.sol";

20:   import "contracts/cash/Proxy.sol";

21:   import "contracts/cash/token/Cash.sol";

22:   import "contracts/cash/interfaces/IMulticall.sol";

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashFactory.sol#L19

File: contracts/cash/factory/CashKYCSenderFactory.sol

19:   import "contracts/cash/external/openzeppelin/contracts/proxy/ProxyAdmin.sol";

20:   import "contracts/cash/Proxy.sol";

21:   import "contracts/cash/token/CashKYCSender.sol";

22:   import "contracts/cash/interfaces/IMulticall.sol";

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderFactory.sol#L19

File: contracts/cash/factory/CashKYCSenderReceiverFactory.sol

19:   import "contracts/cash/external/openzeppelin/contracts/proxy/ProxyAdmin.sol";

20:   import "contracts/cash/Proxy.sol";

21:   import "contracts/cash/token/CashKYCSenderReceiver.sol";

22:   import "contracts/cash/interfaces/IMulticall.sol";

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderReceiverFactory.sol#L19

File: contracts/cash/interfaces/IKYCRegistryClient.sol

18:   import "contracts/cash/interfaces/IKYCRegistry.sol";

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/interfaces/IKYCRegistryClient.sol#L18

File: contracts/cash/kyc/KYCRegistryClientConstructable.sol

17:   import "contracts/cash/kyc/KYCRegistryClient.sol";

21:   import "contracts/cash/kyc/KYCRegistryClient.sol";

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/kyc/KYCRegistryClientConstructable.sol#L17

File: contracts/cash/kyc/KYCRegistryClientInitializable.sol

17:   import "contracts/cash/kyc/KYCRegistryClient.sol";

18:   import "contracts/cash/external/openzeppelin/contracts-upgradeable/proxy/Initializable.sol";

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/kyc/KYCRegistryClientInitializable.sol#L17

File: contracts/cash/kyc/KYCRegistryClient.sol

17:   import "contracts/cash/interfaces/IKYCRegistry.sol";

18:   import "contracts/cash/interfaces/IKYCRegistryClient.sol";

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/kyc/KYCRegistryClient.sol#L17

File: contracts/cash/kyc/KYCRegistry.sol

18:   import "contracts/cash/interfaces/IKYCRegistry.sol";

19:   import "contracts/cash/external/openzeppelin/contracts/access/AccessControlEnumerable.sol";

20:   import "contracts/cash/external/chainalysis/ISanctionsList.sol";

21:   import "contracts/cash/external/openzeppelin/contracts/utils/cryptography/EIP712.sol";

22:   import "contracts/cash/external/openzeppelin/contracts/utils/cryptography/ECDSA.sol";

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/kyc/KYCRegistry.sol#L18

File: contracts/cash/Proxy.sol

18:   import "contracts/cash/external/openzeppelin/contracts/proxy/TransparentUpgradeableProxy.sol";

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/Proxy.sol#L18

File: contracts/cash/token/CashKYCSenderReceiver.sol

18:   import "contracts/cash/external/openzeppelin/contracts-upgradeable/token/ERC20/ERC20PresetMinterPauserUpgradeable.sol";

19:   import "contracts/cash/kyc/KYCRegistryClientInitializable.sol";

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/CashKYCSenderReceiver.sol#L18

File: contracts/cash/token/CashKYCSender.sol

18:   import "contracts/cash/external/openzeppelin/contracts-upgradeable/token/ERC20/ERC20PresetMinterPauserUpgradeable.sol";

19:   import "contracts/cash/kyc/KYCRegistryClientInitializable.sol";

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/CashKYCSender.sol#L18

File: contracts/cash/token/Cash.sol

18:   import "contracts/cash/external/openzeppelin/contracts-upgradeable/token/ERC20/ERC20PresetMinterPauserUpgradeable.sol";

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/Cash.sol#L18

File: contracts/lending/OndoPriceOracle.sol

17:   import "./IOndoPriceOracle.sol";

18:   import "contracts/lending/compound/Ownable.sol";

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracle.sol#L17

File: contracts/lending/OndoPriceOracleV2.sol

17:   import "./IOndoPriceOracleV2.sol";

18:   import "contracts/cash/external/openzeppelin/contracts/access/Ownable.sol";

19:   import "contracts/lending/chainlink/AggregatorV3Interface.sol";

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracleV2.sol#L17

[N‑03] Duplicate import statements

There is 1 instance of this issue:

File: contracts/cash/kyc/KYCRegistryClientConstructable.sol

21:   import "contracts/cash/kyc/KYCRegistryClient.sol";

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/kyc/KYCRegistryClientConstructable.sol#L21

[N‑04] The nonReentrant modifier should occur before all other modifiers

This is a best-practice to protect against reentrancy in other modifiers

There are 3 instances of this issue:

File: contracts/cash/CashManager.sol

201:      nonReentrant

244:    ) external override updateEpoch nonReentrant whenNotPaused checkKYC(user) {

668:      nonReentrant

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L201

[N‑05] Adding a return statement when the function defines a named return variable, is redundant

There is 1 instance of this issue:

File: contracts/cash/CashManager.sol

795:      return totalCashAmountRefunded;

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L795

[N‑06] constants should be defined rather than using magic numbers

Even assembly can benefit from using readable constants instead of hex/numeric literals

There are 3 instances of this issue:

File: contracts/cash/CashManager.sol

/// @audit 1e6
491:      uint256 amountE24 = _scaleUp(collateralAmountIn) * 1e6;

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L491

File: contracts/cash/kyc/KYCRegistry.sol

/// @audit 27
/// @audit 28
87:       require(v == 27 || v == 28, "KYCRegistry: invalid v value in signature");

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/kyc/KYCRegistry.sol#L87

[N‑07] Numeric values having to do with time should use time units for readability

There are units for seconds, minutes, hours, days, and weeks, and since they're defined, they should be used

There is 1 instance of this issue:

File: contracts/lending/OndoPriceOracleV2.sol

/// @audit 90000
77:     uint256 public maxChainlinkOracleTimeDelay = 90000; // 25 hours

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracleV2.sol#L77

[N‑08] Events that mark critical parameter changes should contain both the old and the new value

This should especially be done if the new value is not required to be different from the old value

There are 2 instances of this issue:

File: contracts/cash/CashManager.sol

/// @audit setPendingRedemptionBalance()
870       emit PendingRedemptionBalanceSet(
871         user,
872         epoch,
873         balance,
874         redemptionInfoPerEpoch[epoch].totalBurned
875:      );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L870-L875

File: contracts/lending/OndoPriceOracleV2.sol

/// @audit setFTokenToOracleType()
150:      emit FTokenToOracleTypeSet(fToken, oracleType);

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracleV2.sol#L150

[N‑09] Expressions for constant values such as a call to keccak256(), should use immutable rather than constant

While it doesn't save any gas because the compiler knows that developers often make this mistake, it's still best to use the right tool for the task at hand. There is a difference between constant variables and immutable variables, and they should each be used in their appropriate contexts. constants should be used for literal values written into the code, and immutable variables should be used for expressions, or values calculated in, or passed into the constructor.

There are 14 instances of this issue:

File: contracts/cash/CashManager.sol

122:    bytes32 public constant MANAGER_ADMIN = keccak256("MANAGER_ADMIN");

123:    bytes32 public constant PAUSER_ADMIN = keccak256("PAUSER_ADMIN");

124:    bytes32 public constant SETTER_ADMIN = keccak256("SETTER_ADMIN");

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L122

File: contracts/cash/factory/CashFactory.sol

44:     bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

45:     bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashFactory.sol#L44

File: contracts/cash/factory/CashKYCSenderFactory.sol

44:     bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

45:     bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderFactory.sol#L44

File: contracts/cash/factory/CashKYCSenderReceiverFactory.sol

44:     bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

45:     bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderReceiverFactory.sol#L44

File: contracts/cash/kyc/KYCRegistry.sol

31      bytes32 public constant _APPROVAL_TYPEHASH =
32        keccak256(
33          "KYCApproval(uint256 kycRequirementGroup,address user,uint256 deadline)"
34:       );

36:     bytes32 public constant REGISTRY_ADMIN = keccak256("REGISTRY_ADMIN");

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/kyc/KYCRegistry.sol#L31-L34

File: contracts/cash/token/CashKYCSenderReceiver.sol

26      bytes32 public constant KYC_CONFIGURER_ROLE =
27:       keccak256("KYC_CONFIGURER_ROLE");

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/CashKYCSenderReceiver.sol#L26-L27

File: contracts/cash/token/CashKYCSender.sol

26      bytes32 public constant KYC_CONFIGURER_ROLE =
27:       keccak256("KYC_CONFIGURER_ROLE");

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/CashKYCSender.sol#L26-L27

File: contracts/cash/token/Cash.sol

22:     bytes32 public constant TRANSFER_ROLE = keccak256("TRANSFER_ROLE");

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/Cash.sol#L22

[N‑10] Constant redefined elsewhere

Consider defining in only one contract so that values cannot become out of sync when only one location is updated. A cheap way to store constants in a single location is to create an internal constant in a library. If the variable is a local cache of another contract's value, consider making the cache variable internal or private, which will require external users to query the contract with the source of truth, so that callers don't get out of sync.

There are 7 instances of this issue:

File: contracts/cash/factory/CashKYCSenderFactory.sol

/// @audit seen in contracts/cash/factory/CashFactory.sol 
44:     bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

/// @audit seen in contracts/cash/factory/CashFactory.sol 
45:     bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

/// @audit seen in contracts/cash/factory/CashFactory.sol 
46:     bytes32 public constant DEFAULT_ADMIN_ROLE = bytes32(0);

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderFactory.sol#L44

File: contracts/cash/factory/CashKYCSenderReceiverFactory.sol

/// @audit seen in contracts/cash/factory/CashKYCSenderFactory.sol 
44:     bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

/// @audit seen in contracts/cash/factory/CashKYCSenderFactory.sol 
45:     bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

/// @audit seen in contracts/cash/factory/CashKYCSenderFactory.sol 
46:     bytes32 public constant DEFAULT_ADMIN_ROLE = bytes32(0);

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderReceiverFactory.sol#L44

File: contracts/cash/token/CashKYCSender.sol

/// @audit seen in contracts/cash/token/CashKYCSenderReceiver.sol 
26      bytes32 public constant KYC_CONFIGURER_ROLE =
27:       keccak256("KYC_CONFIGURER_ROLE");

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/CashKYCSender.sol#L26-L27

[N‑11] Typos

There are 5 instances of this issue:

File: contracts/cash/interfaces/IKYCRegistryClient.sol

/// @audit refernce
38:     /// @notice Error for when caller attempts to set the KYC registry refernce

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/interfaces/IKYCRegistryClient.sol#L38

File: contracts/cash/kyc/KYCRegistry.sol

/// @audit sucessfully
62:      *         `kycRequirementGroup`. In order to sucessfully call this function,

/// @audit elligible
205:     * @param addresses           Array of addresses being added as elligible

/// @audit elligible
236:     * @param addresses           Array of addresses being added as elligible

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/kyc/KYCRegistry.sol#L62

File: contracts/lending/IOndoPriceOracleV2.sol

/// @audit assset
95:      * @dev Event for when a price cap is set on an fToken's underlying assset

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/IOndoPriceOracleV2.sol#L95

[N‑12] Constructor visibility is ignored

Remove the public if using solidity at or above 0.7.0

There are 3 instances of this issue:

File: contracts/cash/factory/CashFactory.sol

53:     constructor(address _guardian) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashFactory.sol#L53

File: contracts/cash/factory/CashKYCSenderFactory.sol

53:     constructor(address _guardian) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderFactory.sol#L53

File: contracts/cash/factory/CashKYCSenderReceiverFactory.sol

53:     constructor(address _guardian) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderReceiverFactory.sol#L53

[N‑13] File is missing NatSpec

There is 1 instance of this issue:

File: contracts/cash/Proxy.sol

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/Proxy.sol

[N‑14] NatSpec is incomplete

There are 16 instances of this issue:

File: contracts/cash/CashManager.sol

/// @audit Missing: '@return'
500      *                         (in decimals of `collateral`)
501      */
502     function _getMintFees(
503       uint256 collateralAmount
504:    ) private view returns (uint256) {

/// @audit Missing: '@return'
831      *              of cash tokens for in a given epoch
832      */
833     function getBurnedQuantity(
834       uint256 epoch,
835       address user
836:    ) external view returns (uint256) {

/// @audit Missing: '@return'
947      *       3) value - eth value to call target with
948      */
949     function multiexcall(
950       ExCallData[] calldata exCallData
951     )
952       external
953       payable
954       override
955       nonReentrant
956       onlyRole(MANAGER_ADMIN)
957       whenPaused
958:      returns (bytes[] memory results)

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L500-L504

File: contracts/cash/factory/CashFactory.sol

/// @audit Missing: '@return'
121      *       3) value - eth value to call target with
122      */
123     function multiexcall(
124       ExCallData[] calldata exCallData
125:    ) external payable override onlyGuardian returns (bytes[] memory results) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashFactory.sol#L121-L125

File: contracts/cash/factory/CashKYCSenderFactory.sol

/// @audit Missing: '@param registry'
/// @audit Missing: '@param kycRequirementGroup'
57      /**
58       * @dev This function will deploy an upgradable instance of CashKYCSender
59       *
60       * @param name   The name of the token we want to deploy.
61       * @param ticker The ticker for the token we want to deploy.
62       *
63       * @return address The address of the proxy contract.
64       * @return address The address of the proxyAdmin contract.
65       * @return address The address of the implementation contract.
66       *
67       * @notice 1) Will automatically revoke all deployer roles granted to
68       *            address(this).
69       *         2) Will grant DEFAULT_ADMIN & PAUSER_ROLE(S) to `guardian`
70       *            address specified in constructor.
71       *         3) Will transfer ownership of the proxyAdmin to guardian
72       *            address.
73       *
74       */
75      function deployCashKYCSender(
76        string calldata name,
77        string calldata ticker,
78        address registry,
79        uint256 kycRequirementGroup
80:     ) external onlyGuardian returns (address, address, address) {

/// @audit Missing: '@return'
131      *       3) value - eth value to call target with
132      */
133     function multiexcall(
134       ExCallData[] calldata exCallData
135:    ) external payable override onlyGuardian returns (bytes[] memory results) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderFactory.sol#L57-L80

File: contracts/cash/factory/CashKYCSenderReceiverFactory.sol

/// @audit Missing: '@param registry'
/// @audit Missing: '@param kycRequirementGroup'
57      /**
58       * @dev This function will deploy an upgradable instance of CashKYCSenderReceiver
59       *
60       * @param name   The name of the token we want to deploy.
61       * @param ticker The ticker for the token we want to deploy.
62       *
63       * @return address The address of the proxy contract.
64       * @return address The address of the proxyAdmin contract.
65       * @return address The address of the implementation contract.
66       *
67       * @notice 1) Will automatically revoke all deployer roles granted to
68       *            address(this).
69       *         2) Will grant DEFAULT_ADMIN & PAUSER_ROLE(S) to `guardian`
70       *            address specified in constructor.
71       *         3) Will transfer ownership of the proxyAdmin to guardian
72       *            address.
73       *
74       */
75      function deployCashKYCSenderReceiver(
76        string calldata name,
77        string calldata ticker,
78        address registry,
79        uint256 kycRequirementGroup
80:     ) external onlyGuardian returns (address, address, address) {

/// @audit Missing: '@return'
131      *       3) value - eth value to call target with
132      */
133     function multiexcall(
134       ExCallData[] calldata exCallData
135:    ) external payable override onlyGuardian returns (bytes[] memory results) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderReceiverFactory.sol#L57-L80

File: contracts/cash/kyc/KYCRegistryClient.sol

/// @audit Missing: '@return'
63       * @param account The address to check
64       */
65:     function _getKYCStatus(address account) internal view returns (bool) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/kyc/KYCRegistryClient.sol#L63-L65

File: contracts/cash/kyc/KYCRegistry.sol

/// @audit Missing: '@return'
126      * @param account             Addresses to check KYC status for
127      */
128     function getKYCStatus(
129       uint256 kycRequirementGroup,
130       address account
131:    ) external view override returns (bool) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/kyc/KYCRegistry.sol#L126-L131

File: contracts/lending/OndoPriceOracle.sol

/// @audit Missing: '@return'
59       *      set within `fTokenToCToken` and piggy back on an external price oracle.
60       */
61      function getUnderlyingPrice(
62        address fToken
63:     ) external view override returns (uint256) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracle.sol#L59-L63

File: contracts/lending/OndoPriceOracleV2.sol

/// @audit Missing: '@return'
90       * @dev Only supports oracle prices denominated in USD
91       */
92      function getUnderlyingPrice(
93        address fToken
94:     ) external view override returns (uint256) {

/// @audit Missing: '@param value'
125     /**
126      * @notice Sets the price cap for the provided fToken's underlying asset
127      *
128      * @param fToken fToken contract address
129      */
130     function setPriceCap(
131       address fToken,
132       uint256 value
133:    ) external override onlyOwner {

/// @audit Missing: '@return'
275      * @dev This function is public for observability purposes only.
276      */
277     function getChainlinkOraclePrice(
278       address fToken
279:    ) public view returns (uint256) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracleV2.sol#L90-L94

[N‑15] Consider using delete rather than assigning zero to clear values

The delete keyword more closely matches the semantics of what is being done, and draws more attention to the changing of state, which may lead to a more thorough audit of its associated logic

There are 3 instances of this issue:

File: contracts/cash/CashManager.sol

259:      mintRequestsPerEpoch[epochToClaim][user] = 0;

754:        redemptionInfoPerEpoch[epochToService].addressToBurnAmt[redeemer] = 0;

790:        redemptionInfoPerEpoch[epochToService].addressToBurnAmt[refundee] = 0;

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L259

[N‑16] Contracts should have full test coverage

While 100% code coverage does not guarantee that there are no bugs, it often will catch easy-to-find bugs, and will ensure that there are fewer regressions when the code invariably has to be modified. Furthermore, in order to get full coverage, code authors will often have to re-organize their code so that it is more modular, so that each component can be tested separately, which reduces interdependencies between modules and layers, and makes for code that is easier to reason about and audit.

There is 1 instance of this issue:

File: Various Files

[N‑17] Large or complicated code bases should implement fuzzing tests

Large code bases, or code with lots of inline-assembly, complicated math, or complicated interactions between multiple contracts, should implement fuzzing tests. Fuzzers such as Echidna require the test writer to come up with invariants which should not be violated under any circumstances, and the fuzzer tests various inputs and function calls to ensure that the invariants always hold. Even code with 100% code coverage can still have bugs due to the order of the operations a user performs, and fuzzers, with properly and extensively-written invariants, can close this testing gap significantly.

There is 1 instance of this issue:

File: Various Files

[N‑18] Function ordering does not follow the Solidity style guide

According to the Solidity style guide, functions should be laid out in the following order :constructor(), receive(), fallback(), external, public, internal, private, but the cases below do not follow this pattern

There are 8 instances of this issue:

File: contracts/cash/CashManager.sol

/// @audit _scaleUp() came earlier
526:    function pause() external onlyRole(PAUSER_ADMIN) {

/// @audit transitionEpoch() came earlier
596:    function setMintLimit(uint256 _mintLimit) external onlyRole(MANAGER_ADMIN) {

/// @audit _checkAndUpdateRedeemLimit() came earlier
662     function requestRedemption(
663       uint256 amountCashToRedeem
664     )
665       external
666       override
667       updateEpoch
668       nonReentrant
669       whenNotPaused
670:      checkKYC(msg.sender)

/// @audit _processRefund() came earlier
803     function setAssetSender(
804       address newAssetSender
805:    ) external onlyRole(MANAGER_ADMIN) {

/// @audit _checkAddressesKYC() came earlier
949     function multiexcall(
950       ExCallData[] calldata exCallData
951     )
952       external
953       payable
954       override
955       nonReentrant
956       onlyRole(MANAGER_ADMIN)
957       whenPaused
958:      returns (bytes[] memory results)

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L526

File: contracts/lending/OndoPriceOracleV2.sol

/// @audit _setFTokenToCToken() came earlier
233     function setFTokenToChainlinkOracle(
234       address fToken,
235       address newChainlinkOracle
236:    ) external override onlyOwner {

/// @audit _setFTokenToChainlinkOracle() came earlier
277     function getChainlinkOraclePrice(
278       address fToken
279:    ) public view returns (uint256) {

/// @audit getChainlinkOraclePrice() came earlier
309     function setMaxChainlinkOracleTimeDelay(
310       uint256 _maxChainlinkOracleTimeDelay
311:    ) external override onlyOwner {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracleV2.sol#L233-L236

[N‑19] Contract does not follow the Solidity style guide's suggested layout ordering

The style guide says that, within a contract, the ordering should be 1) Type declarations, 2) State variables, 3) Events, 4) Modifiers, and 5) Functions, but the contract(s) below do not follow this ordering

There are 14 instances of this issue:

File: contracts/cash/CashManager.sol

/// @audit function setEpochDuration came earlier
557     modifier updateEpoch() {
558       transitionEpoch();
559       _;
560:    }

/// @audit function setPendingRedemptionBalance came earlier
885     modifier checkKYC(address account) {
886       _checkKYC(account);
887       _;
888:    }

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L557-L560

File: contracts/cash/factory/CashFactory.sol

/// @audit function multiexcall came earlier
143     event CashDeployed(
144       address proxy,
145       address proxyAdmin,
146       address implementation,
147       string name,
148       string ticker
149:    );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashFactory.sol#L143-L149

File: contracts/cash/factory/CashKYCSenderFactory.sol

/// @audit function multiexcall came earlier
153     event CashKYCSenderDeployed(
154       address proxy,
155       address proxyAdmin,
156       address implementation,
157       string name,
158       string ticker,
159       address registry
160:    );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderFactory.sol#L153-L160

File: contracts/cash/factory/CashKYCSenderReceiverFactory.sol

/// @audit function multiexcall came earlier
153     event CashKYCSenderReceiverDeployed(
154       address proxy,
155       address proxyAdmin,
156       address implementation,
157       string name,
158       string ticker,
159       address registry
160:    );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderReceiverFactory.sol#L153-L160

File: contracts/cash/interfaces/ICashManager.sol

/// @audit function completeRedemptions came earlier
76:     event FeeRecipientSet(address oldFeeRecipient, address newFeeRecipient);

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/interfaces/ICashManager.sol#L76

File: contracts/cash/interfaces/IKYCRegistryClient.sol

/// @audit function setKYCRegistry came earlier
48:     event KYCRegistrySet(address oldRegistry, address newRegistry);

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/interfaces/IKYCRegistryClient.sol#L48

File: contracts/cash/kyc/KYCRegistry.sol

/// @audit function removeKYCAddresses came earlier
195     event RoleAssignedToKYCGroup(
196       uint256 indexed kycRequirementGroup,
197       bytes32 indexed role
198:    );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/kyc/KYCRegistry.sol#L195-L198

File: contracts/lending/IOndoPriceOracle.sol

/// @audit function setOracle came earlier
41      event FTokenToCTokenSet(
42        address indexed fToken,
43        address oldCToken,
44        address newCToken
45:     );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/IOndoPriceOracle.sol#L41-L45

File: contracts/lending/IOndoPriceOracleV2.sol

/// @audit function setOracle came earlier
41      event FTokenToCTokenSet(
42        address indexed fToken,
43        address oldCToken,
44        address newCToken
45:     );

/// @audit event CTokenOracleSet came earlier
71      enum OracleType {
72        UNINITIALIZED,
73        MANUAL,
74        COMPOUND,
75        CHAINLINK
76:     }

/// @audit function setFTokenToOracleType came earlier
101     event PriceCapSet(
102       address indexed fToken,
103       uint256 oldPriceCap,
104       uint256 newPriceCap
105:    );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/IOndoPriceOracleV2.sol#L41-L45

File: contracts/lending/OndoPriceOracle.sol

/// @audit function underlying came earlier
41      CTokenOracle public cTokenOracle =
42:       CTokenOracle(0x65c816077C29b557BEE980ae3cC2dCE80204A0C5);

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracle.sol#L41-L42

File: contracts/lending/OndoPriceOracleV2.sol

/// @audit function decimals came earlier
51      CTokenOracle public cTokenOracle =
52:       CTokenOracle(0x65c816077C29b557BEE980ae3cC2dCE80204A0C5);

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracleV2.sol#L51-L52

[N‑20] Interfaces should be indicated with an I prefix in the contract name

There are 6 instances of this issue:

File: contracts/lending/IOndoPriceOracle.sol

18:   interface PriceOracle {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/IOndoPriceOracle.sol#L18

File: contracts/lending/IOndoPriceOracleV2.sol

18:   interface PriceOracle {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/IOndoPriceOracleV2.sol#L18

File: contracts/lending/OndoPriceOracle.sol

21:   interface CTokenOracle {

27:   interface CTokenLike {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracle.sol#L21

File: contracts/lending/OndoPriceOracleV2.sol

22:   interface CTokenOracle {

28:   interface CTokenLike {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracleV2.sol#L22


Excluded findings

These findings are excluded from awards calculations because there are publicly-available automated tools that find them. The valid ones appear here for completeness

Summary

Low Risk Issues

IssueInstances
[L‑01]Missing checks for address(0x0) when assigning values to address state variables6

Total: 6 instances over 1 issues

Non-critical Issues

IssueInstances
[N‑01]constants should be defined rather than using magic numbers1
[N‑02]Event is missing indexed fields38

Total: 39 instances over 2 issues

Low Risk Issues

[L‑01] Missing checks for address(0x0) when assigning values to address state variables

There are 6 instances of this issue:

File: contracts/cash/CashManager.sol

/// @audit (valid but excluded finding)
456:      feeRecipient = _feeRecipient;

/// @audit (valid but excluded finding)
469:      assetRecipient = _assetRecipient;

/// @audit (valid but excluded finding)
807:      assetSender = newAssetSender;

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L456

File: contracts/cash/factory/CashFactory.sol

/// @audit (valid but excluded finding)
54:       guardian = _guardian;

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashFactory.sol#L54

File: contracts/cash/factory/CashKYCSenderFactory.sol

/// @audit (valid but excluded finding)
54:       guardian = _guardian;

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderFactory.sol#L54

File: contracts/cash/factory/CashKYCSenderReceiverFactory.sol

/// @audit (valid but excluded finding)
54:       guardian = _guardian;

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderReceiverFactory.sol#L54

Non-critical Issues

[N‑01] constants should be defined rather than using magic numbers

Even assembly can benefit from using readable constants instead of hex/numeric literals

There is 1 instance of this issue:

File: contracts/lending/OndoPriceOracleV2.sol

/// @audit 36 - (valid but excluded finding)
261:        (36 -

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracleV2.sol#L261

[N‑02] Event is missing indexed fields

Index event fields make the field more quickly accessible to off-chain tools that parse events. However, note that each index field costs extra gas during emission, so it's not necessarily best to index the maximum allowed per event (three fields). Each event should use three indexed fields if there are three or more fields, and gas usage is not particularly of concern for the events in question. If there are fewer than three fields, all of the fields should be indexed.

There are 38 instances of this issue:

File: contracts/cash/factory/CashFactory.sol

/// @audit (valid but excluded finding)
143     event CashDeployed(
144       address proxy,
145       address proxyAdmin,
146       address implementation,
147       string name,
148       string ticker
149:    );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashFactory.sol#L143-L149

File: contracts/cash/factory/CashKYCSenderFactory.sol

/// @audit (valid but excluded finding)
153     event CashKYCSenderDeployed(
154       address proxy,
155       address proxyAdmin,
156       address implementation,
157       string name,
158       string ticker,
159       address registry
160:    );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderFactory.sol#L153-L160

File: contracts/cash/factory/CashKYCSenderReceiverFactory.sol

/// @audit (valid but excluded finding)
153     event CashKYCSenderReceiverDeployed(
154       address proxy,
155       address proxyAdmin,
156       address implementation,
157       string name,
158       string ticker,
159       address registry
160:    );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderReceiverFactory.sol#L153-L160

File: contracts/cash/interfaces/ICashManager.sol

/// @audit (valid but excluded finding)
76:     event FeeRecipientSet(address oldFeeRecipient, address newFeeRecipient);

/// @audit (valid but excluded finding)
84:     event AssetRecipientSet(address oldAssetRecipient, address newAssetRecipient);

/// @audit (valid but excluded finding)
92:     event AssetSenderSet(address oldAssetSender, address newAssetSender);

/// @audit (valid but excluded finding)
102:    event MinimumDepositAmountSet(uint256 oldMinimum, uint256 newMinimum);

/// @audit (valid but excluded finding)
111:    event MinimumRedeemAmountSet(uint256 oldRedeemMin, uint256 newRedeemMin);

/// @audit (valid but excluded finding)
121:    event MintFeeSet(uint256 oldFee, uint256 newFee);

/// @audit (valid but excluded finding)
133     event MintExchangeRateSet(
134       uint256 indexed epoch,
135       uint256 oldRate,
136       uint256 newRate
137:    );

/// @audit (valid but excluded finding)
151     event MintExchangeRateOverridden(
152       uint256 indexed epoch,
153       uint256 oldRate,
154       uint256 newRate,
155       uint256 lastSetMintExchangeRate
156:    );

/// @audit (valid but excluded finding)
164:    event ExchangeRateDeltaLimitSet(uint256 oldLimit, uint256 newLimit);

/// @audit (valid but excluded finding)
175     event MintExchangeRateCheckFailed(
176       uint256 indexed epoch,
177       uint256 lastEpochRate,
178       uint256 newRate
179:    );

/// @audit (valid but excluded finding)
189:    event MintLimitSet(uint256 oldLimit, uint256 newLimit);

/// @audit (valid but excluded finding)
199:    event RedeemLimitSet(uint256 oldLimit, uint256 newLimit);

/// @audit (valid but excluded finding)
209:    event EpochDurationSet(uint256 oldDuration, uint256 newDuration);

/// @audit (valid but excluded finding)
218     event RedemptionRequested(
219       address indexed user,
220       uint256 cashAmountIn,
221       uint256 indexed epoch
222:    );

/// @audit (valid but excluded finding)
233     event MintRequested(
234       address indexed user,
235       uint256 indexed epoch,
236       uint256 collateralAmountDeposited,
237       uint256 depositAmountAfterFee,
238       uint256 feeAmount
239:    );

/// @audit (valid but excluded finding)
250     event RedemptionCompleted(
251       address indexed user,
252       uint256 cashAmountRequested,
253       uint256 collateralAmountReturned,
254       uint256 indexed epoch
255:    );

/// @audit (valid but excluded finding)
268     event MintCompleted(
269       address indexed user,
270       uint256 cashAmountOut,
271       uint256 collateralAmountDeposited,
272       uint256 exchangeRate,
273       uint256 indexed epochClaimedFrom
274:    );

/// @audit (valid but excluded finding)
284     event RedemptionFeesCollected(
285       address indexed beneficiary,
286       uint256 collateralAmountOut,
287       uint256 indexed epoch
288:    );

/// @audit (valid but excluded finding)
297     event RefundIssued(
298       address indexed user,
299       uint256 cashAmountOut,
300       uint256 indexed epoch
301:    );

/// @audit (valid but excluded finding)
311     event PendingMintBalanceSet(
312       address indexed user,
313       uint256 indexed epoch,
314       uint256 oldBalance,
315       uint256 newBalance
316:    );

/// @audit (valid but excluded finding)
327     event PendingRedemptionBalanceSet(
328       address indexed user,
329       uint256 indexed epoch,
330       uint256 balance,
331       uint256 totalBurned
332:    );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/interfaces/ICashManager.sol#L76

File: contracts/cash/interfaces/IKYCRegistryClient.sol

/// @audit (valid but excluded finding)
48:     event KYCRegistrySet(address oldRegistry, address newRegistry);

/// @audit (valid but excluded finding)
56      event KYCRequirementGroupSet(
57        uint256 oldRequirementGroup,
58        uint256 newRequirementGroup
59:     );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/interfaces/IKYCRegistryClient.sol#L48

File: contracts/cash/kyc/KYCRegistry.sol

/// @audit (valid but excluded finding)
207     event KYCAddressesAdded(
208       address indexed sender,
209       uint256 indexed kycRequirementGroup,
210       address[] addresses
211:    );

/// @audit (valid but excluded finding)
238     event KYCAddressesRemoved(
239       address indexed sender,
240       uint256 indexed kycRequirementGroup,
241       address[] addresses
242:    );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/kyc/KYCRegistry.sol#L207-L211

File: contracts/lending/IOndoPriceOracle.sol

/// @audit (valid but excluded finding)
41      event FTokenToCTokenSet(
42        address indexed fToken,
43        address oldCToken,
44        address newCToken
45:     );

/// @audit (valid but excluded finding)
54      event UnderlyingPriceSet(
55        address indexed fToken,
56        uint256 oldPrice,
57        uint256 newPrice
58:     );

/// @audit (valid but excluded finding)
66:     event CTokenOracleSet(address oldOracle, address newOracle);

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/IOndoPriceOracle.sol#L41-L45

File: contracts/lending/IOndoPriceOracleV2.sol

/// @audit (valid but excluded finding)
41      event FTokenToCTokenSet(
42        address indexed fToken,
43        address oldCToken,
44        address newCToken
45:     );

/// @audit (valid but excluded finding)
54      event UnderlyingPriceSet(
55        address indexed fToken,
56        uint256 oldPrice,
57        uint256 newPrice
58:     );

/// @audit (valid but excluded finding)
66:     event CTokenOracleSet(address oldOracle, address newOracle);

/// @audit (valid but excluded finding)
101     event PriceCapSet(
102       address indexed fToken,
103       uint256 oldPriceCap,
104       uint256 newPriceCap
105:    );

/// @audit (valid but excluded finding)
114     event ChainlinkOracleSet(
115       address indexed fToken,
116       address oldOracle,
117       address newOracle
118:    );

/// @audit (valid but excluded finding)
126     event MaxChainlinkOracleTimeDelaySet(
127       uint256 oldMaxTimeDelay,
128       uint256 newMaxTimeDelay
129:    );

/// @audit (valid but excluded finding)
137:    event FTokenToOracleTypeSet(address indexed fToken, OracleType oracleType);

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/IOndoPriceOracleV2.sol#L41-L45

#0 - c4-judge

2023-01-23T11:15:35Z

trust1995 marked the issue as grade-a

#1 - trust1995

2023-01-23T15:48:01Z

Great report, top 3 QA.

#2 - ypatil12

2023-01-24T15:50:23Z

Amazing report

Awards

32.3616 USDC - $32.36

Labels

bug
G (Gas Optimization)
grade-b
G-13

External Links

Summary

Gas Optimizations

IssueInstancesTotal Gas Saved
[G‑01]Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate4-
[G‑02]Avoid contract existence checks by using low level calls2200
[G‑03]State variables should be cached in stack variables rather than re-reading them from storage151455
[G‑04]Multiple accesses of a mapping/array should use a local variable cache8336
[G‑05]<x> += <y> costs more gas than <x> = <x> + <y> for state variables3339
[G‑06]internal functions only called once can be inlined to save gas480
[G‑07]Add unchecked {} for subtractions where the operands cannot underflow because of a previous require() or if-statement4340
[G‑08]++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for- and while-loops9540
[G‑09]require()/revert() strings longer than 32 bytes cost extra gas12-
[G‑10]Optimize names to save gas8176
[G‑11]Use a more recent version of solidity2-
[G‑12]Splitting require() statements that use && saves gas13
[G‑13]Using private rather than public for constants, saves gas1-
[G‑14]Use custom errors rather than revert()/require() strings to save gas16-
[G‑15]Functions guaranteed to revert when called by normal users can be marked payable34714
[G‑16]Don't use _msgSender() if not supporting EIP-2771348

Total: 126 instances over 16 issues with 4231 gas saved

Gas totals use lower bounds of ranges and count two iterations of each for-loop. All values above are runtime, not deployment, values; deployment values are listed in the individual issue descriptions. The table above as well as its gas numbers do not include any of the excluded findings.

Gas Optimizations

[G‑01] Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate

Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.

There are 4 instances of this issue:

File: contracts/cash/CashManager.sol

79      mapping(uint256 => RedemptionRequests) public redemptionInfoPerEpoch;
80    
81      // Mapping used for getting the exchange rate during a given epoch
82      mapping(uint256 => uint256) public epochToExchangeRate;
83    
84      // Nested mapping containing mint requests for an epoch
85      // { <epoch> : {<user> : <collateralAmount> }
86:     mapping(uint256 => mapping(address => uint256)) public mintRequestsPerEpoch;

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L79-L86

File: contracts/lending/OndoPriceOracle.sol

45      mapping(address => uint256) public fTokenToUnderlyingPrice;
46    
47      /// @notice fToken to cToken associations for piggy backing off
48      ///         of cToken oracles
49:     mapping(address => address) public fTokenToCToken;

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracle.sol#L45-L49

File: contracts/lending/OndoPriceOracleV2.sol

55      mapping(address => OracleType) public fTokenToOracleType;
56    
57      /// @notice Contract storage for fToken's underlying asset prices
58      mapping(address => uint256) public fTokenToUnderlyingPrice;
59    
60      /// @notice fToken to cToken associations for piggy backing off
61      ///         of Compound's Oracle
62:     mapping(address => address) public fTokenToCToken;

70      mapping(address => ChainlinkOracleInfo) public fTokenToChainlinkOracle;
71    
72      /// @notice Price cap for the underlying asset of an fToken. Optional.
73:     mapping(address => uint256) public fTokenToUnderlyingPriceCap;

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracleV2.sol#L55-L62

[G‑02] Avoid contract existence checks by using low level calls

Prior to 0.8.10 the compiler inserted extra code, including EXTCODESIZE (100 gas), to check for contract existence for external function calls. In more recent solidity versions, the compiler will not insert these checks if the external call has a return value. Similar behavior can be achieved in earlier versions by using low-level calls, since low level calls never check for contract existence

There are 2 instances of this issue:

File: contracts/lending/OndoPriceOracle.sol

/// @audit underlying()
/// @audit underlying()
121:        CTokenLike(fToken).underlying() == CTokenLike(cToken).underlying(),

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracle.sol#L121

[G‑03] State variables should be cached in stack variables rather than re-reading them from storage

The instances below point to the second+ access of a state variable within a function. Caching of a state variable replaces each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.

There are 15 instances of this issue:

File: contracts/cash/CashManager.sol

/// @audit feeRecipient on line 725
726:      emit RedemptionFeesCollected(feeRecipient, fees, epochToService);

/// @audit minimumRedeemAmount on line 821
822:      emit MinimumRedeemAmountSet(oldRedeemMin, minimumRedeemAmount);

/// @audit currentEpoch on line 221
225:        currentEpoch,

/// @audit currentEpoch on line 678
681:      redemptionInfoPerEpoch[currentEpoch].totalBurned += amountCashToRedeem;

/// @audit currentEpoch on line 681
685:      emit RedemptionRequested(msg.sender, amountCashToRedeem, currentEpoch);

/// @audit epochDuration on line 172
176:        (block.timestamp % epochDuration);

/// @audit epochDuration on line 578
585:          (block.timestamp % epochDuration);

/// @audit lastSetMintExchangeRate on line 296
297:        rateDifference = exchangeRate - lastSetMintExchangeRate;

/// @audit lastSetMintExchangeRate on line 297
298:      } else if (exchangeRate < lastSetMintExchangeRate) {

/// @audit lastSetMintExchangeRate on line 298
299:        rateDifference = lastSetMintExchangeRate - exchangeRate;

/// @audit lastSetMintExchangeRate on line 299
302:      uint256 maxDifferenceThisEpoch = (lastSetMintExchangeRate *

/// @audit lastSetMintExchangeRate on line 302
310:          lastSetMintExchangeRate,

/// @audit lastSetMintExchangeRate on line 310
314:        uint256 oldExchangeRate = lastSetMintExchangeRate;

/// @audit lastSetMintExchangeRate on line 377
383:        lastSetMintExchangeRate

/// @audit redemptionInfoPerEpoch[epoch].totalBurned on line 867
874:        redemptionInfoPerEpoch[epoch].totalBurned

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L726

[G‑04] Multiple accesses of a mapping/array should use a local variable cache

The instances below point to the second+ access of a value inside a mapping/array, within a function. Caching a mapping's value in a local storage or calldata variable when the value is accessed multiple times, saves ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations. Caching an array's struct avoids recalculating the array offsets into memory/calldata

There are 8 instances of this issue:

File: contracts/cash/CashManager.sol

/// @audit redemptionInfoPerEpoch[currentEpoch] on line 678
681:      redemptionInfoPerEpoch[currentEpoch].totalBurned += amountCashToRedeem;

/// @audit redemptionInfoPerEpoch[epochToService] on line 752
754:        redemptionInfoPerEpoch[epochToService].addressToBurnAmt[redeemer] = 0;

/// @audit redemptionInfoPerEpoch[epochToService] on line 788
790:        redemptionInfoPerEpoch[epochToService].addressToBurnAmt[refundee] = 0;

/// @audit redemptionInfoPerEpoch[epoch] on line 859
865:        redemptionInfoPerEpoch[epoch].totalBurned -= previousBalance - balance;

/// @audit redemptionInfoPerEpoch[epoch] on line 865
867:        redemptionInfoPerEpoch[epoch].totalBurned += balance - previousBalance;

/// @audit redemptionInfoPerEpoch[epoch] on line 867
869:      redemptionInfoPerEpoch[epoch].addressToBurnAmt[user] = balance;

/// @audit redemptionInfoPerEpoch[epoch] on line 869
874:        redemptionInfoPerEpoch[epoch].totalBurned

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L681

File: contracts/lending/OndoPriceOracleV2.sol

/// @audit fTokenToChainlinkOracle[fToken] on line 260
264:      fTokenToChainlinkOracle[fToken].oracle = AggregatorV3Interface(

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracleV2.sol#L264

[G‑05] <x> += <y> costs more gas than <x> = <x> + <y> for state variables

Using the addition operator instead of plus-equals saves 113 gas

There are 3 instances of this issue:

File: contracts/cash/CashManager.sol

582:        currentEpoch += epochDifference;

630:      currentMintAmount += collateralAmountIn;

649:      currentRedeemAmount += amount;

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L582

[G‑06] internal functions only called once can be inlined to save gas

Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.

There are 4 instances of this issue:

File: contracts/lending/OndoPriceOracle.sol

119:    function _setFTokenToCToken(address fToken, address cToken) internal {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracle.sol#L119

File: contracts/lending/OndoPriceOracleV2.sol

210:    function _setFTokenToCToken(address fToken, address cToken) internal {

251     function _setFTokenToChainlinkOracle(
252       address fToken,
253:      address chainlinkOracle

324:    function _min(uint256 a, uint256 b) internal pure returns (uint256) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracleV2.sol#L210

[G‑07] Add unchecked {} for subtractions where the operands cannot underflow because of a previous require() or if-statement

require(a <= b); x = b - a => require(a <= b); unchecked { x = b - a }

There are 4 instances of this issue:

File: contracts/cash/CashManager.sol

/// @audit if-condition on line 296
297:        rateDifference = exchangeRate - lastSetMintExchangeRate;

/// @audit if-condition on line 298
299:        rateDifference = lastSetMintExchangeRate - exchangeRate;

/// @audit if-condition on line 864
865:        redemptionInfoPerEpoch[epoch].totalBurned -= previousBalance - balance;

/// @audit if-condition on line 866
867:        redemptionInfoPerEpoch[epoch].totalBurned += balance - previousBalance;

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L297

[G‑08] ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for- and while-loops

The unchecked keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas per loop

There are 9 instances of this issue:

File: contracts/cash/CashManager.sol

750:      for (uint256 i = 0; i < size; ++i) {

786:      for (uint256 i = 0; i < size; ++i) {

933:      for (uint256 i = 0; i < size; ++i) {

961:      for (uint256 i = 0; i < exCallData.length; ++i) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L750

File: contracts/cash/factory/CashFactory.sol

127:      for (uint256 i = 0; i < exCallData.length; ++i) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashFactory.sol#L127

File: contracts/cash/factory/CashKYCSenderFactory.sol

137:      for (uint256 i = 0; i < exCallData.length; ++i) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderFactory.sol#L137

File: contracts/cash/factory/CashKYCSenderReceiverFactory.sol

137:      for (uint256 i = 0; i < exCallData.length; ++i) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderReceiverFactory.sol#L137

File: contracts/cash/kyc/KYCRegistry.sol

163:      for (uint256 i = 0; i < length; i++) {

180:      for (uint256 i = 0; i < length; i++) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/kyc/KYCRegistry.sol#L163

[G‑09] require()/revert() strings longer than 32 bytes cost extra gas

Each extra memory word of bytes past the original 32 incurs an MSTORE which costs 3 gas

There are 12 instances of this issue:

File: contracts/cash/factory/CashKYCSenderFactory.sol

163       require(
164         msg.sender == guardian,
165         "CashKYCSenderFactory: You are not the Guardian"
166:      );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderFactory.sol#L163-L166

File: contracts/cash/factory/CashKYCSenderReceiverFactory.sol

163       require(
164         msg.sender == guardian,
165         "CashKYCSenderReceiverFactory: You are not the Guardian"
166:      );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderReceiverFactory.sol#L163-L166

File: contracts/cash/kyc/KYCRegistry.sol

88        require(
89          !kycState[kycRequirementGroup][user],
90          "KYCRegistry: user already verified"
91:       );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/kyc/KYCRegistry.sol#L88-L91

File: contracts/cash/token/CashKYCSenderReceiver.sol

63        require(
64          _getKYCStatus(_msgSender()),
65          "CashKYCSenderReceiver: must be KYC'd to initiate transfer"
66:       );

70          require(
71            _getKYCStatus(from),
72            "CashKYCSenderReceiver: `from` address must be KYC'd to send tokens"
73:         );

78          require(
79            _getKYCStatus(to),
80            "CashKYCSenderReceiver: `to` address must be KYC'd to receive tokens"
81:         );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/CashKYCSenderReceiver.sol#L63-L66

File: contracts/cash/token/CashKYCSender.sol

63        require(
64          _getKYCStatus(_msgSender()),
65          "CashKYCSender: must be KYC'd to initiate transfer"
66:       );

70          require(
71            _getKYCStatus(from),
72            "CashKYCSender: `from` address must be KYC'd to send tokens"
73:         );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/CashKYCSender.sol#L63-L66

File: contracts/cash/token/Cash.sol

36        require(
37          hasRole(TRANSFER_ROLE, _msgSender()),
38          "Cash: must have TRANSFER_ROLE to transfer"
39:       );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/Cash.sol#L36-L39

File: contracts/lending/OndoPriceOracle.sol

120       require(
121         CTokenLike(fToken).underlying() == CTokenLike(cToken).underlying(),
122         "cToken and fToken must have the same underlying asset if cToken nonzero"
123:      );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracle.sol#L120-L123

File: contracts/lending/OndoPriceOracleV2.sol

215       require(
216         CTokenLike(fToken).underlying() == CTokenLike(cToken).underlying(),
217         "cToken and fToken must have the same underlying asset"
218:      );

280       require(
281         fTokenToOracleType[fToken] == OracleType.CHAINLINK,
282         "fToken is not configured for Chainlink oracle"
283:      );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracleV2.sol#L215-L218

[G‑10] Optimize names to save gas

public/external function names and public member variable names can be optimized to save gas. See this link for an example of how it works. Below are the interfaces/abstract contracts that can be optimized so that the most frequently-called functions use the least amount of gas possible during method lookup. Method IDs that have two leading zero bytes can save 128 gas each during deployment, and renaming functions to have lower method IDs will save 22 gas per call, per sorted position shifted

There are 8 instances of this issue:

File: contracts/cash/CashManager.sol

/// @audit setPendingMintBalance(), setEpochDuration(), transitionEpoch(), setMintLimit(), setRedeemLimit(), setAssetSender(), setRedeemMinimum(), getBurnedQuantity(), setPendingRedemptionBalance()
28:   contract CashManager is

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L28

File: contracts/cash/factory/CashFactory.sol

/// @audit deployCash()
43:   contract CashFactory is IMulticall {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashFactory.sol#L43

File: contracts/cash/interfaces/ICashManager.sol

/// @audit requestMint(), claimMint(), overrideExchangeRate(), setAssetRecipient(), setFeeRecipient(), setAssetSender(), setMinimumDepositAmount(), setMintFee(), setMintExchangeRate(), setMintExchangeRateDeltaLimit(), requestRedemption(), completeRedemptions()
17:   interface ICashManager {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/interfaces/ICashManager.sol#L17

File: contracts/cash/interfaces/IKYCRegistryClient.sol

/// @audit kycRequirementGroup(), kycRegistry(), setKYCRequirementGroup(), setKYCRegistry()
25:   interface IKYCRegistryClient {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/interfaces/IKYCRegistryClient.sol#L25

File: contracts/cash/kyc/KYCRegistry.sol

/// @audit addKYCAddressViaSignature(), assignRoletoKYCGroup(), addKYCAddresses(), removeKYCAddresses()
30:   contract KYCRegistry is AccessControlEnumerable, IKYCRegistry, EIP712 {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/kyc/KYCRegistry.sol#L30

File: contracts/lending/IOndoPriceOracle.sol

/// @audit setPrice(), setFTokenToCToken(), setOracle()
27:   interface IOndoPriceOracle is PriceOracle {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/IOndoPriceOracle.sol#L27

File: contracts/lending/IOndoPriceOracleV2.sol

/// @audit setPrice(), setFTokenToCToken(), setOracle()
27:   interface IOndoPriceOracle is PriceOracle {

/// @audit setPriceCap(), setFTokenToChainlinkOracle(), setMaxChainlinkOracleTimeDelay(), setFTokenToOracleType()
69:   interface IOndoPriceOracleV2 is IOndoPriceOracle {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/IOndoPriceOracleV2.sol#L27

[G‑11] Use a more recent version of solidity

Use a solidity version of at least 0.8.0 to get overflow protection without SafeMath Use a solidity version of at least 0.8.2 to get simple compiler automatic inlining Use a solidity version of at least 0.8.3 to get better struct packing and cheaper multiple storage reads Use a solidity version of at least 0.8.4 to get custom errors, which are cheaper at deployment than revert()/require() strings Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value

There are 2 instances of this issue:

File: contracts/lending/IOndoPriceOracle.sol

15:   pragma solidity 0.6.12;

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/IOndoPriceOracle.sol#L15

File: contracts/lending/OndoPriceOracle.sol

15:   pragma solidity 0.6.12;

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracle.sol#L15

[G‑12] Splitting require() statements that use && saves gas

See this issue which describes the fact that there is a larger deployment gas cost, but with enough runtime calls, the change ends up being cheaper by 3 gas

There is 1 instance of this issue:

File: contracts/lending/OndoPriceOracleV2.sol

292       require(
293         (answeredInRound >= roundId) &&
294           (updatedAt >= block.timestamp - maxChainlinkOracleTimeDelay),
295         "Chainlink oracle price is stale"
296:      );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracleV2.sol#L292-L296

[G‑13] Using private rather than public for constants, saves gas

If needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public constants. Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it's used, and not adding another entry to the method ID table

There is 1 instance of this issue:

File: contracts/cash/CashManager.sol

93:     uint256 public immutable decimalsMultiplier;

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L93

[G‑14] Use custom errors rather than revert()/require() strings to save gas

Custom errors are available from solidity version 0.8.4. Custom errors save ~50 gas each time they're hit by avoiding having to allocate and store the revert string. Not defining the strings also save deployment gas

There are 16 instances of this issue:

File: contracts/cash/factory/CashKYCSenderFactory.sol

163       require(
164         msg.sender == guardian,
165         "CashKYCSenderFactory: You are not the Guardian"
166:      );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderFactory.sol#L163-L166

File: contracts/cash/factory/CashKYCSenderReceiverFactory.sol

163       require(
164         msg.sender == guardian,
165         "CashKYCSenderReceiverFactory: You are not the Guardian"
166:      );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderReceiverFactory.sol#L163-L166

File: contracts/cash/kyc/KYCRegistry.sol

88        require(
89          !kycState[kycRequirementGroup][user],
90          "KYCRegistry: user already verified"
91:       );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/kyc/KYCRegistry.sol#L88-L91

File: contracts/cash/token/CashKYCSenderReceiver.sol

63        require(
64          _getKYCStatus(_msgSender()),
65          "CashKYCSenderReceiver: must be KYC'd to initiate transfer"
66:       );

70          require(
71            _getKYCStatus(from),
72            "CashKYCSenderReceiver: `from` address must be KYC'd to send tokens"
73:         );

78          require(
79            _getKYCStatus(to),
80            "CashKYCSenderReceiver: `to` address must be KYC'd to receive tokens"
81:         );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/CashKYCSenderReceiver.sol#L63-L66

File: contracts/cash/token/CashKYCSender.sol

63        require(
64          _getKYCStatus(_msgSender()),
65          "CashKYCSender: must be KYC'd to initiate transfer"
66:       );

70          require(
71            _getKYCStatus(from),
72            "CashKYCSender: `from` address must be KYC'd to send tokens"
73:         );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/CashKYCSender.sol#L63-L66

File: contracts/cash/token/Cash.sol

36        require(
37          hasRole(TRANSFER_ROLE, _msgSender()),
38          "Cash: must have TRANSFER_ROLE to transfer"
39:       );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/Cash.sol#L36-L39

File: contracts/lending/OndoPriceOracle.sol

120       require(
121         CTokenLike(fToken).underlying() == CTokenLike(cToken).underlying(),
122         "cToken and fToken must have the same underlying asset if cToken nonzero"
123:      );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracle.sol#L120-L123

File: contracts/lending/OndoPriceOracleV2.sol

164       require(
165         fTokenToOracleType[fToken] == OracleType.MANUAL,
166         "OracleType must be Manual"
167:      );

211       require(
212         fTokenToOracleType[fToken] == OracleType.COMPOUND,
213         "OracleType must be Compound"
214:      );

215       require(
216         CTokenLike(fToken).underlying() == CTokenLike(cToken).underlying(),
217         "cToken and fToken must have the same underlying asset"
218:      );

255       require(
256         fTokenToOracleType[fToken] == OracleType.CHAINLINK,
257         "OracleType must be Chainlink"
258:      );

280       require(
281         fTokenToOracleType[fToken] == OracleType.CHAINLINK,
282         "fToken is not configured for Chainlink oracle"
283:      );

292       require(
293         (answeredInRound >= roundId) &&
294           (updatedAt >= block.timestamp - maxChainlinkOracleTimeDelay),
295         "Chainlink oracle price is stale"
296:      );

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracleV2.sol#L164-L167

[G‑15] Functions guaranteed to revert when called by normal users can be marked payable

If a function modifier such as onlyOwner is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE(2),DUP1(3),ISZERO(3),PUSH2(3),JUMPI(10),PUSH1(3),DUP1(3),REVERT(0),JUMPDEST(1),POP(2), which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost

There are 34 instances of this issue:

File: contracts/cash/CashManager.sol

281     function setMintExchangeRate(
282       uint256 exchangeRate,
283       uint256 epochToSet
284:    ) external override updateEpoch onlyRole(SETTER_ADMIN) {

336     function setPendingMintBalance(
337       address user,
338       uint256 epoch,
339       uint256 oldBalance,
340       uint256 newBalance
341:    ) external updateEpoch onlyRole(MANAGER_ADMIN) {

366     function overrideExchangeRate(
367       uint256 correctExchangeRate,
368       uint256 epochToSet,
369       uint256 _lastSetMintExchangeRate
370:    ) external override updateEpoch onlyRole(MANAGER_ADMIN) {

392     function setMintExchangeRateDeltaLimit(
393       uint256 _exchangeRateDeltaLimit
394:    ) external override onlyRole(MANAGER_ADMIN) {

410     function setMintFee(
411       uint256 _mintFee
412:    ) external override onlyRole(MANAGER_ADMIN) {

433     function setMinimumDepositAmount(
434       uint256 _minimumDepositAmount
435:    ) external override onlyRole(MANAGER_ADMIN) {

452     function setFeeRecipient(
453       address _feeRecipient
454:    ) external override onlyRole(MANAGER_ADMIN) {

465     function setAssetRecipient(
466       address _assetRecipient
467:    ) external override onlyRole(MANAGER_ADMIN) {

546     function setEpochDuration(
547       uint256 _epochDuration
548:    ) external onlyRole(MANAGER_ADMIN) {

609     function setRedeemLimit(
610       uint256 _redeemLimit
611:    ) external onlyRole(MANAGER_ADMIN) {

707     function completeRedemptions(
708       address[] calldata redeemers,
709       address[] calldata refundees,
710       uint256 collateralAmountToDist,
711       uint256 epochToService,
712       uint256 fees
713:    ) external override updateEpoch onlyRole(MANAGER_ADMIN) {

803     function setAssetSender(
804       address newAssetSender
805:    ) external onlyRole(MANAGER_ADMIN) {

817     function setRedeemMinimum(
818       uint256 newRedeemMinimum
819:    ) external onlyRole(MANAGER_ADMIN) {

851     function setPendingRedemptionBalance(
852       address user,
853       uint256 epoch,
854       uint256 balance
855:    ) external updateEpoch onlyRole(MANAGER_ADMIN) {

896     function setKYCRequirementGroup(
897       uint256 _kycRequirementGroup
898:    ) external override onlyRole(MANAGER_ADMIN) {

907     function setKYCRegistry(
908       address _kycRegistry
909:    ) external override onlyRole(MANAGER_ADMIN) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L281-L284

File: contracts/cash/factory/CashFactory.sol

75      function deployCash(
76        string calldata name,
77        string calldata ticker
78:     ) external onlyGuardian returns (address, address, address) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashFactory.sol#L75-L78

File: contracts/cash/factory/CashKYCSenderFactory.sol

75      function deployCashKYCSender(
76        string calldata name,
77        string calldata ticker,
78        address registry,
79        uint256 kycRequirementGroup
80:     ) external onlyGuardian returns (address, address, address) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderFactory.sol#L75-L80

File: contracts/cash/factory/CashKYCSenderReceiverFactory.sol

75      function deployCashKYCSenderReceiver(
76        string calldata name,
77        string calldata ticker,
78        address registry,
79        uint256 kycRequirementGroup
80:     ) external onlyGuardian returns (address, address, address) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderReceiverFactory.sol#L75-L80

File: contracts/cash/kyc/KYCRegistryClientInitializable.sol

44      function __KYCRegistryClientInitializable_init(
45        address _kycRegistry,
46        uint256 _kycRequirementGroup
47:     ) internal onlyInitializing {

58      function __KYCRegistryClientInitializable_init_unchained(
59        address _kycRegistry,
60        uint256 _kycRequirementGroup
61:     ) internal onlyInitializing {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/kyc/KYCRegistryClientInitializable.sol#L44-L47

File: contracts/cash/kyc/KYCRegistry.sol

144     function assignRoletoKYCGroup(
145       uint256 kycRequirementGroup,
146       bytes32 role
147:    ) external onlyRole(REGISTRY_ADMIN) {

158     function addKYCAddresses(
159       uint256 kycRequirementGroup,
160       address[] calldata addresses
161:    ) external onlyRole(kycGroupRoles[kycRequirementGroup]) {

175     function removeKYCAddresses(
176       uint256 kycRequirementGroup,
177       address[] calldata addresses
178:    ) external onlyRole(kycGroupRoles[kycRequirementGroup]) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/kyc/KYCRegistry.sol#L144-L147

File: contracts/cash/token/CashKYCSenderReceiver.sol

34      function setKYCRequirementGroup(
35        uint256 group
36:     ) external override onlyRole(KYC_CONFIGURER_ROLE) {

40      function setKYCRegistry(
41        address registry
42:     ) external override onlyRole(KYC_CONFIGURER_ROLE) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/CashKYCSenderReceiver.sol#L34-L36

File: contracts/cash/token/CashKYCSender.sol

34      function setKYCRequirementGroup(
35        uint256 group
36:     ) external override onlyRole(KYC_CONFIGURER_ROLE) {

40      function setKYCRegistry(
41        address registry
42:     ) external override onlyRole(KYC_CONFIGURER_ROLE) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/CashKYCSender.sol#L34-L36

File: contracts/lending/OndoPriceOracle.sol

92      function setFTokenToCToken(
93        address fToken,
94        address cToken
95:     ) external override onlyOwner {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracle.sol#L92-L95

File: contracts/lending/OndoPriceOracleV2.sol

130     function setPriceCap(
131       address fToken,
132       uint256 value
133:    ) external override onlyOwner {

145     function setFTokenToOracleType(
146       address fToken,
147       OracleType oracleType
148:    ) external override onlyOwner {

194     function setFTokenToCToken(
195       address fToken,
196       address cToken
197:    ) external override onlyOwner {

233     function setFTokenToChainlinkOracle(
234       address fToken,
235       address newChainlinkOracle
236:    ) external override onlyOwner {

309     function setMaxChainlinkOracleTimeDelay(
310       uint256 _maxChainlinkOracleTimeDelay
311:    ) external override onlyOwner {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracleV2.sol#L130-L133

[G‑16] Don't use _msgSender() if not supporting EIP-2771

Use msg.sender if the code does not implement EIP-2771 trusted forwarder support

There are 3 instances of this issue:

File: contracts/cash/token/CashKYCSenderReceiver.sol

64:         _getKYCStatus(_msgSender()),

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/CashKYCSenderReceiver.sol#L64

File: contracts/cash/token/CashKYCSender.sol

64:         _getKYCStatus(_msgSender()),

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/CashKYCSender.sol#L64

File: contracts/cash/token/Cash.sol

37:         hasRole(TRANSFER_ROLE, _msgSender()),

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/Cash.sol#L37


Excluded findings

These findings are excluded from awards calculations because there are publicly-available automated tools that find them. The valid ones appear here for completeness

Summary

Gas Optimizations

IssueInstancesTotal Gas Saved
[G‑01]Using calldata instead of memory for read-only arguments in external functions saves gas4480
[G‑02]<array>.length should not be looked up in every loop of a for-loop412
[G‑03]require()/revert() strings longer than 32 bytes cost extra gas2-
[G‑04]Using bools for storage incurs overhead117100
[G‑05]++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too)210
[G‑06]Using private rather than public for constants, saves gas18-
[G‑07]Use custom errors rather than revert()/require() strings to save gas8-
[G‑08]Functions guaranteed to revert when called by normal users can be marked payable7147

Total: 46 instances over 8 issues with 17749 gas saved

Gas totals use lower bounds of ranges and count two iterations of each for-loop. All values above are runtime, not deployment, values; deployment values are listed in the individual issue descriptions. The table above as well as its gas numbers do not include any of the excluded findings.

Gas Optimizations

[G‑01] Using calldata instead of memory for read-only arguments in external functions saves gas

When a function with a memory array is called externally, the abi.decode() step has to use a for-loop to copy each index of the calldata to the memory index. Each iteration of this for-loop costs at least 60 gas (i.e. 60 * <mem_array>.length). Using calldata directly, obliviates the need for such a loop in the contract code and runtime execution. Note that even if an interface defines a function as having memory arguments, it's still valid for implementation contracs to use calldata arguments instead.

If the array is passed to an internal function which passes the array to another internal function where the array is modified and therefore memory is used in the external call, it's still more gass-efficient to use calldata when the external function uses modifiers, since the modifiers may prevent the internal functions from being called. Structs have the same overhead as an array of length one

Note that I've also flagged instances where the function is public but can be marked as external since it's not called by the contract, and cases where a constructor is involved

There are 4 instances of this issue:

File: contracts/cash/token/CashKYCSenderReceiver.sol

/// @audit name - (valid but excluded finding)
/// @audit symbol - (valid but excluded finding)
46      function initialize(
47        string memory name,
48        string memory symbol,
49        address kycRegistry,
50        uint256 kycRequirementGroup
51:     ) public initializer {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/CashKYCSenderReceiver.sol#L46-L51

File: contracts/cash/token/CashKYCSender.sol

/// @audit name - (valid but excluded finding)
/// @audit symbol - (valid but excluded finding)
46      function initialize(
47        string memory name,
48        string memory symbol,
49        address kycRegistry,
50        uint256 kycRequirementGroup
51:     ) public initializer {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/CashKYCSender.sol#L46-L51

[G‑02] <array>.length should not be looked up in every loop of a for-loop

The overheads outlined below are PER LOOP, excluding the first loop

  • storage arrays incur a Gwarmaccess (100 gas)
  • memory arrays use MLOAD (3 gas)
  • calldata arrays use CALLDATALOAD (3 gas)

Caching the length changes each of these to a DUP<N> (3 gas), and gets rid of the extra DUP<N> needed to store the stack offset

There are 4 instances of this issue:

File: contracts/cash/CashManager.sol

/// @audit (valid but excluded finding)
961:      for (uint256 i = 0; i < exCallData.length; ++i) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L961

File: contracts/cash/factory/CashFactory.sol

/// @audit (valid but excluded finding)
127:      for (uint256 i = 0; i < exCallData.length; ++i) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashFactory.sol#L127

File: contracts/cash/factory/CashKYCSenderFactory.sol

/// @audit (valid but excluded finding)
137:      for (uint256 i = 0; i < exCallData.length; ++i) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderFactory.sol#L137

File: contracts/cash/factory/CashKYCSenderReceiverFactory.sol

/// @audit (valid but excluded finding)
137:      for (uint256 i = 0; i < exCallData.length; ++i) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderReceiverFactory.sol#L137

[G‑03] require()/revert() strings longer than 32 bytes cost extra gas

Each extra memory word of bytes past the original 32 incurs an MSTORE which costs 3 gas

There are 2 instances of this issue:

File: contracts/cash/factory/CashFactory.sol

/// @audit (valid but excluded finding)
152:      require(msg.sender == guardian, "CashFactory: You are not the Guardian");

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashFactory.sol#L152

File: contracts/cash/kyc/KYCRegistry.sol

/// @audit (valid but excluded finding)
87:       require(v == 27 || v == 28, "KYCRegistry: invalid v value in signature");

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/kyc/KYCRegistry.sol#L87

[G‑04] Using bools for storage incurs overhead

    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/58f635312aa21f947cae5f8578638a85aa2519f5/contracts/security/ReentrancyGuard.sol#L23-L27 Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas) for the extra SLOAD, and to avoid Gsset (20000 gas) when changing from false to true, after having been true in the past

There is 1 instance of this issue:

File: contracts/cash/kyc/KYCRegistry.sol

/// @audit (valid but excluded finding)
39:     mapping(uint256 => mapping(address => bool)) public kycState;

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/kyc/KYCRegistry.sol#L39

[G‑05] ++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too)

Saves 5 gas per loop

There are 2 instances of this issue:

File: contracts/cash/kyc/KYCRegistry.sol

/// @audit (valid but excluded finding)
163:      for (uint256 i = 0; i < length; i++) {

/// @audit (valid but excluded finding)
180:      for (uint256 i = 0; i < length; i++) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/kyc/KYCRegistry.sol#L163

[G‑06] Using private rather than public for constants, saves gas

If needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public constants. Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it's used, and not adding another entry to the method ID table

There are 18 instances of this issue:

File: contracts/cash/CashManager.sol

/// @audit (valid but excluded finding)
89:     uint256 public constant BPS_DENOMINATOR = 10_000;

/// @audit (valid but excluded finding)
122:    bytes32 public constant MANAGER_ADMIN = keccak256("MANAGER_ADMIN");

/// @audit (valid but excluded finding)
123:    bytes32 public constant PAUSER_ADMIN = keccak256("PAUSER_ADMIN");

/// @audit (valid but excluded finding)
124:    bytes32 public constant SETTER_ADMIN = keccak256("SETTER_ADMIN");

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L89

File: contracts/cash/factory/CashFactory.sol

/// @audit (valid but excluded finding)
44:     bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

/// @audit (valid but excluded finding)
45:     bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

/// @audit (valid but excluded finding)
46:     bytes32 public constant DEFAULT_ADMIN_ROLE = bytes32(0);

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashFactory.sol#L44

File: contracts/cash/factory/CashKYCSenderFactory.sol

/// @audit (valid but excluded finding)
44:     bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

/// @audit (valid but excluded finding)
45:     bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

/// @audit (valid but excluded finding)
46:     bytes32 public constant DEFAULT_ADMIN_ROLE = bytes32(0);

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderFactory.sol#L44

File: contracts/cash/factory/CashKYCSenderReceiverFactory.sol

/// @audit (valid but excluded finding)
44:     bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

/// @audit (valid but excluded finding)
45:     bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

/// @audit (valid but excluded finding)
46:     bytes32 public constant DEFAULT_ADMIN_ROLE = bytes32(0);

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderReceiverFactory.sol#L44

File: contracts/cash/kyc/KYCRegistry.sol

/// @audit (valid but excluded finding)
31      bytes32 public constant _APPROVAL_TYPEHASH =
32        keccak256(
33          "KYCApproval(uint256 kycRequirementGroup,address user,uint256 deadline)"
34:       );

/// @audit (valid but excluded finding)
36:     bytes32 public constant REGISTRY_ADMIN = keccak256("REGISTRY_ADMIN");

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/kyc/KYCRegistry.sol#L31-L34

File: contracts/cash/token/CashKYCSenderReceiver.sol

/// @audit (valid but excluded finding)
26      bytes32 public constant KYC_CONFIGURER_ROLE =
27:       keccak256("KYC_CONFIGURER_ROLE");

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/CashKYCSenderReceiver.sol#L26-L27

File: contracts/cash/token/CashKYCSender.sol

/// @audit (valid but excluded finding)
26      bytes32 public constant KYC_CONFIGURER_ROLE =
27:       keccak256("KYC_CONFIGURER_ROLE");

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/CashKYCSender.sol#L26-L27

File: contracts/cash/token/Cash.sol

/// @audit (valid but excluded finding)
22:     bytes32 public constant TRANSFER_ROLE = keccak256("TRANSFER_ROLE");

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/Cash.sol#L22

[G‑07] Use custom errors rather than revert()/require() strings to save gas

Custom errors are available from solidity version 0.8.4. Custom errors save ~50 gas each time they're hit by avoiding having to allocate and store the revert string. Not defining the strings also save deployment gas

There are 8 instances of this issue:

File: contracts/cash/CashManager.sol

/// @audit (valid but excluded finding)
965:        require(success, "Call Failed");

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L965

File: contracts/cash/factory/CashFactory.sol

/// @audit (valid but excluded finding)
131:        require(success, "Call Failed");

/// @audit (valid but excluded finding)
152:      require(msg.sender == guardian, "CashFactory: You are not the Guardian");

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashFactory.sol#L131

File: contracts/cash/factory/CashKYCSenderFactory.sol

/// @audit (valid but excluded finding)
141:        require(success, "Call Failed");

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderFactory.sol#L141

File: contracts/cash/factory/CashKYCSenderReceiverFactory.sol

/// @audit (valid but excluded finding)
141:        require(success, "Call Failed");

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/factory/CashKYCSenderReceiverFactory.sol#L141

File: contracts/cash/kyc/KYCRegistry.sol

/// @audit (valid but excluded finding)
87:       require(v == 27 || v == 28, "KYCRegistry: invalid v value in signature");

/// @audit (valid but excluded finding)
92:       require(block.timestamp <= deadline, "KYCRegistry: signature expired");

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/kyc/KYCRegistry.sol#L87

File: contracts/lending/OndoPriceOracleV2.sol

/// @audit (valid but excluded finding)
297:      require(answer >= 0, "Price cannot be negative");

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracleV2.sol#L297

[G‑08] Functions guaranteed to revert when called by normal users can be marked payable

If a function modifier such as onlyOwner is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE(2),DUP1(3),ISZERO(3),PUSH2(3),JUMPI(10),PUSH1(3),DUP1(3),REVERT(0),JUMPDEST(1),POP(2), which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost

There are 7 instances of this issue:

File: contracts/cash/CashManager.sol

/// @audit (valid but excluded finding)
526:    function pause() external onlyRole(PAUSER_ADMIN) {

/// @audit (valid but excluded finding)
533:    function unpause() external onlyRole(MANAGER_ADMIN) {

/// @audit (valid but excluded finding)
596:    function setMintLimit(uint256 _mintLimit) external onlyRole(MANAGER_ADMIN) {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L526

File: contracts/lending/OndoPriceOracle.sol

/// @audit (valid but excluded finding)
80:     function setPrice(address fToken, uint256 price) external override onlyOwner {

/// @audit (valid but excluded finding)
106:    function setOracle(address newOracle) external override onlyOwner {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracle.sol#L80

File: contracts/lending/OndoPriceOracleV2.sol

/// @audit (valid but excluded finding)
163:    function setPrice(address fToken, uint256 price) external override onlyOwner {

/// @audit (valid but excluded finding)
182:    function setOracle(address newOracle) external override onlyOwner {

https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/OndoPriceOracleV2.sol#L163

#0 - c4-judge

2023-01-23T11:14:54Z

trust1995 marked the issue as grade-b

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