- Total Prize Pool: $60,500 USDC
- HM awards: $42,500 USDC
- QA report awards: $5,000 USDC
- Gas report awards: $2,500 USDC
- Judge + presort awards: $10,000 USDC
- Scout awards: $500 USDC
- Join C4 Discord to register
- Submit findings using the C4 form
- Read our guidelines for more details
- Starts January 11, 2023 20:00 UTC
- Ends January 17, 2023 20:00 UTC
The C4audit output for the contest can be found here.
Note for C4 wardens: Anything included in the C4udit output is considered a publicly known issue and is ineligible for awards.
Ondo's CASH protocol allows for whitelisted (KYC'd) users to hold exposure to Real World Assets (RWAs) through yield-bearing ERC20 ("Cash") tokens. Flux Protocol is a Compound V2 fork that supports both permissioned and permissionless assets.
For the initial launch, the CASH protocol's CashManager contract will receive USDC and mint a Cash token, whose name will be "OUSG". This Cash token will act as an on chain representation of a share within the underlying real world asset pool. Additionally, the Flux protocol will support lending markets for OUSG and DAI (subject to governance vote). Users will be able to supply DAI and OUSG but only be able to borrow DAI.
The directory structure of this repo splits the contracts, tests, and scripts based on whether they are a part of the Cash or Flux protocol.
- Directory locations for Cash related contracts can be found under
contracts/cash
, while Flux related contracts can be found undercontracts/lending
. - We utilize the Foundry framework for tests. Tests for both Cash and Flux can be found inside
forge-tests/
- We utilize the hardhat framework for scripting and deployments under
scripts/
anddeploy/
File | SLOC | Description |
---|---|---|
Contracts (19) | ||
contracts/cash/Proxy.sol | 9 | Token Proxy contract for upgradeable Cash Tokens. Inherits from OZ's TransparentUpgradeableProxy |
contracts/cash/token/Cash.sol 🧮 | 19 | Upgradeable Cash token that checks the initiator of a transfer has the TRANSFER_ROLE |
contracts/lending/tokens/cCash/CCashDelegate.sol | 24 | Final deployed contract for CCash tokens. Inherits from CCash |
contracts/lending/tokens/cToken/CTokenDelegate.sol | 24 | Final deployed contract for fDAI token. Inherits from fDAI |
contracts/cash/token/CashKYCSender.sol 🧮 | 49 | Upgradeable Cash token that checks the initiator and sender of a transfer are KYC'd |
contracts/lending/OndoPriceOracle.sol | 50 | Oracle used to determine prices of assets in lending market |
contracts/cash/token/CashKYCSenderReceiver.sol 🧮 | 55 | Upgradeable Cash token that checks the initiator, sender, and receiver of a transfer are KYC'd |
contracts/cash/factory/CashFactory.sol 💰 🧮 🌀 | 73 | Deploys an upgradeable Cash token. Permissions admin roles for the token and ProxyAdmin to the guardian |
contracts/cash/factory/CashKYCSenderFactory.sol 💰 🧮 🌀 | 87 | Deploys an upgradeable CashKYCSender token. Permissions admin roles for the token and ProxyAdmin to the guardian |
contracts/cash/factory/CashKYCSenderReceiverFactory.sol 💰 🧮 🌀 | 87 | Deploys an upgradeable CashKYCSenderReceiver token. Permissions admin roles for the token and ProxyAdmin to the guardian |
contracts/lending/JumpRateModelV2.sol | 103 | Modified JumpRateModel contract with an updated blocks/year |
contracts/cash/kyc/KYCRegistry.sol 🧮 | 112 | Manages all KYC'd addresses that interact with Ondo protocol |
contracts/lending/tokens/cCash/CCash.sol 🖥 📤 | 142 | CErc20 contract that inherits from and wraps an underlying CTokenCash contract into an ERC20 token |
contracts/lending/tokens/cToken/CErc20.sol 🖥 📤 | 142 | CErc20 contract that inherits from and wraps an underlying CTokenSanction contract into an ERC20 token |
contracts/lending/OndoPriceOracleV2.sol | 158 | Oracle used to determine prices of additional assets in lending market |
contracts/lending/tokens/cCash/CTokenInterfacesModifiedCash.sol | 196 | Modified cToken interface that adds KYC-specific storage |
contracts/lending/tokens/cToken/CTokenInterfacesModified.sol | 196 | Modified cToken interface that adds KYC-specific storage |
contracts/cash/CashManager.sol 💰 🧮 | 518 | Main contract of Cash protocol that mints and burns Cash tokens to users |
contracts/lending/tokens/cErc20ModifiedDelegator.sol 🖥 💰 👥 | 656 | Delegator (proxy) contract with KYC-specific storage for fDAIDelegate and CCashDelegate implementation contracts |
Abstracts (5) | ||
contracts/cash/kyc/KYCRegistryClientConstructable.sol | 9 | Inherits from KYCRegistryClient and used by non-upgradeable KYC client contracts |
contracts/cash/kyc/KYCRegistryClient.sol | 23 | Manages state for client contracts that interact with the KYCRegistry |
contracts/cash/kyc/KYCRegistryClientInitializable.sol | 24 | Inherits from KYCRegistryClient and used by upgradeable KYC client contracts, such as Cash tokens |
contracts/lending/tokens/cCash/CTokenCash.sol | 687 | cToken contract that adds KYC checks to specific functions |
contracts/lending/tokens/cToken/CTokenModified.sol | 690 | cToken contract that adds KYC/sanctions checks to specific functions |
Interfaces (6) | ||
contracts/cash/interfaces/IKYCRegistry.sol | 7 | Interface for KYCRegistry |
contracts/cash/interfaces/IMulticall.sol 💰 | 11 | Interface which, when implemented, allows a privileged actor to batch arbitrary calls from the CashManager |
contracts/cash/interfaces/IKYCRegistryClient.sol | 14 | Interface for IKYCRegistryClient |
contracts/lending/IOndoPriceOracle.sol | 20 | Interface for OndoPriceOracle |
contracts/lending/IOndoPriceOracleV2.sol | 55 | Interface for OndoPriceOracleV2 |
contracts/cash/interfaces/ICashManager.sol | 125 | Interface for CashManager |
Total (over 30 files): | 4365 |
While the files listed above are fully in-scope for Medium and High findings, note that many are forks of Compound with minimal changes (see the description below for the relevant commits, and for the scope of gas/QA findings in those files).
The KYCRegistry acts as a gating mechanism for actions that must be behind KYC checks. Contracts from the Cash and Flux protocol query this contract to check that addresses are KYC verified before executing certain actions. Users KYC off-chain by submitting their personal information to Ondo. If successful, they receive a digest signed by Ondo in return. Users can provide this signed digest to the KYCRegistry contract's addKYCAddressViaSignature
function to add their KYC status to the contract's storage. This function takes in a signature of an EIP-712 message digest and verifies that it has been signed by a wallet that has been whitelisted in the access control functionality of the KYC Registry. For example, if a user wants to be KYC'd in kycRequirementGroup
1, she must have her message digest signed by a user with the kycGroupRoles[1]
role.
The contract also has functions that allow for privileged accounts to modify the KYC status for users as well without relying on any user activity.
Abstract Contract that allows contracts that inherit from it to access the KYCRegistry. Inheritors of this contract must implement functions that set the client contract's kycRequirementGroup (setKYCRequirementGroup
) and KYCRegistry (setKYCRegistry
). These functions must also be gated by an appropriate access-control check.
KYCRegistryClientConstructable
is a wrapper around KYCRegistryClient
that is designed to be inherited by non-upgradeable contracts (eg. CashManager
). KYCRegistryClientInitializable
is a wrapper around KYCRegistryClient
that is designed to be inherited by upgradeable contracts (eg. Cash Tokens).
Note: The Flux protocol tokens (fDAI & fCASH) have the same logic and storage from the KYCRegistryClient copied directly into the CToken and CTokenInterfacesModified contracts.
The Cash tokens (Cash
, CashKYCSender
, CashKYCSenderReceiver
) are upgradeable and allow a guardian account to mint tokens and pause the contract. Each contract gates transfers with the following checks:
Contract | Check |
---|---|
Cash | Initiator address has the TRANSFER_ROLE |
CashKYCSender | Initiator and from address are KYC'd in a given kycRequirementGroup |
CashKYCSenderReceiver | Initiator, from, and to addresses are KYC'd in a given kycRequirementGroup |
Each of these contracts inherits from OZ's ERC20PresetMinterPauserUpgradeable
The Cash token factories (CashFactory
, CashKYCFactory
CashKYCSenderReceiverFactory
) deploy their respective implementation, proxy (Proxy.sol
), and ProxyAdmin contract for the Cash token. After calling the deploy function on the factory contract, all permissions for the token and ProxyAdmins are atomically revoked from the factory and granted to the guardian
address.
The largest and most important contract in the Cash protocol. CashManager mints and redeems Cash tokens for users. The contract gives Cash holders exposure to RWAs by transferring the deposited USDC to 3rd-party custodians. The CashManager divides time into epochs, during which users can make mint and redeem requests. Within an epoch, the amount of Cash that can be minted or burned (for redemptions) is rate limited.
To mint Cash tokens, a user must send USDC to the contract and call requestMint
. At some point after the epoch has ended an external account will call setMintExchangeRate
, which sets the exchange rate of USDC to Cash for that epoch. This exchange rate is calculated off chain and is based on the NAV of the underlying RWA pool. Once the exchange rate is set, users can claim their CASH token by calling claimMint
.
To redeem Cash for USDC, users must burn their Cash tokens by calling requestRedemption
. At some point after the epoch has ended, a MANAGER_ADMIN
will calculate the amount of collateral to distribute to redeemers call completeRedemptions
, which will distribute earned USDC and/or refund any Cash tokens to certain accounts.
Flux is a fork of Compound V2. The comptroller and contracts in the contracts/lending/compound
and contracts/lending/tokens/cErc20Delegate
folders are unchanged from Compound's on-chain lending market deployments. The primary changes to the protocol are in the cToken contracts (cTokenCash and cTokenModified), which add sanctions and KYC checks to specific functions in the markets. The contracts are forked directly from etherscan. For reference, the deployed cToken contract can be found at this commit. All other contracts (Comptroller, CErc20Delegator, InterestRateModel, etc.) are found in the previous commit. Note that we linted our contracts and have different import paths.
Each of the upgradeable fToken contracts consists of 4 primary contracts: CErc20DelegatorKYC
(Proxy), CTokenDelegate
(Implementation), which inherits from cTokenInterfacesModified
, and CTokenModified
. These contracts are forked with minor changes from Compound's on-chain cDAI contract. CTokenModified
and cTokenInterfacesModified
are also forked from Compound's cDAI contract, but they add storage and logic for KYC/sanctions checks. In addition cTokenInterfacesModified
changes the protocolSeizeShareMantissa
from 2.8% to 1.75%. CTokenModified
guards the following functions with checks:
Function | Check |
---|---|
transferTokens | Sanction |
mint | Sanction |
redeem | Sanction |
borrow | KYC |
repayBorrow | KYC |
seize | Sanction |
Note: liquidateBorrow
has no checks on it since it calls into seize
on the collateral and repayBorrow
on the borrowed asset.
Since fTokens are clients of the KYCRegistry contract, the logic for KYC checks are added throughout various functions within the CTokenModified
contract. The storage modifications for KYC/Sanctions checks are in CTokenInterfacesModified
in this section. The storage and logic is forked directly from KYCRegistryClient
, without the use of custom errors.
Like fTokens, the upgradeable cCash is forked from Compound's on-chain cDAI contract and consists of 4 primary contracts: cCash
, cCashDelegate
, cTokenInterfacesModifiedCash
, and CTokenCash
. cTokenInterfacesModifiedCash
updates the protocolSeizeShareMantissa
from 2.8% to 0%. CTokenCash
guards the following functions with checks:
Function | Check |
---|---|
transferTokens | KYC |
mint | KYC |
redeem | KYC |
borrow | KYC |
repayBorrow | KYC |
seize | KYC |
Note: cCASH is not borrowable in the MVP, so the borrow
, repayBorrow
, and liquidateBorrow
functions aren't relevant.
Similar to CTokenModified, the logic changes for cCash consist of checks on various functions in the cTokenCash
contract. The storage changes modifications for KYC checks can be found in CTokenInterfacesModifiedCash
in this section.
This contract is forked from Compound's cDAI cErc20Delegator
contract. Since this contract acts as a proxy for Flux's cErc20
and cCash
implementation contracts, corresponding storage updates were made in the contract. As one can expect, the constructor was modified to add kycRegistry
and kycRequirementGroup
parameters.
The JumpRateModelV2 contract is forked from Compound's cDAI InterestRateModel. The only modified value is the blocksPerYear
.
Acts as the price oracle for the lending market. To get the price of DAI, the contract makes an external call into Compound's UniswapAnchoredView
oracle contract with Compound's cDAI address. The oracle can support both assets with custom prices (i.e. CASH tokens) and assets listed on Compound (UNI, USDC, USDT, etc.). The price of CASH is set by a trusted off-chain party with privileged access that calculates the price based on the NAV of the RWA fund backing the CASH token, similar to the CashManager
contract.
This contract has all the features of OndoPriceOracle
, but adds the ability to set price caps and retrieve prices from Chainlink oracles. To do so, an fToken
must have one of 3 different OracleTypes
- Manual
, Compound
, Chainlink
. The oracle also contains price caps to attempt to mitigate the fallout of a stablecoin depegging upwards. Makes external calls to Chainlink Oracles and Compound's UniswapAnchoredView oracle contract. We intend to upgrade the oracle in the comptroller to OndoPriceOracleV2
at a later point with markets that aren't supported by OndoPriceOracle
(FRAX, LUSD, etc).
We invite wardens to submit bug findings for Flux based on the parameters we will set for the lending market on deployment. We will initially launch with the params in V1 Deployment and then both add markets and update the oracle to support V2 Deployment. The setup in the foundry deploy scripts mimics the exact same parameters below.
- LiquidationIncentive: 5%
- CTokenCash protocolSeizeShare: 0%
- CTokenModified protocolSeizeShare: 1.75%
- Interest Rate Model Params: 3.8% APY at Kink (80% Util). 10% APY at 100% Util. The IR Model Parameters are only be in-scope for V1 Deployment assets
Asset | Lendable | Borrowable | CollateralFactor |
---|---|---|---|
USDC | Yes | Yes | 85% |
OUSG (CASH) | Yes* | No | 90% |
DAI | Yes | Yes | 0% |
Note: If an asset has a CollateralFactor of 0, it cannot be used as collateral.
To set an asset as non-borrowable, we call _setBorrowPaused
on the Comptroller. OUSG is lendable in the sense that it can be used to mint fTokens, which will later collateralize a borrow position. However, these fTokens will not be earning yield.
Same assets/configuration as V1, with the following added:
Asset | Lendable | Borrowable | CollateralFactor |
---|---|---|---|
USDT | Yes | Yes | 0% |
FRAX | Yes | Yes | 0% |
LUSD | Yes | Yes | 0% |
To support V2 Deployment assets, we must update the oracle and set the OracleType
for all fTokens. A sample for how this will be done can be found here.
- Centralization Risk - we are aware that our management functions and contract upgradeability results in a significantly more centralized system than Compound V2.
- Bad debt risk from misconfiguration - we are aware that
- pushing to 98% the collateral factor may provoke some bad debt if
CollateralFactor + Liquidation Incentive > 100
- liquidators need to be on the whitelist (KYC’d), and if none decide to liquidate, the protocol can accrue bad debt
- the protocol does not accrue reserves on some/all assets
- pushing to 98% the collateral factor may provoke some bad debt if
- Liquidation Profitability - We understand that if
LiquidationIncentive < ProtocolSeizeShare
(as percents), then liquidations are unprofitable - Duplicated code - we are aware that there are significant opportunities throughout the repo to reduce the quantity of duplicated code. This is largely due to timing and our attempts to keep the code base as similar as possible to verified Compound contract code on Etherscan.
- Gas Optimizations - Per https://docs.code4rena.com/awarding/incentive-model-and-awards, we only want 5% our of pool to be dedicated to gas improvements.
- We would only like to consider custom code (not compound) for these optimizations
- In cToken contracts, the only gas optimization considered will be for KYC/Sanctions Checks
- There are unimplemented hooks in C*Delegate.sol files that we have left to be consistent with Compound - these should not be considered
- KYC/Sanction related edge cases specifically when a user’s KYC status or Sanction status changes in between different actions, leaving them at risk of their funds being locked in the protocols or being liquidated in Flux.
- If someone gets sanctioned they can not supply collateral (CASH or stablecoin)
- If someone loses KYC status they can not repay borrow or have someone repay borrow on behalf of them
- Third Party Upgradability Risk - we assume that third parties such as other stablecoins or oracles will not make upgrades resulting in malfunctions or loss of funds.
- If you have a public code repo, please share it here: N/A
- How many contracts are in scope?: 30
- Total SLoC for these contracts?: 4365
- How many external imports are there?: 4
- How many separate interfaces and struct definitions are there for the contracts within scope?: ~15 interfaces; ~40 structs
- Does most of your code generally use composition or inheritance?: Inheritance
- How many external calls?: 2 (Chainlink and Compound oracles)
- What is the overall line coverage percentage provided by your tests?: Unknown
- Is there a need to understand a separate part of the codebase / get context in order to audit this part of the protocol?: No
- Please describe required context:
- Does it use an oracle?: Yes; (Compound and Chainlink oracles)
- Does the token conform to the ERC20 standard?: Yes
- Are there any novel or unique curve logic or mathematical models?: None (Compound fork)
- Does it use a timelock function?: No
- Is it an NFT?: No
- Does it have an AMM?: No
- Is it a fork of a popular project?: Yes
- Does it use rollups?: No
- Is it multi-chain?: No
- Does it use a side-chain?: No
- Install Node >= 16
- Run
yarn install
- Install forge
- Copy
.env.example
to a new file.env
in the root directory of the repo. Keep theFORK_FROM_BLOCK_NUMBER
value the same. Fill in a dummy mnemonic and add a RPC_URL to populateFORGE_API_KEY_ETHEREUM
andETHEREUM_RPC_URL
. These RPC urls can be the same, but be sure to remove any quotes fromFORGE_API_KEY_ETHEREUM
- Run
yarn init-repo
- Start a local blockchain:
yarn local-node
- The scripts found under
scripts/<cash or lending>/ci/event_coverage.ts
aim to interact with the contracts in a way that maximizes the count of distinct event types emitted. For example:
- The scripts found under
yarn hardhat run --network localhost scripts/<cash or lending>/ci/event_coverage.ts
-
Run Tests:
yarn test-forge
- Run Cash Tests:
yarn test-forge-cash
- Run Flux Tests:
yarn test-forge-lending
- Run Cash Tests:
-
Generate Gas Report:
yarn test-forge --gas-report
For testing with Foundry, forge-tests/lending/DeployBasicLendingMarket.t.sol
& forge-tests/BasicDeployment.sol
were added to allow for users to easily deploy and setup the CASH/CASH+ dapp, and Flux lending market for local testing.
To setup and write tests for contracts within foundry from a deployed state please include the following layout within your testing file. Helper functions are provided within each of these respective setup files.
pragma solidity 0.8.16;
import "forge-tests/lending/DeployBasicLendingMarket.t.sol";
contract Test_case_someDescription is BasicLendingMarket {
function testName() public {
console.log(fCASH.name());
console.log(fCASH.symbol());
}
}
Note:
BasicLendingMarket
inherits fromBasicDeployment
.- Within the foundry tests
address(this)
is given certain permissioned roles. Please use a freshly generated address when writing POC's related to bypassing access controls.
export FORK_URL="<your-mainnet-rpc-url>" && rm -Rf 2023-01-ondo || true && git clone https://github.com/code-423n4/2023-01-ondo.git --recurse-submodules && cd 2023-01-ondo && nvm install 16.0 && echo -e "FORGE_API_KEY_ETHEREUM = $FORK_URL\nETHEREUM_RPC_URL = \"$FORK_URL\"\nMNEMONIC='test test test test test test test test test test test junk'\nFORK_FROM_BLOCK_NUMBER=15958078" > .env && yarn install && foundryup && yarn init-repo && yarn test-forge --gas-report
CTRL+Click in Vs Code may not work due to usage of relative and absolute import paths.
The entire suite of Flux and CASH contracts have been deployed to polygon. These contracts may not match exactly what is in repo as they were deployed before certain changes were made. They can be found at the following addresses: