This is a Compound III Collateral Plugin for the Reserve Protocol.
This plugin enables the use of cUSDCv3
(Compound III's cUSDC) as collateral within the Reserve Protocol. Some important notes about Compound III:
- Unlike Compound v2, where you can borrow multiple different assets, Compound III only allows you to borrow the base asset. Its base asset is
USDC
. - The only way to earn interest is to supply the base asset. Supplying any other asset other than the base asset just gives you better borrow rates.
- There is only one
CToken
in Compound III, unlike in Compound v2. This token iscUSDCv3
and it is given to users that supply USDC to the protocol. - Compound III is also known as Comet. In this document, we will be using Compound III, Comet and cUSDCv3 interchangeably.
cUSDCv3
is a rebasing token unlikeCToken
s in Compound v2.- Compound v2 users are being encouraged to move to Compound III.
tok |
ref |
target |
UoA |
---|---|---|---|
cUSDCv3 | USDC | USD | USD |
Since cUSDCv3
is a rebasing token, we use a Wrapper Token that converts it into an exchange-rate token so we can use it within Reserve. Technically, our collateral token is WcUSDCv3
(Wrapped cUSDCv3) but to keep things simple, we will treat cUSDCv3
as our collateral token.
The WcUSDCv3
contract keeps track of user participation so they get interest accruals on USDC and the reward accruals as if they had kept ownership of their cUSDCv3
.
We use the exchange rate of WcUSDCv3
:cUSDCv3
as refPerTok. This value mostly increases but can decrease if a large majority of the supply of WcUSDCv3
has been burned. Basing on my own tests, this happens when ~90-100% of the supply is burned in one go. Apart from this exceptional circumstance, it will be increasing.
The collateral becomes disabled in the following scenarios:
- refPerTok() decreases. This happens when the exchange rate of
WcUSDCv3
:cUSDCv3
decreases. This has been described above. - Compound III's reserves have become less than the configured
reservesThresholdDisabled
. Unhealthy levels of reserves may mean the protocol may soon become insolvent.
The collateral becomes iffy in the following scenarios:
- The reference unit has not been able to maintain peg with the target unit within a configured threshold.
- Compound III's reserves have become less than the configured
reservesThresholdIffy
.
Unlike other Collateral Plugins, we do not need to call an update transaction for this to get an updated balance. Compound III is able to compute for latest balances even when state variables for tracking accruals have not been called. This leads to a cheaper refresh() in terms of gas costs relative to other plugins.
When possible or practical, we do mainnet forking in our tests. Tests that involve interacting with USDC and Compound III rely on the mainnet versions of those protocols. When testing interactions with the Reserve protocol, we deploy our own version of Reserve.
This comes with a deploy script and configuration. It is already fully configured for deployment
to Mainnet and Goerli. You may optionally set cusdcV3Wrapper
and oracleLib
if you want to use existing deployments for the Wrapped cUSDCv3 and OracleLib, respectively.
For the contracts to compile, run the following:
$ npm install
$ npx hardhat compile
To run the tests and/or the deployment scripts, a .env
file is expected with the following environment variables:
- MAINNET_RPC_URL - an RPC URL for ethereum mainnet
- MNEMONIC - mnemonic phrase for the private key
- GOERLI_RPC_URL - an RPC URL for ethereum goerli
Once .env
is setup and dependencies are installed, tests can be run with: npx hardhat test
or npm run test
Below are Slither warnings that were hidden since they were found to be non-issues.
ICollateral is re-used - (contracts/ICollateral.sol)
IRewardable is re-used - (contracts/ICollateral.sol)
IAsset is re-used - (contracts/ICollateral.sol)
- This gets triggered since we are compiling contracts from the Reserve protocol repo. Instead of changing the name, I am silencing the warning since these contracts in the Reserve dependency and in this repo are identical. I am keeping the interfaces needed for the Collateral contract in this repo for easy reference.
CusdcV3Wrapper.underlyingBalanceOf(address) (contracts/CusdcV3Wrapper.sol) uses a dangerous strict equality:
CusdcV3Wrapper.exchangeRate() (contracts/CusdcV3Wrapper.sol) uses a dangerous strict equality
- The strict equalities are only used to check if user has a balance of the Wrapped Tokens or if there is any supply of the Wrapped Tokens. No balance or supply should return 0.
Reentrancy in CusdcV3Wrapper.accrueAccount(address) (contracts/CusdcV3Wrapper.sol)
Reentrancy in CusdcV3Wrapper.claimTo(address,address) (contracts/CusdcV3Wrapper.sol)
Reentrancy in CusdcV3Wrapper._withdraw(address,address,address,uint256) (contracts/CusdcV3Wrapper.sol)
Reentrancy in CusdcV3Wrapper._deposit(address,address,address,uint256) (contracts/CusdcV3Wrapper.sol)
Comet.accrueAccount()
needs to be called prior to updating user data stored in state variables so we get the updated supply indices from Comet. Without callingaccrueAccount
we may get outdated supply indices and the records we have of user'sprincipal
andbaseTrackingAccrued
will be inaccurate. This will mean inaccurate balances and accrued rewards. Also, we are calling a known contract andComet.accrueAccount
does not do any re-entrancy.
CusdcV3Wrapper.claimTo(address,address) (contracts/CusdcV3Wrapper.sol) uses timestamp for comparisons
CusdcV3Wrapper.getRewardOwed(address) (contracts/CusdcV3Wrapper.sol) uses timestamp for comparisons
CusdcV3Wrapper.updatedAccountIndices(CusdcV3Wrapper.UserBasic,int256) (contracts/CusdcV3Wrapper.sol) uses timestamp for comparisons
- We need to use
block.timestamp
for computing interest and rewards accruals. This is the same logic as is used by Comet/cUSDCv3. Since timestamp can not be manipulated to be too far into the future, there is little incentive to manipulate because interest and rewards accruals per second is a very small fraction. Also, the risk is taken on by Comet because they will be the one paying for the accruals in USDC.
Different versions of Solidity are used
- The contracts related to the
Wrapped cUSDCv3
use0.8.17
since it is the latest version of Solidity. The Collateral contracts use0.8.9
since Reserve's contracts use0.8.9
and they are used as dependencies.
CusdcV3Wrapper (contracts/CusdcV3Wrapper.sol) should inherit from ICusdcV3Wrapper (contracts/ICusdcV3Wrapper.sol)
ICusdcV3Wrapper
is only used byCTokenV3Collateral
to interface withCusdcV3Wrapper
.
- Twitter - https://twitter.com/gjaldon
- Discord - gjaldon#9165