VaultSushiFlipToFlip.sol

Created Diff never expires
0 removals
310 lines
17 additions
327 lines
// SPDX-License-Identifier: MIT
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
pragma experimental ABIEncoderV2;


/*
/*
___ _ _
___ _ _
| _ )_ _ _ _ _ _ _ _ | | | |
| _ )_ _ _ _ _ _ _ _ | | | |
| _ \ || | ' \| ' \ || | |_| |_|
| _ \ || | ' \| ' \ || | |_| |_|
|___/\_,_|_||_|_||_\_, | (_) (_)
|___/\_,_|_||_|_||_\_, | (_) (_)
|__/
|__/


*
*
* MIT License
* MIT License
* ===========
* ===========
*
*
* Copyright (c) 2020 BunnyFinance
* Copyright (c) 2020 BunnyFinance
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* furnished to do so, subject to the following conditions:
*
*
* The above copyright notice and this permission notice shall be included in all
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* copies or substantial portions of the Software.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
*/
*/


import "@pancakeswap/pancake-swap-lib/contracts/token/BEP20/SafeBEP20.sol";
import "@pancakeswap/pancake-swap-lib/contracts/token/BEP20/SafeBEP20.sol";
import "@openzeppelin/contracts/math/Math.sol";
import "@openzeppelin/contracts/math/Math.sol";


import {PoolConstant} from "../library/PoolConstant.sol";
import {PoolConstant} from "../library/PoolConstant.sol";
import "../interfaces/IUniswapV2Pair.sol";
import "../interfaces/IUniswapV2Pair.sol";
import "../interfaces/IUniswapV2Factory.sol";
import "../interfaces/IUniswapV2Factory.sol";
import "../interfaces/IStrategy.sol";
import "../interfaces/IStrategy.sol";
import "../interfaces/ISushiMiniChefV2.sol";
import "../interfaces/ISushiMiniChefV2.sol";
import "../interfaces/IZap.sol";
import "../interfaces/IZap.sol";


import "./VaultController.sol";
import "./VaultController.sol";


