From 10d782b7ce10510d9957067c847e88c2852f75ba Mon Sep 17 00:00:00 2001 From: rahul-kothari Date: Wed, 2 Nov 2022 12:53:10 +0000 Subject: [PATCH] WIP: compound v3 asset proxy --- contracts/Compound3AssetProxy.sol | 151 ++++++++ .../external/CometMainInterface.sol | 336 ++++++++++++++++++ 2 files changed, 487 insertions(+) create mode 100644 contracts/Compound3AssetProxy.sol create mode 100644 contracts/interfaces/external/CometMainInterface.sol diff --git a/contracts/Compound3AssetProxy.sol b/contracts/Compound3AssetProxy.sol new file mode 100644 index 00000000..a24e25f1 --- /dev/null +++ b/contracts/Compound3AssetProxy.sol @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0; + +import "./interfaces/IERC20.sol"; +import "./WrappedPosition.sol"; +import "./interfaces/external/CometMainInterface.sol"; +import "./libraries/Authorizable.sol"; + +/// @author Element Finance +/// @title Compound Asset Proxy +contract Compound3AssetProxy is WrappedPosition, Authorizable { + uint8 public immutable underlyingDecimals; + // The comet contract + CometMainInterface public immutable comet; + // The comet reward contract + CometRewards public immutable cometRewards; + // cTokens issued + uint256 public yieldSharesIssued; + + /// @notice Constructs this contract and stores needed data + /// @param _comet The underlying ctoken + /// @param _cometRewards The Compound rewards contract + /// @param _token The underlying token + /// @param _name The name of the token created + /// @param _symbol The symbol of the token created + /// @param _owner The contract owner who is authorized to collect rewards + constructor( + address _comet, + address _cometRewards, + IERC20 _token, + string memory _name, + string memory _symbol, + address _owner + ) WrappedPosition(_token, _name, _symbol) { + _authorize(_owner); + // Authorize the contract owner + setOwner(_owner); + + comet = CometMainInterface(_comet); + cometRewards = CometRewards(_cometRewards); + // Set approval for the proxy + _token.approve(_comet, type(uint256).max); + underlyingDecimals = _token.decimals(); + + // require(underlyingDecimals == comet.decimals(), "inconsistent decimals"); + // // We must assume the ctoken has 8 decimals to make the correct calculation for exchangeRate + // require( + // IERC20(_comet).decimals() == 8, + // "breaks our assumption in exchange rate" + // ); + + // Check that the underlying token is the same as ctoken's underlying + require(address(_token) == CometMainInterface(_comet).baseToken()); + } + + /// @notice Makes the actual ctoken deposit + /// @return Tuple (the shares minted, amount underlying used) + function _deposit() internal override returns (uint256, uint256) { + // Load balance of contract + uint256 depositAmount = token.balanceOf(address(this)); + // cToken balance before depositing + uint256 beforeBalance = comet.balanceOf(address(this)); + // Deposit into compound + // 'token' is an immutable in WrappedPosition + comet.supply(address(token), depositAmount); + // upon depositing, comet mints cToken == depositAmount. Balance of cToken increases as it accrues interest. + // We track number of tokens we have minted at the deposit call. + yieldSharesIssued += depositAmount; + // Get ctoken balance after minting + uint256 afterBalance = comet.balanceOf(address(this)); + // Calculate ctoken shares minted - this should vbe equal to depositAmount. + uint256 shares = afterBalance - beforeBalance; + // Return the amount of shares the user has produced and the amount of underlying used for it. + return (shares, depositAmount); + } + + /// @notice Withdraw the number of shares + /// @param _shares The number of shares to withdraw + /// @param _destination The address to send the output funds + // @param _underlyingPerShare The possibly precomputed underlying per share + /// @return Amount of funds freed by doing a withdraw + function _withdraw( + uint256 _shares, + address _destination, + uint256 + ) internal override returns (uint256) { + // comet doesn't return how much was withdrawn, so we get the balance before and after withdrawal. + // 'token' is an immutable in WrappedPosition + uint256 beforeBalance = token.balanceOf(address(this)); + // withdraw from comet + comet.withdraw(address(token), _shares); + // Get underlying balance after withdrawing + uint256 afterBalance = token.balanceOf(address(this)); + // Calculate the amount of funds that were freed + // cTokens increase with balance. So amountReceived is close to _shares. + uint256 amountReceived = afterBalance - beforeBalance; + // Transfer the underlying to the destination + token.transfer(_destination, amountReceived); + // Return the amount of underlying + return amountReceived; + } + + /// @notice Get the underlying amount of tokens per shares given + /// @param _amount The amount of shares you want to know the value of + /// @return Value of shares in underlying token + function _underlying(uint256 _amount) + internal + view + override + returns (uint256) + { + return yieldSharesAsUnderlying(_amount); + } + + /// @notice Calculates the yieldShare value for an amount of underlying + /// @return yieldShares `YieldShares` is an internal and inferred constant + /// time representation of a depositors claim of a growing pool of + /// deposited underlying by this contract in the Compound protocol. + /// The rationale to do so is due to Compounds non-constant + /// representation of "share" balances being directly the underlying + /// deposited + the interest accrued. Integrations with this + /// protocol must represent shares in a fixed amount so we infer + /// this artificially using `yieldSharesIssued` + function underlyingAsYieldShares(uint256 underlying) + public + view + returns (uint256 yieldShares) + { + yieldShares = + (yieldSharesIssued * underlying) / + comet.balanceOf(address(this)); + } + + /// @notice Calculates the underlying value for an amount of yieldShares + /// @return underlying The token yield is denominated in + function yieldSharesAsUnderlying(uint256 yieldShares) + public + view + returns (uint256 underlying) + { + underlying = + (comet.balanceOf(address(this)) * yieldShares) / + yieldSharesIssued; + } + + /// @notice Collect the comp rewards accrued + /// @param _destination The address to send the rewards to + function collectRewards(address _destination) external onlyAuthorized { + cometRewards.claimTo(address(comet), address(this), _destination, true); + } +} diff --git a/contracts/interfaces/external/CometMainInterface.sol b/contracts/interfaces/external/CometMainInterface.sol new file mode 100644 index 00000000..7c266eed --- /dev/null +++ b/contracts/interfaces/external/CometMainInterface.sol @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +// import "./CometCore.sol"; + +abstract contract CometCore { + struct AssetInfo { + uint8 offset; + address asset; + address priceFeed; + uint64 scale; + uint64 borrowCollateralFactor; + uint64 liquidateCollateralFactor; + uint64 liquidationFactor; + uint128 supplyCap; + } +} + +interface CometRewards { + function claim( + address comet, + address src, + bool shouldAccrue + ) external; + + function claimTo( + address comet, + address src, + address to, + bool shouldAccrue + ) external; +} + +/** + * @title Compound's Comet Main Interface (without Ext) + * @notice An efficient monolithic money market protocol + * @author Compound + */ +abstract contract CometMainInterface is CometCore { + event Supply(address indexed from, address indexed dst, uint256 amount); + event Transfer(address indexed from, address indexed to, uint256 amount); + event Withdraw(address indexed src, address indexed to, uint256 amount); + + event SupplyCollateral( + address indexed from, + address indexed dst, + address indexed asset, + uint256 amount + ); + event TransferCollateral( + address indexed from, + address indexed to, + address indexed asset, + uint256 amount + ); + event WithdrawCollateral( + address indexed src, + address indexed to, + address indexed asset, + uint256 amount + ); + + /// @notice Event emitted when a borrow position is absorbed by the protocol + event AbsorbDebt( + address indexed absorber, + address indexed borrower, + uint256 basePaidOut, + uint256 usdValue + ); + + /// @notice Event emitted when a user's collateral is absorbed by the protocol + event AbsorbCollateral( + address indexed absorber, + address indexed borrower, + address indexed asset, + uint256 collateralAbsorbed, + uint256 usdValue + ); + + /// @notice Event emitted when a collateral asset is purchased from the protocol + event BuyCollateral( + address indexed buyer, + address indexed asset, + uint256 baseAmount, + uint256 collateralAmount + ); + + /// @notice Event emitted when an action is paused/unpaused + event PauseAction( + bool supplyPaused, + bool transferPaused, + bool withdrawPaused, + bool absorbPaused, + bool buyPaused + ); + + /// @notice Event emitted when reserves are withdrawn by the governor + event WithdrawReserves(address indexed to, uint256 amount); + + function supply(address asset, uint256 amount) external virtual; + + function supplyTo( + address dst, + address asset, + uint256 amount + ) external virtual; + + function supplyFrom( + address from, + address dst, + address asset, + uint256 amount + ) external virtual; + + function transfer(address dst, uint256 amount) + external + virtual + returns (bool); + + function transferFrom( + address src, + address dst, + uint256 amount + ) external virtual returns (bool); + + function transferAsset( + address dst, + address asset, + uint256 amount + ) external virtual; + + function transferAssetFrom( + address src, + address dst, + address asset, + uint256 amount + ) external virtual; + + function withdraw(address asset, uint256 amount) external virtual; + + function withdrawTo( + address to, + address asset, + uint256 amount + ) external virtual; + + function withdrawFrom( + address src, + address to, + address asset, + uint256 amount + ) external virtual; + + function approveThis( + address manager, + address asset, + uint256 amount + ) external virtual; + + function withdrawReserves(address to, uint256 amount) external virtual; + + function absorb(address absorber, address[] calldata accounts) + external + virtual; + + function buyCollateral( + address asset, + uint256 minAmount, + uint256 baseAmount, + address recipient + ) external virtual; + + function quoteCollateral(address asset, uint256 baseAmount) + public + view + virtual + returns (uint256); + + function getAssetInfo(uint8 i) + public + view + virtual + returns (AssetInfo memory); + + function getAssetInfoByAddress(address asset) + public + view + virtual + returns (AssetInfo memory); + + function getReserves() public view virtual returns (int256); + + function getPrice(address priceFeed) public view virtual returns (uint256); + + function isBorrowCollateralized(address account) + public + view + virtual + returns (bool); + + function isLiquidatable(address account) public view virtual returns (bool); + + function totalSupply() external view virtual returns (uint256); + + function totalBorrow() external view virtual returns (uint256); + + function balanceOf(address owner) public view virtual returns (uint256); + + function borrowBalanceOf(address account) + public + view + virtual + returns (uint256); + + function pause( + bool supplyPaused, + bool transferPaused, + bool withdrawPaused, + bool absorbPaused, + bool buyPaused + ) external virtual; + + function isSupplyPaused() public view virtual returns (bool); + + function isTransferPaused() public view virtual returns (bool); + + function isWithdrawPaused() public view virtual returns (bool); + + function isAbsorbPaused() public view virtual returns (bool); + + function isBuyPaused() public view virtual returns (bool); + + function accrueAccount(address account) external virtual; + + function getSupplyRate(uint256 utilization) + public + view + virtual + returns (uint64); + + function getBorrowRate(uint256 utilization) + public + view + virtual + returns (uint64); + + function getUtilization() public view virtual returns (uint256); + + function governor() external view virtual returns (address); + + function pauseGuardian() external view virtual returns (address); + + function baseToken() external view virtual returns (address); + + function baseTokenPriceFeed() external view virtual returns (address); + + function extensionDelegate() external view virtual returns (address); + + /// @dev uint64 + function supplyKink() external view virtual returns (uint256); + + /// @dev uint64 + function supplyPerSecondInterestRateSlopeLow() + external + view + virtual + returns (uint256); + + /// @dev uint64 + function supplyPerSecondInterestRateSlopeHigh() + external + view + virtual + returns (uint256); + + /// @dev uint64 + function supplyPerSecondInterestRateBase() + external + view + virtual + returns (uint256); + + /// @dev uint64 + function borrowKink() external view virtual returns (uint256); + + /// @dev uint64 + function borrowPerSecondInterestRateSlopeLow() + external + view + virtual + returns (uint256); + + /// @dev uint64 + function borrowPerSecondInterestRateSlopeHigh() + external + view + virtual + returns (uint256); + + /// @dev uint64 + function borrowPerSecondInterestRateBase() + external + view + virtual + returns (uint256); + + /// @dev uint64 + function storeFrontPriceFactor() external view virtual returns (uint256); + + /// @dev uint64 + function baseScale() external view virtual returns (uint256); + + /// @dev uint64 + function trackingIndexScale() external view virtual returns (uint256); + + /// @dev uint64 + function baseTrackingSupplySpeed() external view virtual returns (uint256); + + /// @dev uint64 + function baseTrackingBorrowSpeed() external view virtual returns (uint256); + + /// @dev uint104 + function baseMinForRewards() external view virtual returns (uint256); + + /// @dev uint104 + function baseBorrowMin() external view virtual returns (uint256); + + /// @dev uint104 + function targetReserves() external view virtual returns (uint256); + + function numAssets() external view virtual returns (uint8); + + function decimals() external view virtual returns (uint8); + + function initializeStorage() external virtual; +}