contract SushiSLP is Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    ReceiptToken public receiptToken;
    address public slp;
    address public token;
    address public weth;
    address public sushi;
    address payable public treasuryAddress;

    IUniswapRouter public sushiswapRouter;
    IUniswapFactory public sushiswapFactory;
    IMasterChef public masterChef;

    /// @notice Info of each user.
    struct UserInfo {
        uint256 amount; // How many SLP tokens the user has provided.
        uint256 sushiRewardDebt; // Reward debt for Sushi rewards. See explanation below.
        uint256 userTreasuryEth; //how much eth the user sent to treasury
        uint256 userCollectedFees; //how much eth the user sent to fee address
        uint256 userAccumulatedSushi; //how many rewards this user has
        uint256 amountReceiptToken; //receipt tokens printed for user; should be equal to amountfDai
        bool wasUserBlacklisted; //if user was blacklist at deposit time, he is not receiving receipt tokens
        uint256 timestamp; //first deposit timestamp; used for withdrawal lock time check
        uint256 earnedRewards; //before fees
        //
        //   pending reward = (user.amount * pool.accRewardsPerShare) - user.rewardDebt
        //
        // Whenever a user deposits or withdraws tokens to a pool. Here's what happens:
        //   1. The pool's `accRewardsPerShare` (and `lastRewardBlock`) gets updated.
        //   2. User receives the pending reward sent to his/her address.
        //   3. User's `amount` gets updated.
        //   4. User's `rewardDebt` gets updated.
    }

    mapping(address => UserInfo) public userInfo;
    mapping(address => bool) public blacklisted; //blacklisted users do not receive a receipt token

    uint256 public masterChefPoolId = 0;

    uint256 public ethDust;
    uint256 public tokenDust;
    uint256 public treasueryEthDust;
    uint256 public treasuryTokenDust;

    uint256 public cap = uint256(1000); //SLP cap
    uint256 public totalSLP; //total invested eth
    uint256 public ethPrice; //for UI; to be updated from a script
    uint256 public lockTime = 10368000; //120 days

    address payable public feeAddress;
    uint256 public fee = uint256(50);
    uint256 constant feeFactor = uint256(10000);

    //events
    event RewardsExchanged(
        address indexed user,
        uint256 rewardsAmount,
        uint256 obtainedEth
    );
    event RewardsEarned(address indexed user, uint256 amount);
    event FeeSet(address indexed sender, uint256 feeAmount);
    event FeeAddressSet(address indexed sender, address indexed feeAddress);

    /// @notice Event emitted when blacklist status for an address changes
    event BlacklistChanged(
        string actionType,
        address indexed user,
        bool oldVal,
        bool newVal
    );
    /// @notice Event emitted when user makes a deposit and receipt token is minted
    event ReceiptMinted(address indexed user, uint256 amount);
    /// @notice Event emitted when user withdraws and receipt token is burned
    event ReceiptBurned(address indexed user, uint256 amount);

    /// @notice Event emitted when owner changes the master chef pool id
    event PoolIdChanged(address indexed sender, uint256 oldPid, uint256 newPid);

    /// @notice Event emitted when user makes a deposit
    event Deposit(
        address indexed user,
        address indexed origin,
        uint256 pid,
        uint256 amount
    );

    /// @notice Event emitted when user withdraws
    event Withdraw(
        address indexed user,
        address indexed origin,
        uint256 pid,
        uint256 amount,
        uint256 auctionedAmount
    );

    /// @notice Event emitted when owner changes any contract address
    event ChangedAddress(
        string indexed addressType,
        address indexed oldAddress,
        address indexed newAddress
    );

    /// @notice Event emitted when owner makes a rescue dust request
    event RescuedDust(string indexed dustType, uint256 amount);

    //internal
    mapping(address => bool) public approved; //to defend against non whitelisted contracts

    /// @notice Used internally for avoiding "stack-too-deep" error when depositing
    struct DepositData {
        uint256 liquidity;
        uint256 pendingSushiTokens;
    }

    /// @notice Used internally for avoiding "stack-too-deep" error when withdrawing
    struct WithdrawData {
        uint256 prevSlpAmount;
        uint256 slpAmount;
        uint256 totalSushi;
        uint256 feeableEth;
        uint256 auctionedEth;
        uint256 prevDustEthBalance;
        uint256 prevDustTokenBalance;
        uint256 feeTokenAmount;
        uint256 feeEthAmount;
        uint256 feeableEthForToken;
        uint256[] feeSwapAmounts;
        uint256 feeObtainedTokens;
        uint256 liquidityTokenAmount;
        uint256 liquidityEthAmount;
        uint256 liquidity;
    }

    /**
     * @notice Create a new SushiSLP contract
     * @param _token Token address
     * @param _weth WETH address
     * @param _sushi SUSHI address
     * @param _sushiswapRouter Sushiswap Router address
     * @param _sushiswapFactory Sushiswap Factory address
     * @param _masterChef SushiSwap MasterChef address
     * @param _treasuryAddress treasury address
     * @param _receiptToken Receipt token that is minted and burned
     * @param _feeAddress fee address
     */
    constructor(
        address _slp,
        address _token,
        address _weth,
        address _sushi,
        address _sushiswapRouter,
        address _sushiswapFactory,
        address _masterChef,
        address payable _treasuryAddress,
        uint256 _poolId,
        address _receiptToken,
        address payable _feeAddress
    ) public {
        require(_slp != address(0), "slp_0x0");
        require(_token != address(0), "token_0x0");
        require(_weth != address(0), "WETH_0x0");
        require(_sushi != address(0), "SUSHI_0x0");
        require(_sushiswapRouter != address(0), "ROUTER_0x0");
        require(_sushiswapFactory != address(0), "FACTORY_0x0");
        require(_masterChef != address(0), "CHEF_0x0");
        require(_treasuryAddress != address(0), "TREASURY_0x0");
        require(_receiptToken != address(0), "RECEIPT_0x0");
        require(_feeAddress != address(0), "FEE_0x0");

        slp = _slp;
        token = _token;
        weth = _weth;
        sushi = _sushi;
        sushiswapRouter = IUniswapRouter(_sushiswapRouter);
        sushiswapFactory = IUniswapFactory(_sushiswapFactory);
        masterChef = IMasterChef(_masterChef);
        treasuryAddress = _treasuryAddress;
        masterChefPoolId = _poolId;
        receiptToken = ReceiptToken(_receiptToken);
        feeAddress = _feeAddress;
    }

    //-----------------------------------------------------------------------------------------------------------------//
    //------------------------------------ Setters -------------------------------------------------//
    //-----------------------------------------------------------------------------------------------------------------//

    /**
     * @notice Update the address of TOKEN
     * @dev Can only be called by the owner
     * @param _token Address of TOKEN
     */
    function setTokenAddress(address _token) public onlyOwner {
        require(_token != address(0), "0x0");
        emit ChangedAddress("TOKEN", address(token), address(_token));
        token = _token;
    }

    /**
     * @notice Update the address of WETH
     * @dev Can only be called by the owner
     * @param _weth Address of WETH
     */
    function setWethAddress(address _weth) public onlyOwner {
        require(_weth != address(0), "0x0");
        emit ChangedAddress("WETH", address(weth), address(_weth));
        weth = _weth;
    }

    /**
     * @notice Update the address of Sushi
     * @dev Can only be called by the owner
     * @param _sushi Address of Sushi
     */
    function setSushiAddress(address _sushi) public onlyOwner {
        require(_sushi != address(0), "0x0");
        emit ChangedAddress("SUSHI", address(sushi), address(_sushi));
        sushi = _sushi;
    }

    /**
     * @notice Update the address of Sushiswap Router
     * @dev Can only be called by the owner
     * @param _sushiswapRouter Address of Sushiswap Router
     */
    function setSushiswapRouter(address _sushiswapRouter) public onlyOwner {
        require(_sushiswapRouter != address(0), "0x0");
        emit ChangedAddress(
            "SUSHISWAP_ROUTER",
            address(sushiswapRouter),
            address(_sushiswapRouter)
        );
        sushiswapRouter = IUniswapRouter(_sushiswapRouter);
    }

    /**
     * @notice Update the address of Sushiswap Factory
     * @dev Can only be called by the owner
     * @param _sushiswapFactory Address of Sushiswap Factory
     */
    function setSushiswapFactory(address _sushiswapFactory) public onlyOwner {
        require(_sushiswapFactory != address(0), "0x0");
        emit ChangedAddress(
            "SUSHISWAP_FACTORY",
            address(sushiswapFactory),
            address(_sushiswapFactory)
        );
        sushiswapFactory = IUniswapFactory(_sushiswapFactory);
    }

    /**
     * @notice Update the address of Sushiswap Masterchef
     * @dev Can only be called by the owner
     * @param _masterChef Address of Sushiswap Masterchef
     */
    function setMasterChef(address _masterChef) public onlyOwner {
        require(_masterChef != address(0), "0x0");
        emit ChangedAddress(
            "MASTER_CHEF",
            address(masterChef),
            address(_masterChef)
        );
        masterChef = IMasterChef(_masterChef);
    }

    /**
     * @notice Update the address for fees
     * @dev Can only be called by the owner
     * @param _feeAddress Fee's address
     */
    function setTreasury(address payable _feeAddress) public onlyOwner {
        require(_feeAddress != address(0), "0x0");
        emit ChangedAddress(
            "TREASURY",
            address(treasuryAddress),
            address(_feeAddress)
        );
        treasuryAddress = _feeAddress;
    }

    /**
     * @notice Approve contract (only approved contracts or msg.sender==tx.origin can call this strategy)
     * @dev Can only be called by the owner
     * @param account Contract's address
     */
    function approveContractAccess(address account) external onlyOwner {
        require(account != address(0), "0x0");
        approved[account] = true;
    }

    /**
     * @notice Revoke contract's access (only approved contracts or msg.sender==tx.origin can call this strategy)
     * @dev Can only be called by the owner
     * @param account Contract's address
     */
    function revokeContractAccess(address account) external onlyOwner {
        require(account != address(0), "0x0");
        approved[account] = false;
    }

    /**
     * @notice Blacklist address; blacklisted addresses do not receive receipt tokens
     * @dev Can only be called by the owner
     * @param account User/contract address
     */
    function blacklistAddress(address account) external onlyOwner {
        require(account != address(0), "0x0");
        emit BlacklistChanged("BLACKLIST", account, blacklisted[account], true);
        blacklisted[account] = true;
    }

    /**
     * @notice Remove address from blacklisted addresses; blacklisted addresses do not receive receipt tokens
     * @dev Can only be called by the owner
     * @param account User/contract address
     */
    function removeFromBlacklist(address account) external onlyOwner {
        require(account != address(0), "0x0");
        emit BlacklistChanged("REMOVE", account, blacklisted[account], false);
        blacklisted[account] = false;
    }

    /**
     * @notice Set max ETH cap for this strategy
     * @dev Can only be called by the owner
     * @param _cap ETH amount
     */
    function setCap(uint256 _cap) external onlyOwner {
        cap = _cap;
    }

    /**
     * @notice Set ETH price
     * @dev Can only be called by the owner
     * @param _price ETH price
     */
    function setEthPrice(uint256 _price) external onlyOwner {
        require(_price > 0, "PRICE_0");
        ethPrice = _price;
    }

    /**
     * @notice Set lock time
     * @dev Can only be called by the owner
     * @param _lockTime lock time in seconds
     */
    function setLockTime(uint256 _lockTime) external onlyOwner {
        require(_lockTime > 0, "TIME_0");
        lockTime = _lockTime;
    }

    /**
     * @notice Update the pool id
     * @dev Can only be called by the owner
     * @param _pid pool id
     */
    function setPoolId(uint256 _pid) public onlyOwner {
        uint256 old = masterChefPoolId;
        masterChefPoolId = _pid;
        emit PoolIdChanged(msg.sender, old, _pid);
    }

    function setFeeAddress(address payable _feeAddress) public onlyOwner {
        feeAddress = _feeAddress;
        emit FeeAddressSet(msg.sender, _feeAddress);
    }

    function setFee(uint256 _fee) public onlyOwner {
        require(_fee <= uint256(9000), "FEE_TOO_HIGH");
        fee = _fee;
        emit FeeSet(msg.sender, _fee);
    }

    /**
     * @notice Rescue dust resulted from swaps/liquidity
     * @dev Can only be called by the owner
     */
    function rescueDust() public onlyOwner {
        if (ethDust > 0) {
            treasuryAddress.transfer(ethDust);
            treasueryEthDust = treasueryEthDust.add(ethDust);
            emit RescuedDust("ETH", ethDust);
            ethDust = 0;
        }
        if (tokenDust > 0) {
            IERC20(token).safeTransfer(treasuryAddress, tokenDust);
            treasuryTokenDust = treasuryTokenDust.add(tokenDust);
            emit RescuedDust("TOKEN", tokenDust);
            tokenDust = 0;
        }
    }

    /**
     * @notice Rescue any non-reward token that was airdropped to this contract
     * @dev Can only be called by the owner
     */
    function rescueAirdroppedTokens(address _token, address to)
        public
        onlyOwner
    {
        require(_token != address(0), "token_0x0");
        require(to != address(0), "to_0x0");
        require(_token != sushi, "rescue_reward_error");

        uint256 balanceOfToken = IERC20(_token).balanceOf(address(this));
        require(balanceOfToken > 0, "balance_0");

        require(IERC20(_token).transfer(to, balanceOfToken), "rescue_failed");
    }

    /**
     * @notice Check if user can withdraw based on current lock time
     * @param user Address of the user
     * @return true or false
     */
    function isWithdrawalAvailable(address user) public view returns (bool) {
        if (lockTime > 0) {
            return userInfo[user].timestamp.add(lockTime) <= block.timestamp;
        }
        return true;
    }

    /**
     * @notice Deposit to this strategy for rewards
     * @param deadline Number of blocks until transaction expires
     * @return Amount of LPs
     */
    function deposit(uint256 slpAmount, uint256 deadline)
        public
        nonReentrant
        returns (uint256)
    {
        // -----
        // validate
        // -----
        _defend();
        require(slpAmount > 0, "SLP_0");
        require(deadline >= block.timestamp, "DEADLINE_ERROR");
        require(totalSLP.add(slpAmount) <= cap, "CAP_REACHED");

        uint256 prevTokenBalance = IERC20(token).balanceOf(address(this));

        DepositData memory results;
        UserInfo storage user = userInfo[msg.sender];

        if (user.amount == 0) {
            user.wasUserBlacklisted = blacklisted[msg.sender];
        }
        if (user.timestamp == 0) {
            user.timestamp = block.timestamp;
        }
        totalSLP = totalSLP.add(slpAmount);

        results.liquidity = slpAmount;
        IERC20(slp).safeTransferFrom(
            msg.sender,
            address(this),
            results.liquidity
        );
        IERC20(slp).safeIncreaseAllowance(
            address(masterChef),
            results.liquidity
        );

        // -----
        // deposit into master chef
        // -----
        masterChef.updatePool(masterChefPoolId);
        results.pendingSushiTokens = user
            .amount
            .mul(masterChef.poolInfo(masterChefPoolId).accSushiPerShare)
            .div(1e12)
            .sub(user.sushiRewardDebt);

        user.amount = user.amount.add(results.liquidity);
        if (!user.wasUserBlacklisted) {
            user.amountReceiptToken = user.amountReceiptToken.add(
                results.liquidity
            );
            receiptToken.mint(msg.sender, results.liquidity);
            emit ReceiptMinted(msg.sender, results.liquidity);
        }

        masterChef.updatePool(masterChefPoolId);
        user.sushiRewardDebt = user
            .amount
            .mul(masterChef.poolInfo(masterChefPoolId).accSushiPerShare)
            .div(1e12);

        uint256 prevSushiBalance = IERC20(sushi).balanceOf(address(this));
        masterChef.deposit(masterChefPoolId, results.liquidity);

        if (results.pendingSushiTokens > 0) {
            uint256 sushiBalance = IERC20(sushi).balanceOf(address(this));
            uint256 actualSushiTokens = sushiBalance.sub(prevSushiBalance);

            if (results.pendingSushiTokens > actualSushiTokens) {
                user.userAccumulatedSushi = user.userAccumulatedSushi.add(
                    actualSushiTokens
                );
            } else {
                user.userAccumulatedSushi = user.userAccumulatedSushi.add(
                    results.pendingSushiTokens
                );
            }
        }

        emit Deposit(
            msg.sender,
            tx.origin,
            masterChefPoolId,
            results.liquidity
        );

        tokenDust = tokenDust.add(
            (IERC20(token).balanceOf(address(this))).sub(prevTokenBalance)
        );

        return results.liquidity;
    }

    /**
     * @notice Withdraw tokens and claim rewards
     * @param deadline Number of blocks until transaction expires
     * @return Amount of ETH obtained
     */
    function withdraw(uint256 amount, uint256 deadline)
        public
        nonReentrant
        returns (uint256)
    {
        // -----
        // validation
        // -----
        uint256 receiptBalance = receiptToken.balanceOf(msg.sender);

        _defend();
        require(deadline >= block.timestamp, "DEADLINE_ERROR");
        require(amount > 0, "AMOUNT_0");
        UserInfo storage user = userInfo[msg.sender];
        require(user.amount >= amount, "AMOUNT_GREATER_THAN_BALANCE");
        if (!user.wasUserBlacklisted) {
            require(receiptBalance >= user.amount, "RECEIPT_AMOUNT");
        }

        if (lockTime > 0) {
            require(
                user.timestamp.add(lockTime) <= block.timestamp,
                "LOCK_TIME"
            );
        }

        WithdrawData memory results;

        results.prevDustEthBalance = address(this).balance;
        results.prevDustTokenBalance = IERC20(token).balanceOf(address(this));

        // -----
        // withdraw from sushi master chef
        // -----
        masterChef.updatePool(masterChefPoolId);
        uint256 pendingSushiTokens =
            user
                .amount
                .mul(masterChef.poolInfo(masterChefPoolId).accSushiPerShare)
                .div(1e12)
                .sub(user.sushiRewardDebt);

        (results.slpAmount) = _witdraw(amount);
        require(results.slpAmount > 0, "SLP_AMOUNT_0");

        user.amount = user.amount.sub(amount);
        if (!user.wasUserBlacklisted) {
            user.amountReceiptToken = user.amountReceiptToken.sub(amount);
            receiptToken.burn(msg.sender, amount);
            emit ReceiptBurned(msg.sender, amount);
        }

        user.sushiRewardDebt = user
            .amount
            .mul(masterChef.poolInfo(masterChefPoolId).accSushiPerShare)
            .div(1e12);

        uint256 sushiBalance = IERC20(sushi).balanceOf(address(this));
        if (pendingSushiTokens > 0) {
            if (pendingSushiTokens > sushiBalance) {
                user.userAccumulatedSushi = user.userAccumulatedSushi.add(
                    sushiBalance
                );
            } else {
                user.userAccumulatedSushi = user.userAccumulatedSushi.add(
                    pendingSushiTokens
                );
            }
        }

        results.totalSushi = user.userAccumulatedSushi;

        address[] memory swapPath = new address[](2);
        swapPath[0] = sushi;
        swapPath[1] = weth;

        if (results.totalSushi > 0) {
            emit RewardsEarned(msg.sender, results.totalSushi);
            user.earnedRewards = user.earnedRewards.add(results.totalSushi);

            IERC20(sushi).safeIncreaseAllowance(
                address(sushiswapRouter),
                results.totalSushi
            );

            uint256[] memory sushiSwapAmounts =
                sushiswapRouter.swapExactTokensForETH(
                    results.totalSushi,
                    uint256(0),
                    swapPath,
                    address(this),
                    deadline
                );

            emit RewardsExchanged(
                msg.sender,
                results.totalSushi,
                sushiSwapAmounts[sushiSwapAmounts.length - 1]
            );
            results.feeableEth = results.feeableEth.add(
                sushiSwapAmounts[sushiSwapAmounts.length - 1]
            );
        }

        // -----
        // transfer ETH to user
        // -----
        results.auctionedEth = results.feeableEth.div(2);
        results.feeableEth = results.feeableEth.sub(results.auctionedEth); //needs to be converted to SLP

        treasuryAddress.transfer(results.auctionedEth);

        if (user.amount > results.slpAmount) {
            user.amount = user.amount.sub(results.slpAmount);
        } else {
            user.amount = 0;
        }

        if (results.slpAmount < totalSLP) {
            totalSLP = totalSLP.sub(results.slpAmount);
        } else {
            totalSLP = 0;
        }

        //at some point we might not have any fees
        if (fee > 0) {
            uint256 feeSLP = _calculateFee(results.slpAmount);
            results.slpAmount = results.slpAmount.sub(feeSLP);

            IERC20(slp).safeIncreaseAllowance(
                address(sushiswapRouter),
                feeSLP.mul(3)
            );
            (results.feeTokenAmount, results.feeEthAmount) = sushiswapRouter
                .removeLiquidityETH(
                token,
                feeSLP,
                uint256(0),
                uint256(0),
                address(this),
                deadline
            );

            swapPath[0] = token;
            swapPath[1] = weth;

            IERC20(token).safeIncreaseAllowance(
                address(sushiswapRouter),
                results.feeTokenAmount
            );

            uint256[] memory feeTokenSwapAmounts =
                sushiswapRouter.swapExactTokensForETH(
                    results.feeTokenAmount,
                    uint256(0),
                    swapPath,
                    address(this),
                    deadline
                );

            results.feeEthAmount = results.feeEthAmount.add(
                feeTokenSwapAmounts[feeTokenSwapAmounts.length - 1]
            );

            feeAddress.transfer(results.feeEthAmount);
            user.userCollectedFees = user.userCollectedFees.add(
                results.feeEthAmount
            );
        }

        results.feeableEthForToken = results.feeableEth.div(2);
        results.feeableEth = results.feeableEth.sub(results.feeableEthForToken);

        swapPath[0] = weth;
        swapPath[1] = token;

        results.feeSwapAmounts = sushiswapRouter.swapExactETHForTokens{
            value: results.feeableEthForToken
        }(uint256(0), swapPath, address(this), deadline);

        results.feeObtainedTokens = results.feeSwapAmounts[
            results.feeSwapAmounts.length - 1
        ];

        IERC20(token).safeIncreaseAllowance(
            address(sushiswapRouter),
            results.feeObtainedTokens
        );
        (
            results.liquidityTokenAmount,
            results.liquidityEthAmount,
            results.liquidity
        ) = sushiswapRouter.addLiquidityETH{value: results.feeableEth}(
            token,
            results.feeObtainedTokens,
            uint256(0),
            uint256(0),
            address(this),
            deadline
        );
        results.slpAmount = results.slpAmount.add(results.liquidity);
        IERC20(slp).safeTransfer(msg.sender, results.slpAmount);

        user.userTreasuryEth = user.userTreasuryEth.add(results.auctionedEth);

        emit Withdraw(
            msg.sender,
            tx.origin,
            masterChefPoolId,
            results.slpAmount,
            results.auctionedEth
        );
        user.userAccumulatedSushi = 0;

        ethDust = ethDust.add(
            address(this).balance.sub(results.prevDustEthBalance)
        );
        tokenDust = tokenDust.add(
            (IERC20(token).balanceOf(address(this))).sub(
                results.prevDustTokenBalance
            )
        );

        return results.slpAmount;
    }

    function _witdraw(uint256 amount) private returns (uint256) {
        WithdrawData memory results;

        results.prevSlpAmount = IERC20(slp).balanceOf(address(this));

        masterChef.updatePool(masterChefPoolId);
        masterChef.withdraw(masterChefPoolId, amount);

        results.slpAmount = (IERC20(slp).balanceOf(address(this))).sub(
            results.prevSlpAmount
        );

        return (results.slpAmount);
    }

    //-----------------------------------------------------------------------------------------------------------------//
    //------------------------------------ Getters -------------------------------------------------//
    //-----------------------------------------------------------------------------------------------------------------//
    /**
     * @notice View function to see pending rewards for account.
     * @param account user account to check
     * @return pending rewards
     */
    function getPendingRewards(address account) public view returns (uint256) {
        UserInfo storage user = userInfo[account];
        IMasterChef.PoolInfo memory sushiPool =
            masterChef.poolInfo(masterChefPoolId);
        uint256 sushiPerBlock = masterChef.sushiPerBlock();
        uint256 totalSushiAllocPoint = masterChef.totalAllocPoint();
        uint256 accSushiPerShare = sushiPool.accSushiPerShare;
        uint256 lpSupply = sushiPool.lpToken.balanceOf(address(masterChef));

        if (block.number > sushiPool.lastRewardBlock && lpSupply != 0) {
            uint256 multiplier =
                masterChef.getMultiplier(
                    sushiPool.lastRewardBlock,
                    block.number
                );
            uint256 sushiReward =
                multiplier.mul(sushiPerBlock).mul(sushiPool.allocPoint).div(
                    totalSushiAllocPoint
                );
            accSushiPerShare = accSushiPerShare.add(
                sushiReward.mul(1e12).div(lpSupply)
            );
        }

        uint256 accumulatedSushi = user.amount.mul(accSushiPerShare).div(1e12);

        if (accumulatedSushi < user.sushiRewardDebt) {
            return 0;
        }

        return accumulatedSushi.sub(user.sushiRewardDebt);
    }

    function _calculateFee(uint256 amount) private view returns (uint256) {
        return (amount.mul(fee)).div(feeFactor);
    }

    /// @notice Private method to check if contract is approved or this strategy is called by a normal user
    function _defend() private view returns (bool) {
        require(
            approved[msg.sender] || msg.sender == tx.origin,
            "access_denied"
        );
    }

    receive() external payable {}
}
/*
 |Strategy Flow| 
 - User shows up with an ETH/USDT-SLP, ETH/WBTC-SLP or ETH/yUSD-SLP
 - Then we deposit SLPs in MasterChef and we get SUSHI rewards
 
 - Withdrawal flow does same thing, but backwards. 
 */
