From 3c6a43ee8caa83a8b829ed45bb79ad4400eefd51 Mon Sep 17 00:00:00 2001 From: massun-onibakuchi Date: Thu, 12 Jan 2023 13:01:44 +0900 Subject: [PATCH] add some modules --- contracts/interfaces/morpho/IMorpho.sol | 46 ++++ .../interfaces/morpho/IMorphoCompoundLens.sol | 20 ++ contracts/modules/BaseBorrowModule.sol | 8 + contracts/modules/BaseDepositModule.sol | 6 + contracts/modules/BaseSwapModule.sol | 6 + contracts/modules/EulerDepositModule.sol | 20 ++ contracts/modules/MorphoBorrowModule.sol | 20 ++ .../strategies/BaseCollateralStrategy.sol | 196 ++++++++++++++++++ test/foundry/TestCollateralStrategy.sol | 41 ++++ 9 files changed, 363 insertions(+) create mode 100644 contracts/interfaces/morpho/IMorpho.sol create mode 100644 contracts/modules/BaseBorrowModule.sol create mode 100644 contracts/modules/BaseDepositModule.sol create mode 100644 contracts/modules/BaseSwapModule.sol create mode 100644 contracts/modules/EulerDepositModule.sol create mode 100644 contracts/modules/MorphoBorrowModule.sol create mode 100644 contracts/strategies/BaseCollateralStrategy.sol create mode 100644 test/foundry/TestCollateralStrategy.sol diff --git a/contracts/interfaces/morpho/IMorpho.sol b/contracts/interfaces/morpho/IMorpho.sol new file mode 100644 index 00000000..a618aa84 --- /dev/null +++ b/contracts/interfaces/morpho/IMorpho.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.5.0; + +interface IMorpho { + function supply(address _poolToken, uint256 _amount) external; + + function supply(address _poolToken, address _onBehalf, uint256 _amount) external; + + /// @notice Supplies underlying tokens to a specific market, on behalf of a given user, + /// specifying a gas threshold at which to cut the matching engine. + /// @dev `msg.sender` must have approved Morpho's contract to spend the underlying `_amount`. + /// @param _poolToken The address of the market the user wants to interact with. + /// @param _onBehalf The address of the account whose positions will be updated. + /// @param _amount The amount of token (in underlying) to supply. + /// @param _maxGasForMatching The gas threshold at which to stop the matching engine. + function supply(address _poolToken, address _onBehalf, uint256 _amount, uint256 _maxGasForMatching) external; + + function borrow(address _poolToken, uint256 _amount) external; + + /// @notice Borrows underlying tokens from a specific market, specifying a gas threshold at which to stop the matching engine. + /// @param _poolToken The address of the market the user wants to interact with. + /// @param _amount The amount of token (in underlying). + /// @param _maxGasForMatching The gas threshold at which to stop the matching engine. + function borrow(address _poolToken, uint256 _amount, uint256 _maxGasForMatching) external; + + function withdraw(address _poolToken, uint256 _amount) external; + + /// @notice Withdraws underlying tokens from a specific market. + /// @param _poolToken The address of the market the user wants to interact with. + /// @param _amount The amount of tokens (in underlying) to withdraw from supply. + /// @param _receiver The address to send withdrawn tokens to. + function withdraw(address _poolToken, uint256 _amount, address _receiver) external; + + function repay(address _poolToken, uint256 _amount) external; + + /// @notice Repays debt of a given user, up to the amount provided. + /// @dev `msg.sender` must have approved Morpho's contract to spend the underlying `_amount`. + /// @param _poolToken The address of the market the user wants to interact with. + /// @param _onBehalf The address of the account whose positions will be updated. + /// @param _amount The amount of token (in underlying) to repay from borrow. + function repay(address _poolToken, address _onBehalf, uint256 _amount) external; + + function claimRewards(address[] calldata _cTokenAddresses, bool _tradeForMorphoToken) + external + returns (uint256 claimedAmount); +} diff --git a/contracts/interfaces/morpho/IMorphoCompoundLens.sol b/contracts/interfaces/morpho/IMorphoCompoundLens.sol index df7bd3c1..8a5b9b3f 100644 --- a/contracts/interfaces/morpho/IMorphoCompoundLens.sol +++ b/contracts/interfaces/morpho/IMorphoCompoundLens.sol @@ -15,4 +15,24 @@ interface IMorphoCompoundLens { uint256 p2pSupplyAmount, uint256 poolSupplyAmount ); + + function getUserHealthFactor(address _user, address[] calldata _updatedMarkets) external view returns (uint256); + + function getCurrentSupplyBalanceInOf(address _poolToken, address _user) + external + view + returns ( + uint256 balanceOnPool, + uint256 balanceInP2P, + uint256 totalBalance + ); + + function getCurrentBorrowBalanceInOf(address _poolToken, address _user) + external + view + returns ( + uint256 balanceOnPool, + uint256 balanceInP2P, + uint256 totalBalance + ); } diff --git a/contracts/modules/BaseBorrowModule.sol b/contracts/modules/BaseBorrowModule.sol new file mode 100644 index 00000000..04196612 --- /dev/null +++ b/contracts/modules/BaseBorrowModule.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.10; + +abstract contract BaseBorrowModule { + function _borrowAsset(address _token, uint256 _debt) internal virtual returns (uint256 _debtAdded); + + function _repayAsset(address _token, uint256 _debts) internal virtual returns (uint256); +} diff --git a/contracts/modules/BaseDepositModule.sol b/contracts/modules/BaseDepositModule.sol new file mode 100644 index 00000000..df3644d1 --- /dev/null +++ b/contracts/modules/BaseDepositModule.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.10; + +abstract contract BaseDepositModule { + function _depositCollateral(address _token, uint256 _amount) internal virtual returns (uint256 _collateralAdded); +} diff --git a/contracts/modules/BaseSwapModule.sol b/contracts/modules/BaseSwapModule.sol new file mode 100644 index 00000000..5713c560 --- /dev/null +++ b/contracts/modules/BaseSwapModule.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.10; + +abstract contract BaseSwapModule { + function _swapForAsset(address _token, uint256 _amountIn) internal virtual returns (uint256 _amountOut); +} diff --git a/contracts/modules/EulerDepositModule.sol b/contracts/modules/EulerDepositModule.sol new file mode 100644 index 00000000..6fb3bdf5 --- /dev/null +++ b/contracts/modules/EulerDepositModule.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.10; + +import "./BaseDepositModule.sol"; +import "../interfaces/euler/IEToken.sol"; + +contract EulerDepositModule is BaseDepositModule { + /// @notice Euler markets contract address + address internal constant EULER_MARKETS = 0x3520d5a913427E6F0D6A83E07ccD4A4da316e4d3; + uint256 internal constant SUB_ACCOUNT_ID = 0; + + function _depositCollateral(address _token, uint256 _amount) + internal + virtual + override + returns (uint256 _collateralAdded) + { + IEToken(_token).deposit(SUB_ACCOUNT_ID, _amount); + } +} diff --git a/contracts/modules/MorphoBorrowModule.sol b/contracts/modules/MorphoBorrowModule.sol new file mode 100644 index 00000000..292f37a5 --- /dev/null +++ b/contracts/modules/MorphoBorrowModule.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.10; + +import "./BaseBorrowModule.sol"; + +import "../interfaces/morpho/IMorpho.sol"; + +contract MorphoBorrowModule is BaseBorrowModule { + address internal constant MORPHO = 0x8888882f8f843896699869179fB6E4f7e3B58888; + + function _borrowAsset(address _token, uint256 _debts) internal virtual override returns (uint256) { + IMorpho(MORPHO).borrow(_token, _debts); + return _debts; + } + + function _repayAsset(address _token, uint256 _debts) internal virtual override returns (uint256) { + IMorpho(MORPHO).borrow(_token, _debts); + return _debts; + } +} diff --git a/contracts/strategies/BaseCollateralStrategy.sol b/contracts/strategies/BaseCollateralStrategy.sol new file mode 100644 index 00000000..2279bcad --- /dev/null +++ b/contracts/strategies/BaseCollateralStrategy.sol @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +import "../interfaces/IIdleCDOStrategy.sol"; +import "../interfaces/IERC20Detailed.sol"; + +import "../modules/BaseBorrowModule.sol"; +import "../modules/BaseDepositModule.sol"; +import "../modules/BaseSwapModule.sol"; + +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; + +abstract contract BaseCollateralStrategy is + Initializable, + OwnableUpgradeable, + ERC20Upgradeable, + ReentrancyGuardUpgradeable, + IIdleCDOStrategy, + BaseDepositModule, + BaseBorrowModule, + BaseSwapModule +{ + using SafeERC20Upgradeable for IERC20Detailed; + + uint256 internal constant ONE_SCALE = 1e18; + + /// @notice underlying token address (ex: DAI) + address public override token; + + /// @notice strategy token address (ex: fDAI) + address public override strategyToken; + + address public debt; + + address public asset; + + /// @notice decimals of the underlying asset + uint256 public override tokenDecimals; + + /// @notice one underlying token + uint256 public override oneToken; + + /// @notice underlying ERC20 token contract + IERC20Detailed public underlyingToken; + + /// @notice address of the IdleCDO + address public idleCDO; + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + token = address(1); + } + + /// @notice can be only called once + /// @param _name name of this strategy ERC20 tokens + /// @param _symbol symbol of this strategy ERC20 tokens + /// @param _strategyToken address of the vault token + /// @param _token address of the underlying token + /// @param _owner owner of this contract + function _initialize( + string memory _name, + string memory _symbol, + address _strategyToken, + address _token, + address _owner + ) internal virtual initializer { + require(token == address(0), "Token is already initialized"); + + OwnableUpgradeable.__Ownable_init(); + ReentrancyGuardUpgradeable.__ReentrancyGuard_init(); + ERC20Upgradeable.__ERC20_init(_name, _symbol); + + //----- // -------// + strategyToken = _strategyToken; + token = _token; + underlyingToken = IERC20Detailed(token); + tokenDecimals = underlyingToken.decimals(); + oneToken = 10**(tokenDecimals); // underlying decimals + // NOTE: tokenized position has 18 decimals + + transferOwnership(_owner); + } + + /// @dev msg.sender should approve this contract first to spend `_amount` of `token` + /// @param _amount amount of `token` to deposit + /// @return shares strategyTokens minted + function deposit(uint256 _amount) external virtual override onlyIdleCDO returns (uint256 shares) { + if (_amount != 0) { + // Get current price + uint256 _price = price(); + + // Send tokens to the strategy + IERC20Detailed(token).safeTransferFrom(msg.sender, address(this), _amount); + + // Calls internal deposit function + _amount = _depositCollateral(token, _amount); + + uint256 debtsAdded = getDebtToBorrow(token, _amount); + debtsAdded = _borrowAsset(debt, debtsAdded); + + uint256 assets = debt == asset ? debtsAdded : _swapForAsset(debt, debtsAdded); + + _depositAsset(asset, assets); + + // Mint shares + shares = (_amount * ONE_SCALE) / _price; + _mint(msg.sender, shares); + } + } + + function getDebtToBorrow(address collateralAsset, uint256 _collateralAdded) public virtual returns (uint256); + + // /// @dev makes the actual deposit into the `strategy` + // /// @param _amount amount of tokens to deposit + // function _depositCollateral(address _token, uint256 _amount) internal virtual returns (uint256 _collateralAdded); + + // function _swapForAsset(address _token, uint256 _amountIn) internal virtual returns (uint256 _amountOut); + + // function _borrowAsset(address _token, uint256 _debts) internal virtual returns (uint256 _debtsAdded); + + // function _repayAsset(address _token, uint256 _debts) internal virtual returns (uint256); + + function _depositAsset(address _token, uint256 _assets) internal virtual returns (uint256 _assetsDeposited); + + /// @dev msg.sender should approve this contract first to spend `_amount` of `strategyToken` + /// @param _shares amount of strategyTokens to redeem + /// @return redeemed amount of underlyings redeemed + function redeem(uint256 _shares) external virtual override onlyIdleCDO returns (uint256 redeemed) { + return _redeem(_shares); + } + + /// @notice Redeem Tokens + /// @param _amount amount of underlying tokens to redeem + /// @return redeemed amount of underlyings redeemed + function redeemUnderlying(uint256 _amount) external virtual onlyIdleCDO returns (uint256 redeemed) {} + + function _redeem(uint256 _shares) internal virtual returns (uint256 redeemed) { + if (_shares != 0) { + IERC20Detailed(strategyToken).safeTransferFrom(msg.sender, address(this), _shares); + // TODO: + } + } + + /// @notice redeem the rewards + /// @return rewards amount of reward that is deposited to the ` strategy` + function redeemRewards(bytes calldata data) + public + virtual + onlyIdleCDO + nonReentrant + returns (uint256[] memory rewards) + {} + + /// @dev deprecated method + /// @notice pull stkedAave + function pullStkAAVE() external override returns (uint256 pulledAmount) {} + + /// @notice net price in underlyings of 1 strategyToken + /// @return _price denominated in decimals of underlyings + function price() public view virtual override returns (uint256) {} + + function getApr() external view virtual returns (uint256 apr); + + /// @notice This contract should not have funds at the end of each tx (except for stkAAVE), this method is just for leftovers + /// @dev Emergency method + /// @param _token address of the token to transfer + /// @param value amount of `_token` to transfer + /// @param _to receiver address + function transferToken( + address _token, + uint256 value, + address _to + ) external onlyOwner nonReentrant { + IERC20Detailed(_token).safeTransfer(_to, value); + } + + /// @notice deleverage position + /// @param debtAsset token to be repayed + /// @param amount amount of debtAsset + function deleverage(address debtAsset, uint256 amount) external onlyOwner {} + + /// @notice allow to update whitelisted address + function setWhitelistedCDO(address _cdo) external onlyOwner { + require(_cdo != address(0), "IS_0"); + idleCDO = _cdo; + } + + /// @notice Modifier to make sure that caller os only the idleCDO contract + modifier onlyIdleCDO() { + require(idleCDO == msg.sender, "Only IdleCDO can call"); + _; + } +} diff --git a/test/foundry/TestCollateralStrategy.sol b/test/foundry/TestCollateralStrategy.sol new file mode 100644 index 00000000..78528ee0 --- /dev/null +++ b/test/foundry/TestCollateralStrategy.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +import "../../contracts/strategies/BaseCollateralStrategy.sol"; + +import "../../contracts/modules/MorphoBorrowModule.sol"; +import "../../contracts/modules/EulerDepositModule.sol"; + +import "forge-std/Test.sol"; + +contract TestCollateralStrategy is BaseCollateralStrategy, EulerDepositModule, MorphoBorrowModule { + function getDebtToBorrow(address collateralAsset, uint256 _collateralAdded) public override returns (uint256) {} + + // /// @dev makes the actual deposit into the `strategy` + // /// @param _amount amount of tokens to deposit + // function _depositCollateral(address _token, uint256 _amount) internal override returns (uint256 _collateralAdded) {} + + // function _borrowAsset(address _token, uint256 _debts) + // internal + // override(MorphoBorrowModule, BaseCollateralStrategy) + // returns (uint256 _debtsAdded) + // { + // return MorphoBorrowModule._borrowAsset(_token, _debts); + // } + + // function _repayAsset(address _token, uint256 _debts) + // internal + // override(MorphoBorrowModule, BaseCollateralStrategy) + // returns (uint256) + // { + // return MorphoBorrowModule._repayAsset(_token, _debts); + // } + + function _swapForAsset(address _token, uint256 _amountIn) internal override returns (uint256 _amountOut) {} + + function _depositAsset(address _token, uint256 _assets) internal override returns (uint256 _assetsDeposited) {} + + function getApr() external view override returns (uint256) {} + + function getRewardTokens() external view override returns (address[] memory) {} +}