Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: BaseCollateralStrategy #63

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions contracts/interfaces/morpho/IMorpho.sol
Original file line number Diff line number Diff line change
@@ -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);
}
20 changes: 20 additions & 0 deletions contracts/interfaces/morpho/IMorphoCompoundLens.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}
8 changes: 8 additions & 0 deletions contracts/modules/BaseBorrowModule.sol
Original file line number Diff line number Diff line change
@@ -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);
}
6 changes: 6 additions & 0 deletions contracts/modules/BaseDepositModule.sol
Original file line number Diff line number Diff line change
@@ -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);
}
6 changes: 6 additions & 0 deletions contracts/modules/BaseSwapModule.sol
Original file line number Diff line number Diff line change
@@ -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);
}
20 changes: 20 additions & 0 deletions contracts/modules/EulerDepositModule.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
20 changes: 20 additions & 0 deletions contracts/modules/MorphoBorrowModule.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
196 changes: 196 additions & 0 deletions contracts/strategies/BaseCollateralStrategy.sol
Original file line number Diff line number Diff line change
@@ -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");
_;
}
}
41 changes: 41 additions & 0 deletions test/foundry/TestCollateralStrategy.sol
Original file line number Diff line number Diff line change
@@ -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) {}
}