diff --git a/contracts/strategies/StrategyCurvesUSDVoterProxy.sol b/contracts/strategies/StrategyCurvesUSDVoterProxy.sol new file mode 100644 index 00000000..d1175cee --- /dev/null +++ b/contracts/strategies/StrategyCurvesUSDVoterProxy.sol @@ -0,0 +1,703 @@ +// SPDX-License-Identifier: AGPL-3.0 + +pragma solidity ^0.5.17; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. Does not include + * the optional functions; to access them see {ERC20Detailed}. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, "SafeMath: subtraction overflow"); + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * - Subtraction cannot overflow. + * + * _Available since v2.4.0._ + */ + function sub( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, "SafeMath: division by zero"); + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + * + * _Available since v2.4.0._ + */ + function div( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + // Solidity only automatically asserts when dividing by 0 + require(b > 0, errorMessage); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return mod(a, b, "SafeMath: modulo by zero"); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts with custom message when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + * + * _Available since v2.4.0._ + */ + function mod( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + require(b != 0, errorMessage); + return a % b; + } +} + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function isContract(address account) internal view returns (bool) { + // According to EIP-1052, 0x0 is the value returned for not-yet created accounts + // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned + // for accounts without code, i.e. `keccak256('')` + bytes32 codehash; + bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + // solhint-disable-next-line no-inline-assembly + assembly { + codehash := extcodehash(account) + } + return (codehash != accountHash && codehash != 0x0); + } + + /** + * @dev Converts an `address` into `address payable`. Note that this is + * simply a type cast: the actual underlying value is not changed. + * + * _Available since v2.4.0._ + */ + function toPayable(address account) internal pure returns (address payable) { + return address(uint160(account)); + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + * + * _Available since v2.4.0._ + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + // solhint-disable-next-line avoid-call-value + (bool success, ) = recipient.call.value(amount)(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } +} + +/** + * @title SafeERC20 + * @dev Wrappers around ERC20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +library SafeERC20 { + using SafeMath for uint256; + using Address for address; + + function safeTransfer( + IERC20 token, + address to, + uint256 value + ) internal { + callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom( + IERC20 token, + address from, + address to, + uint256 value + ) internal { + callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + function safeApprove( + IERC20 token, + address spender, + uint256 value + ) internal { + // safeApprove should only be called when setting an initial allowance, + // or when resetting it to zero. To increase and decrease it, use + // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' + // solhint-disable-next-line max-line-length + require((value == 0) || (token.allowance(address(this), spender) == 0), "SafeERC20: approve from non-zero to non-zero allowance"); + callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); + } + + function safeIncreaseAllowance( + IERC20 token, + address spender, + uint256 value + ) internal { + uint256 newAllowance = token.allowance(address(this), spender).add(value); + callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + function safeDecreaseAllowance( + IERC20 token, + address spender, + uint256 value + ) internal { + uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); + callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. + + // A Solidity high level call has three parts: + // 1. The target address is checked to verify it contains contract code + // 2. The call itself is made, and success asserted + // 3. The return value is decoded, which in turn checks the size of the returned data. + // solhint-disable-next-line max-line-length + require(address(token).isContract(), "SafeERC20: call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = address(token).call(data); + require(success, "SafeERC20: low-level call failed"); + + if (returndata.length > 0) { + // Return data is optional + // solhint-disable-next-line max-line-length + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} + +interface IController { + function withdraw(address, uint256) external; + + function balanceOf(address) external view returns (uint256); + + function earn(address, uint256) external; + + function want(address) external view returns (address); + + function rewards() external view returns (address); + + function vaults(address) external view returns (address); + + function strategies(address) external view returns (address); + + function approveStrategy(address, address) external; + + function setStrategy(address, address) external; + + function setVault(address, address) external; +} + +interface Gauge { + function deposit(uint256) external; + + function balanceOf(address) external view returns (uint256); + + function withdraw(uint256) external; +} + +interface Mintr { + function mint(address) external; +} + +interface Uni { + function swapExactTokensForTokens( + uint256, + uint256, + address[] calldata, + address, + uint256 + ) external; +} + +interface ICurveFi { + function add_liquidity(uint256[4] calldata amounts, uint256 min_mint_amount) external; +} + +interface VoterProxy { + 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 StrategyCurvesUSDVoterProxy { + using SafeERC20 for IERC20; + using Address for address; + using SafeMath for uint256; + + address public constant want = address(0xC25a3A3b969415c80451098fa907EC722572917F); + address public constant crv = address(0xD533a949740bb3306d119CC777fa900bA034cd52); + + address public constant curve = address(0xA5407eAE9Ba41422680e2e00537571bcC53efBfD); + address public constant gauge = address(0xA90996896660DEcC6E997655E065b23788857849); + address public constant voter = address(0xF147b8125d2ef93FB6965Db97D6746952a133934); + + address public constant uniswap = address(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); + address public constant sushiswap = address(0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F); + address public constant dai = address(0x6B175474E89094C44Da98b954EedeAC495271d0F); + address public constant weth = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); // used for crv <> weth <> dai route + address public constant snx = address(0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F); + + uint256 public keepCRV = 1000; + 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 = sushiswap; + } + + function getName() external pure returns (string memory) { + return "StrategyCurvesUSDVoterProxy"; + } + + 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 switchDex(bool isUniswap) external { + require(msg.sender == strategist || msg.sender == governance, "!authorized"); + if (isUniswap) { + dex = uniswap; + } else { + dex = sushiswap; + } + } + + function deposit() public { + uint256 _want = IERC20(want).balanceOf(address(this)); + if (_want > 0) { + IERC20(want).safeTransfer(proxy, _want); + VoterProxy(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(dai != address(_asset), "dai"); + 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 VoterProxy(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 { + VoterProxy(proxy).withdrawAll(gauge, want); + } + + function harvest() public { + require(msg.sender == keeper || msg.sender == strategist || msg.sender == governance, "!keepers"); + VoterProxy(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] = dai; + + Uni(dex).swapExactTokensForTokens(_crv, uint256(0), path, address(this), now.add(1800)); + } + VoterProxy(proxy).claimRewards(gauge, snx); + uint256 _snx = IERC20(snx).balanceOf(address(this)); + if (_snx > 0) { + IERC20(snx).safeApprove(dex, 0); + IERC20(snx).safeApprove(dex, _snx); + + address[] memory path = new address[](3); + path[0] = snx; + path[1] = weth; + path[2] = dai; + + Uni(dex).swapExactTokensForTokens(_snx, uint256(0), path, address(this), now.add(1800)); + } + uint256 _dai = IERC20(dai).balanceOf(address(this)); + if (_dai > 0) { + IERC20(dai).safeApprove(curve, 0); + IERC20(dai).safeApprove(curve, _dai); + ICurveFi(curve).add_liquidity([_dai, 0, 0, 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(); + } + VoterProxy(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 VoterProxy(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; + } +}