// SPDX-License-Identifier: MIT pragma solidity 0.6.12; import "./libs/IERC20.sol"; import "./libs/SafeERC20.sol"; import "./libs/SafeMath.sol"; import "./StratManagerLP.sol"; import "./FeeManagerLP.sol"; import "./libs/IRewardPool.sol"; import "./libs/IUniswapRouterETH.sol"; import "./libs/IUniswapV2Pair.sol"; import "./libs/IMiniChefV2.sol"; import "./libs/IRewarder.sol"; import "./libs/IVault.sol"; contract StrategySushiLPV2 is StratManagerLP, FeeManagerLP { using SafeERC20 for IERC20; using SafeMath for uint256; struct UserInfo { // How many assets the user has provided. uint256 stake; // How many staked $Core user had at his last action uint256 autoCoreShares; // Core shares not entitled to the user uint256 rewardDebt; // Timestamp of last user deposit uint256 lastDepositedTime; } // Tokens used address public native; //CELO address public output; //SUSHI address public want; //LP Pair Token e.g. cUSD/USDC address public coreToken; //ORANGE IVault public coreVault; //ORANGE Vault address // Third party contracts address public chef; //Sushi MiniChef uint256 public poolId; //index of sushi pool Id for above LP Token uint256 public lastHarvest; bool public harvestOnDeposit; // Routes address[] public outputToNativeRoute; address[] public nativeToCoreRoute; // Runtime data mapping(address => UserInfo) public userInfo; // Info of users uint256 public accSharesPerStakedToken; // Accumulated shares per core token, times 1e18. /** * @dev Event that is fired each time someone harvests the strat. */ event StratHarvest(address indexed harvester, uint256 wantHarvested, uint256 tvl); event Deposit(uint256 tvl); event Withdraw(uint256 tvl); event E1(); event E2(); event E3(); event E4(); event E5(); event E6(); event E7(); event E8(); event E9(); event E10(); constructor( IVault _coreVault, address _want, uint256 _poolId, address _chef, address _vault, address _unirouter, address _keeper, address _strategist, address _beefyFeeRecipient, address[] memory _outputToNativeRoute, address[] memory _nativeToCoreRoute ) StratManagerLP(_keeper, _strategist, _unirouter, _vault, _beefyFeeRecipient) public { coreToken = address(IVault(_coreVault).want()); coreVault = _coreVault; want = _want; poolId = _poolId; chef = _chef; require(_outputToNativeRoute.length >= 2); output = _outputToNativeRoute[0]; native = _outputToNativeRoute[_outputToNativeRoute.length - 1]; outputToNativeRoute = _outputToNativeRoute; // setup lp routing require(_nativeToCoreRoute[0] == native); require(_nativeToCoreRoute[_nativeToCoreRoute.length - 1] == coreToken); nativeToCoreRoute = _nativeToCoreRoute; _giveAllowances(); } // puts the funds to work function deposit() public whenNotPaused { uint256 wantBal = IERC20(want).balanceOf(address(this)); if (wantBal > 0) { IMiniChefV2(chef).deposit(poolId, wantBal, address(this)); UserInfo storage user = userInfo[msg.sender]; user.autoCoreShares = user.autoCoreShares.add(user.stake.mul(accSharesPerStakedToken).div(1e18).sub(user.rewardDebt)); user.stake = user.stake.add(wantBal); user.rewardDebt = user.stake.mul(accSharesPerStakedToken).div(1e18); user.lastDepositedTime = block.timestamp; emit Deposit(balanceOf()); } } function withdraw(uint256 _amount, uint256 sharePerc) external { require(msg.sender == vault, "!vault"); UserInfo storage user = userInfo[msg.sender]; uint256 wantBal = IERC20(want).balanceOf(address(this)); if (wantBal < _amount) { IMiniChefV2(chef).withdraw(poolId, _amount.sub(wantBal), address(this)); wantBal = IERC20(want).balanceOf(address(this)); } if (wantBal > _amount) { wantBal = _amount; } user.autoCoreShares = user.autoCoreShares.add(user.stake.mul(accSharesPerStakedToken).div(1e18).sub(user.rewardDebt)); user.stake = user.stake.sub(_amount); user.rewardDebt = user.stake.mul(accSharesPerStakedToken).div(1e18); // Withdraw rewards if user leaves if (user.stake == 0 && user.autoCoreShares > 0) { _claimRewards(user.autoCoreShares); } else if (sharePerc > 0) { // withdraw rewards based on share percentage _claimRewards(user.autoCoreShares.mul(sharePerc)); } if (tx.origin == owner() || paused()) { IERC20(want).safeTransfer(vault, wantBal); } else { uint256 withdrawalFeeAmount = wantBal.mul(withdrawalFee).div(WITHDRAWAL_MAX); IERC20(want).safeTransfer(vault, wantBal.sub(withdrawalFeeAmount)); } emit Withdraw(balanceOf()); } function _claimRewards(uint256 _shares) private { UserInfo storage user = userInfo[msg.sender]; user.autoCoreShares = user.autoCoreShares.sub(_shares); uint256 coreBalanceBefore = _coreBalance(); IVault(coreVault).withdraw(_shares); uint256 withdrawAmount = _coreBalance().sub(coreBalanceBefore); _safeCoreTransfer(msg.sender, withdrawAmount); } // Safe Core transfer function, just in case if rounding error causes pool to not have enough function _safeCoreTransfer(address _to, uint256 _amount) private { uint256 balance = _coreBalance(); if (_amount > balance) { IERC20(coreToken).transfer(_to, balance); } else { IERC20(coreToken).transfer(_to, _amount); } } function beforeDeposit() external override { if (harvestOnDeposit) { require(msg.sender == vault, "!vault"); _harvest(tx.origin); } } function harvest() external virtual { _harvest(tx.origin); } function harvestWithCallFeeRecipient(address callFeeRecipient) external virtual { _harvest(callFeeRecipient); } function managerHarvest() external onlyManager { _harvest(tx.origin); } // compounds earnings and charges performance fee function _harvest(address callFeeRecipient) internal whenNotPaused { emit E1(); IMiniChefV2(chef).harvest(poolId, address(this)); emit E2(); uint256 outputBal = IERC20(output).balanceOf(address(this)); uint256 nativeBal = IERC20(native).balanceOf(address(this)); if (outputBal > 0 || nativeBal > 0) { emit E3(); chargeFees(callFeeRecipient); emit E4(); uint256 wantHarvested = balanceOfWant(); depositToCoreVault(); lastHarvest = block.timestamp; emit StratHarvest(msg.sender, wantHarvested, balanceOf()); } } // performance fees function chargeFees(address callFeeRecipient) internal { uint256 toNative = IERC20(output).balanceOf(address(this)); IUniswapRouterETH(unirouter).swapExactTokensForTokens(toNative, 0, outputToNativeRoute, address(this), block.timestamp); uint256 feeBal = IERC20(native).balanceOf(address(this)).mul(45).div(1000); uint256 callFeeAmount = feeBal.mul(callFee).div(MAX_FEE); IERC20(native).safeTransfer(callFeeRecipient, callFeeAmount); uint256 beefyFeeAmount = feeBal.mul(beefyFee).div(MAX_FEE); IERC20(native).safeTransfer(beefyFeeRecipient, beefyFeeAmount); uint256 strategistFee = feeBal.mul(STRATEGIST_FEE).div(MAX_FEE); IERC20(native).safeTransfer(strategist, strategistFee); } // convert native to core token and deposit to vault function depositToCoreVault() internal { emit E5(); uint256 nativeToken = IERC20(native).balanceOf(address(this)); if (coreToken != native) { emit E6(); IUniswapRouterETH(unirouter).swapExactTokensForTokens(nativeToken, 0, nativeToCoreRoute, address(this), block.timestamp); } uint256 coreBal = IERC20(coreToken).balanceOf(address(this)); if (coreBal > 0) { emit E7(); uint256 previousShares = totalAutoCoreShares(); emit E8(); // _approveTokenIfNeeded(coreToken,coreBal,address(coreVault)); IVault(coreVault).deposit(coreBal); emit E9(); uint256 currentShares = totalAutoCoreShares(); accSharesPerStakedToken = accSharesPerStakedToken.add( currentShares.sub(previousShares).mul(1e18).div(totalStake()) ); emit E10(); } } function totalStake() public view returns (uint256) { (uint256 amount, ) = IMiniChefV2(chef).userInfo(poolId, address(this)); return amount; } function totalAutoCoreShares() public view returns (uint256) { uint256 shares = IVault(coreVault).userInfo(address(this)); return shares; } function _coreBalance() private view returns (uint256) { return IERC20(coreToken).balanceOf(address(this)); } // calculate the total underlaying 'want' held by the strat. function balanceOf() public view returns (uint256) { return balanceOfWant().add(balanceOfPool()); } // it calculates how much 'want' this contract holds. function balanceOfWant() public view returns (uint256) { return IERC20(want).balanceOf(address(this)); } // it calculates how much 'want' the strategy has working in the farm. function balanceOfPool() public view returns (uint256) { (uint256 _amount, ) = IMiniChefV2(chef).userInfo(poolId, address(this)); return _amount; } // called as part of strat migration. Sends all the available funds back to the vault. function retireStrat() external { require(msg.sender == vault, "!vault"); IMiniChefV2(chef).emergencyWithdraw(poolId, address(this)); uint256 wantBal = IERC20(want).balanceOf(address(this)); IERC20(want).transfer(vault, wantBal); } // returns rewards unharvested function rewardsAvailable() public view returns (uint256) { return IMiniChefV2(chef).pendingSushi(poolId, address(this)); } // native reward amount for calling harvest function callReward() public view returns (uint256) { uint256 pendingReward; address rewarder = IMiniChefV2(chef).rewarder(poolId); if (rewarder != address(0)) { pendingReward = IRewarder(rewarder).pendingToken(poolId, address(this)); } uint256 outputBal = rewardsAvailable(); uint256 nativeOut; if (outputBal > 0) { try IUniswapRouterETH(unirouter).getAmountsOut(outputBal, outputToNativeRoute) returns (uint256[] memory amountOut) { nativeOut = amountOut[amountOut.length -1]; } catch {} } uint256 totNative = nativeOut.add(pendingReward); return totNative.mul(45).div(1000).mul(callFee).div(MAX_FEE); } function setHarvestOnDeposit(bool _harvestOnDeposit) external onlyManager { harvestOnDeposit = _harvestOnDeposit; if (harvestOnDeposit) { setWithdrawalFee(0); } else { setWithdrawalFee(10); } } // pauses deposits and withdraws all funds from third party systems. function panic() public onlyManager { pause(); IMiniChefV2(chef).emergencyWithdraw(poolId, address(this)); } function pause() public onlyManager { _pause(); _removeAllowances(); } function unpause() external onlyManager { _unpause(); _giveAllowances(); deposit(); } function _giveAllowances() internal { IERC20(want).safeApprove(chef, type(uint256).max); IERC20(native).safeApprove(unirouter, type(uint256).max); IERC20(output).safeApprove(unirouter, type(uint256).max); IERC20(coreToken).safeApprove(unirouter, 0); IERC20(coreToken).safeApprove(unirouter, type(uint256).max); IERC20(coreToken).safeApprove(address(coreVault), type(uint256).max); } function _removeAllowances() internal { IERC20(want).safeApprove(chef, 0); IERC20(native).safeApprove(unirouter, 0); IERC20(output).safeApprove(unirouter, 0); IERC20(coreToken).safeApprove(unirouter, 0); IERC20(coreToken).safeApprove(address(coreVault), 0); } }