From 2caae7035e4cf71e6a80a8dcb1336bada4356499 Mon Sep 17 00:00:00 2001 From: Xuefeng Zhu Date: Sat, 8 May 2021 20:30:37 -0700 Subject: [PATCH 1/5] Add cream compound protocol --- .../StrategyCompoundCreamLendingPool.sol | 235 ++++++++++++++++++ .../strategies/StrategyCreamLendingPool.sol | 148 +++++++++++ .../interfaces/compound/ICErc20.sol | 6 + 3 files changed, 389 insertions(+) create mode 100644 contracts/strategies/StrategyCompoundCreamLendingPool.sol create mode 100644 contracts/strategies/StrategyCreamLendingPool.sol diff --git a/contracts/strategies/StrategyCompoundCreamLendingPool.sol b/contracts/strategies/StrategyCompoundCreamLendingPool.sol new file mode 100644 index 0000000..10460a1 --- /dev/null +++ b/contracts/strategies/StrategyCompoundCreamLendingPool.sol @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts/utils/Address.sol"; +import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +import "./interfaces/IStrategy.sol"; +import "./interfaces/compound/ICErc20.sol"; +import "./interfaces/compound/IComptroller.sol"; +import "./interfaces/uniswap/IUniswapV2.sol"; + +/** + * Deposits ERC20 token into Compound or Cream Lending Pool based on interest rate and issues stCreamLendingToken(e.g. stCreamLendingDAI) in L2. Holds cToken (Cream interest-bearing tokens). + */ +contract StrategyCreamLendingPool is IStrategy, Ownable { + using SafeERC20 for IERC20; + using Address for address; + using SafeMath for uint256; + + // Info of supplying erc20 token to Cream lending pool + // The symbol of the supplying token + string public symbol; + // The address of supplying token (e.g. DAI, USDT) + address public supplyToken; + + // The address of Compound interest-bearing token (e.g. cDAI, cUSDT) + address public cErc20; + + // The address of Cream interest-bearing token (e.g. crDAI, crUSDT) + address public crErc20; + + // The address is used for claim COMP token + address public comptroller; + + // The address is used for claim CREAM token + address public creamtroller; + + // The address of COMP token + address public comp; + + // The address of CREAM token + address public cream; + + address public uniswap; + // The address of WETH token + address public weth; + + address public controller; + + constructor( + string memory _symbol, + address _supplyToken, + address _cErc20, + address _crErc20, + address _comptroller, + address _creamtroller, + address _comp, + address _cream, + address _uniswap, + address _weth, + address _controller + ) { + symbol = _symbol; + supplyToken = _supplyToken; + cErc20 = _cErc20; + crErc20 = _crErc20; + comptroller = _comptroller; + creamtroller = _creamtroller; + comp = _comp; + cream = _cream; + uniswap = _uniswap; + weth = _weth; + controller = _controller; + } + + /** + * @dev Require that the caller must be an EOA account to avoid flash loans. + */ + modifier onlyEOA() { + require(msg.sender == tx.origin, "Not EOA"); + _; + } + + function getAssetAddress() external view override returns (address) { + return supplyToken; + } + + function syncBalance() external override returns (uint256) { + // Supplying token(e.g. DAI, USDT) balance of this contract. + // supplyTokenBalance is equal to the cToken balance multiplied by the Exchange Rate. + uint256 supplyTokenBalance = + ICErc20(cErc20).balanceOfUnderlying(address(this)) + ICErc20(crErc20).balanceOfUnderlying(address(this)); + return supplyTokenBalance; + } + + function harvest() external override onlyEOA { + // Claim COMP token. + IComptroller(comptroller).claimComp(address(this)); + uint256 compBalance = IERC20(comp).balanceOf(address(this)); + if (compBalance > 0) { + // Sell COMP token for obtain more supplying token(e.g. DAI, USDT) + IERC20(comp).safeIncreaseAllowance(uniswap, compBalance); + + address[] memory paths = new address[](3); + paths[0] = comp; + paths[1] = weth; + paths[2] = supplyToken; + + IUniswapV2(uniswap).swapExactTokensForTokens( + compBalance, + uint256(0), + paths, + address(this), + block.timestamp.add(1800) + ); + + // Deposit supplying token to Cream Erc20 Lending Pool and mint cToken. + uint256 obtainedSupplyTokenAmount = IERC20(supplyToken).balanceOf(address(this)); + IERC20(supplyToken).safeIncreaseAllowance(cErc20, obtainedSupplyTokenAmount); + uint256 mintResult = ICErc20(cErc20).mint(obtainedSupplyTokenAmount); + require(mintResult == 0, "Couldn't mint cToken"); + } + + // Claim CREAM token. + IComptroller(creamtroller).claimComp(address(this)); + uint256 creamBalance = IERC20(cream).balanceOf(address(this)); + if (creamBalance > 0) { + // Sell CREAM token for obtain more supplying token(e.g. DAI, USDT) + IERC20(cream).safeIncreaseAllowance(uniswap, creamBalance); + + address[] memory paths = new address[](3); + paths[0] = cream; + paths[1] = weth; + paths[2] = supplyToken; + + IUniswapV2(uniswap).swapExactTokensForTokens( + creamBalance, + uint256(0), + paths, + address(this), + block.timestamp.add(1800) + ); + + // Deposit supplying token to Cream Erc20 Lending Pool and mint cToken. + uint256 obtainedSupplyTokenAmount = IERC20(supplyToken).balanceOf(address(this)); + IERC20(supplyToken).safeIncreaseAllowance(crErc20, obtainedSupplyTokenAmount); + uint256 mintResult = ICErc20(crErc20).mint(obtainedSupplyTokenAmount); + require(mintResult == 0, "Couldn't mint crToken"); + } + } + + function aggregateCommit(uint256 _commitAmount) external override { + require(msg.sender == controller, "Not controller"); + require(_commitAmount > 0, "Nothing to commit"); + + // Pull supplying token(e.g. DAI, USDT) from Controller + IERC20(supplyToken).safeTransferFrom(msg.sender, address(this), _commitAmount); + uint256 mintResult; + + if (ICErc20(cErc20).supplyRatePerBlock() > ICErc20(crErc20).supplyRatePerBlock()) { + // Deposit supplying token to Compound Erc20 Lending Pool and mint cErc20. + IERC20(supplyToken).safeIncreaseAllowance(cErc20, _commitAmount); + mintResult = ICErc20(cErc20).mint(_commitAmount); + } else { + // Deposit supplying token to Cream Erc20 Lending Pool and mint crErc20. + IERC20(supplyToken).safeIncreaseAllowance(crErc20, _commitAmount); + mintResult = ICErc20(crErc20).mint(_commitAmount); + } + + require(mintResult == 0, "Couldn't mint cToken/crToken"); + + emit Committed(_commitAmount); + } + + function aggregateUncommit(uint256 _uncommitAmount) external override { + require(msg.sender == controller, "Not controller"); + require(_uncommitAmount > 0, "Nothing to uncommit"); + + ICErc20 lowRateProtocol = ICErc20(cErc20); + ICErc20 highRateProtocol = ICErc20(crErc20); + if (lowRateProtocol.supplyRatePerBlock() > highRateProtocol.supplyRatePerBlock()) { + lowRateProtocol = ICErc20(crErc20); + highRateProtocol = ICErc20(cErc20); + } + + uint256 redeemResult; + uint256 lowRateBalance = lowRateProtocol.balanceOfUnderlying(address(this)); + if (_uncommitAmount < lowRateBalance) { + lowRateBalance = _uncommitAmount; + } else { + redeemResult = highRateProtocol.redeemUnderlying(_uncommitAmount - lowRateBalance); + require(redeemResult == 0, "Couldn't redeem cToken"); + } + + redeemResult = lowRateProtocol.redeemUnderlying(lowRateBalance); + require(redeemResult == 0, "Couldn't redeem cToken"); + + // Transfer supplying token(e.g. DAI, USDT) to Controller + uint256 supplyTokenBalance = IERC20(supplyToken).balanceOf(address(this)); + IERC20(supplyToken).safeTransfer(msg.sender, supplyTokenBalance); + + emit UnCommitted(_uncommitAmount); + } + + function reditribute() external { + require(msg.sender == controller, "Not controller"); + + ICErc20 lowRateProtocol = ICErc20(cErc20); + ICErc20 highRateProtocol = ICErc20(crErc20); + if (lowRateProtocol.supplyRatePerBlock() > highRateProtocol.supplyRatePerBlock()) { + lowRateProtocol = ICErc20(crErc20); + highRateProtocol = ICErc20(cErc20); + } + + uint256 lowRateBalance = lowRateProtocol.balanceOfUnderlying(address(this)); + + uint256 redeemResult = lowRateProtocol.redeemUnderlying(lowRateBalance); + require(redeemResult == 0, "Couldn't redeem cToken/crToken"); + + uint256 supplyTokenBalance = IERC20(supplyToken).balanceOf(address(this)); + IERC20(supplyToken).safeIncreaseAllowance(address(highRateProtocol), supplyTokenBalance); + uint256 mintResult = highRateProtocol.mint(supplyTokenBalance); + require(mintResult == 0, "Couldn't mint cToken/crToken"); + } + + function setController(address _controller) external onlyOwner { + emit ControllerChanged(controller, _controller); + controller = _controller; + } +} diff --git a/contracts/strategies/StrategyCreamLendingPool.sol b/contracts/strategies/StrategyCreamLendingPool.sol new file mode 100644 index 0000000..d20c30c --- /dev/null +++ b/contracts/strategies/StrategyCreamLendingPool.sol @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts/utils/Address.sol"; +import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +import "./interfaces/IStrategy.sol"; +import "./interfaces/compound/ICErc20.sol"; +import "./interfaces/compound/IComptroller.sol"; +import "./interfaces/uniswap/IUniswapV2.sol"; + +/** + * Deposits ERC20 token into Cream Lending Pool and issues stCreamLendingToken(e.g. stCreamLendingDAI) in L2. Holds cToken (Cream interest-bearing tokens). + */ +contract StrategyCreamLendingPool is IStrategy, Ownable { + using SafeERC20 for IERC20; + using Address for address; + using SafeMath for uint256; + + // Info of supplying erc20 token to Cream lending pool + // The symbol of the supplying token + string public symbol; + // The address of supplying token (e.g. DAI, USDT) + address public supplyToken; + + // The address of Cream interest-bearing token (e.g. crDAI, crUSDT) + address public crErc20; + + // The address is used for claim CREAM token + address public creamtroller; + // The address of CREAM token + address public cream; + + address public uniswap; + // The address of WETH token + address public weth; + + address public controller; + + constructor( + string memory _symbol, + address _supplyToken, + address _crErc20, + address _creamtroller, + address _cream, + address _uniswap, + address _weth, + address _controller + ) { + symbol = _symbol; + supplyToken = _supplyToken; + crErc20 = _crErc20; + creamtroller = _creamtroller; + cream = _cream; + uniswap = _uniswap; + weth = _weth; + controller = _controller; + } + + /** + * @dev Require that the caller must be an EOA account to avoid flash loans. + */ + modifier onlyEOA() { + require(msg.sender == tx.origin, "Not EOA"); + _; + } + + function getAssetAddress() external view override returns (address) { + return supplyToken; + } + + function syncBalance() external override returns (uint256) { + // Supplying token(e.g. DAI, USDT) balance of this contract. + // supplyTokenBalance is equal to the cToken balance multiplied by the Exchange Rate. + uint256 supplyTokenBalance = ICErc20(crErc20).balanceOfUnderlying(address(this)); + return supplyTokenBalance; + } + + function harvest() external override onlyEOA { + // Claim CREAM token. + IComptroller(creamtroller).claimComp(address(this)); + uint256 creamBalance = IERC20(cream).balanceOf(address(this)); + if (creamBalance > 0) { + // Sell CREAM token for obtain more supplying token(e.g. DAI, USDT) + IERC20(cream).safeIncreaseAllowance(uniswap, creamBalance); + + address[] memory paths = new address[](3); + paths[0] = cream; + paths[1] = weth; + paths[2] = supplyToken; + + IUniswapV2(uniswap).swapExactTokensForTokens( + creamBalance, + uint256(0), + paths, + address(this), + block.timestamp.add(1800) + ); + + // Deposit supplying token to Cream Erc20 Lending Pool and mint cToken. + uint256 obtainedSupplyTokenAmount = IERC20(supplyToken).balanceOf(address(this)); + IERC20(supplyToken).safeIncreaseAllowance(crErc20, obtainedSupplyTokenAmount); + uint256 mintResult = ICErc20(crErc20).mint(obtainedSupplyTokenAmount); + require(mintResult == 0, "Couldn't mint crToken"); + } + } + + function aggregateCommit(uint256 _commitAmount) external override { + require(msg.sender == controller, "Not controller"); + require(_commitAmount > 0, "Nothing to commit"); + + // Pull supplying token(e.g. DAI, USDT) from Controller + IERC20(supplyToken).safeTransferFrom(msg.sender, address(this), _commitAmount); + + // Deposit supplying token to Cream Erc20 Lending Pool and mint crErc20. + IERC20(supplyToken).safeIncreaseAllowance(crErc20, _commitAmount); + uint256 mintResult = ICErc20(crErc20).mint(_commitAmount); + require(mintResult == 0, "Couldn't mint cToken"); + + emit Committed(_commitAmount); + } + + function aggregateUncommit(uint256 _uncommitAmount) external override { + require(msg.sender == controller, "Not controller"); + require(_uncommitAmount > 0, "Nothing to uncommit"); + + // Withdraw supplying token from Cream Erc20 Lending Pool + // based on an amount of the supplying token(e.g. DAI, USDT). + uint256 redeemResult = ICErc20(crErc20).redeemUnderlying(_uncommitAmount); + require(redeemResult == 0, "Couldn't redeem cToken"); + + // Transfer supplying token(e.g. DAI, USDT) to Controller + uint256 supplyTokenBalance = IERC20(supplyToken).balanceOf(address(this)); + IERC20(supplyToken).safeTransfer(msg.sender, supplyTokenBalance); + + emit UnCommitted(_uncommitAmount); + } + + function setController(address _controller) external onlyOwner { + emit ControllerChanged(controller, _controller); + controller = _controller; + } +} diff --git a/contracts/strategies/interfaces/compound/ICErc20.sol b/contracts/strategies/interfaces/compound/ICErc20.sol index e53e9c8..ca932a7 100644 --- a/contracts/strategies/interfaces/compound/ICErc20.sol +++ b/contracts/strategies/interfaces/compound/ICErc20.sol @@ -26,4 +26,10 @@ interface ICErc20 { * @return 0 = success, otherwise a failure */ function redeemUnderlying(uint256 redeemAmount) external returns (uint256); + + /** + * @notice Returns the current per-block supply interest rate for this cToken + * @return The supply interest rate per block, scaled by 1e18 + */ + function supplyRatePerBlock() external view returns (uint256); } From 0bc5ee5d92b945ed8c75f011f222bb92700e1ded Mon Sep 17 00:00:00 2001 From: Xuefeng Zhu Date: Sun, 9 May 2021 01:16:33 -0700 Subject: [PATCH 2/5] minor update --- .../StrategyCompoundCreamLendingPool.sol | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/contracts/strategies/StrategyCompoundCreamLendingPool.sol b/contracts/strategies/StrategyCompoundCreamLendingPool.sol index 10460a1..5810d34 100644 --- a/contracts/strategies/StrategyCompoundCreamLendingPool.sol +++ b/contracts/strategies/StrategyCompoundCreamLendingPool.sol @@ -118,12 +118,6 @@ contract StrategyCreamLendingPool is IStrategy, Ownable { address(this), block.timestamp.add(1800) ); - - // Deposit supplying token to Cream Erc20 Lending Pool and mint cToken. - uint256 obtainedSupplyTokenAmount = IERC20(supplyToken).balanceOf(address(this)); - IERC20(supplyToken).safeIncreaseAllowance(cErc20, obtainedSupplyTokenAmount); - uint256 mintResult = ICErc20(cErc20).mint(obtainedSupplyTokenAmount); - require(mintResult == 0, "Couldn't mint cToken"); } // Claim CREAM token. @@ -145,13 +139,9 @@ contract StrategyCreamLendingPool is IStrategy, Ownable { address(this), block.timestamp.add(1800) ); - - // Deposit supplying token to Cream Erc20 Lending Pool and mint cToken. - uint256 obtainedSupplyTokenAmount = IERC20(supplyToken).balanceOf(address(this)); - IERC20(supplyToken).safeIncreaseAllowance(crErc20, obtainedSupplyTokenAmount); - uint256 mintResult = ICErc20(crErc20).mint(obtainedSupplyTokenAmount); - require(mintResult == 0, "Couldn't mint crToken"); } + + reditribute() } function aggregateCommit(uint256 _commitAmount) external override { @@ -207,7 +197,7 @@ contract StrategyCreamLendingPool is IStrategy, Ownable { emit UnCommitted(_uncommitAmount); } - function reditribute() external { + function reditribute() public { require(msg.sender == controller, "Not controller"); ICErc20 lowRateProtocol = ICErc20(cErc20); From d982eec64e879ca82fd9d22d308bb61e2b3e01a3 Mon Sep 17 00:00:00 2001 From: Xuefeng Zhu Date: Sun, 9 May 2021 03:12:25 -0700 Subject: [PATCH 3/5] update commit function --- .../StrategyCompoundCreamLendingPool.sol | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/contracts/strategies/StrategyCompoundCreamLendingPool.sol b/contracts/strategies/StrategyCompoundCreamLendingPool.sol index 5810d34..28f1478 100644 --- a/contracts/strategies/StrategyCompoundCreamLendingPool.sol +++ b/contracts/strategies/StrategyCompoundCreamLendingPool.sol @@ -150,19 +150,7 @@ contract StrategyCreamLendingPool is IStrategy, Ownable { // Pull supplying token(e.g. DAI, USDT) from Controller IERC20(supplyToken).safeTransferFrom(msg.sender, address(this), _commitAmount); - uint256 mintResult; - - if (ICErc20(cErc20).supplyRatePerBlock() > ICErc20(crErc20).supplyRatePerBlock()) { - // Deposit supplying token to Compound Erc20 Lending Pool and mint cErc20. - IERC20(supplyToken).safeIncreaseAllowance(cErc20, _commitAmount); - mintResult = ICErc20(cErc20).mint(_commitAmount); - } else { - // Deposit supplying token to Cream Erc20 Lending Pool and mint crErc20. - IERC20(supplyToken).safeIncreaseAllowance(crErc20, _commitAmount); - mintResult = ICErc20(crErc20).mint(_commitAmount); - } - - require(mintResult == 0, "Couldn't mint cToken/crToken"); + reditribute(); emit Committed(_commitAmount); } From f6b721ce8debba9a695b63317fec9b959966ccc4 Mon Sep 17 00:00:00 2001 From: Xuefeng Zhu Date: Sun, 9 May 2021 03:14:23 -0700 Subject: [PATCH 4/5] recever commit --- .../StrategyCompoundCreamLendingPool.sol | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/contracts/strategies/StrategyCompoundCreamLendingPool.sol b/contracts/strategies/StrategyCompoundCreamLendingPool.sol index 28f1478..5810d34 100644 --- a/contracts/strategies/StrategyCompoundCreamLendingPool.sol +++ b/contracts/strategies/StrategyCompoundCreamLendingPool.sol @@ -150,7 +150,19 @@ contract StrategyCreamLendingPool is IStrategy, Ownable { // Pull supplying token(e.g. DAI, USDT) from Controller IERC20(supplyToken).safeTransferFrom(msg.sender, address(this), _commitAmount); - reditribute(); + uint256 mintResult; + + if (ICErc20(cErc20).supplyRatePerBlock() > ICErc20(crErc20).supplyRatePerBlock()) { + // Deposit supplying token to Compound Erc20 Lending Pool and mint cErc20. + IERC20(supplyToken).safeIncreaseAllowance(cErc20, _commitAmount); + mintResult = ICErc20(cErc20).mint(_commitAmount); + } else { + // Deposit supplying token to Cream Erc20 Lending Pool and mint crErc20. + IERC20(supplyToken).safeIncreaseAllowance(crErc20, _commitAmount); + mintResult = ICErc20(crErc20).mint(_commitAmount); + } + + require(mintResult == 0, "Couldn't mint cToken/crToken"); emit Committed(_commitAmount); } From c790151a829bb004cb1ada5a8e1f51e8ed546876 Mon Sep 17 00:00:00 2001 From: Xuefeng Zhu Date: Sun, 9 May 2021 04:41:54 -0700 Subject: [PATCH 5/5] fix compliling --- contracts/strategies/StrategyCompoundCreamLendingPool.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/strategies/StrategyCompoundCreamLendingPool.sol b/contracts/strategies/StrategyCompoundCreamLendingPool.sol index 5810d34..21a6bcd 100644 --- a/contracts/strategies/StrategyCompoundCreamLendingPool.sol +++ b/contracts/strategies/StrategyCompoundCreamLendingPool.sol @@ -141,7 +141,7 @@ contract StrategyCreamLendingPool is IStrategy, Ownable { ); } - reditribute() + reditribute(); } function aggregateCommit(uint256 _commitAmount) external override {