contract VaultSushiFlipToFlip is VaultController, IStrategy {
contract VaultSushiFlipToFlip is VaultController, IStrategy {
using SafeBEP20 for IBEP20;
using SafeBEP20 for IBEP20;
using SafeMath for uint256;
using SafeMath for uint256;


/* ========== CONSTANTS ============= */
/* ========== CONSTANTS ============= */


IBEP20 private constant SUSHI = IBEP20(0x0b3F868E0BE5597D5DB7fEB59E1CADBb0fdDa50a);
IBEP20 private constant SUSHI = IBEP20(0x0b3F868E0BE5597D5DB7fEB59E1CADBb0fdDa50a);
IBEP20 private constant WMATIC = IBEP20(0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270);
IBEP20 private constant WMATIC = IBEP20(0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270);
PoolConstant.PoolTypes public constant override poolType = PoolConstant.PoolTypes.FlipToFlip;
PoolConstant.PoolTypes public constant override poolType = PoolConstant.PoolTypes.FlipToFlip;
ISushiMiniChefV2 private constant SUSHI_MINI_CHEF = ISushiMiniChefV2(0x0769fd68dFb93167989C6f7254cd0D766Fb2841F);
ISushiMiniChefV2 private constant SUSHI_MINI_CHEF = ISushiMiniChefV2(0x0769fd68dFb93167989C6f7254cd0D766Fb2841F);
IZap private constant zap = IZap(0x93bCE7E49E26AF0f87b74583Ba6551DF5E4867B7);
IZap private constant zap = IZap(0x93bCE7E49E26AF0f87b74583Ba6551DF5E4867B7);


uint private constant DUST = 1000;
uint private constant DUST = 1000;


/* ========== STATE VARIABLES ========== */
/* ========== STATE VARIABLES ========== */


uint public override pid;
uint public override pid;


address private _token0;
address private _token0;
address private _token1;
address private _token1;


uint public totalShares;
uint public totalShares;
mapping (address => uint) private _shares;
mapping (address => uint) private _shares;
mapping (address => uint) private _principal;
mapping (address => uint) private _principal;
mapping (address => uint) private _depositedAt;
mapping (address => uint) private _depositedAt;


uint public sushiHarvested;
uint public sushiHarvested;
uint public wmaticHarvested;
uint public wmaticHarvested;


uint public totalBalance;


/* ========== MODIFIER ========== */
/* ========== MODIFIER ========== */


modifier updateSushiHarvested {
modifier updateSushiHarvested {
uint _before = SUSHI.balanceOf(address(this));
uint _before = SUSHI.balanceOf(address(this));
uint _beforeWmatic = WMATIC.balanceOf(address(this));
uint _beforeWmatic = WMATIC.balanceOf(address(this));
_;
_;
uint _after = SUSHI.balanceOf(address(this));
uint _after = SUSHI.balanceOf(address(this));
uint _afterWmatic = WMATIC.balanceOf(address(this));
uint _afterWmatic = WMATIC.balanceOf(address(this));
sushiHarvested = sushiHarvested.add(_after).sub(_before);
sushiHarvested = sushiHarvested.add(_after).sub(_before);
wmaticHarvested = wmaticHarvested.add(_afterWmatic).sub(_beforeWmatic);
wmaticHarvested = wmaticHarvested.add(_afterWmatic).sub(_beforeWmatic);
}
}


/* ========== INITIALIZER ========== */
/* ========== INITIALIZER ========== */


function initialize(uint _pid, address _token) external initializer {
function initialize(uint _pid, address _token) external initializer {
__VaultController_init(IBEP20(_token));
__VaultController_init(IBEP20(_token));
_stakingToken.safeApprove(address(SUSHI_MINI_CHEF), uint(- 1));
_stakingToken.safeApprove(address(SUSHI_MINI_CHEF), uint(- 1));
pid = _pid;
pid = _pid;


SUSHI.safeApprove(address(zap), uint(- 1));
SUSHI.safeApprove(address(zap), uint(- 1));
WMATIC.safeApprove(address(zap), uint(- 1));
WMATIC.safeApprove(address(zap), uint(- 1));
}
}


/* ========== VIEW FUNCTIONS ========== */
/* ========== VIEW FUNCTIONS ========== */


function totalSupply() external view override returns (uint) {
function totalSupply() external view override returns (uint) {
return totalShares;
return totalShares;
}
}


function balance() public view override returns (uint amount) {
function balance() public view override returns (uint amount) {
(amount,) = SUSHI_MINI_CHEF.userInfo(pid, address(this));
(amount,) = SUSHI_MINI_CHEF.userInfo(pid, address(this));
amount = Math.min(amount, totalBalance);
}
}


function balanceOf(address account) public view override returns(uint) {
function balanceOf(address account) public view override returns(uint) {
if (totalShares == 0) return 0;
if (totalShares == 0) return 0;
return balance().mul(sharesOf(account)).div(totalShares);
return balance().mul(sharesOf(account)).div(totalShares);
}
}


function withdrawableBalanceOf(address account) public view override returns (uint) {
function withdrawableBalanceOf(address account) public view override returns (uint) {
return balanceOf(account);
return balanceOf(account);
}
}


function sharesOf(address account) public view override returns (uint) {
function sharesOf(address account) public view override returns (uint) {
return _shares[account];
return _shares[account];
}
}


function principalOf(address account) public view override returns (uint) {
function principalOf(address account) public view override returns (uint) {
return _principal[account];
return _principal[account];
}
}


function earned(address account) public view override returns (uint) {
function earned(address account) public view override returns (uint) {
if (balanceOf(account) >= principalOf(account) + DUST) {
if (balanceOf(account) >= principalOf(account) + DUST) {
return balanceOf(account).sub(principalOf(account));
return balanceOf(account).sub(principalOf(account));
} else {
} else {
return 0;
return 0;
}
}
}
}


function depositedAt(address account) external view override returns (uint) {
function depositedAt(address account) external view override returns (uint) {
return _depositedAt[account];
return _depositedAt[account];
}
}


function rewardsToken() external view override returns (address) {
function rewardsToken() external view override returns (address) {
return address(_stakingToken);
return address(_stakingToken);
}
}


function priceShare() external view override returns(uint) {
function priceShare() external view override returns(uint) {
if (totalShares == 0) return 1e18;
if (totalShares == 0) return 1e18;
return balance().mul(1e18).div(totalShares);
return balance().mul(1e18).div(totalShares);
}
}


/* ========== MUTATIVE FUNCTIONS ========== */
/* ========== MUTATIVE FUNCTIONS ========== */


function deposit(uint _amount) public override {
function deposit(uint _amount) public override {
_depositTo(_amount, msg.sender);
_depositTo(_amount, msg.sender);
}
}


function depositAll() external override {
function depositAll() external override {
deposit(_stakingToken.balanceOf(msg.sender));
deposit(_stakingToken.balanceOf(msg.sender));
}
}


function withdrawAll() external override {
function withdrawAll() external override {
uint amount = balanceOf(msg.sender);
uint amount = balanceOf(msg.sender);
uint principal = principalOf(msg.sender);
uint principal = principalOf(msg.sender);
uint depositTimestamp = _depositedAt[msg.sender];
uint depositTimestamp = _depositedAt[msg.sender];


totalBalance = totalBalance.sub(amount);
totalShares = totalShares.sub(_shares[msg.sender]);
totalShares = totalShares.sub(_shares[msg.sender]);
delete _shares[msg.sender];
delete _shares[msg.sender];
delete _principal[msg.sender];
delete _principal[msg.sender];
delete _depositedAt[msg.sender];
delete _depositedAt[msg.sender];


amount = _withdrawTokenWithCorrection(amount);
amount = _withdrawTokenWithCorrection(amount);
uint profit = amount > principal ? amount.sub(principal) : 0;
uint profit = amount > principal ? amount.sub(principal) : 0;


uint withdrawalFee = canMint() ? _minter.withdrawalFee(principal, depositTimestamp) : 0;
uint withdrawalFee = canMint() ? _minter.withdrawalFee(principal, depositTimestamp) : 0;
uint performanceFee = canMint() ? _minter.performanceFee(profit) : 0;
uint performanceFee = canMint() ? _minter.performanceFee(profit) : 0;
if (withdrawalFee.add(performanceFee) > DUST) {
if (withdrawalFee.add(performanceFee) > DUST) {
_minter.mintForV2(address(_stakingToken), withdrawalFee, performanceFee, msg.sender, depositTimestamp);
_minter.mintForV2(address(_stakingToken), withdrawalFee, performanceFee, msg.sender, depositTimestamp);


if (performanceFee > 0) {
if (performanceFee > 0) {
emit ProfitPaid(msg.sender, profit, performanceFee);
emit ProfitPaid(msg.sender, profit, performanceFee);
}
}
amount = amount.sub(withdrawalFee).sub(performanceFee);
amount = amount.sub(withdrawalFee).sub(performanceFee);
}
}


_stakingToken.safeTransfer(msg.sender, amount);
_stakingToken.safeTransfer(msg.sender, amount);
emit Withdrawn(msg.sender, amount, withdrawalFee);
emit Withdrawn(msg.sender, amount, withdrawalFee);
}
}


function harvest() external override onlyKeeper {
function harvest() external override onlyKeeper {
_harvest();
_harvest();


uint before = _stakingToken.balanceOf(address(this));
uint before = _stakingToken.balanceOf(address(this));
zap.zapInToken(address(SUSHI), sushiHarvested, address(_stakingToken));
zap.zapInToken(address(SUSHI), sushiHarvested, address(_stakingToken));
zap.zapInToken(address(WMATIC), wmaticHarvested, address(_stakingToken));
zap.zapInToken(address(WMATIC), wmaticHarvested, address(_stakingToken));
uint harvested = _stakingToken.balanceOf(address(this)).sub(before);
uint harvested = _stakingToken.balanceOf(address(this)).sub(before);


totalBalance = totalBalance.add(harvested);
SUSHI_MINI_CHEF.deposit(pid, harvested, address(this));
SUSHI_MINI_CHEF.deposit(pid, harvested, address(this));
emit Harvested(harvested);
emit Harvested(harvested);


sushiHarvested = 0;
sushiHarvested = 0;
wmaticHarvested = 0;
wmaticHarvested = 0;
}
}


function _harvest() private updateSushiHarvested {
function _harvest() private updateSushiHarvested {
SUSHI_MINI_CHEF.harvest(pid, address(this));
SUSHI_MINI_CHEF.harvest(pid, address(this));
}
}


function withdraw(uint shares) external override onlyWhitelisted {
function withdraw(uint shares) external override onlyWhitelisted {
uint amount = balance().mul(shares).div(totalShares);
uint amount = balance().mul(shares).div(totalShares);
totalBalance = totalBalance.sub(amount);
totalShares = totalShares.sub(shares);
totalShares = totalShares.sub(shares);
_shares[msg.sender] = _shares[msg.sender].sub(shares);
_shares[msg.sender] = _shares[msg.sender].sub(shares);


amount = _withdrawTokenWithCorrection(amount);
amount = _withdrawTokenWithCorrection(amount);
_stakingToken.safeTransfer(msg.sender, amount);
_stakingToken.safeTransfer(msg.sender, amount);
emit Withdrawn(msg.sender, amount, 0);
emit Withdrawn(msg.sender, amount, 0);
}
}


// @dev underlying only + withdrawal fee + no perf fee
// @dev underlying only + withdrawal fee + no perf fee
function withdrawUnderlying(uint _amount) external {
function withdrawUnderlying(uint _amount) external {
uint amount = Math.min(_amount, _principal[msg.sender]);
uint amount = Math.min(_amount, _principal[msg.sender]);
uint shares = Math.min(amount.mul(totalShares).div(balance()), _shares[msg.sender]);
uint shares = Math.min(amount.mul(totalShares).div(balance()), _shares[msg.sender]);

totalBalance = totalBalance.sub(amount);
totalShares = totalShares.sub(shares);
totalShares = totalShares.sub(shares);
_shares[msg.sender] = _shares[msg.sender].sub(shares);
_shares[msg.sender] = _shares[msg.sender].sub(shares);
_principal[msg.sender] = _principal[msg.sender].sub(amount);
_principal[msg.sender] = _principal[msg.sender].sub(amount);


amount = _withdrawTokenWithCorrection(amount);
amount = _withdrawTokenWithCorrection(amount);
uint depositTimestamp = _depositedAt[msg.sender];
uint depositTimestamp = _depositedAt[msg.sender];
uint withdrawalFee = canMint() ? _minter.withdrawalFee(amount, depositTimestamp) : 0;
uint withdrawalFee = canMint() ? _minter.withdrawalFee(amount, depositTimestamp) : 0;
if (withdrawalFee > DUST) {
if (withdrawalFee > DUST) {
_minter.mintForV2(address(_stakingToken), withdrawalFee, 0, msg.sender, depositTimestamp);
_minter.mintForV2(address(_stakingToken), withdrawalFee, 0, msg.sender, depositTimestamp);
amount = amount.sub(withdrawalFee);
amount = amount.sub(withdrawalFee);
}
}


_stakingToken.safeTransfer(msg.sender, amount);
_stakingToken.safeTransfer(msg.sender, amount);
emit Withdrawn(msg.sender, amount, withdrawalFee);
emit Withdrawn(msg.sender, amount, withdrawalFee);
}
}


// @dev profits only (underlying + bunny) + no withdraw fee + perf fee
// @dev profits only (underlying + bunny) + no withdraw fee + perf fee
function getReward() external override {
function getReward() external override {
uint amount = earned(msg.sender);
uint amount = earned(msg.sender);
uint shares = Math.min(amount.mul(totalShares).div(balance()), _shares[msg.sender]);
uint shares = Math.min(amount.mul(totalShares).div(balance()), _shares[msg.sender]);
totalBalance = totalBalance.sub(amount);
totalShares = totalShares.sub(shares);
totalShares = totalShares.sub(shares);
_shares[msg.sender] = _shares[msg.sender].sub(shares);
_shares[msg.sender] = _shares[msg.sender].sub(shares);
_cleanupIfDustShares();
_cleanupIfDustShares();


amount = _withdrawTokenWithCorrection(amount);
amount = _withdrawTokenWithCorrection(amount);
uint depositTimestamp = _depositedAt[msg.sender];
uint depositTimestamp = _depositedAt[msg.sender];
uint performanceFee = canMint() ? _minter.performanceFee(amount) : 0;
uint performanceFee = canMint() ? _minter.performanceFee(amount) : 0;
if (performanceFee > DUST) {
if (performanceFee > DUST) {
_minter.mintForV2(address(_stakingToken), 0, performanceFee, msg.sender, depositTimestamp);
_minter.mintForV2(address(_stakingToken), 0, performanceFee, msg.sender, depositTimestamp);
amount = amount.sub(performanceFee);
amount = amount.sub(performanceFee);
}
}


_stakingToken.safeTransfer(msg.sender, amount);
_stakingToken.safeTransfer(msg.sender, amount);
emit ProfitPaid(msg.sender, amount, performanceFee);
emit ProfitPaid(msg.sender, amount, performanceFee);
}
}


/* ========== PRIVATE FUNCTIONS ========== */
/* ========== PRIVATE FUNCTIONS ========== */


function _depositTo(uint _amount, address _to) private notPaused updateSushiHarvested {
function _depositTo(uint _amount, address _to) private notPaused updateSushiHarvested {
uint _pool = balance();
uint _pool = balance();
uint _before = _stakingToken.balanceOf(address(this));
uint _before = _stakingToken.balanceOf(address(this));
_stakingToken.safeTransferFrom(msg.sender, address(this), _amount);
_stakingToken.safeTransferFrom(msg.sender, address(this), _amount);
uint _after = _stakingToken.balanceOf(address(this));
uint _after = _stakingToken.balanceOf(address(this));
_amount = _after.sub(_before); // Additional check for deflationary tokens
_amount = _after.sub(_before); // Additional check for deflationary tokens
uint shares = 0;
uint shares = 0;
if (totalShares == 0) {
if (totalShares == 0) {
shares = _amount;
shares = _amount;
} else {
} else {
shares = (_amount.mul(totalShares)).div(_pool);
shares = (_amount.mul(totalShares)).div(_pool);
}
}


totalBalance = totalBalance.add(_amount);
totalShares = totalShares.add(shares);
totalShares = totalShares.add(shares);
_shares[_to] = _shares[_to].add(shares);
_shares[_to] = _shares[_to].add(shares);
_principal[_to] = _principal[_to].add(_amount);
_principal[_to] = _principal[_to].add(_amount);
_depositedAt[_to] = block.timestamp;
_depositedAt[_to] = block.timestamp;


SUSHI_MINI_CHEF.deposit(pid, _amount, address(this));
SUSHI_MINI_CHEF.deposit(pid, _amount, address(this));
emit Deposited(_to, _amount);
emit Deposited(_to, _amount);
}
}


function _withdrawTokenWithCorrection(uint amount) private updateSushiHarvested returns (uint) {
function _withdrawTokenWithCorrection(uint amount) private updateSushiHarvested returns (uint) {
uint before = _stakingToken.balanceOf(address(this));
uint before = _stakingToken.balanceOf(address(this));
SUSHI_MINI_CHEF.withdraw(pid, amount, address(this));
SUSHI_MINI_CHEF.withdraw(pid, amount, address(this));
return _stakingToken.balanceOf(address(this)).sub(before);
return _stakingToken.balanceOf(address(this)).sub(before);
}
}


function _cleanupIfDustShares() private {
function _cleanupIfDustShares() private {
uint shares = _shares[msg.sender];
uint shares = _shares[msg.sender];
if (shares > 0 && shares < DUST) {
if (shares > 0 && shares < DUST) {
totalShares = totalShares.sub(shares);
totalShares = totalShares.sub(shares);
delete _shares[msg.sender];
delete _shares[msg.sender];
}
}
}
}


/* ========== SALVAGE PURPOSE ONLY ========== */
/* ========== SALVAGE PURPOSE ONLY ========== */


// @dev stakingToken must not remain balance in this contract. So dev should salvage staking token transferred by mistake.
// @dev stakingToken must not remain balance in this contract. So dev should salvage staking token transferred by mistake.
function recoverToken(address token, uint amount) external override onlyOwner {
function recoverToken(address token, uint amount) external override onlyOwner {
if (token == address(SUSHI)) {
if (token == address(SUSHI)) {
uint sushiBalance = SUSHI.balanceOf(address(this));
uint sushiBalance = SUSHI.balanceOf(address(this));
require(amount <= sushiBalance.sub(sushiHarvested), "VaultFlipToFlip: cannot recover lp's harvested sushi");
require(amount <= sushiBalance.sub(sushiHarvested), "VaultFlipToFlip: cannot recover lp's harvested sushi");
}
}
if (token == address(WMATIC)){
if (token == address(WMATIC)){
uint wmaticBalance = WMATIC.balanceOf(address(this));
uint wmaticBalance = WMATIC.balanceOf(address(this));
require(amount <= wmaticBalance.sub(wmaticHarvested));
require(amount <= wmaticBalance.sub(wmaticHarvested));
}
}


IBEP20(token).safeTransfer(owner(), amount);
IBEP20(token).safeTransfer(owner(), amount);
emit Recovered(token, amount);
emit Recovered(token, amount);
}
}

function setTotalBalance() external onlyOwner {
require(totalBalance == 0, "VaultSushiFlipToFlip: can't update totalBalance");
(uint amount,) = SUSHI_MINI_CHEF.userInfo(pid, address(this));
totalBalance = amount;
}

}
}