From 9341a6449cdcecf4704de3a4414d8bd44a4067f7 Mon Sep 17 00:00:00 2001 From: vasa Date: Fri, 29 Jan 2021 00:50:51 +0530 Subject: [PATCH 1/5] refactor: add StrategyCurveRSVVoterProxy License: MIT Signed-off-by: Vaibhav Saini --- .../strategies/StrategyCurveRSVVoterProxy.sol | 257 ++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 contracts/strategies/StrategyCurveRSVVoterProxy.sol diff --git a/contracts/strategies/StrategyCurveRSVVoterProxy.sol b/contracts/strategies/StrategyCurveRSVVoterProxy.sol new file mode 100644 index 0000000..49fb174 --- /dev/null +++ b/contracts/strategies/StrategyCurveRSVVoterProxy.sol @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: AGPL-3.0 + +pragma solidity ^0.5.17; + +import "@openzeppelinV2/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelinV2/contracts/math/SafeMath.sol"; +import "@openzeppelinV2/contracts/utils/Address.sol"; +import "@openzeppelinV2/contracts/token/ERC20/SafeERC20.sol"; + +import "../../interfaces/yearn/IController.sol"; +import "../../interfaces/curve/Gauge.sol"; +import "../../interfaces/curve/Mintr.sol"; +import "../../interfaces/uniswap/Uni.sol"; +import "../../interfaces/curve/Curve.sol"; + +interface IVoterProxy { + function withdraw( + address _gauge, + address _token, + uint256 _amount + ) external returns (uint256); + + function balanceOf(address _gauge) external view returns (uint256); + + function withdrawAll(address _gauge, address _token) external returns (uint256); + + function deposit(address _gauge, address _token) external; + + function harvest(address _gauge) external; + + function lock() external; + + function claimRewards(address _gauge, address _token) external; + + function approveStrategy(address _gauge, address _strategy) external; +} + +contract StrategyCurveRSVVoterProxy { + using SafeERC20 for IERC20; + using Address for address; + using SafeMath for uint256; + + address public constant want = address(0xC2Ee6b0334C261ED60C72f6054450b61B8f18E35); + address public constant crv = address(0xD533a949740bb3306d119CC777fa900bA034cd52); + + address public constant curve = address(0xC18cC39da8b11dA8c3541C598eE022258F9744da); + address public constant gauge = address(0x4dC4A289a8E33600D8bD4cf5F6313E43a37adec7); + address public constant voter = address(0xF147b8125d2ef93FB6965Db97D6746952a133934); + + address public constant rsv = address(0x196f4727526eA7FB1e17b2071B3d8eAA38486988); + address public constant rsr = address(0x8762db106B2c2A0bccB3A80d1Ed41273552616E8); + address public constant weth = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); // used for crv/rsr <> weth <> rsv route + + address public constant uniswap = address(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); + + uint256 public keepCRV = 500; + uint256 public treasuryFee = 1000; + uint256 public strategistReward = 1000; + uint256 public withdrawalFee = 0; + uint256 public constant FEE_DENOMINATOR = 10000; + + address public proxy; + address public dex; + + address public governance; + address public controller; + address public strategist; + address public keeper; + + uint256 public earned; // lifetime strategy earnings denominated in `want` token + + event Harvested(uint256 wantEarned, uint256 lifetimeEarned); + + constructor(address _controller) public { + governance = msg.sender; + strategist = msg.sender; + keeper = msg.sender; + controller = _controller; + // standardize constructor + proxy = address(0x9a3a03C614dc467ACC3e81275468e033c98d960E); + dex = uniswap; + } + + function getName() external pure returns (string memory) { + return "StrategyCurveRSVVoterProxy"; + } + + function setStrategist(address _strategist) external { + require(msg.sender == strategist || msg.sender == governance, "!authorized"); + strategist = _strategist; + } + + function setKeeper(address _keeper) external { + require(msg.sender == strategist || msg.sender == governance, "!authorized"); + keeper = _keeper; + } + + function setKeepCRV(uint256 _keepCRV) external { + require(msg.sender == governance, "!governance"); + keepCRV = _keepCRV; + } + + function setWithdrawalFee(uint256 _withdrawalFee) external { + require(msg.sender == governance, "!governance"); + withdrawalFee = _withdrawalFee; + } + + function setTreasuryFee(uint256 _treasuryFee) external { + require(msg.sender == governance, "!governance"); + treasuryFee = _treasuryFee; + } + + function setStrategistReward(uint256 _strategistReward) external { + require(msg.sender == governance, "!governance"); + strategistReward = _strategistReward; + } + + function setProxy(address _proxy) external { + require(msg.sender == governance, "!governance"); + proxy = _proxy; + } + + function deposit() public { + uint256 _want = IERC20(want).balanceOf(address(this)); + if (_want > 0) { + IERC20(want).safeTransfer(proxy, _want); + IVoterProxy(proxy).deposit(gauge, want); + } + } + + // Controller only function for creating additional rewards from dust + function withdraw(IERC20 _asset) external returns (uint256 balance) { + require(msg.sender == controller, "!controller"); + require(want != address(_asset), "want"); + require(crv != address(_asset), "crv"); + require(rsv != address(_asset), "rsv"); + balance = _asset.balanceOf(address(this)); + _asset.safeTransfer(controller, balance); + } + + // Withdraw partial funds, normally used with a vault withdrawal + function withdraw(uint256 _amount) external { + require(msg.sender == controller, "!controller"); + uint256 _balance = IERC20(want).balanceOf(address(this)); + if (_balance < _amount) { + _amount = _withdrawSome(_amount.sub(_balance)); + _amount = _amount.add(_balance); + } + + uint256 _fee = _amount.mul(withdrawalFee).div(FEE_DENOMINATOR); + + IERC20(want).safeTransfer(IController(controller).rewards(), _fee); + address _vault = IController(controller).vaults(address(want)); + require(_vault != address(0), "!vault"); // additional protection so we don't burn the funds + IERC20(want).safeTransfer(_vault, _amount.sub(_fee)); + } + + function _withdrawSome(uint256 _amount) internal returns (uint256) { + return IVoterProxy(proxy).withdraw(gauge, want, _amount); + } + + // Withdraw all funds, normally used when migrating strategies + function withdrawAll() external returns (uint256 balance) { + require(msg.sender == controller, "!controller"); + _withdrawAll(); + + balance = IERC20(want).balanceOf(address(this)); + + address _vault = IController(controller).vaults(address(want)); + require(_vault != address(0), "!vault"); // additional protection so we don't burn the funds + IERC20(want).safeTransfer(_vault, balance); + } + + function _withdrawAll() internal { + IVoterProxy(proxy).withdrawAll(gauge, want); + } + + function harvest() public { + require(msg.sender == keeper || msg.sender == strategist || msg.sender == governance, "!keepers"); + + // harvest CRV rewards + IVoterProxy(proxy).harvest(gauge); + uint256 _crv = IERC20(crv).balanceOf(address(this)); + if (_crv > 0) { + uint256 _keepCRV = _crv.mul(keepCRV).div(FEE_DENOMINATOR); + IERC20(crv).safeTransfer(voter, _keepCRV); + _crv = _crv.sub(_keepCRV); + + IERC20(crv).safeApprove(dex, 0); + IERC20(crv).safeApprove(dex, _crv); + + address[] memory path = new address[](3); + path[0] = crv; + path[1] = weth; + path[2] = rsv; + + Uni(dex).swapExactTokensForTokens(_crv, uint256(0), path, address(this), now.add(1800)); + } + + // harvest RSR rewards + IVoterProxy(proxy).claimRewards(gauge, rsr); + uint256 _rsr = IERC20(rsr).balanceOf(address(this)); + + if (_rsr > 0) { + IERC20(rsr).safeApprove(dex, 0); + IERC20(rsr).safeApprove(dex, _rsr); + + address[] memory path = new address[](3); + path[0] = rsr; + path[1] = weth; + path[2] = rsv; + + Uni(dex).swapExactTokensForTokens(_rsr, uint256(0), path, address(this), now.add(1800)); + } + + // deposit all RSV to the Curve pool + uint256 _rsv = IERC20(rsv).balanceOf(address(this)); + if (_rsv > 0) { + IERC20(rsv).safeApprove(curve, 0); + IERC20(rsv).safeApprove(curve, _rsv); + ICurveFi(curve).add_liquidity([_rsv, 0], 0); + } + uint256 _want = IERC20(want).balanceOf(address(this)); + if (_want > 0) { + uint256 _fee = _want.mul(treasuryFee).div(FEE_DENOMINATOR); + uint256 _reward = _want.mul(strategistReward).div(FEE_DENOMINATOR); + IERC20(want).safeTransfer(IController(controller).rewards(), _fee); + IERC20(want).safeTransfer(strategist, _reward); + deposit(); + } + IVoterProxy(proxy).lock(); + earned = earned.add(_want); + emit Harvested(_want, earned); + } + + function balanceOfWant() public view returns (uint256) { + return IERC20(want).balanceOf(address(this)); + } + + function balanceOfPool() public view returns (uint256) { + return IVoterProxy(proxy).balanceOf(gauge); + } + + function balanceOf() public view returns (uint256) { + return balanceOfWant().add(balanceOfPool()); + } + + function setGovernance(address _governance) external { + require(msg.sender == governance, "!governance"); + governance = _governance; + } + + function setController(address _controller) external { + require(msg.sender == governance, "!governance"); + controller = _controller; + } +} From 7b10e578692fde3f8204a2e37ab14c5b99cbec8a Mon Sep 17 00:00:00 2001 From: vasa Date: Fri, 29 Jan 2021 06:02:09 +0530 Subject: [PATCH 2/5] refactor: use 1inch for swap License: MIT Signed-off-by: Vaibhav Saini --- .../strategies/StrategyCurveRSVVoterProxy.sol | 41 ++++++++----------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/contracts/strategies/StrategyCurveRSVVoterProxy.sol b/contracts/strategies/StrategyCurveRSVVoterProxy.sol index 49fb174..52761b5 100644 --- a/contracts/strategies/StrategyCurveRSVVoterProxy.sol +++ b/contracts/strategies/StrategyCurveRSVVoterProxy.sol @@ -8,9 +8,6 @@ import "@openzeppelinV2/contracts/utils/Address.sol"; import "@openzeppelinV2/contracts/token/ERC20/SafeERC20.sol"; import "../../interfaces/yearn/IController.sol"; -import "../../interfaces/curve/Gauge.sol"; -import "../../interfaces/curve/Mintr.sol"; -import "../../interfaces/uniswap/Uni.sol"; import "../../interfaces/curve/Curve.sol"; interface IVoterProxy { @@ -51,7 +48,7 @@ contract StrategyCurveRSVVoterProxy { address public constant rsr = address(0x8762db106B2c2A0bccB3A80d1Ed41273552616E8); address public constant weth = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); // used for crv/rsr <> weth <> rsv route - address public constant uniswap = address(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); + address public oneinch = address(0x111111125434b319222CdBf8C261674aDB56F3ae); uint256 public keepCRV = 500; uint256 public treasuryFee = 1000; @@ -78,7 +75,6 @@ contract StrategyCurveRSVVoterProxy { controller = _controller; // standardize constructor proxy = address(0x9a3a03C614dc467ACC3e81275468e033c98d960E); - dex = uniswap; } function getName() external pure returns (string memory) { @@ -175,7 +171,19 @@ contract StrategyCurveRSVVoterProxy { IVoterProxy(proxy).withdrawAll(gauge, want); } - function harvest() public { + function swapViaOneInch( + address _token, + uint256 _amount, + bytes memory callData + ) public { + IERC20(_token).approve(oneinch, _amount); + + // solium-disable-next-line security/no-call-value + (bool success, ) = address(oneinch).call(callData); + if (!success) revert("1Inch-swap-failed"); + } + + function harvest(bytes memory crvCallData, bytes memory rsrCallData) public { require(msg.sender == keeper || msg.sender == strategist || msg.sender == governance, "!keepers"); // harvest CRV rewards @@ -185,16 +193,7 @@ contract StrategyCurveRSVVoterProxy { uint256 _keepCRV = _crv.mul(keepCRV).div(FEE_DENOMINATOR); IERC20(crv).safeTransfer(voter, _keepCRV); _crv = _crv.sub(_keepCRV); - - IERC20(crv).safeApprove(dex, 0); - IERC20(crv).safeApprove(dex, _crv); - - address[] memory path = new address[](3); - path[0] = crv; - path[1] = weth; - path[2] = rsv; - - Uni(dex).swapExactTokensForTokens(_crv, uint256(0), path, address(this), now.add(1800)); + swapViaOneInch(crv, _crv, crvCallData); } // harvest RSR rewards @@ -202,15 +201,7 @@ contract StrategyCurveRSVVoterProxy { uint256 _rsr = IERC20(rsr).balanceOf(address(this)); if (_rsr > 0) { - IERC20(rsr).safeApprove(dex, 0); - IERC20(rsr).safeApprove(dex, _rsr); - - address[] memory path = new address[](3); - path[0] = rsr; - path[1] = weth; - path[2] = rsv; - - Uni(dex).swapExactTokensForTokens(_rsr, uint256(0), path, address(this), now.add(1800)); + swapViaOneInch(rsr, _rsr, rsrCallData); } // deposit all RSV to the Curve pool From 16d17d2a4ac627b3962a8e3ed110fbf415b4dd55 Mon Sep 17 00:00:00 2001 From: vasa Date: Fri, 29 Jan 2021 06:09:01 +0530 Subject: [PATCH 3/5] refactor: make swapViaOneInch internal License: MIT Signed-off-by: Vaibhav Saini --- contracts/strategies/StrategyCurveRSVVoterProxy.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/strategies/StrategyCurveRSVVoterProxy.sol b/contracts/strategies/StrategyCurveRSVVoterProxy.sol index 52761b5..ead77b5 100644 --- a/contracts/strategies/StrategyCurveRSVVoterProxy.sol +++ b/contracts/strategies/StrategyCurveRSVVoterProxy.sol @@ -175,7 +175,7 @@ contract StrategyCurveRSVVoterProxy { address _token, uint256 _amount, bytes memory callData - ) public { + ) internal { IERC20(_token).approve(oneinch, _amount); // solium-disable-next-line security/no-call-value From f6c3e38f18d2466fbed68445363883c706d9bcf5 Mon Sep 17 00:00:00 2001 From: vasa Date: Fri, 29 Jan 2021 06:17:20 +0530 Subject: [PATCH 4/5] refactor: make oneinch constant License: MIT Signed-off-by: Vaibhav Saini --- contracts/strategies/StrategyCurveRSVVoterProxy.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/strategies/StrategyCurveRSVVoterProxy.sol b/contracts/strategies/StrategyCurveRSVVoterProxy.sol index ead77b5..3f6b197 100644 --- a/contracts/strategies/StrategyCurveRSVVoterProxy.sol +++ b/contracts/strategies/StrategyCurveRSVVoterProxy.sol @@ -48,7 +48,7 @@ contract StrategyCurveRSVVoterProxy { address public constant rsr = address(0x8762db106B2c2A0bccB3A80d1Ed41273552616E8); address public constant weth = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); // used for crv/rsr <> weth <> rsv route - address public oneinch = address(0x111111125434b319222CdBf8C261674aDB56F3ae); + address public constant oneinch = address(0x111111125434b319222CdBf8C261674aDB56F3ae); uint256 public keepCRV = 500; uint256 public treasuryFee = 1000; From 45b7127a8b87499b4ea8e279644c400a8855b5d5 Mon Sep 17 00:00:00 2001 From: vasa Date: Fri, 29 Jan 2021 06:19:43 +0530 Subject: [PATCH 5/5] refactor: remove weth address License: MIT Signed-off-by: Vaibhav Saini --- contracts/strategies/StrategyCurveRSVVoterProxy.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/strategies/StrategyCurveRSVVoterProxy.sol b/contracts/strategies/StrategyCurveRSVVoterProxy.sol index 3f6b197..a0cbecb 100644 --- a/contracts/strategies/StrategyCurveRSVVoterProxy.sol +++ b/contracts/strategies/StrategyCurveRSVVoterProxy.sol @@ -46,7 +46,6 @@ contract StrategyCurveRSVVoterProxy { address public constant rsv = address(0x196f4727526eA7FB1e17b2071B3d8eAA38486988); address public constant rsr = address(0x8762db106B2c2A0bccB3A80d1Ed41273552616E8); - address public constant weth = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); // used for crv/rsr <> weth <> rsv route address public constant oneinch = address(0x111111125434b319222CdBf8C261674aDB56F3ae);