BaseStrategy
295 lines
// SPDX-License-Identifier: MIT
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma solidity 0.6.12;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.1.0/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.1.0/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.1.0/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.1.0/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./libs/IStrategyFish.sol";
import "./libs/IStrategyFish.sol";
import "./libs/IUniPair.sol";
import "./libs/IUniPair.sol";
import "./libs/IUniRouter02.sol";
import "./libs/IUniRouter02.sol";
abstract contract BaseStrategy is Ownable, ReentrancyGuard, Pausable {
abstract contract BaseStrategy is Ownable, ReentrancyGuard, Pausable {
using SafeMath for uint256;
using SafeMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for IERC20;
address public wantAddress;
address public wantAddress;
address public token0Address;
address public token0Address;
address public token1Address;
address public token1Address;
address public earnedAddress;
address public earnedAddress;
address public uniRouterAddress;
address public uniRouterAddress;
address public constant usdcAddress = 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174;
address public constant usdcAddress = 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174;
address public constant fishAddress = 0x3a3Df212b7AA91Aa0402B9035b098891d276572B;
address public constant fishAddress = 0x76bF0C28e604CC3fE9967c83b3C3F31c213cfE64;
address public constant rewardAddress = 0x917FB15E8aAA12264DCBdC15AFef7cD3cE76BA39;
address public constant rewardAddress = 0x917FB15E8aAA12264DCBdC15AFef7cD3cE76BA39;
address public constant withdrawFeeAddress = 0x4879712c5D1A98C0B88Fb700daFF5c65d12Fd729;
address public constant withdrawFeeAddress = 0x5386881b46C37CdD30A748f7771CF95D7B213637;
address public constant feeAddress = 0x1cb757f1eB92F25A917CE9a92ED88c1aC0734334;
address public constant feeAddress = 0x5386881b46C37CdD30A748f7771CF95D7B213637;
address public vaultChefAddress;
address public vaultChefAddress;
address public govAddress;
address public govAddress;
uint256 public lastEarnBlock = block.number;
uint256 public lastEarnBlock = block.number;
uint256 public sharesTotal = 0;
uint256 public sharesTotal = 0;
address public constant buyBackAddress = 0x000000000000000000000000000000000000dEaD;
address public constant buyBackAddress = 0x000000000000000000000000000000000000dEaD;
uint256 public controllerFee = 50;
uint256 public controllerFee = 50;
uint256 public rewardRate = 0;
uint256 public rewardRate = 0;
uint256 public buyBackRate = 450;
uint256 public buyBackRate = 450;
uint256 public constant feeMaxTotal = 1000;
uint256 public constant feeMaxTotal = 1000;
uint256 public constant feeMax = 10000; // 100 = 1%
uint256 public constant feeMax = 10000; // 100 = 1%
uint256 public withdrawFeeFactor = 10000; // 0% withdraw fee
uint256 public withdrawFeeFactor = 9990; // 0.1% withdraw fee
uint256 public constant withdrawFeeFactorMax = 10000;
uint256 public constant withdrawFeeFactorMax = 10000;
uint256 public constant withdrawFeeFactorLL = 9900;
uint256 public constant withdrawFeeFactorLL = 9900;
uint256 public slippageFactor = 950; // 5% default slippage tolerance
uint256 public slippageFactor = 950; // 5% default slippage tolerance
uint256 public constant slippageFactorUL = 995;
uint256 public constant slippageFactorUL = 995;
// Frontend variables
uint256 public tolerance;
uint256 public burnedAmount;
address[] public earnedToWmaticPath;
address[] public earnedToWmaticPath;
address[] public earnedToUsdcPath;
address[] public earnedToUsdcPath;
address[] public earnedToFishPath;
address[] public earnedToFishPath;
address[] public earnedToToken0Path;
address[] public earnedToToken0Path;
address[] public earnedToToken1Path;
address[] public earnedToToken1Path;
address[] public token0ToEarnedPath;
address[] public token0ToEarnedPath;
address[] public token1ToEarnedPath;
address[] public token1ToEarnedPath;
event SetSettings(
event SetSettings(
uint256 _controllerFee,
uint256 _controllerFee,
uint256 _rewardRate,
uint256 _rewardRate,
uint256 _buyBackRate,
uint256 _buyBackRate,
uint256 _withdrawFeeFactor,
uint256 _withdrawFeeFactor,
uint256 _slippageFactor,
uint256 _slippageFactor,
uint256 _tolerance,
address _uniRouterAddress
address _uniRouterAddress
);
);
modifier onlyGov() {
modifier onlyGov() {
require(msg.sender == govAddress, "!gov");
require(msg.sender == govAddress, "!gov");
_;
_;
}
}
function _vaultDeposit(uint256 _amount) internal virtual;
function _vaultDeposit(uint256 _amount) internal virtual;
function _vaultWithdraw(uint256 _amount) internal virtual;
function _vaultWithdraw(uint256 _amount) internal virtual;
function earn() external virtual;
function earn() external virtual;
function vaultSharesTotal() public virtual view returns (uint256);
function vaultSharesTotal() public virtual view returns (uint256);
function wantLockedTotal() public virtual view returns (uint256);
function wantLockedTotal() public virtual view returns (uint256);
function _resetAllowances() internal virtual;
function _resetAllowances() internal virtual;
function _emergencyVaultWithdraw() internal virtual;
function _emergencyVaultWithdraw() internal virtual;
function deposit(address _userAddress, uint256 _wantAmt) external onlyOwner nonReentrant whenNotPaused returns (uint256) {
function deposit(address _userAddress, uint256 _wantAmt) external onlyOwner nonReentrant whenNotPaused returns (uint256) {
// Call must happen before transfer
// Call must happen before transfer
uint256 wantLockedBefore = wantLockedTotal();
uint256 wantLockedBefore = wantLockedTotal();
IERC20(wantAddress).safeTransferFrom(
IERC20(wantAddress).safeTransferFrom(
address(msg.sender),
address(msg.sender),
address(this),
address(this),
_wantAmt
_wantAmt
);
);
// Proper deposit amount for tokens with fees, or vaults with deposit fees
// Proper deposit amount for tokens with fees, or vaults with deposit fees
uint256 sharesAdded = _farm();
uint256 sharesAdded = _farm();
if (sharesTotal > 0) {
if (sharesTotal > 0) {
sharesAdded = sharesAdded.mul(sharesTotal).div(wantLockedBefore);
sharesAdded = sharesAdded.mul(sharesTotal).div(wantLockedBefore);
}
}
sharesTotal = sharesTotal.add(sharesAdded);
sharesTotal = sharesTotal.add(sharesAdded);
return sharesAdded;
return sharesAdded;
}
}
function _farm() internal returns (uint256) {
function _farm() internal returns (uint256) {
uint256 wantAmt = IERC20(wantAddress).balanceOf(address(this));
uint256 wantAmt = IERC20(wantAddress).balanceOf(address(this));
if (wantAmt == 0) return 0;
if (wantAmt == 0) return 0;
uint256 sharesBefore = vaultSharesTotal();
uint256 sharesBefore = vaultSharesTotal();
_vaultDeposit(wantAmt);
_vaultDeposit(wantAmt);
uint256 sharesAfter = vaultSharesTotal();
uint256 sharesAfter = vaultSharesTotal();
return sharesAfter.sub(sharesBefore);
return sharesAfter.sub(sharesBefore);
}
}
function withdraw(address _userAddress, uint256 _wantAmt) external onlyOwner nonReentrant returns (uint256) {
function withdraw(address _userAddress, uint256 _wantAmt) external onlyOwner nonReentrant returns (uint256) {
require(_wantAmt > 0, "_wantAmt is 0");
require(_wantAmt > 0, "_wantAmt is 0");
uint256 wantAmt = IERC20(wantAddress).balanceOf(address(this));
uint256 wantAmt = IERC20(wantAddress).balanceOf(address(this));
// Check if strategy has tokens from panic
// Check if strategy has tokens from panic
if (_wantAmt > wantAmt) {
if (_wantAmt > wantAmt) {
_vaultWithdraw(_wantAmt.sub(wantAmt));
_vaultWithdraw(_wantAmt.sub(wantAmt));
wantAmt = IERC20(wantAddress).balanceOf(address(this));
wantAmt = IERC20(wantAddress).balanceOf(address(this));
}
}
if (_wantAmt > wantAmt) {
if (_wantAmt > wantAmt) {
_wantAmt = wantAmt;
_wantAmt = wantAmt;
}
}
if (_wantAmt > wantLockedTotal()) {
if (_wantAmt > wantLockedTotal()) {
_wantAmt = wantLockedTotal();
_wantAmt = wantLockedTotal();
}
}
uint256 sharesRemoved = _wantAmt.mul(sharesTotal).div(wantLockedTotal());
uint256 sharesRemoved = _wantAmt.mul(sharesTotal).div(wantLockedTotal());
if (sharesRemoved > sharesTotal) {
if (sharesRemoved > sharesTotal) {
sharesRemoved = sharesTotal;
sharesRemoved = sharesTotal;
}
}
sharesTotal = sharesTotal.sub(sharesRemoved);
sharesTotal = sharesTotal.sub(sharesRemoved);
// Withdraw fee
// Withdraw fee
uint256 withdrawFee = _wantAmt
uint256 withdrawFee = _wantAmt
.mul(withdrawFeeFactorMax.sub(withdrawFeeFactor))
.mul(withdrawFeeFactorMax.sub(withdrawFeeFactor))
.div(withdrawFeeFactorMax);
.div(withdrawFeeFactorMax);
if (withdrawFee > 0) {
if (withdrawFee > 0) {
IERC20(wantAddress).safeTransfer(withdrawFeeAddress, withdrawFee);
IERC20(wantAddress).safeTransfer(withdrawFeeAddress, withdrawFee);
}
}
_wantAmt = _wantAmt.sub(withdrawFee);
_wantAmt = _wantAmt.sub(withdrawFee);
IERC20(wantAddress).safeTransfer(vaultChefAddress, _wantAmt);
IERC20(wantAddress).safeTransfer(vaultChefAddress, _wantAmt);
return sharesRemoved;
return sharesRemoved;
}
}
// To pay for earn function
// To pay for earn function
function distributeFees(uint256 _earnedAmt) internal returns (uint256) {
function distributeFees(uint256 _earnedAmt) internal returns (uint256) {
if (controllerFee > 0) {
if (controllerFee > 0) {
uint256 fee = _earnedAmt.mul(controllerFee).div(feeMax);
uint256 fee = _earnedAmt.mul(controllerFee).div(feeMax);
_safeSwapWmatic(
_safeSwapWmatic(
fee,
fee,
earnedToWmaticPath,
earnedToWmaticPath,
feeAddress
feeAddress
);
);
_earnedAmt = _earnedAmt.sub(fee);
_earnedAmt = _earnedAmt.sub(fee);
}
}
return _earnedAmt;
return _earnedAmt;
}
}
function distributeRewards(uint256 _earnedAmt) internal returns (uint256) {
function distributeRewards(uint256 _earnedAmt) internal returns (uint256) {
if (rewardRate > 0) {
if (rewardRate > 0) {
uint256 fee = _earnedAmt.mul(rewardRate).div(feeMax);
uint256 fee = _earnedAmt.mul(rewardRate).div(feeMax);
uint256 usdcBefore = IERC20(usdcAddress).balanceOf(address(this));
uint256 usdcBefore = IERC20(usdcAddress).balanceOf(address(this));
_safeSwap(
_safeSwap(
fee,
fee,
earnedToUsdcPath,
earnedToUsdcPath,
address(this)
address(this)
);
);
uint256 usdcAfter = IERC20(usdcAddress).balanceOf(address(this)).sub(usdcBefore);
uint256 usdcAfter = IERC20(usdcAddress).balanceOf(address(this)).sub(usdcBefore);
IStrategyFish(rewardAddress).depositReward(usdcAfter);
IStrategyFish(rewardAddress).depositReward(usdcAfter);
_earnedAmt = _earnedAmt.sub(fee);
_earnedAmt = _earnedAmt.sub(fee);
}
}
return _earnedAmt;
return _earnedAmt;
}
}
function buyBack(uint256 _earnedAmt) internal virtual returns (uint256) {
function buyBack(uint256 _earnedAmt) internal virtual returns (uint256) {
if (buyBackRate > 0) {
if (buyBackRate > 0) {
uint256 buyBackAmt = _earnedAmt.mul(buyBackRate).div(feeMax);
uint256 buyBackAmt = _earnedAmt.mul(buyBackRate).div(feeMax);
_safeSwap(
_safeSwap(
buyBackAmt,
buyBackAmt,
earnedToFishPath,
earnedToFishPath,
buyBackAddress
buyBackAddress
);
);
_earnedAmt = _earnedAmt.sub(buyBackAmt);
_earnedAmt = _earnedAmt.sub(buyBackAmt);
}
}
return _earnedAmt;
return _earnedAmt;
}
}
function resetAllowances() external onlyGov {
function resetAllowances() external onlyGov {
_resetAllowances();
_resetAllowances();
}
}
function pause() external onlyGov {
function pause() external onlyGov {
_pause();
_pause();
}
}
function unpause() external onlyGov {
function unpause() external onlyGov {
_unpause();
_unpause();
_resetAllowances();
_resetAllowances();
}
}
function panic() external onlyGov {
function panic() external onlyGov {
_pause();
_pause();
_emergencyVaultWithdraw();
_emergencyVaultWithdraw();
}
}
function unpanic() external onlyGov {
function unpanic() external onlyGov {
_unpause();
_unpause();
_farm();
_farm();
}
}
function setGov(address _govAddress) external onlyGov {
function setGov(address _govAddress) external onlyGov {
govAddress = _govAddress;
govAddress = _govAddress;
}
}
function setSettings(
function setSettings(
uint256 _controllerFee,
uint256 _controllerFee,
uint256 _rewardRate,
uint256 _rewardRate,
uint256 _buyBackRate,
uint256 _buyBackRate,
uint256 _withdrawFeeFactor,
uint256 _withdrawFeeFactor,
uint256 _slippageFactor,
uint256 _slippageFactor,
uint256 _tolerance,
address _uniRouterAddress
address _uniRouterAddress
) external onlyGov {
) external onlyGov {
require(_controllerFee.add(_rewardRate).add(_buyBackRate) <= feeMaxTotal, "Max fee of 10%");
require(_controllerFee.add(_rewardRate).add(_buyBackRate) <= feeMaxTotal, "Max fee of 10%");
require(_withdrawFeeFactor >= withdrawFeeFactorLL, "_withdrawFeeFactor too low");
require(_withdrawFeeFactor >= withdrawFeeFactorLL, "_withdrawFeeFactor too low");
require(_withdrawFeeFactor <= withdrawFeeFactorMax, "_withdrawFeeFactor too high");
require(_withdrawFeeFactor <= withdrawFeeFactorMax, "_withdrawFeeFactor too high");
require(_slippageFactor <= slippageFactorUL, "_slippageFactor too high");
require(_slippageFactor <= slippageFactorUL, "_slippageFactor too high");
controllerFee = _controllerFee;
controllerFee = _controllerFee;
rewardRate = _rewardRate;
rewardRate = _rewardRate;
buyBackRate = _buyBackRate;
buyBackRate = _buyBackRate;
withdrawFeeFactor = _withdrawFeeFactor;
withdrawFeeFactor = _withdrawFeeFactor;
slippageFactor = _slippageFactor;
slippageFactor = _slippageFactor;
tolerance = _tolerance;
uniRouterAddress = _uniRouterAddress;
uniRouterAddress = _uniRouterAddress;
emit SetSettings(
emit SetSettings(
_controllerFee,
_controllerFee,
_rewardRate,
_rewardRate,
_buyBackRate,
_buyBackRate,
_withdrawFeeFactor,
_withdrawFeeFactor,
_slippageFactor,
_slippageFactor,
_tolerance,
_uniRouterAddress
_uniRouterAddress
);
);
}
}
function _safeSwap(
function _safeSwap(
uint256 _amountIn,
uint256 _amountIn,
address[] memory _path,
address[] memory _path,
address _to
address _to
) internal {
) internal {
uint256[] memory amounts = IUniRouter02(uniRouterAddress).getAmountsOut(_amountIn, _path);
uint256[] memory amounts = IUniRouter02(uniRouterAddress).getAmountsOut(_amountIn, _path);
uint256 amountOut = amounts[amounts.length.sub(1)];
uint256 amountOut = amounts[amounts.length.sub(1)];
if (_path[_path.length.sub(1)] == fishAddress && _to == buyBackAddress) {
burnedAmount = burnedAmount.add(amountOut);
}
IUniRouter02(uniRouterAddress).swapExactTokensForTokens(
IUniRouter02(uniRouterAddress).swapExactTokensForTokens(
_amountIn,
_amountIn,
amountOut.mul(slippageFactor).div(1000),
amountOut.mul(slippageFactor).div(1000),
_path,
_path,
_to,
_to,
now.add(600)
now.add(600)
);
);
}
}
function _safeSwapWmatic(
function _safeSwapWmatic(
uint256 _amountIn,
uint256 _amountIn,
address[] memory _path,
address[] memory _path,
address _to
address _to
) internal {
) internal {
uint256[] memory amounts = IUniRouter02(uniRouterAddress).getAmountsOut(_amountIn, _path);
uint256[] memory amounts = IUniRouter02(uniRouterAddress).getAmountsOut(_amountIn, _path);
uint256 amountOut = amounts[amounts.length.sub(1)];
uint256 amountOut = amounts[amounts.length.sub(1)];
IUniRouter02(uniRouterAddress).swapExactTokensForETH(
IUniRouter02(uniRouterAddress).swapExactTokensForETH(
_amountIn,
_amountIn,
amountOut.mul(slippageFactor).div(1000),
amountOut.mul(slippageFactor).div(1000),
_path,
_path,
_to,
_to,
now.add(600)
now.add(600)
);
);
}
}
}
}