function withdraw(uint256 amount, uint256 deadline)
    public
    nonReentrant
    returns (uint256)
{
    // -----
    // validation
    // -----
    uint256 receiptBalance = receiptToken.balanceOf(msg.sender);

    _defend();
    require(deadline >= block.timestamp, "DEADLINE_ERROR");
    require(amount > 0, "AMOUNT_0");
    UserInfo storage user = userInfo[msg.sender];
    require(user.amount >= amount, "AMOUNT_GREATER_THAN_BALANCE");
    if (!user.wasUserBlacklisted) {
        require(receiptBalance >= user.amount, "RECEIPT_AMOUNT");
    }

    if (lockTime > 0) {
        require(
            user.timestamp.add(lockTime) <= block.timestamp,
            "LOCK_TIME"
        );
    }

    WithdrawData memory results;

    results.prevDustEthBalance = address(this).balance;
    results.prevDustTokenBalance = IERC20(token).balanceOf(address(this));

    // -----
    // withdraw from sushi master chef
    // -----
    masterChef.updatePool(masterChefPoolId);
    uint256 pendingSushiTokens =
        user
            .amount
            .mul(masterChef.poolInfo(masterChefPoolId).accSushiPerShare)
            .div(1e12)
            .sub(user.sushiRewardDebt);

    (results.slpAmount) = _witdraw(amount);
    require(results.slpAmount > 0, "SLP_AMOUNT_0");

    user.amount = user.amount.sub(amount);
    if (!user.wasUserBlacklisted) {
        user.amountReceiptToken = user.amountReceiptToken.sub(amount);
        receiptToken.burn(msg.sender, amount);
        emit ReceiptBurned(msg.sender, amount);
    }

    user.sushiRewardDebt = user
        .amount
        .mul(masterChef.poolInfo(masterChefPoolId).accSushiPerShare)
        .div(1e12);

    uint256 sushiBalance = IERC20(sushi).balanceOf(address(this));
    if (pendingSushiTokens > 0) {
        if (pendingSushiTokens > sushiBalance) {
            user.userAccumulatedSushi = user.userAccumulatedSushi.add(
                sushiBalance
            );
        } else {
            user.userAccumulatedSushi = user.userAccumulatedSushi.add(
                pendingSushiTokens
            );
        }
    }

    results.totalSushi = user.userAccumulatedSushi;

    address[] memory swapPath = new address[](2);
    swapPath[0] = sushi;
    swapPath[1] = weth;

    if (results.totalSushi > 0) {
        emit RewardsEarned(msg.sender, results.totalSushi);
        user.earnedRewards = user.earnedRewards.add(results.totalSushi);

        IERC20(sushi).safeIncreaseAllowance(
            address(sushiswapRouter),
            results.totalSushi
        );

        uint256[] memory sushiSwapAmounts =
            sushiswapRouter.swapExactTokensForETH(
                results.totalSushi,
                uint256(0),
                swapPath,
                address(this),
                deadline
            );

        emit RewardsExchanged(
            msg.sender,
            results.totalSushi,
            sushiSwapAmounts[sushiSwapAmounts.length - 1]
        );
        results.feeableEth = results.feeableEth.add(
            sushiSwapAmounts[sushiSwapAmounts.length - 1]
        );
    }

    // -----
    // transfer ETH to user
    // -----
    results.auctionedEth = results.feeableEth.div(2);
    results.feeableEth = results.feeableEth.sub(results.auctionedEth); //needs to be converted to SLP

    treasuryAddress.transfer(results.auctionedEth);

    if (user.amount > results.slpAmount) {
        user.amount = user.amount.sub(results.slpAmount);
    } else {
        user.amount = 0;
    }

    if (results.slpAmount < totalSLP) {
        totalSLP = totalSLP.sub(results.slpAmount);
    } else {
        totalSLP = 0;
    }

    //at some point we might not have any fees
    if (fee > 0) {
        uint256 feeSLP = _calculateFee(results.slpAmount);
        results.slpAmount = results.slpAmount.sub(feeSLP);

        IERC20(slp).safeIncreaseAllowance(
            address(sushiswapRouter),
            feeSLP.mul(3)
        );
        (results.feeTokenAmount, results.feeEthAmount) = sushiswapRouter
            .removeLiquidityETH(
            token,
            feeSLP,
            uint256(0),
            uint256(0),
            address(this),
            deadline
        );

        swapPath[0] = token;
        swapPath[1] = weth;

        IERC20(token).safeIncreaseAllowance(
            address(sushiswapRouter),
            results.feeTokenAmount
        );

        uint256[] memory feeTokenSwapAmounts =
            sushiswapRouter.swapExactTokensForETH(
                results.feeTokenAmount,
                uint256(0),
                swapPath,
                address(this),
                deadline
            );

        results.feeEthAmount = results.feeEthAmount.add(
            feeTokenSwapAmounts[feeTokenSwapAmounts.length - 1]
        );

        feeAddress.transfer(results.feeEthAmount);
        user.userCollectedFees = user.userCollectedFees.add(
            results.feeEthAmount
        );
    }

    results.feeableEthForToken = results.feeableEth.div(2);
    results.feeableEth = results.feeableEth.sub(results.feeableEthForToken);

    swapPath[0] = weth;
    swapPath[1] = token;

    results.feeSwapAmounts = sushiswapRouter.swapExactETHForTokens{
        value: results.feeableEthForToken
    }(uint256(0), swapPath, address(this), deadline);

    results.feeObtainedTokens = results.feeSwapAmounts[
        results.feeSwapAmounts.length - 1
    ];

    IERC20(token).safeIncreaseAllowance(
        address(sushiswapRouter),
        results.feeObtainedTokens
    );
    (
        results.liquidityTokenAmount,
        results.liquidityEthAmount,
        results.liquidity
    ) = sushiswapRouter.addLiquidityETH{value: results.feeableEth}(
        token,
        results.feeObtainedTokens,
        uint256(0),
        uint256(0),
        address(this),
        deadline
    );
    results.slpAmount = results.slpAmount.add(results.liquidity);
    IERC20(slp).safeTransfer(msg.sender, results.slpAmount);

    user.userTreasuryEth = user.userTreasuryEth.add(results.auctionedEth);

    emit Withdraw(
        msg.sender,
        tx.origin,
        masterChefPoolId,
        results.slpAmount,
        results.auctionedEth
    );
    user.userAccumulatedSushi = 0;

    ethDust = ethDust.add(
        address(this).balance.sub(results.prevDustEthBalance)
    );
    tokenDust = tokenDust.add(
        (IERC20(token).balanceOf(address(this))).sub(
            results.prevDustTokenBalance
        )
    );

    return results.slpAmount;
}

/**
 * @notice Withdraw tokens and claim rewards
 * @param deadline Number of blocks until transaction expires
 * @return Amount of ETH obtained
 */