Skip to content

Commit

Permalink
Compoundv3 USDT Plugin (#1213)
Browse files Browse the repository at this point in the history
Co-authored-by: Patrick McKelvy <[email protected]>
  • Loading branch information
julianmrodri and pmckelvy1 authored Oct 30, 2024
1 parent f9380fd commit db527b7
Show file tree
Hide file tree
Showing 27 changed files with 1,628 additions and 1,257 deletions.
3 changes: 3 additions & 0 deletions common/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export interface ITokens {
cUSDCv3?: string
wcUSDCv3?: string
cUSDbCv3?: string
cUSDTv3?: string
ONDO?: string
sFRAX?: string
sDAI?: string
Expand Down Expand Up @@ -233,6 +234,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
apxETH: '0x9Ba021B0a9b958B5E75cE9f6dff97C7eE52cb3E6',
cUSDCv3: '0xc3d688B66703497DAA19211EEdff47f25384cdc3',
wcUSDCv3: '0x27F2f159Fe990Ba83D57f39Fd69661764BEbf37a',
cUSDTv3: '0x3Afdc9BCA9213A35503b077a6072F3D0d5AB0840',
ONDO: '0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3',
sFRAX: '0xA663B02CF0a4b149d2aD41910CB81e23e1c41c32',
sDAI: '0x83f20f44975d03b1b09e64809b757c47f942beea',
Expand Down Expand Up @@ -553,6 +555,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
USDC: '0xaf88d065e77c8cc2239327c5edb3a432268e5831',
USDT: '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9',
cUSDCv3: '0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf',
cUSDTv3: '0xd98Be00b5D27fc98112BdE293e487f8D4cA57d07',
WETH: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1',
WBTC: '0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f',
aArbUSDCn: '0x724dc807b04555b71ed48a6896b6f41593b8c637', // aArbUSDCn wraps USDC!
Expand Down
4 changes: 2 additions & 2 deletions contracts/facade/FacadeMonitor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import "../interfaces/IRToken.sol";
import "../libraries/Fixed.sol";
import "../p1/RToken.sol";
import "../plugins/assets/compoundv2/DEPRECATED_CTokenWrapper.sol";
import "../plugins/assets/compoundv3/ICusdcV3Wrapper.sol";
import "../plugins/assets/compoundv3/ICFiatV3Wrapper.sol";
import "../plugins/assets/stargate/StargateRewardableWrapper.sol";
import { StaticATokenV3LM } from "../plugins/assets/aave-v3/vendor/StaticATokenV3LM.sol";
import "../plugins/assets/morpho-aave/MorphoAaveV2TokenisedDeposit.sol";
Expand Down Expand Up @@ -174,7 +174,7 @@ contract FacadeMonitor is Initializable, OwnableUpgradeable, UUPSUpgradeable, IF
backingBalance = (cTokenBal * exchangeRate) / 1e18;
availableLiquidity = underlying.balanceOf(address(cToken));
} else if (collType == CollPluginType.COMPOUND_V3) {
ICusdcV3Wrapper cTokenV3Wrapper = ICusdcV3Wrapper(address(erc20));
ICFiatV3Wrapper cTokenV3Wrapper = ICFiatV3Wrapper(address(erc20));
CometInterface cTokenV3 = CometInterface(address(cTokenV3Wrapper.underlyingComet()));
IERC20 underlying = IERC20(cTokenV3.baseToken());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,62 +5,72 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "./vendor/CometInterface.sol";
import "./WrappedERC20.sol";
import "./vendor/ICometRewards.sol";
import "./ICusdcV3Wrapper.sol";
import "./ICFiatV3Wrapper.sol";
import "./CometHelpers.sol";

/**
* @title CusdcV3Wrapper
* @notice Wrapper for cUSDCV3 / COMET that acts as a stable-balance ERC20, instead of rebasing
* token. {comet} will be used as the unit for the underlying token, and {wComet} will be used
* as the unit for wrapped tokens.
* @title CFiatV3Wrapper
* @notice Wrapper for Compound V3 fiat coins such as cUSDCv3, cUSDTv3 / COMET that acts
* as a stable-balance ERC20, instead of rebasing token. {comet} will be used as the unit
* for the underlying token, and {wComet} will be used as the unit for wrapped tokens.
*/
contract CusdcV3Wrapper is ICusdcV3Wrapper, WrappedERC20, CometHelpers {
contract CFiatV3Wrapper is ICFiatV3Wrapper, WrappedERC20, CometHelpers {
using SafeERC20 for IERC20;

/// From cUSDCv3, used in principal <> present calculations
uint256 public constant TRACKING_INDEX_SCALE = 1e15;
/// From cUSDCv3, scaling factor for USDC rewards
uint256 public constant RESCALE_FACTOR = 1e12;

CometInterface public immutable underlyingComet;
ICometRewards public immutable rewardsAddr;
IERC20 public immutable rewardERC20;
uint256 public immutable trackingIndexScale;
uint256 public immutable rescaleFactor;
uint256 internal immutable accrualDescaleFactor;
uint256 public immutable multiplier;
uint8 internal immutable cometDecimals;

mapping(address => uint64) public baseTrackingIndex; // uint64 for consistency with CometHelpers
mapping(address => uint256) public baseTrackingAccrued; // uint256 to avoid overflow in L:199
mapping(address => uint256) public rewardsClaimed;

constructor(
address cusdcv3,
address ctokenv3,
address rewardsAddr_,
address rewardERC20_
) WrappedERC20("Wrapped cUSDCv3", "wcUSDCv3") {
if (cusdcv3 == address(0)) revert ZeroAddress();
address rewardERC20_,
string memory name,
string memory symbol,
uint256 rewardMultiplier
) WrappedERC20(name, symbol) {
if (ctokenv3 == address(0)) revert ZeroAddress();

rewardsAddr = ICometRewards(rewardsAddr_);
rewardERC20 = IERC20(rewardERC20_);
underlyingComet = CometInterface(cusdcv3);
underlyingComet = CometInterface(ctokenv3);
cometDecimals = underlyingComet.decimals();
// for principal <> present calculations
trackingIndexScale = underlyingComet.trackingIndexScale();
// scaling factor for rewards
rescaleFactor = 10**(18 - cometDecimals);
accrualDescaleFactor = 10**(cometDecimals - 6);
multiplier = rewardMultiplier;
}

/// @return number of decimals
function decimals() public pure override(IERC20Metadata, WrappedERC20) returns (uint8) {
return 6;
function decimals() public view override(IERC20Metadata, WrappedERC20) returns (uint8) {
return cometDecimals;
}

/// @param amount {Comet} The amount of cUSDCv3 to deposit
/// @param amount {Comet} The amount of cTokenV3 to deposit
function deposit(uint256 amount) external {
_deposit(msg.sender, msg.sender, msg.sender, amount);
}

/// @param dst The dst to deposit into
/// @param amount {Comet} The amount of cUSDCv3 to deposit
/// @param amount {Comet} The amount of cTokenV3 to deposit
function depositTo(address dst, uint256 amount) external {
_deposit(msg.sender, msg.sender, dst, amount);
}

/// @param src The address to deposit from
/// @param dst The address to deposit to
/// @param amount {Comet} The amount of cUSDCv3 to deposit
/// @param amount {Comet} The amount of cTokenV3 to deposit
function depositFrom(
address src,
address dst,
Expand All @@ -70,11 +80,11 @@ contract CusdcV3Wrapper is ICusdcV3Wrapper, WrappedERC20, CometHelpers {
}

/// Only called internally to run the deposit logic
/// Takes `amount` fo cUSDCv3 from `src` and deposits to `dst` account in the wrapper.
/// Takes `amount` fo cTokenV3 from `src` and deposits to `dst` account in the wrapper.
/// @param operator The address calling the contract (msg.sender)
/// @param src The address to deposit from
/// @param dst The address to deposit to
/// @param amount {Comet} The amount of cUSDCv3 to deposit
/// @param amount {Comet} The amount of cTokenv3 to deposit
function _deposit(
address operator,
address src,
Expand Down Expand Up @@ -102,20 +112,20 @@ contract CusdcV3Wrapper is ICusdcV3Wrapper, WrappedERC20, CometHelpers {
_mint(dst, uint104(wrapperPostPrinc - wrapperPrePrinc));
}

/// @param amount {Comet} The amount of cUSDCv3 to withdraw
/// @param amount {Comet} The amount of cTokenV3 to withdraw
function withdraw(uint256 amount) external {
_withdraw(msg.sender, msg.sender, msg.sender, amount);
}

/// @param dst The address to withdraw cUSDCv3 to
/// @param amount {Comet} The amount of cUSDCv3 to withdraw
/// @param dst The address to withdraw cTokenv3 to
/// @param amount {Comet} The amount of cTokenv3 to withdraw
function withdrawTo(address dst, uint256 amount) external {
_withdraw(msg.sender, msg.sender, dst, amount);
}

/// @param src The address to withdraw from
/// @param dst The address to withdraw cUSDCv3 to
/// @param amount {Comet} The amount of cUSDCv3 to withdraw
/// @param dst The address to withdraw cTokenv3 to
/// @param amount {Comet} The amount of cTokenv3 to withdraw
function withdrawFrom(
address src,
address dst,
Expand All @@ -125,12 +135,12 @@ contract CusdcV3Wrapper is ICusdcV3Wrapper, WrappedERC20, CometHelpers {
}

/// Internally called to run the withdraw logic
/// Withdraws `amount` cUSDCv3 from `src` account in the wrapper and sends to `dst`
/// Withdraws `amount` cTokenv3 from `src` account in the wrapper and sends to `dst`
/// @dev Rounds conservatively so as not to over-withdraw from the wrapper
/// @param operator The address calling the contract (msg.sender)
/// @param src The address to withdraw from
/// @param dst The address to withdraw cUSDCv3 to
/// @param amount {Comet} The amount of cUSDCv3 to withdraw
/// @param dst The address to withdraw cTokenv3 to
/// @param amount {Comet} The amount of cTokenv3 to withdraw
function _withdraw(
address operator,
address src,
Expand Down Expand Up @@ -195,7 +205,7 @@ contract CusdcV3Wrapper is ICusdcV3Wrapper, WrappedERC20, CometHelpers {

accrueAccount(src);
uint256 claimed = rewardsClaimed[src];
uint256 accrued = baseTrackingAccrued[src] * RESCALE_FACTOR;
uint256 accrued = (baseTrackingAccrued[src] * rescaleFactor * multiplier) / 1e18;
uint256 owed;
if (accrued > claimed) {
owed = accrued - claimed;
Expand All @@ -210,20 +220,20 @@ contract CusdcV3Wrapper is ICusdcV3Wrapper, WrappedERC20, CometHelpers {
emit RewardsClaimed(rewardERC20, owed);
}

/// Accure the cUSDCv3 account of the wrapper
/// Accure the cTokenv3 account of the wrapper
function accrue() public {
underlyingComet.accrueAccount(address(this));
}

/// @param account The address to accrue, first in cUSDCv3, then locally
/// @param account The address to accrue, first in cTokenv3, then locally
function accrueAccount(address account) public {
underlyingComet.accrueAccount(address(this));
accrueAccountRewards(account);
}

/// Get the balance of cUSDCv3 that is represented by the `accounts` wrapper value.
/// @param account The address to calculate the cUSDCv3 balance of
/// @return {Comet} The cUSDCv3 balance that `account` holds in the wrapper
/// Get the balance of cTokenv3 that is represented by the `accounts` wrapper value.
/// @param account The address to calculate the cTokenv3 balance of
/// @return {Comet} The cTokenv3 balance that `account` holds in the wrapper
function underlyingBalanceOf(address account) public view returns (uint256) {
uint256 balance = balanceOf(account);
if (balance == 0) {
Expand All @@ -239,7 +249,7 @@ contract CusdcV3Wrapper is ICusdcV3Wrapper, WrappedERC20, CometHelpers {
}

/// @param amount The value of {wComet} to convert to {Comet}
/// @return {Comet} The amount of cUSDCv3 represented by `amount of {wComet}
/// @return {Comet} The amount of cTokenv3 represented by `amount of {wComet}
function convertStaticToDynamic(uint104 amount) public view returns (uint256) {
(uint64 baseSupplyIndex, ) = getUpdatedSupplyIndicies();
return presentValueSupply(baseSupplyIndex, amount);
Expand All @@ -260,10 +270,11 @@ contract CusdcV3Wrapper is ICusdcV3Wrapper, WrappedERC20, CometHelpers {
uint256 indexDelta = uint256(trackingSupplyIndex - baseTrackingIndex[account]);
uint256 newBaseTrackingAccrued = baseTrackingAccrued[account] +
(safe104(balanceOf(account)) * indexDelta) /
TRACKING_INDEX_SCALE;
trackingIndexScale /
accrualDescaleFactor;

uint256 claimed = rewardsClaimed[account];
uint256 accrued = newBaseTrackingAccrued * RESCALE_FACTOR;
uint256 accrued = (newBaseTrackingAccrued * rescaleFactor * multiplier) / 1e18;
uint256 owed = accrued > claimed ? accrued - claimed : 0;

return owed;
Expand All @@ -289,7 +300,10 @@ contract CusdcV3Wrapper is ICusdcV3Wrapper, WrappedERC20, CometHelpers {
(, uint64 trackingSupplyIndex) = getSupplyIndices();
uint256 indexDelta = uint256(trackingSupplyIndex - baseTrackingIndex[account]);

baseTrackingAccrued[account] += (safe104(accountBal) * indexDelta) / TRACKING_INDEX_SCALE;
baseTrackingAccrued[account] +=
(safe104(accountBal) * indexDelta) /
trackingIndexScale /
accrualDescaleFactor;
baseTrackingIndex[account] = trackingSupplyIndex;
}

Expand Down
14 changes: 7 additions & 7 deletions contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "../../../libraries/Fixed.sol";
import "../AppreciatingFiatCollateral.sol";
import "../OracleLib.sol";
import "./ICusdcV3Wrapper.sol";
import "./ICFiatV3Wrapper.sol";
import "./vendor/IComet.sol";

/**
* @title CTokenV3Collateral
* @notice Collateral plugin for Compound V3,
* tok = wcUSDC
* ref = USDC
* tok = wcToken (wcUSDC, wcUSDT, etc)
* ref = USDC/USDT/etc
* tar = USD
* UoA = USD
*/
Expand All @@ -31,8 +31,8 @@ contract CTokenV3Collateral is AppreciatingFiatCollateral {
AppreciatingFiatCollateral(config, revenueHiding)
{
require(config.defaultThreshold != 0, "defaultThreshold zero");
comp = ICusdcV3Wrapper(address(config.erc20)).rewardERC20();
comet = IComet(address(ICusdcV3Wrapper(address(erc20)).underlyingComet()));
comp = ICFiatV3Wrapper(address(config.erc20)).rewardERC20();
comet = IComet(address(ICFiatV3Wrapper(address(erc20)).underlyingComet()));
cometDecimals = comet.decimals();
}

Expand All @@ -46,7 +46,7 @@ contract CTokenV3Collateral is AppreciatingFiatCollateral {
function underlyingRefPerTok() public view virtual override returns (uint192) {
return
shiftl_toFix(
ICusdcV3Wrapper(address(erc20)).exchangeRate(),
ICFiatV3Wrapper(address(erc20)).exchangeRate(),
-int8(cometDecimals),
FLOOR
);
Expand All @@ -55,7 +55,7 @@ contract CTokenV3Collateral is AppreciatingFiatCollateral {
/// Refresh exchange rates and update default status.
/// @dev Should not need to override: can handle collateral with variable refPerTok()
function refresh() public virtual override {
ICusdcV3Wrapper(address(erc20)).accrue();
ICFiatV3Wrapper(address(erc20)).accrue();

CollateralStatus oldStatus = status();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import "./vendor/CometInterface.sol";
import "./IWrappedERC20.sol";
import "../../../interfaces/IRewardable.sol";

interface ICusdcV3Wrapper is IWrappedERC20, IRewardable {
interface ICFiatV3Wrapper is IWrappedERC20, IRewardable {
struct UserBasic {
uint104 principal;
uint64 baseTrackingIndex;
Expand Down
2 changes: 1 addition & 1 deletion contracts/plugins/assets/compoundv3/WrappedERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ abstract contract WrappedERC20 is IWrappedERC20 {
/**
* @dev Returns the decimals places of the token.
*/
function decimals() public pure virtual returns (uint8) {
function decimals() public view virtual returns (uint8) {
return 18;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ interface ICometRewards {
address token;
uint64 rescaleFactor;
bool shouldUpscale;
uint256 multiplier;
}

struct RewardOwed {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;

import "../assets/compoundv3/CusdcV3Wrapper.sol";
import "../assets/compoundv3/ICusdcV3Wrapper.sol";
import "../assets/compoundv3/CFiatV3Wrapper.sol";
import "../assets/compoundv3/ICFiatV3Wrapper.sol";

interface ICusdcV3WrapperMock is ICusdcV3Wrapper {
interface ICFiatV3WrapperMock is ICFiatV3Wrapper {
function setMockExchangeRate(bool setMock, uint256 mockValue) external;
}

contract CusdcV3WrapperMock {
contract CFiatV3WrapperMock {
uint256[20] private __gap;
address internal mockTarget;
mapping(bytes4 => bool) internal isMocking;
Expand All @@ -33,7 +33,7 @@ contract CusdcV3WrapperMock {
if (isMocking[this.exchangeRate.selector]) {
return mockExchangeRate_;
} else {
return CusdcV3Wrapper(mockTarget).exchangeRate();
return CFiatV3Wrapper(mockTarget).exchangeRate();
}
}

Expand Down
8 changes: 5 additions & 3 deletions scripts/addresses/1-tmp-assets-collateral.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@
"ETHx": "0x73a36258E6A48D0095D1997Fec7F51e191B4Ec81",
"apxETH": "0x05ffDaAA2aF48e1De1CE34d633db018a28e3B3F5",
"sUSDe": "0x35081Ca24319835e5f759163F7e75eaB753e0b7E",
"pyUSD": "0xa5cde4fB1132daF8f4a0D3140859271208d944E9"
"pyUSD": "0xa5cde4fB1132daF8f4a0D3140859271208d944E9",
"cUSDTv3": "0x1B2256a88Bb9F2E54cC8D355D3161a2F069a320B"
},
"erc20s": {
"stkAAVE": "0x4da27a545c0c5B758a6BA100e3a049001de870f5",
Expand Down Expand Up @@ -125,6 +126,7 @@
"ETHx": "0xA35b1B31Ce002FBF2058D22F30f95D405200A15b",
"apxETH": "0x9Ba021B0a9b958B5E75cE9f6dff97C7eE52cb3E6",
"sUSDe": "0x9D39A5DE30e57443BfF2A8307A4256c8797A3497",
"pyUSD": "0x6c3ea9036406852006290770bedfcaba0e23a0e8"
"pyUSD": "0x6c3ea9036406852006290770bedfcaba0e23a0e8",
"cUSDTv3": "0xEB74EC1d4C1DAB412D5d6674F6833FD19d3118Ce"
}
}
}
Loading

0 comments on commit db527b7

Please sign in to comment.