Contract 0x9d1a92e601db0901e69bd810029f2c14bcca3128

 

Contract Overview

Balance:
0 MATIC

MATIC Value:
$0.00

Token:
 
Txn Hash Method
Block
From
To
Value [Txn Fee]
0xdf4beb3cc892f0e1bd61d78da0d1ae527fcf5c1ab8bcf9b562b2ce9f5d5a283cSwap338935212022-10-03 21:14:5349 mins ago0x69715b5f9fcf662fb6a0a31f1d31e29bcf05ee94 IN  0x9d1a92e601db0901e69bd810029f2c14bcca31280.179999999999999 MATIC0.014496716001 31.000000003
0x2b3d549e6ea96249a5ea7ed829128c770f774505203f7396c5653cc34efe6cc2Swap338787172022-10-03 12:43:159 hrs 21 mins ago0xb07e9b220e66594da29790bfd9a034b891e25ef3 IN  0x9d1a92e601db0901e69bd810029f2c14bcca31280.2 MATIC0.014028810006 30.000000014
0x102c7697ee32f36c12b024d762a2217eabeca13acde26d7e8449f77ad6bcac7aSwap338776442022-10-03 12:06:259 hrs 58 mins ago0x610c740ec6906589ad74be62c6bdb02a0ffdeaf5 IN  0x9d1a92e601db0901e69bd810029f2c14bcca312820 MATIC0.01382028001 30.000000022
0x4561397c9158bd93d55a8aa36912ae7990482f21d749adc710e1d8a3a3adf404Swap338635052022-10-03 3:51:4118 hrs 12 mins ago0x114f991316099d96937259d10d3605d711ed12fc IN  0x9d1a92e601db0901e69bd810029f2c14bcca31280 MATIC0.014681340007 30.000000016
0xe9e76dd5e402f6ec5aadf95a7b0ddc74fafce043bc2c80d49e56ad284c329567Swap338634442022-10-03 3:49:3518 hrs 15 mins ago0x114f991316099d96937259d10d3605d711ed12fc IN  0x9d1a92e601db0901e69bd810029f2c14bcca312852.242814396112635 MATIC0.014029680007 30.000000015
0xff9d72542abb8328251fb7a60a59a785abef5f8b68ebdb4bbcb883cd560fcc67Swap338368222022-10-02 12:27:561 day 9 hrs ago0xb9dccd57c41604f815b836e7b721759e5b626d64 IN  0x9d1a92e601db0901e69bd810029f2c14bcca31280 MATIC0.009506400006 30.000000021
0x78fff44357dc6ce9820dd2299d586366930501cd03512a3216943942640e504aSwap338354682022-10-02 11:41:241 day 10 hrs ago0x46bc5004a5e26226aef842babd31015d28267411 IN  0x9d1a92e601db0901e69bd810029f2c14bcca31280.07 MATIC0.01402812000730.000000016
0x681c7f9e670e59f4d83811fb73463c6e24a4b9fe8220cdfbff1e786c5a4cbb72Swap338178182022-10-02 1:24:011 day 20 hrs ago0x114f991316099d96937259d10d3605d711ed12fc IN  0x9d1a92e601db0901e69bd810029f2c14bcca312830 MATIC0.013820400005 30.000000012
0x89b979dc4e490ca67099564cac8e675caafe61108fd7d0b8bed46d9cfd955342Swap338097912022-10-01 20:47:582 days 1 hr ago0x5a218dd9fcb2986ff19f85239ef28dce604e0073 IN  0x9d1a92e601db0901e69bd810029f2c14bcca31280.9 MATIC0.014287762445 30.555000012
0x0d6bcd7ebf659037636b70547c730f13f0d179a3146dc2beafce1c51e7fc9c23Swap337996142022-10-01 14:53:322 days 7 hrs ago0x13ca39c566acf798c49e481b9960e35dd9860da9 IN  0x9d1a92e601db0901e69bd810029f2c14bcca31280 MATIC0.014472660054 30.000000112
0x159369791782261c804594ca804346282fa73b28afa133a64ed730a6c8636b87Swap337739522022-10-01 0:07:422 days 21 hrs ago0xcfa7db433f7af2fe5902d5f6246f6cb7b92d90e6 IN  0x9d1a92e601db0901e69bd810029f2c14bcca31282,500 MATIC0.014162606046 30.285404622
0xe3474202678cd54e43666e66f364ea1a9c25d3946695744b314b5a436a20693cSwap337654982022-09-30 19:10:313 days 2 hrs ago0x651817b6b7464b61c6c6ef87da2bc84ee0e64377 IN  0x9d1a92e601db0901e69bd810029f2c14bcca31280 MATIC0.015230831413 31.121373706
0x0ca59b0338271ce9d3d382c27670380573ce32a60e0dcfdaea503017b23f8e97Swap337612942022-09-30 16:43:133 days 5 hrs ago0xa29468905d60d4d375b7a7362187d750a9f327b4 IN  0x9d1a92e601db0901e69bd810029f2c14bcca31281 MATIC0.014031151788 30.005906141
0xf9443419b0bf4e20b486873b78dfd4aa866673931562867687d664b0146067d9Swap337450002022-09-30 7:14:133 days 14 hrs ago0x0c0f401f2c26530406a287f267cf88a6da10238f IN  0x9d1a92e601db0901e69bd810029f2c14bcca31280 MATIC0.009506820005 30.000000017
0x9dd2fe62bcacee5f8c42d482280b6da16dc7ac2011cbcabf3d0113c5a5dee67dSwap337249342022-09-29 19:34:404 days 2 hrs ago0xad88ad293c622e6a5d70737b9edf9516e7a1e539 IN  0x9d1a92e601db0901e69bd810029f2c14bcca3128195.301752659791304 MATIC0.014380421995 30.749999991
0x3620ff143bd8dcb85f9cb2a70636ac9dd0e2e03cd46be78842f6d513ff6d09fdSwap336940142022-09-29 1:34:344 days 20 hrs ago0x7e529acbcfe502e2868423c714e1f4600421ca37 IN  0x9d1a92e601db0901e69bd810029f2c14bcca31280 MATIC0.010098048055 32.000000175
0x425fde0d2b6eb3b0c71bc8a47f9b39c5f9ed66152d4a5d2c757f5a9d6faeb04cSwap336873392022-09-28 21:42:545 days 21 mins ago0x747276019e3340104c96397bf6537ad01f93d7df IN  0x9d1a92e601db0901e69bd810029f2c14bcca31280.295899999999999 MATIC0.014255052041 30.483222082
0xa1345754dd8f929a9ea1a010a41d081d0c6be69415b076c760da303d37f6a176Swap336799822022-09-28 17:29:365 days 4 hrs ago0x610c740ec6906589ad74be62c6bdb02a0ffdeaf5 IN  0x9d1a92e601db0901e69bd810029f2c14bcca31280 MATIC0.015438279673 31.990600002
0x7a173eb434e43e4cddc4d20dc25fc980f07a15cf3d8cc7ea9f060abfb45fac38Swap336799822022-09-28 17:29:365 days 4 hrs ago0x610c740ec6906589ad74be62c6bdb02a0ffdeaf5 IN  0x9d1a92e601db0901e69bd810029f2c14bcca31280 MATIC0.014681640005 30.000000012
0x6d3e5f4947b1e2d374f539c0a72ea9babc2bfc8e96fb53674dd9de31fe4c56f5Swap336796942022-09-28 17:19:445 days 4 hrs ago0x610c740ec6906589ad74be62c6bdb02a0ffdeaf5 IN  0x9d1a92e601db0901e69bd810029f2c14bcca312811 MATIC0.014028690005 30.000000012
0xd6fe51ea0e5ccceddd9ffb62866d0348f7975808b66429a340ca0d660eb9244dSwap336397332022-09-27 18:13:336 days 3 hrs ago0x4c5a723084e1b4fd05de43c117e0df1324413421 IN  0x9d1a92e601db0901e69bd810029f2c14bcca31280 MATIC0.014682030106 30.000000217
0x5f428b1bf8838b1764dccc88de188ea600221923098bf83c6f18fa87605208c9Swap336390162022-09-27 17:48:556 days 4 hrs ago0x3c55a07f52d886e51bde308742fdd139d6463cca IN  0x9d1a92e601db0901e69bd810029f2c14bcca31280 MATIC0.014762995752 30.16543847
0x2bdb35f010d3310abb52cf652487fcb53fa394bb2401b9c0872e7c0ab8e890b0Swap336304952022-09-27 12:41:496 days 9 hrs ago0x72e33617d6f56d1d0170802852f5e68c5e82e9ed IN  0x9d1a92e601db0901e69bd810029f2c14bcca31280 MATIC0.01471628814 30.070000144
0xe2927e827fb5847136d044eef9527dac14dc4bc8ead0a73b58cb8832120ac3d2Swap336166382022-09-27 4:12:066 days 17 hrs ago0x610c740ec6906589ad74be62c6bdb02a0ffdeaf5 IN  0x9d1a92e601db0901e69bd810029f2c14bcca31280 MATIC0.001239600001 30.000000026
0xf0eb0e4cfea9e601df7ee35e0d6b7c685915ef5c33042e9539667f8e533a9d6fSwap336166382022-09-27 4:12:066 days 17 hrs ago0x610c740ec6906589ad74be62c6bdb02a0ffdeaf5 IN  0x9d1a92e601db0901e69bd810029f2c14bcca31280 MATIC0.0012396 30.000000015
[ Download CSV Export 
Latest 25 internal transaction
Parent Txn Hash Block From To Value
0xdf4beb3cc892f0e1bd61d78da0d1ae527fcf5c1ab8bcf9b562b2ce9f5d5a283c338935212022-10-03 21:14:5349 mins ago 0x9d1a92e601db0901e69bd810029f2c14bcca3128 Polygon: WMATIC Token0.179999999999999993 MATIC
0x2b3d549e6ea96249a5ea7ed829128c770f774505203f7396c5653cc34efe6cc2338787172022-10-03 12:43:159 hrs 21 mins ago 0x9d1a92e601db0901e69bd810029f2c14bcca3128 Polygon: WMATIC Token0.200000000000000011 MATIC
0x102c7697ee32f36c12b024d762a2217eabeca13acde26d7e8449f77ad6bcac7a338776442022-10-03 12:06:259 hrs 58 mins ago 0x9d1a92e601db0901e69bd810029f2c14bcca3128 Polygon: WMATIC Token20 MATIC
0x4561397c9158bd93d55a8aa36912ae7990482f21d749adc710e1d8a3a3adf404338635052022-10-03 3:51:4118 hrs 12 mins ago 0x9d1a92e601db0901e69bd810029f2c14bcca31280x114f991316099d96937259d10d3605d711ed12fc1.702193624556313313 MATIC
0x4561397c9158bd93d55a8aa36912ae7990482f21d749adc710e1d8a3a3adf404338635052022-10-03 3:51:4118 hrs 12 mins ago Polygon: WMATIC Token 0x9d1a92e601db0901e69bd810029f2c14bcca31281.702193624556313313 MATIC
0xe9e76dd5e402f6ec5aadf95a7b0ddc74fafce043bc2c80d49e56ad284c329567338634442022-10-03 3:49:3518 hrs 15 mins ago 0x9d1a92e601db0901e69bd810029f2c14bcca3128 Polygon: WMATIC Token52.242814396112635222 MATIC
0xff9d72542abb8328251fb7a60a59a785abef5f8b68ebdb4bbcb883cd560fcc67338368222022-10-02 12:27:561 day 9 hrs ago 0x9d1a92e601db0901e69bd810029f2c14bcca31280xb9dccd57c41604f815b836e7b721759e5b626d6426.025086655291572659 MATIC
0xff9d72542abb8328251fb7a60a59a785abef5f8b68ebdb4bbcb883cd560fcc67338368222022-10-02 12:27:561 day 9 hrs ago Polygon: WMATIC Token 0x9d1a92e601db0901e69bd810029f2c14bcca312826.025086655291572659 MATIC
0x78fff44357dc6ce9820dd2299d586366930501cd03512a3216943942640e504a338354682022-10-02 11:41:241 day 10 hrs ago 0x9d1a92e601db0901e69bd810029f2c14bcca3128 Polygon: WMATIC Token0.070000000000000007 MATIC
0x681c7f9e670e59f4d83811fb73463c6e24a4b9fe8220cdfbff1e786c5a4cbb72338178182022-10-02 1:24:011 day 20 hrs ago 0x9d1a92e601db0901e69bd810029f2c14bcca3128 Polygon: WMATIC Token30 MATIC
0x89b979dc4e490ca67099564cac8e675caafe61108fd7d0b8bed46d9cfd955342338097912022-10-01 20:47:582 days 1 hr ago 0x9d1a92e601db0901e69bd810029f2c14bcca3128 Polygon: WMATIC Token0.900000000000000022 MATIC
0x0d6bcd7ebf659037636b70547c730f13f0d179a3146dc2beafce1c51e7fc9c23337996142022-10-01 14:53:322 days 7 hrs ago 0x9d1a92e601db0901e69bd810029f2c14bcca31280x13ca39c566acf798c49e481b9960e35dd9860da96.8829743484029912 MATIC
0x0d6bcd7ebf659037636b70547c730f13f0d179a3146dc2beafce1c51e7fc9c23337996142022-10-01 14:53:322 days 7 hrs ago Polygon: WMATIC Token 0x9d1a92e601db0901e69bd810029f2c14bcca31286.8829743484029912 MATIC
0xe4c409fdfdfeab6ac99ea0860dd1268ecb8f923c78efdce111192f7e446ba471337926022022-10-01 10:51:332 days 11 hrs ago 0x9d1a92e601db0901e69bd810029f2c14bcca31280x2a55c9992a57bfe8a5db7132b7f50fd66f3adc631.121803197845081454 MATIC
0xe4c409fdfdfeab6ac99ea0860dd1268ecb8f923c78efdce111192f7e446ba471337926022022-10-01 10:51:332 days 11 hrs ago Polygon: WMATIC Token 0x9d1a92e601db0901e69bd810029f2c14bcca31281.121803197845081454 MATIC
0xed82f898faa4b290f0a26b01220bd3ea54228aec02faf96f5d25f4c784ef9d3e337909042022-10-01 9:53:082 days 12 hrs ago 0x9d1a92e601db0901e69bd810029f2c14bcca31280xa1f40a36346e4ef4c712c1e07f7e68e90b6b79c12.562779883604011273 MATIC
0xed82f898faa4b290f0a26b01220bd3ea54228aec02faf96f5d25f4c784ef9d3e337909042022-10-01 9:53:082 days 12 hrs ago Polygon: WMATIC Token 0x9d1a92e601db0901e69bd810029f2c14bcca31282.562779883604011273 MATIC
0xe7f134a9cbd12dc548a306526b0ff8e2f3ea1f27e8b9372aa97dd7b59adbfe9e337829072022-10-01 5:15:392 days 16 hrs ago 0x9d1a92e601db0901e69bd810029f2c14bcca31280x6704f485044cb51f4571b0d9e6e2cd68f081106986.911424693183888369 MATIC
0xe7f134a9cbd12dc548a306526b0ff8e2f3ea1f27e8b9372aa97dd7b59adbfe9e337829072022-10-01 5:15:392 days 16 hrs ago Polygon: WMATIC Token 0x9d1a92e601db0901e69bd810029f2c14bcca312886.911424693183888369 MATIC
0x159369791782261c804594ca804346282fa73b28afa133a64ed730a6c8636b87337739522022-10-01 0:07:422 days 21 hrs ago 0x9d1a92e601db0901e69bd810029f2c14bcca3128 Polygon: WMATIC Token2,500 MATIC
0xb87cd668961509dd38a588826173c5effbd58825462a5c4c9cc898bcc6249a2c337719202022-09-30 22:55:372 days 23 hrs ago 0x9d1a92e601db0901e69bd810029f2c14bcca31280x04a6b171582bf89567aa91780db5ad2479fc383c1.14686934839307705 MATIC
0xb87cd668961509dd38a588826173c5effbd58825462a5c4c9cc898bcc6249a2c337719202022-09-30 22:55:372 days 23 hrs ago Polygon: WMATIC Token 0x9d1a92e601db0901e69bd810029f2c14bcca31281.14686934839307705 MATIC
0xe3474202678cd54e43666e66f364ea1a9c25d3946695744b314b5a436a20693c337654982022-09-30 19:10:313 days 2 hrs ago 0x9d1a92e601db0901e69bd810029f2c14bcca31280x651817b6b7464b61c6c6ef87da2bc84ee0e643772.230528369336424159 MATIC
0xe3474202678cd54e43666e66f364ea1a9c25d3946695744b314b5a436a20693c337654982022-09-30 19:10:313 days 2 hrs ago Polygon: WMATIC Token 0x9d1a92e601db0901e69bd810029f2c14bcca31282.230528369336424159 MATIC
0x0ca59b0338271ce9d3d382c27670380573ce32a60e0dcfdaea503017b23f8e97337612942022-09-30 16:43:133 days 5 hrs ago 0x9d1a92e601db0901e69bd810029f2c14bcca3128 Polygon: WMATIC Token1 MATIC
[ Download CSV Export 
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
WooRouterV2

Compiler Version
v0.6.12+commit.27d51765

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion

Contract Source Code (Solidity Standard Json-Input format)

File 1 of 106 : backup.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/utils/EnumerableSet.sol';
import '@openzeppelin/contracts/utils/Pausable.sol';
import '@openzeppelin/contracts/utils/ReentrancyGuard.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import './interfaces/ILendingVault.sol';
import './interfaces/IWooStakingVault.sol';
import './interfaces/IWETH.sol';
import './interfaces/IStrategy.sol';
import './interfaces/IWooAccessManager.sol';

/// @title WOOFi LendingVault.
contract BackupLendingVault is ERC20, Ownable, ReentrancyGuard, Pausable, ILendingVault {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;
    using EnumerableSet for EnumerableSet.AddressSet;

    // *************** //
    // *** STRUCTS *** //
    // *************** //

    struct UserInfo {
        // ADD in `requestWithdraw` and SET 0 in `cancelRequestWithdraw` && `weeklySettle`
        uint256 requestedShares;
        // ADD in `weeklySettle` and SET 0 in `withdraw`
        uint256 settledAssets;
        uint256 costSharePrice;
    }

    struct StrategyCandidate {
        address implementation;
        uint256 proposedTime;
    }

    // ******************************** //
    // *** CONSTANTS AND IMMUTABLES *** //
    // ******************************** //

    address public immutable weth;
    address public immutable override asset;

    uint256 private constant INTEREST_RATE_COEFF = 31536000; // 365 * 24 * 3600
    uint256 public constant MAX_PERCENTAGE = 10000; // 100%

    // ***************** //
    // *** VARIABLES *** //
    // ***************** //

    // 3rd party protocol auto-compounding strategy
    address public strategy;
    // Treasury for `instantWithdraw`
    address public treasury;
    address public wooAccessManager;

    // For `instantWithdraw`, update to 0 each week
    uint256 public leftInstantWithdrawAssets;
    // For `instantWithdraw`, update each week
    uint256 public maxInstantWithdrawAssets;

    uint256 public allowBorrowPercentage = 9000; // 90%
    uint256 public instantWithdrawFeePercentage = 30; // 0.3%
    uint256 public interestRatePercentage; // SET by market maker

    // User request the amount of `asset` to claim in the next epoch, no interest anymore
    uint256 public totalSettledAssets;
    // Market maker borrow `asset` and pay interest, SET 0 when `settle`
    uint256 public totalBorrowedAssets;
    // Market maker borrow interest, SET 0 when `settle`
    uint256 public totalInterestAssets;
    // Market maker borrow interest, SET 0 when `settle`
    uint256 public weeklyInterestAssets;
    // Market maker repay the `assets` and store in contract locally, waiting for user to withdraw
    uint256 public totalRepaySettledAssets;
    // Market maker debt after `settle`, will subtract when `repay`
    uint256 public totalDebtSettledAssets;

    // Record last `settleInterest` timestamp for calculating interest
    uint256 public lastSettleInterest;
    // Record last `weeklySettle` timestamp for safety consideration
    uint256 public lastWeeklySettle;

    uint256 public weeklySettleDiff = 6.5 days; // 561600

    uint256 public approvalDelay = 48 hours;

    bool public allowRequestWithdraw = true;

    StrategyCandidate public strategyCandidate;

    EnumerableSet.AddressSet private requestUsers;
    mapping(address => UserInfo) public userInfo;

    // ******************* //
    // *** CONSTRUCTOR *** //
    // ******************* //

    constructor(
        address _weth,
        address _asset
    )
        public
        ERC20(
            string(abi.encodePacked('WOOFi ', ERC20(_asset).name())),
            string(abi.encodePacked('w ', ERC20(_asset).symbol()))
        )
    {
        weth = _weth;
        asset = _asset;
        // solhint-disable-next-line not-rely-on-time
        lastSettleInterest = block.timestamp;
        // solhint-disable-next-line not-rely-on-time
        lastWeeklySettle = block.timestamp;
    }

    // ***************** //
    // *** MODIFIERS *** //
    // ***************** //

    modifier onlyAdmin() {
        require(
            owner() == msg.sender || IWooAccessManager(wooAccessManager).isVaultAdmin(msg.sender),
            'LendingVault: Not admin'
        );
        _;
    }

    // ************************ //
    // *** PUBLIC FUNCTIONS *** //
    // ************************ //

    /// @inheritdoc ILendingVault
    function totalAssets() public view override returns (uint256 totalManagedAssets) {
        uint256 localAssets_ = localAssets();
        uint256 strategyAssets = IStrategy(strategy).balanceOf();

        totalManagedAssets = localAssets_.add(strategyAssets).add(totalBorrowedAssets).add(totalInterestAssets).add(
            weeklyInterestAssets
        );
    }

    /// @inheritdoc ILendingVault
    function localAssets() public view override returns (uint256 assets) {
        assets = IERC20(asset).balanceOf(address(this)).sub(totalRepaySettledAssets);
    }

    /// @inheritdoc ILendingVault
    function getPricePerFullShare() public view override returns (uint256 sharePrice) {
        sharePrice = _convertToAssets(1e18, false);
    }

    /// @inheritdoc ILendingVault
    function convertToShares(uint256 assets) public view override returns (uint256 shares) {
        shares = _convertToShares(assets, false);
    }

    /// @inheritdoc ILendingVault
    function convertToAssets(uint256 shares) public view override returns (uint256 assets) {
        assets = _convertToAssets(shares, false);
    }

    /// @inheritdoc ILendingVault
    function maxDeposit(address) public view override returns (uint256 maxAssets) {
        maxAssets = paused() ? 0 : uint256(-1);
    }

    /// @inheritdoc ILendingVault
    function maxWithdraw(address user) public view override returns (uint256 maxAssets) {
        UserInfo memory userInfo_ = userInfo[user];
        maxAssets = userInfo_.settledAssets;
    }

    /// @inheritdoc ILendingVault
    function maxRequestWithdraw(address user) public view override returns (uint256 maxAssets) {
        maxAssets = _convertToAssets(balanceOf(user), false);
    }

    /// @inheritdoc ILendingVault
    function maxInstantWithdraw(address user) public view override returns (uint256 maxAssets) {
        uint256 assets = _convertToAssets(balanceOf(user), false);
        maxAssets = leftInstantWithdrawAssets > assets ? assets : leftInstantWithdrawAssets;
    }

    /// @inheritdoc ILendingVault
    function isStrategyActive() public view override returns (bool active) {
        active = strategy != address(0) && !IStrategy(strategy).paused();
    }

    // ************************** //
    // *** EXTERNAL FUNCTIONS *** //
    // ************************** //

    /// @inheritdoc ILendingVault
    function deposit(uint256 assets) external payable override nonReentrant whenNotPaused returns (uint256 shares) {
        require((shares = _convertToShares(assets, false)) != 0, 'LendingVault: Zero shares');

        uint256 assetsBefore = localAssets();
        if (asset == weth) {
            require(msg.value == assets, 'LendingVault: msg.value insufficient');
            IWETH(weth).deposit{value: msg.value}();
        } else {
            require(msg.value == 0, 'LendingVault: msg.value invalid');
            TransferHelper.safeTransferFrom(asset, msg.sender, address(this), assets);
        }
        uint256 assetsAfter = localAssets();
        require(assetsAfter.sub(assetsBefore) >= assets, 'LendingVault: assets not enough');

        _updateCostSharePrice(msg.sender, assets, shares);
        _mint(msg.sender, shares);
        _farmAtStrategy();

        emit Deposit(msg.sender, assets, shares);
    }

    /// @inheritdoc ILendingVault
    function withdraw() external override nonReentrant {
        UserInfo storage userInfo_ = userInfo[msg.sender];
        uint256 assets = userInfo_.settledAssets;
        require(totalSettledAssets >= assets, 'LendingVault: Not settle, please wait');
        require(totalRepaySettledAssets >= assets, 'LendingVault: Not repay, please wait');
        userInfo_.settledAssets = 0;
        totalSettledAssets = totalSettledAssets.sub(assets);
        totalRepaySettledAssets = totalRepaySettledAssets.sub(assets);

        if (asset == weth) {
            IWETH(weth).withdraw(assets);
            TransferHelper.safeTransferETH(msg.sender, assets);
        } else {
            TransferHelper.safeTransfer(asset, msg.sender, assets);
        }

        emit Withdraw(msg.sender, assets);
    }

    /// @inheritdoc ILendingVault
    function requestWithdraw(uint256 assets) external override nonReentrant returns (uint256 shares) {
        require(allowRequestWithdraw, 'LendingVault: Not allow yet, please wait');
        require(assets <= maxRequestWithdraw(msg.sender), 'LendingVault: msg.sender assets insufficient');
        require((shares = _convertToShares(assets, true)) != 0, 'LendingVault: Zero shares');

        // Get shares from msg.sender to contract, burn these shares when user `withdraw`
        TransferHelper.safeTransferFrom(address(this), msg.sender, address(this), shares);

        UserInfo storage userInfo_ = userInfo[msg.sender];
        userInfo_.requestedShares = userInfo_.requestedShares.add(shares);
        requestUsers.add(msg.sender);

        // `assets` is not the final result, share price will increase until next epoch
        emit RequestWithdraw(msg.sender, assets, shares);
    }

    /// @inheritdoc ILendingVault
    function cancelRequestWithdraw() external override nonReentrant returns (uint256 shares) {
        require(allowRequestWithdraw, 'LendingVault: Not allow yet, please wait');
        UserInfo storage userInfo_ = userInfo[msg.sender];
        require((shares = userInfo_.requestedShares) != 0, 'LendingVault: Zero shares');

        userInfo_.requestedShares = 0;
        requestUsers.remove(msg.sender);

        TransferHelper.safeTransfer(address(this), msg.sender, shares);

        uint256 assets = _convertToAssets(shares, false);
        // `assets` is not the final result, share price will increase until next epoch
        emit CancelRequestWithdraw(msg.sender, assets, shares);
    }

    /// @inheritdoc ILendingVault
    function instantWithdraw(uint256 assets) external override nonReentrant returns (uint256 shares) {
        require(assets <= maxInstantWithdraw(msg.sender), 'LendingVault: msg.sender assets insufficient');
        require((shares = _convertToShares(assets, true)) != 0, 'LendingVault: Zero shares');

        if (isStrategyActive()) {
            IStrategy(strategy).beforeWithdraw();
        }

        _burn(msg.sender, shares);

        _withdrawStrategyIfNeed(assets);
        require(assets <= localAssets(), 'LendingVault: assets exceed');

        leftInstantWithdrawAssets = leftInstantWithdrawAssets.sub(assets);

        uint256 fees;
        if (IWooAccessManager(wooAccessManager).isZeroFeeVault(msg.sender)) {
            fees = 0;
        } else {
            fees = assets.mul(instantWithdrawFeePercentage).div(MAX_PERCENTAGE);
        }

        if (asset == weth) {
            IWETH(weth).withdraw(assets);
            TransferHelper.safeTransferETH(msg.sender, assets.sub(fees));
            if (fees > 0) {
                TransferHelper.safeTransferETH(treasury, fees);
            }
        } else {
            TransferHelper.safeTransfer(asset, msg.sender, assets.sub(fees));
            if (fees > 0) {
                TransferHelper.safeTransfer(asset, treasury, fees);
            }
        }

        emit InstantWithdraw(msg.sender, assets, shares, fees);
    }

    // *********************** //
    // *** ADMIN FUNCTIONS *** //
    // *********************** //

    function settleInterest() public onlyAdmin {
        // solhint-disable-next-line not-rely-on-time
        uint256 currentSettleInterest = block.timestamp;
        require(currentSettleInterest >= lastSettleInterest, 'LendingVault: Timestamp exceed');

        uint256 diff = currentSettleInterest.sub(lastSettleInterest);
        uint256 rate = diff.mul(1e18).mul(interestRatePercentage).div(INTEREST_RATE_COEFF).div(MAX_PERCENTAGE);
        uint256 interestAssets = totalBorrowedAssets.mul(rate).div(1e18);

        weeklyInterestAssets = weeklyInterestAssets.add(interestAssets);
        lastSettleInterest = currentSettleInterest;

        emit SettleInterest(msg.sender, diff, rate, interestAssets, weeklyInterestAssets);
    }

    /// @notice Controlled by backend script to weekly/settle update
    function setWeeklyMaxInstantWithdrawAssets() public onlyAdmin returns (uint256 maxAssets) {
        maxAssets = totalAssets().mul(MAX_PERCENTAGE.sub(allowBorrowPercentage)).div(MAX_PERCENTAGE);
        leftInstantWithdrawAssets = maxAssets;
        maxInstantWithdrawAssets = maxAssets;

        emit SetWeeklyMaxInstantWithdrawAssets(maxAssets);
    }

    function setInterestRatePercentage(uint256 _interestRatePercentage) external onlyAdmin {
        require(_interestRatePercentage <= MAX_PERCENTAGE, 'LendingVault: _interestRatePercentage exceed');
        settleInterest();
        interestRatePercentage = _interestRatePercentage;
    }

    function setWeeklySettleDiff(uint256 _weeklySettleDiff) external onlyAdmin {
        weeklySettleDiff = _weeklySettleDiff;
    }

    /// @notice Trigger in the next epoch
    function weeklySettle() external onlyAdmin {
        require(lastWeeklySettle.add(weeklySettleDiff) < block.timestamp, 'LendingVault: Not ready to settle');

        if (isStrategyActive()) {
            IStrategy(strategy).beforeWithdraw();
        }
        settleInterest();

        uint256 weeklyBurnShares = 0;
        uint256 weeklySettledAssets = 0;
        uint256 length = requestUsers.length();
        for (uint256 i = 0; i < length; i++) {
            address user = requestUsers.at(0);
            UserInfo storage userInfo_ = userInfo[user];
            uint256 shares = userInfo_.requestedShares;
            weeklyBurnShares = weeklyBurnShares.add(shares);
            uint256 assets = _convertToAssets(shares, false);
            userInfo_.requestedShares = 0;
            userInfo_.settledAssets = userInfo_.settledAssets.add(assets);
            weeklySettledAssets = weeklySettledAssets.add(assets);
            totalSettledAssets = totalSettledAssets.add(assets);
            requestUsers.remove(user);
            emit WeeklySettle(msg.sender, user, assets, shares);
        }
        uint256 totalAssets_ = totalAssets();
        uint256 leftInterestAssets = weeklyInterestAssets.mul(totalAssets_.sub(weeklySettledAssets)).div(totalAssets_);
        totalInterestAssets = totalInterestAssets.add(leftInterestAssets);
        // SET 0 to `weeklyInterestAssets` means the new epoch is ready to accumulate the interest
        weeklyInterestAssets = 0;
        _burn(address(this), weeklyBurnShares);

        totalAssets_ = totalAssets();
        uint256 allowBorrowAssets = totalAssets_.mul(allowBorrowPercentage).div(MAX_PERCENTAGE);

        if (weeklySettledAssets > allowBorrowAssets.sub(totalBorrowedAssets)) {
            uint256 debtSettledAssets = weeklySettledAssets.sub(totalAssets_.sub(totalBorrowedAssets));
            totalBorrowedAssets = totalBorrowedAssets.sub(debtSettledAssets);
            // Don't update instant withdraw limit when debt exist
            totalDebtSettledAssets = totalDebtSettledAssets.add(debtSettledAssets);
        } else {
            // Automatic update instant withdraw limit when debt not exist in this settlement
            setWeeklyMaxInstantWithdrawAssets();
        }

        lastWeeklySettle = block.timestamp;
    }

    function borrow(uint256 assets) external onlyAdmin {
        if (isStrategyActive()) {
            IStrategy(strategy).beforeWithdraw();
        }

        uint256 allowBorrowAssets = totalAssets().mul(allowBorrowPercentage).div(MAX_PERCENTAGE);
        require(totalBorrowedAssets.add(assets) <= allowBorrowAssets, 'LendingVault: assets exceed');

        _withdrawStrategyIfNeed(assets);
        require(assets <= localAssets(), 'LendingVault: assets exceed');

        settleInterest();
        totalBorrowedAssets = totalBorrowedAssets.add(assets);
        TransferHelper.safeTransfer(asset, msg.sender, assets);

        emit Borrow(assets);
    }

    function repay(uint256 assets, bool repaySettle) external onlyAdmin {
        if (assets > 0) {
            require(
                (!repaySettle && totalBorrowedAssets >= assets) || (repaySettle && totalDebtSettledAssets >= assets),
                'LendingVault: repaySettle error or assets too much'
            );

            uint256 assetsBefore = localAssets();
            TransferHelper.safeTransferFrom(asset, msg.sender, address(this), assets);
            uint256 assetsAfter = localAssets();
            require(assetsAfter.sub(assetsBefore) >= assets, 'LendingVault: assets not enough');

            if (repaySettle) {
                totalRepaySettledAssets = totalRepaySettledAssets.add(assets);
                totalDebtSettledAssets = totalDebtSettledAssets.sub(assets);
                // When debt equal to 0, update instant withdraw limit
                if (totalDebtSettledAssets == 0) {
                    setWeeklyMaxInstantWithdrawAssets();
                }
            } else {
                totalBorrowedAssets = totalBorrowedAssets.sub(assets);
                _farmAtStrategy();
            }

            emit Repay(assets, repaySettle);
        }
    }

    function setTreasury(address _treasury) external onlyAdmin {
        require(_treasury != address(0), 'LendingVault: _treasury not set');
        treasury = _treasury;
    }

    function setWooAccessManager(address _wooAccessManager) external onlyAdmin {
        require(_wooAccessManager != address(0), 'LendingVault: _wooAccessManager not set');
        wooAccessManager = _wooAccessManager;
    }

    /// @notice Controlled by backend script to daily update
    function setDailyMaxInstantWithdrawAssets() external onlyAdmin returns (uint256 maxAssets) {
        maxAssets = totalAssets().mul(MAX_PERCENTAGE.sub(allowBorrowPercentage)).div(MAX_PERCENTAGE);
        if (maxAssets > maxInstantWithdrawAssets) {
            uint256 increaseAssets = maxAssets.sub(maxInstantWithdrawAssets);
            leftInstantWithdrawAssets = leftInstantWithdrawAssets.add(increaseAssets);
            maxInstantWithdrawAssets = maxAssets;
        }

        emit SetDailyMaxInstantWithdrawAssets(maxInstantWithdrawAssets, leftInstantWithdrawAssets, maxAssets);
    }

    function setAllowBorrowPercentage(uint256 _allowBorrowPercentage) external onlyAdmin {
        require(_allowBorrowPercentage <= MAX_PERCENTAGE, 'LendingVault: _allowBorrowPercentage exceed');
        require(
            totalBorrowedAssets <= localAssets().mul(_allowBorrowPercentage).div(MAX_PERCENTAGE),
            'LendingVault: _allowBorrowPercentage too small'
        );
        allowBorrowPercentage = _allowBorrowPercentage;
    }

    function setInstantWithdrawFeePercentage(uint256 _instantWithdrawFeePercentage) external onlyAdmin {
        require(_instantWithdrawFeePercentage <= MAX_PERCENTAGE, 'LendingVault: _instantWithdrawFeePercentage exceed');
        instantWithdrawFeePercentage = _instantWithdrawFeePercentage;
    }

    function setApprovalDelay(uint256 _approvalDelay) external onlyAdmin {
        approvalDelay = _approvalDelay;
    }

    function setAllowRequestWithdraw(bool _allowRequestWithdraw) external onlyAdmin {
        allowRequestWithdraw = _allowRequestWithdraw;
    }

    function setupStrategy(address _strategy) external onlyAdmin {
        require(_strategy != address(0), 'LendingVault: _strategy not set');
        require(address(strategy) == address(0), 'LendingVault: strategy already set');
        require(address(this) == IStrategy(_strategy).vault(), 'LendingVault: _strategy vault invalid');
        require(asset == IStrategy(_strategy).want(), 'LendingVault: _strategy want invalid');
        strategy = _strategy;

        emit UpgradeStrategy(_strategy);
    }

    function proposeStrat(address _strategy) external onlyAdmin {
        require(address(this) == IStrategy(_strategy).vault(), 'LendingVault: _strategy vault invalid');
        require(asset == IStrategy(_strategy).want(), 'LendingVault: _strategy want invalid');
        // solhint-disable-next-line not-rely-on-time
        strategyCandidate = StrategyCandidate({implementation: _strategy, proposedTime: block.timestamp});

        emit NewStrategyCandidate(_strategy);
    }

    function upgradeStrat() external onlyAdmin {
        require(strategyCandidate.implementation != address(0), 'LendingVault: No candidate');
        // solhint-disable-next-line not-rely-on-time
        require(strategyCandidate.proposedTime.add(approvalDelay) < block.timestamp, 'LendingVault: Time invalid');

        IStrategy(strategy).retireStrat();
        strategy = strategyCandidate.implementation;
        strategyCandidate.implementation = address(0);
        strategyCandidate.proposedTime = 5000000000; // 100+ years to ensure proposedTime check

        _farmAtStrategy();

        emit UpgradeStrategy(strategyCandidate.implementation);
    }

    function inCaseTokensGetStuck(address token) external onlyAdmin {
        require(token != asset, 'LendingVault: token not allow');
        require(token != address(0), 'LendingVault: token not set');
        uint256 tokenBal = IERC20(token).balanceOf(address(this));
        if (tokenBal > 0) {
            TransferHelper.safeTransfer(token, msg.sender, tokenBal);
        }
    }

    function inCaseNativeTokensGetStuck() external onlyAdmin {
        // Vault never needs native tokens to do the yield farming,
        // this native token balance indicates a user's incorrect transfer.
        uint256 nativeBal = address(this).balance;
        if (nativeBal > 0) {
            TransferHelper.safeTransferETH(msg.sender, nativeBal);
        }
    }

    // ************************** //
    // *** INTERNAL FUNCTIONS *** //
    // ************************** //

    function _convertToAssets(uint256 shares, bool roundUp) internal view returns (uint256 assets) {
        uint256 totalSupply_ = totalSupply();
        if (totalSupply_ == 0) {
            assets = shares;
        } else {
            uint256 totalAssets_ = totalAssets();
            assets = shares.mul(totalAssets_).div(totalSupply_);
            if (roundUp && assets.mul(totalSupply_).div(totalAssets_) < shares) {
                assets = assets.add(1);
            }
        }
    }

    function _convertToShares(uint256 assets, bool roundUp) internal view returns (uint256 shares) {
        uint256 totalSupply_ = totalSupply();
        if (totalSupply_ == 0) {
            shares = assets;
        } else {
            uint256 totalAssets_ = totalAssets();
            shares = assets.mul(totalSupply_).div(totalAssets_);
            if (roundUp && shares.mul(totalAssets_).div(totalSupply_) < assets) {
                shares = shares.add(1);
            }
        }
    }

    function _updateCostSharePrice(
        address user,
        uint256 assets,
        uint256 shares
    ) internal {
        uint256 sharesBefore = balanceOf(user);
        UserInfo storage userInfo_ = userInfo[user];
        uint256 costBefore = userInfo_.costSharePrice;

        userInfo_.costSharePrice = (sharesBefore.mul(costBefore).add(assets.mul(1e18))).div(sharesBefore.add(shares));
    }

    function _farmAtStrategy() internal {
        if (isStrategyActive()) {
            uint256 localAssets_ = localAssets();
            TransferHelper.safeTransfer(asset, strategy, localAssets_);
            IStrategy(strategy).deposit();
        }
    }

    function _withdrawStrategyIfNeed(uint256 assets) internal {
        uint256 localAssets_ = localAssets();
        if (localAssets_ < assets) {
            uint256 withdrawAmount = assets.sub(localAssets_);
            require(isStrategyActive(), 'LendingVault: Strategy inactive');
            IStrategy(strategy).withdraw(withdrawAmount);
        }
    }

    // ************************** //
    // *** CALLBACK FUNCTIONS *** //
    // ************************** //

    // solhint-disable-next-line no-empty-blocks
    receive() external payable {}
}

File 2 of 106 : Ownable.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

import "../utils/Context.sol";
/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor () internal {
        address msgSender = _msgSender();
        _owner = msgSender;
        emit OwnershipTransferred(address(0), msgSender);
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
}

File 3 of 106 : SafeMath.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

/**
 * @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, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        uint256 c = a + b;
        if (c < a) return (false, 0);
        return (true, c);
    }

    /**
     * @dev Returns the substraction of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        if (b > a) return (false, 0);
        return (true, a - b);
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, 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 (true, 0);
        uint256 c = a * b;
        if (c / a != b) return (false, 0);
        return (true, c);
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        if (b == 0) return (false, 0);
        return (true, a / b);
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        if (b == 0) return (false, 0);
        return (true, a % b);
    }

    /**
     * @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) {
        require(b <= a, "SafeMath: subtraction overflow");
        return a - b;
    }

    /**
     * @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) {
        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, reverting 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) {
        require(b > 0, "SafeMath: division by zero");
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting 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) {
        require(b > 0, "SafeMath: modulo by zero");
        return a % b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {trySub}.
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        return a - b;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryDiv}.
     *
     * 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, string memory errorMessage) internal pure returns (uint256) {
        require(b > 0, errorMessage);
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting with custom message when dividing by zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryMod}.
     *
     * 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, string memory errorMessage) internal pure returns (uint256) {
        require(b > 0, errorMessage);
        return a % b;
    }
}

File 4 of 106 : IERC20.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
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);
}

File 5 of 106 : ERC20.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

import "../../utils/Context.sol";
import "./IERC20.sol";
import "../../math/SafeMath.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * We have followed general OpenZeppelin guidelines: functions revert instead
 * of returning `false` on failure. This behavior is nonetheless conventional
 * and does not conflict with the expectations of ERC20 applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20 is Context, IERC20 {
    using SafeMath for uint256;

    mapping (address => uint256) private _balances;

    mapping (address => mapping (address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;
    uint8 private _decimals;

    /**
     * @dev Sets the values for {name} and {symbol}, initializes {decimals} with
     * a default value of 18.
     *
     * To select a different value for {decimals}, use {_setupDecimals}.
     *
     * All three of these values are immutable: they can only be set once during
     * construction.
     */
    constructor (string memory name_, string memory symbol_) public {
        _name = name_;
        _symbol = symbol_;
        _decimals = 18;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5,05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
     * called.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual returns (uint8) {
        return _decimals;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `recipient` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * Requirements:
     *
     * - `sender` and `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     * - the caller must have allowance for ``sender``'s tokens of at least
     * `amount`.
     */
    function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
        return true;
    }

    /**
     * @dev Moves tokens `amount` from `sender` to `recipient`.
     *
     * This is internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `sender` cannot be the zero address.
     * - `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     */
    function _transfer(address sender, address recipient, uint256 amount) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(sender, recipient, amount);

        _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
        _balances[recipient] = _balances[recipient].add(amount);
        emit Transfer(sender, recipient, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply = _totalSupply.add(amount);
        _balances[account] = _balances[account].add(amount);
        emit Transfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
        _totalSupply = _totalSupply.sub(amount);
        emit Transfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @dev Sets {decimals} to a value other than the default one of 18.
     *
     * WARNING: This function should only be called from the constructor. Most
     * applications that interact with token contracts will not expect
     * {decimals} to ever change, and may work incorrectly if it does.
     */
    function _setupDecimals(uint8 decimals_) internal virtual {
        _decimals = decimals_;
    }

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be to transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
}

File 6 of 106 : SafeERC20.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

import "./IERC20.sol";
import "../../math/SafeMath.sol";
import "../../utils/Address.sol";

/**
 * @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 IERC20;` 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));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    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. We use {Address.functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data, "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");
        }
    }
}

File 7 of 106 : EnumerableSet.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

/**
 * @dev Library for managing
 * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
 * types.
 *
 * Sets have the following properties:
 *
 * - Elements are added, removed, and checked for existence in constant time
 * (O(1)).
 * - Elements are enumerated in O(n). No guarantees are made on the ordering.
 *
 * ```
 * contract Example {
 *     // Add the library methods
 *     using EnumerableSet for EnumerableSet.AddressSet;
 *
 *     // Declare a set state variable
 *     EnumerableSet.AddressSet private mySet;
 * }
 * ```
 *
 * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
 * and `uint256` (`UintSet`) are supported.
 */
library EnumerableSet {
    // To implement this library for multiple types with as little code
    // repetition as possible, we write it in terms of a generic Set type with
    // bytes32 values.
    // The Set implementation uses private functions, and user-facing
    // implementations (such as AddressSet) are just wrappers around the
    // underlying Set.
    // This means that we can only create new EnumerableSets for types that fit
    // in bytes32.

    struct Set {
        // Storage of set values
        bytes32[] _values;

        // Position of the value in the `values` array, plus 1 because index 0
        // means a value is not in the set.
        mapping (bytes32 => uint256) _indexes;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
            set._values.push(value);
            // The value is stored at length-1, but we add 1 to all indexes
            // and use 0 as a sentinel value
            set._indexes[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function _remove(Set storage set, bytes32 value) private returns (bool) {
        // We read and store the value's index to prevent multiple reads from the same storage slot
        uint256 valueIndex = set._indexes[value];

        if (valueIndex != 0) { // Equivalent to contains(set, value)
            // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
            // the array, and then remove the last element (sometimes called as 'swap and pop').
            // This modifies the order of the array, as noted in {at}.

            uint256 toDeleteIndex = valueIndex - 1;
            uint256 lastIndex = set._values.length - 1;

            // When the value to delete is the last one, the swap operation is unnecessary. However, since this occurs
            // so rarely, we still do the swap anyway to avoid the gas cost of adding an 'if' statement.

            bytes32 lastvalue = set._values[lastIndex];

            // Move the last value to the index where the value to delete is
            set._values[toDeleteIndex] = lastvalue;
            // Update the index for the moved value
            set._indexes[lastvalue] = toDeleteIndex + 1; // All indexes are 1-based

            // Delete the slot where the moved value was stored
            set._values.pop();

            // Delete the index for the deleted slot
            delete set._indexes[value];

            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
        return set._indexes[value] != 0;
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function _length(Set storage set) private view returns (uint256) {
        return set._values.length;
    }

   /**
    * @dev Returns the value stored at position `index` in the set. O(1).
    *
    * Note that there are no guarantees on the ordering of values inside the
    * array, and it may change when more values are added or removed.
    *
    * Requirements:
    *
    * - `index` must be strictly less than {length}.
    */
    function _at(Set storage set, uint256 index) private view returns (bytes32) {
        require(set._values.length > index, "EnumerableSet: index out of bounds");
        return set._values[index];
    }

    // Bytes32Set

    struct Bytes32Set {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _add(set._inner, value);
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _remove(set._inner, value);
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
        return _contains(set._inner, value);
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(Bytes32Set storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

   /**
    * @dev Returns the value stored at position `index` in the set. O(1).
    *
    * Note that there are no guarantees on the ordering of values inside the
    * array, and it may change when more values are added or removed.
    *
    * Requirements:
    *
    * - `index` must be strictly less than {length}.
    */
    function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
        return _at(set._inner, index);
    }

    // AddressSet

    struct AddressSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(AddressSet storage set, address value) internal returns (bool) {
        return _add(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(AddressSet storage set, address value) internal returns (bool) {
        return _remove(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(AddressSet storage set, address value) internal view returns (bool) {
        return _contains(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(AddressSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

   /**
    * @dev Returns the value stored at position `index` in the set. O(1).
    *
    * Note that there are no guarantees on the ordering of values inside the
    * array, and it may change when more values are added or removed.
    *
    * Requirements:
    *
    * - `index` must be strictly less than {length}.
    */
    function at(AddressSet storage set, uint256 index) internal view returns (address) {
        return address(uint160(uint256(_at(set._inner, index))));
    }


    // UintSet

    struct UintSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(UintSet storage set, uint256 value) internal returns (bool) {
        return _remove(set._inner, bytes32(value));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
        return _contains(set._inner, bytes32(value));
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function length(UintSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

   /**
    * @dev Returns the value stored at position `index` in the set. O(1).
    *
    * Note that there are no guarantees on the ordering of values inside the
    * array, and it may change when more values are added or removed.
    *
    * Requirements:
    *
    * - `index` must be strictly less than {length}.
    */
    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
        return uint256(_at(set._inner, index));
    }
}

File 8 of 106 : Pausable.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

import "./Context.sol";

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract Pausable is Context {
    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event Unpaused(address account);

    bool private _paused;

    /**
     * @dev Initializes the contract in unpaused state.
     */
    constructor () internal {
        _paused = false;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        return _paused;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        require(!paused(), "Pausable: paused");
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    modifier whenPaused() {
        require(paused(), "Pausable: not paused");
        _;
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual whenNotPaused {
        _paused = true;
        emit Paused(_msgSender());
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        _paused = false;
        emit Unpaused(_msgSender());
    }
}

File 9 of 106 : ReentrancyGuard.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor () internal {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and make it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        // On the first call to nonReentrant, _notEntered will be true
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;

        _;

        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
}

File 10 of 106 : TransferHelper.sol
// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity >=0.6.0;

// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false
library TransferHelper {
    function safeApprove(
        address token,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('approve(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::safeApprove: approve failed'
        );
    }

    function safeTransfer(
        address token,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('transfer(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::safeTransfer: transfer failed'
        );
    }

    function safeTransferFrom(
        address token,
        address from,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::transferFrom: transferFrom failed'
        );
    }

    function safeTransferETH(address to, uint256 value) internal {
        (bool success, ) = to.call{value: value}(new bytes(0));
        require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
    }
}

File 11 of 106 : ILendingVault.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

/// @title WOOFi LendingVault interface.
interface ILendingVault {
    // ************** //
    // *** EVENTS *** //
    // ************** //

    event Deposit(address indexed user, uint256 assets, uint256 shares);

    event Withdraw(address indexed user, uint256 assets);

    event RequestWithdraw(address indexed user, uint256 assets, uint256 shares);

    event CancelRequestWithdraw(address indexed user, uint256 assets, uint256 shares);

    event InstantWithdraw(address indexed user, uint256 assets, uint256 shares, uint256 fees);

    event SettleInterest(
        address indexed caller,
        uint256 diff,
        uint256 rate,
        uint256 interestAssets,
        uint256 weeklyInterestAssets
    );

    event WeeklySettle(address indexed caller, address indexed user, uint256 assets, uint256 shares);

    event SetDailyMaxInstantWithdrawAssets(
        uint256 maxInstantWithdrawAssets,
        uint256 leftInstantWithdrawAssets,
        uint256 maxAssets
    );

    event SetWeeklyMaxInstantWithdrawAssets(uint256 maxAssets);

    event Borrow(uint256 assets);

    event Repay(uint256 assets, bool repaySettle);

    event UpgradeStrategy(address strategy);

    event NewStrategyCandidate(address strategy);

    // ***************** //
    // *** FUNCTIONS *** //
    // ***************** //

    /// @dev ERC20 token for deposit && two withdraw ways(instant/request).
    /// @return assetTokenAddress Address of ERC20 token.
    function asset() external view returns (address assetTokenAddress);

    /// @dev Total amount of `asset` in Vault for share converting.
    /// @return totalManagedAssets Total amount of `asset`.
    function totalAssets() external view returns (uint256 totalManagedAssets);

    /// @dev Amount of `asset` in Vault locally,
    /// SUBTRACT the amount of `asset` that market maker repay for weekly settle.
    /// @return assets Amount of `asset` in Vault locally.
    function localAssets() external view returns (uint256 assets);

    /// @dev Calculate the share price for convert unit shares(1e18) to assets.
    /// @return sharePrice Result of unit shares(1e18) convert to assets.
    function getPricePerFullShare() external view returns (uint256 sharePrice);

    /// @dev According to `totalAssets` and `totalSupply`, convert `assets` to `shares`.
    /// @param assets Amount of `asset` convert to `shares`
    /// @return shares Result of `assets` convert to `shares`.
    function convertToShares(uint256 assets) external view returns (uint256 shares);

    /// @dev According to `totalAssets` and `totalSupply`, convert `shares` to `assets`.
    /// @param shares Amount of `share` convert to `assets`
    /// @return assets Result of `shares` convert to `assets`.
    function convertToAssets(uint256 shares) external view returns (uint256 assets);

    /// @dev Limit of total `deposit` amount of `asset` from user,
    /// `uint256(-1)` means no limit, `0` means Vault paused.
    /// @param user Address of Vault user.
    /// @return maxAssets Result of `asset` deposit limitations to user.
    function maxDeposit(address user) external view returns (uint256 maxAssets);

    /// @dev Total withdrawable amount of `asset` from user execute `withdraw`,
    /// @param user Address of Vault user.
    /// @return maxAssets Result of `asset` that user can withdraw.
    function maxWithdraw(address user) external view returns (uint256 maxAssets);

    /// @dev Max amount of `asset` that convert user shares to assets when `requestWithdraw`,
    /// only for safety check, not the final result until `weeklySettle` is done.
    /// @param user Address of Vault user.
    /// @return maxAssets Result of `dev` above.
    function maxRequestWithdraw(address user) external view returns (uint256 maxAssets);

    /// @dev Max amount of `asset` that user can withdraw immediately(not SUBTRACT fees here),
    /// related to user `shares` and weekly limit of `instantWithdraw`.
    /// @param user Address of Vault user.
    /// @return maxAssets Result of `dev` above.
    function maxInstantWithdraw(address user) external view returns (uint256 maxAssets);

    /// @dev Check if the `strategy` is active,
    /// only true if `strategy != address(0)` and strategy not paused.
    /// @return active Status of `strategy`, `true` means strategy working, `false` means not working now.
    function isStrategyActive() external view returns (bool active);

    /// @dev Deposit an amount of `asset` represented in `assets`.
    /// @param assets Amount of `asset` to deposit.
    /// @return shares The deposited amount repesented in shares.
    function deposit(uint256 assets) external payable returns (uint256 shares);

    /// @dev Withdraw total settled amount of `asset` from a user account,
    /// not accept `assets` as parameter,
    /// represented by the last epoch requested withdraw shares.
    function withdraw() external;

    /// @dev Request withdraw an amount of `asset` from a user account(no fees),
    /// represented in `shares` and keep in Vault(safeTransferFrom).
    /// @param assets Amount of `asset` to request withdraw.
    /// @return shares The request withdrew amount repesented in shares.
    function requestWithdraw(uint256 assets) external returns (uint256 shares);

    /// @dev Cancel total requested amount of `share` from a user account(no fees),
    /// and payback the total requested amount of `share`(safeTransfer).
    /// @return shares The request withdrew shares that been canceled.
    function cancelRequestWithdraw() external returns (uint256 shares);

    /// @dev Withdraw an amount of `asset` from a user account immediately(fees exist).
    /// @param assets Amount of `asset` to withdraw.
    /// @return shares The withdrew amount repesented in shares.
    function instantWithdraw(uint256 assets) external returns (uint256 shares);
}

File 12 of 106 : IWooStakingVault.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

/// @title WOOFi WooStakingVault interface.
/// @notice WooStakingVault has been deployed, but not define `IWooStakingVault` at that time.
/// This interface is only for other contracts that want to call `WooStakingVault` functions.
interface IWooStakingVault {
    // ************** //
    // *** EVENTS *** //
    // ************** //

    event Deposit(address indexed user, uint256 depositAmount, uint256 mintShares);
    event ReserveWithdraw(address indexed user, uint256 reserveAmount, uint256 burnShares);
    event Withdraw(address indexed user, uint256 withdrawAmount, uint256 withdrawFee);
    event InstantWithdraw(address indexed user, uint256 withdrawAmount, uint256 withdrawFee);
    event RewardAdded(
        address indexed sender,
        uint256 balanceBefore,
        uint256 sharePriceBefore,
        uint256 balanceAfter,
        uint256 sharePriceAfter
    );

    // ***************** //
    // *** FUNCTIONS *** //
    // ***************** //

    /// @dev ERC20 token for deposit && two withdraw ways(instant/reserve).
    /// @return stakedTokenAddress Address of ERC20 token.
    function stakedToken() external view returns (address stakedTokenAddress);

    /// @dev Get user cost share price.
    /// @param user Address of Vault user.
    /// @return cost Result of `dev`.
    function costSharePrice(address user) external view returns (uint256 cost);

    /// @dev Get user amount of `stakedToken` by reserve withdraw && last reserve withdraw timestamp.
    /// @param user Address of Vault user.
    /// @return reserveAmount Result of `dev`.
    /// @return lastReserveWithdrawTime Result of `dev`.
    function userInfo(address user) external view returns (uint256 reserveAmount, uint256 lastReserveWithdrawTime);

    /// @dev Calculate the share price for convert unit shares(1e18) to assets.
    /// @return sharePrice Result of unit shares(1e18) convert to assets.
    function getPricePerFullShare() external view returns (uint256 sharePrice);

    /// @dev Get the `stakedToken` balance in Vault, SUBTRACT total `stakedToken` amount of user reserved withdraw.
    /// @return wooBalance Result of `dev`.
    function balance() external view returns (uint256 wooBalance);

    /// @dev Deposit an amount of `stakedToken`.
    /// @param amount Amount of `stakedToken` to deposit.
    function deposit(uint256 amount) external;

    /// @dev Reserve withdraw an amount of `stakedToken` from a user account(no fees).
    /// @param shares Amount of `asset` to request withdraw.
    function reserveWithdraw(uint256 shares) external;

    /// @dev Withdraw total reserved amount of `stakedToken` from a user account.
    function withdraw() external;

    /// @dev Withdraw an amount of `stakedToken` from a user account immediately(fees exist).
    /// @param shares Amount of `share`(e.g. xWOO) to withdraw.
    function instantWithdraw(uint256 shares) external;

    /// @dev For transfer rewards(e.g. WOO) to Vault(need approve).
    /// @notice Please to be sure calling this function will not get any rewards!!!
    /// @param amount Amount of reward that transfer to Vault.
    function addReward(uint256 amount) external;
}

File 13 of 106 : IWETH.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

/// @title Wrapped ETH.
/// BSC: https://bscscan.com/address/0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c#code
interface IWETH {
    /// @dev Deposit ETH into WETH
    function deposit() external payable;

    /// @dev Transfer WETH to receiver
    /// @param to address of WETH receiver
    /// @param value amount of WETH to transfer
    /// @return get true when succeed, else false
    function transfer(address to, uint256 value) external returns (bool);

    /// @dev Withdraw WETH to ETH
    function withdraw(uint256) external;
}

File 14 of 106 : IStrategy.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

*
* MIT License
* ===========
*
* Copyright (c) 2020 WooTrade
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* 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 SOFTWARE.
*/
interface IStrategy {
    function vault() external view returns (address);

    function want() external view returns (address);

    function beforeDeposit() external;

    function beforeWithdraw() external;

    function deposit() external;

    function withdraw(uint256) external;

    function balanceOf() external view returns (uint256);

    function balanceOfWant() external view returns (uint256);

    function balanceOfPool() external view returns (uint256);

    function harvest() external;

    function retireStrat() external;

    function emergencyExit() external;

    function paused() external view returns (bool);

    function inCaseTokensGetStuck(address stuckToken) external;
}

File 15 of 106 : IWooAccessManager.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

/// @title Reward manager interface for WooFi Swap.
/// @notice this is for swap rebate or potential incentive program
interface IWooAccessManager {
    /* ----- Events ----- */

    event FeeAdminUpdated(address indexed feeAdmin, bool flag);

    event VaultAdminUpdated(address indexed vaultAdmin, bool flag);

    event RebateAdminUpdated(address indexed rebateAdmin, bool flag);

    event ZeroFeeVaultUpdated(address indexed vault, bool flag);

    /* ----- External Functions ----- */

    function isFeeAdmin(address feeAdmin) external returns (bool);

    function isVaultAdmin(address vaultAdmin) external returns (bool);

    function isRebateAdmin(address rebateAdmin) external returns (bool);

    function isZeroFeeVault(address vault) external returns (bool);

    /* ----- Admin Functions ----- */

    /// @notice Sets feeAdmin
    function setFeeAdmin(address feeAdmin, bool flag) external;

    /// @notice Batch sets feeAdmin
    function batchSetFeeAdmin(address[] calldata feeAdmins, bool[] calldata flags) external;

    /// @notice Sets vaultAdmin
    function setVaultAdmin(address vaultAdmin, bool flag) external;

    /// @notice Batch sets vaultAdmin
    function batchSetVaultAdmin(address[] calldata vaultAdmins, bool[] calldata flags) external;

    /// @notice Sets rebateAdmin
    function setRebateAdmin(address rebateAdmin, bool flag) external;

    /// @notice Batch sets rebateAdmin
    function batchSetRebateAdmin(address[] calldata rebateAdmins, bool[] calldata flags) external;

    /// @notice Sets zeroFeeVault
    function setZeroFeeVault(address vault, bool flag) external;

    /// @notice Batch sets zeroFeeVault
    function batchSetZeroFeeVault(address[] calldata vaults, bool[] calldata flags) external;
}

File 16 of 106 : Context.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

/*
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with GSN meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address payable) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes memory) {
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
        return msg.data;
    }
}

File 17 of 106 : Address.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.2 <0.8.0;

/**
 * @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) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.

        uint256 size;
        // solhint-disable-next-line no-inline-assembly
        assembly { size := extcodesize(account) }
        return size > 0;
    }

    /**
     * @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].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
        (bool success, ) = recipient.call{ value: amount }("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain`call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
      return functionCall(target, data, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        require(isContract(target), "Address: call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.call{ value: value }(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
        require(isContract(target), "Address: static call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.staticcall(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
        require(isContract(target), "Address: delegate call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
        if (success) {
            return returndata;
        } else {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly

                // solhint-disable-next-line no-inline-assembly
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

File 18 of 106 : WooVaultManager.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

import './libraries/InitializableOwnable.sol';
import './libraries/DecimalMath.sol';
import './interfaces/IWooracle.sol';
import './interfaces/IWooVaultManager.sol';
import './interfaces/IWooGuardian.sol';
import './interfaces/AggregatorV3Interface.sol';
import './interfaces/IWooAccessManager.sol';

import '@openzeppelin/contracts/utils/ReentrancyGuard.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import '@openzeppelin/contracts/utils/EnumerableSet.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

contract WooVaultManager is InitializableOwnable, ReentrancyGuard, IWooVaultManager {
    using SafeMath for uint256;
    using DecimalMath for uint256;
    using SafeERC20 for IERC20;
    using EnumerableSet for EnumerableSet.AddressSet;

    mapping(address => uint256) public override vaultWeight;
    uint256 public totalWeight;

    IWooPP private wooPP;

    address public immutable override quoteToken; // USDT
    address public immutable rewardToken; // WOO

    EnumerableSet.AddressSet private vaultSet;

    IWooAccessManager public accessManager;

    /* ----- Modifiers ----- */

    modifier onlyAdmin() {
        require(msg.sender == _OWNER_ || accessManager.isVaultAdmin(msg.sender), 'WooVaultManager: NOT_ADMIN');
        _;
    }

    constructor(
        address newQuoteToken,
        address newRewardToken,
        address newAccessManager
    ) public {
        require(newQuoteToken != address(0), 'WooVaultManager: INVALID_QUOTE');
        require(newRewardToken != address(0), 'WooVaultManager: INVALID_RAWARD_TOKEN');
        initOwner(msg.sender);
        quoteToken = newQuoteToken;
        rewardToken = newRewardToken;
        accessManager = IWooAccessManager(newAccessManager);
    }

    function allVaults() external view override returns (address[] memory) {
        address[] memory vaults = new address[](vaultSet.length());
        for (uint256 i = 0; i < vaultSet.length(); ++i) {
            vaults[i] = vaultSet.at(i);
        }
        return vaults;
    }

    function addReward(uint256 amount) external override nonReentrant {
        if (amount == 0) {
            return;
        }

        uint256 balanceBefore = IERC20(quoteToken).balanceOf(address(this));
        TransferHelper.safeTransferFrom(quoteToken, msg.sender, address(this), amount);
        uint256 balanceAfter = IERC20(quoteToken).balanceOf(address(this));
        require(balanceAfter.sub(balanceBefore) >= amount, 'WooVaultManager: amount INSUFF');
    }

    function pendingReward(address vaultAddr) external view override returns (uint256) {
        require(vaultAddr != address(0), 'WooVaultManager: vaultAddr_ZERO_ADDR');
        uint256 totalReward = IERC20(quoteToken).balanceOf(address(this));
        return totalReward.mul(vaultWeight[vaultAddr]).div(totalWeight);
    }

    function pendingAllReward() external view override returns (uint256) {
        return IERC20(quoteToken).balanceOf(address(this));
    }

    // ----------- Admin Functions ------------- //

    function setVaultWeight(address vaultAddr, uint256 weight) external override onlyAdmin {
        require(vaultAddr != address(0), 'WooVaultManager: vaultAddr_ZERO_ADDR');

        // NOTE: First clear all the pending reward if > 100u to keep the things fair
        if (IERC20(quoteToken).balanceOf(address(this)) >= 1e20) {
            distributeAllReward();
        }

        uint256 prevWeight = vaultWeight[vaultAddr];
        vaultWeight[vaultAddr] = weight;
        totalWeight = totalWeight.add(weight).sub(prevWeight);

        if (weight == 0) {
            vaultSet.remove(vaultAddr);
        } else {
            vaultSet.add(vaultAddr);
        }

        emit VaultWeightUpdated(vaultAddr, weight);
    }

    function distributeAllReward() public override onlyAdmin {
        uint256 totalRewardInQuote = IERC20(quoteToken).balanceOf(address(this));
        if (totalRewardInQuote == 0 || totalWeight == 0) {
            return;
        }

        uint256 balanceBefore = IERC20(rewardToken).balanceOf(address(this));
        TransferHelper.safeApprove(quoteToken, address(wooPP), totalRewardInQuote);
        uint256 wooAmount = IWooPP(wooPP).sellQuote(rewardToken, totalRewardInQuote, 0, address(this), address(0));
        uint256 balanceAfter = IERC20(rewardToken).balanceOf(address(this));
        require(balanceAfter.sub(balanceBefore) >= wooAmount, 'WooVaultManager: woo amount INSUFF');

        for (uint256 i = 0; i < vaultSet.length(); ++i) {
            address vaultAddr = vaultSet.at(i);
            uint256 vaultAmount = wooAmount.mul(vaultWeight[vaultAddr]).div(totalWeight);
            if (vaultAmount > 0) {
                TransferHelper.safeTransfer(rewardToken, vaultAddr, vaultAmount);
            }
            emit RewardDistributed(vaultAddr, vaultAmount);
        }
    }

    function setWooPP(address newWooPP) external onlyAdmin {
        require(newWooPP != address(0), 'WooVaultManager: newWooPP_ZERO_ADDR');
        wooPP = IWooPP(newWooPP);
        require(wooPP.quoteToken() == quoteToken, 'WooVaultManager: wooPP_quote_token_INVALID');
    }

    function setAccessManager(address newAccessManager) external onlyOwner {
        require(newAccessManager != address(0), 'WooVaultManager: newAccessManager_ZERO_ADDR');
        accessManager = IWooAccessManager(newAccessManager);
    }

    function emergencyWithdraw(address token, address to) public onlyOwner {
        require(token != address(0), 'WooVaultManager: token_ZERO_ADDR');
        require(to != address(0), 'WooVaultManager: to_ZERO_ADDR');
        TransferHelper.safeTransfer(token, to, IERC20(token).balanceOf(address(this)));
    }
}

File 19 of 106 : InitializableOwnable.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

/**
 * @title Ownable initializable contract.
 *
 * @notice Ownership related functions
 */
contract InitializableOwnable {
    address public _OWNER_;
    address public _NEW_OWNER_;
    bool internal _INITIALIZED_;

    // ============ Events ============

    event OwnershipTransferPrepared(address indexed previousOwner, address indexed newOwner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    // ============ Modifiers ============

    modifier notInitialized() {
        require(!_INITIALIZED_, 'InitializableOwnable: SHOULD_NOT_BE_INITIALIZED');
        _;
    }

    modifier onlyOwner() {
        require(msg.sender == _OWNER_, 'InitializableOwnable: NOT_OWNER');
        _;
    }

    // ============ Functions ============

    /// @dev Init _OWNER_ from newOwner and set _INITIALIZED_ as true
    /// @param newOwner new owner address
    function initOwner(address newOwner) public notInitialized {
        _INITIALIZED_ = true;
        _OWNER_ = newOwner;
    }

    /// @dev Set _NEW_OWNER_ from newOwner
    /// @param newOwner new owner address
    function transferOwnership(address newOwner) public onlyOwner {
        emit OwnershipTransferPrepared(_OWNER_, newOwner);
        _NEW_OWNER_ = newOwner;
    }

    /// @dev Set _OWNER_ from _NEW_OWNER_ and set _NEW_OWNER_ equal zero address
    function claimOwnership() public {
        require(msg.sender == _NEW_OWNER_, 'InitializableOwnable: INVALID_CLAIM');
        emit OwnershipTransferred(_OWNER_, _NEW_OWNER_);
        _OWNER_ = _NEW_OWNER_;
        _NEW_OWNER_ = address(0);
    }
}

File 20 of 106 : DecimalMath.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

import '@openzeppelin/contracts/math/SafeMath.sol';

/**
 * @title DecimalMath
 *
 * @notice Functions for fixed point number with 18 decimals
 */
library DecimalMath {
    using SafeMath for uint256;

    uint256 internal constant ONE = 10**18;
    uint256 internal constant TWO = 2 * 10**18;
    uint256 internal constant ONE2 = 10**36;

    function mulFloor(uint256 target, uint256 d) internal pure returns (uint256) {
        return target.mul(d) / (10**18);
    }

    function mulCeil(uint256 target, uint256 d) internal pure returns (uint256) {
        return _divCeil(target.mul(d), 10**18);
    }

    function divFloor(uint256 target, uint256 d) internal pure returns (uint256) {
        return target.mul(10**18).div(d);
    }

    function divCeil(uint256 target, uint256 d) internal pure returns (uint256) {
        return _divCeil(target.mul(10**18), d);
    }

    function reciprocalFloor(uint256 target) internal pure returns (uint256) {
        return uint256(10**36).div(target);
    }

    function reciprocalCeil(uint256 target) internal pure returns (uint256) {
        return _divCeil(uint256(10**36), target);
    }

    function _divCeil(uint256 a, uint256 b) private pure returns (uint256) {
        uint256 quotient = a.div(b);
        uint256 remainder = a - quotient * b;
        if (remainder > 0) {
            return quotient + 1;
        } else {
            return quotient;
        }
    }
}

File 21 of 106 : IWooracle.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

/// @title The oracle interface by Woo.Network.
/// @notice update and posted the latest price info by Woo.
interface IWooracle {
    /// @dev the quote token for Wooracle's pricing.
    /// @return the quote token
    function quoteToken() external view returns (address);

    /// @dev the price for the given base token
    /// @param base baseToken address
    /// @return priceNow the current price of base token
    /// @return feasible whether the current price is feasible and valid
    function price(address base) external view returns (uint256 priceNow, bool feasible);

    function getPrice(address base) external view returns (uint256);

    function getSpread(address base) external view returns (uint256);

    function getCoeff(address base) external view returns (uint256);

    /// @dev returns the state for the given base token.
    /// @param base baseToken address
    /// @return priceNow the current price of base token
    /// @return spreadNow the current spread of base token
    /// @return coeffNow the slippage coefficient of base token
    /// @return feasible whether the current state is feasible and valid
    function state(address base)
        external
        view
        returns (
            uint256 priceNow,
            uint256 spreadNow,
            uint256 coeffNow,
            bool feasible
        );

    /// @dev returns the last updated timestamp
    /// @return the last updated timestamp
    function timestamp() external view returns (uint256);

    /// @dev returns whether the base token price is valid.
    /// @param base baseToken address
    /// @return whether the base token price is valid.
    function isFeasible(address base) external view returns (bool);
}

File 22 of 106 : IWooVaultManager.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

/// @title Vault reward manager interface for WooFi Swap.
interface IWooVaultManager {
    event VaultWeightUpdated(address indexed vaultAddr, uint256 weight);
    event RewardDistributed(address indexed vaultAddr, uint256 amount);

    /// @dev Gets the reward weight for the given vault.
    /// @param vaultAddr the vault address
    /// @return The weight of the given vault.
    function vaultWeight(address vaultAddr) external view returns (uint256);

    /// @dev Sets the reward weight for the given vault.
    /// @param vaultAddr the vault address
    /// @param weight the vault weight
    function setVaultWeight(address vaultAddr, uint256 weight) external;

    /// @dev Adds the reward quote amount.
    /// Note: The reward will be stored in this manager contract for
    ///       further weight adjusted distribution.
    /// @param quoteAmount the reward amount in quote token.
    function addReward(uint256 quoteAmount) external;

    /// @dev Pending amount in quote token for the given vault.
    /// @param vaultAddr the vault address
    function pendingReward(address vaultAddr) external view returns (uint256);

    /// @dev All pending amount in quote token.
    /// @return the total pending reward
    function pendingAllReward() external view returns (uint256);

    /// @dev Distributes the reward to all the vaults based on the weights.
    function distributeAllReward() external;

    /// @dev All the vaults
    /// @return the vault address array
    function allVaults() external view returns (address[] memory);

    /// @dev get the quote token address
    /// @return address of quote token
    function quoteToken() external view returns (address);
}

File 23 of 106 : IWooGuardian.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

import '../interfaces/IWooPP.sol';

/// @title Guardian interface to ensure the trading price and volume correct
interface IWooGuardian {
    event ChainlinkRefOracleUpdated(address indexed token, address indexed chainlinkRefOracle);

    /* ----- Main check APIs ----- */

    function checkSwapPrice(
        uint256 price,
        address fromToken,
        address toToken
    ) external view;

    function checkInputAmount(address token, uint256 inputAmount) external view;

    function checkSwapAmount(
        address fromToken,
        address toToken,
        uint256 fromAmount,
        uint256 toAmount
    ) external view;
}

File 24 of 106 : AggregatorV3Interface.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

interface AggregatorV3Interface {
    function decimals() external view returns (uint8);

    function description() external view returns (string memory);

    function version() external view returns (uint256);

    /// getRoundData and latestRoundData should both raise "No data present"
    /// if they do not have data to report, instead of returning unset values
    /// which could be misinterpreted as actual reported values.
    function getRoundData(uint80 _roundId)
        external
        view
        returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        );

    function latestRoundData()
        external
        view
        returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        );
}

File 25 of 106 : IWooPP.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

/// @title Woo private pool for swap.
/// @notice Use this contract to directly interfact with woo's synthetic proactive
///         marketing making pool.
/// @author woo.network
interface IWooPP {
    /* ----- Type declarations ----- */

    /// @dev struct info to store the token info
    struct TokenInfo {
        uint112 reserve; // Token balance
        uint112 threshold; // Threshold for reserve update
        uint32 lastResetTimestamp; // Timestamp for last param update
        uint64 R; // Rebalance coefficient [0, 1]
        uint112 target; // Targeted balance for pricing
        bool isValid; // is this token info valid
    }

    /* ----- Events ----- */

    event StrategistUpdated(address indexed strategist, bool flag);
    event FeeManagerUpdated(address indexed newFeeManager);
    event RewardManagerUpdated(address indexed newRewardManager);
    event WooracleUpdated(address indexed newWooracle);
    event WooGuardianUpdated(address indexed newWooGuardian);
    event ParametersUpdated(address indexed baseToken, uint256 newThreshold, uint256 newR);
    event Withdraw(address indexed token, address indexed to, uint256 amount);
    event WooSwap(
        address indexed fromToken,
        address indexed toToken,
        uint256 fromAmount,
        uint256 toAmount,
        address from,
        address indexed to,
        address rebateTo
    );

    /* ----- External Functions ----- */

    /// @dev Swap baseToken into quoteToken
    /// @param baseToken the base token
    /// @param baseAmount amount of baseToken that user want to swap
    /// @param minQuoteAmount minimum amount of quoteToken that user accept to receive
    /// @param to quoteToken receiver address
    /// @param rebateTo the wallet address for rebate
    /// @return quoteAmount the swapped amount of quote token
    function sellBase(
        address baseToken,
        uint256 baseAmount,
        uint256 minQuoteAmount,
        address to,
        address rebateTo
    ) external returns (uint256 quoteAmount);

    /// @dev Swap quoteToken into baseToken
    /// @param baseToken the base token
    /// @param quoteAmount amount of quoteToken that user want to swap
    /// @param minBaseAmount minimum amount of baseToken that user accept to receive
    /// @param to baseToken receiver address
    /// @param rebateTo the wallet address for rebate
    /// @return baseAmount the swapped amount of base token
    function sellQuote(
        address baseToken,
        uint256 quoteAmount,
        uint256 minBaseAmount,
        address to,
        address rebateTo
    ) external returns (uint256 baseAmount);

    /// @dev Query the amount for selling the base token amount.
    /// @param baseToken the base token to sell
    /// @param baseAmount the amount to sell
    /// @return quoteAmount the swapped quote amount
    function querySellBase(address baseToken, uint256 baseAmount) external view returns (uint256 quoteAmount);

    /// @dev Query the amount for selling the quote token.
    /// @param baseToken the base token to receive (buy)
    /// @param quoteAmount the amount to sell
    /// @return baseAmount the swapped base token amount
    function querySellQuote(address baseToken, uint256 quoteAmount) external view returns (uint256 baseAmount);

    /// @dev get the quote token address (immutable)
    /// @return address of quote token
    function quoteToken() external view returns (address);
}

File 26 of 106 : WooStakingVault.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/Address.sol';
import '@openzeppelin/contracts/utils/Pausable.sol';
import '@openzeppelin/contracts/utils/ReentrancyGuard.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import './libraries/DecimalMath.sol';
import './interfaces/IWooAccessManager.sol';

contract WooStakingVault is ERC20, Ownable, ReentrancyGuard, Pausable {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;
    using DecimalMath for uint256;

    struct UserInfo {
        uint256 reserveAmount; // amount of stakedToken user reverseWithdraw
        uint256 lastReserveWithdrawTime; // keeps track of reverseWithdraw time for potential penalty
    }

    /* ----- Events ----- */

    event Deposit(address indexed user, uint256 depositAmount, uint256 mintShares);
    event ReserveWithdraw(address indexed user, uint256 reserveAmount, uint256 burnShares);
    event Withdraw(address indexed user, uint256 withdrawAmount, uint256 withdrawFee);
    event InstantWithdraw(address indexed user, uint256 withdrawAmount, uint256 withdrawFee);
    event RewardAdded(
        address indexed sender,
        uint256 balanceBefore,
        uint256 sharePriceBefore,
        uint256 balanceAfter,
        uint256 sharePriceAfter
    );

    /* ----- State variables ----- */

    IERC20 public immutable stakedToken;
    mapping(address => uint256) public costSharePrice;
    mapping(address => UserInfo) public userInfo;

    uint256 public totalReserveAmount = 0; // affected by reserveWithdraw and withdraw
    uint256 public withdrawFeePeriod = 7 days;
    uint256 public withdrawFee = 500; // 5% (10000 as denominator)

    address public treasury;
    IWooAccessManager public wooAccessManager;

    /* ----- Constant variables ----- */

    uint256 public constant MAX_WITHDRAW_FEE_PERIOD = 7 days;
    uint256 public constant MAX_WITHDRAW_FEE = 500; // 5% (10000 as denominator)

    constructor(
        address initialStakedToken,
        address initialTreasury,
        address initialWooAccessManager
    )
        public
        ERC20(
            string(abi.encodePacked('Interest Bearing ', ERC20(initialStakedToken).name())),
            string(abi.encodePacked('x', ERC20(initialStakedToken).symbol()))
        )
    {
        require(initialStakedToken != address(0), 'WooStakingVault: initialStakedToken_ZERO_ADDR');
        require(initialTreasury != address(0), 'WooStakingVault: initialTreasury_ZERO_ADDR');
        require(initialWooAccessManager != address(0), 'WooStakingVault: initialWooAccessManager_ZERO_ADDR');

        stakedToken = IERC20(initialStakedToken);
        treasury = initialTreasury;
        wooAccessManager = IWooAccessManager(initialWooAccessManager);
    }

    /* ----- External Functions ----- */

    function deposit(uint256 amount) external nonReentrant whenNotPaused {
        require(amount > 0, 'WooStakingVault: amount_CAN_NOT_BE_ZERO');

        uint256 balanceBefore = balance();
        TransferHelper.safeTransferFrom(address(stakedToken), msg.sender, address(this), amount);
        uint256 balanceAfter = balance();
        amount = balanceAfter.sub(balanceBefore);

        uint256 xTotalSupply = totalSupply();
        uint256 shares = xTotalSupply == 0 ? amount : amount.mul(xTotalSupply).div(balanceBefore);

        // must be executed before _mint
        _updateCostSharePrice(amount, shares);

        _mint(msg.sender, shares);

        emit Deposit(msg.sender, amount, shares);
    }

    function reserveWithdraw(uint256 shares) external nonReentrant {
        require(shares > 0, 'WooStakingVault: shares_CAN_NOT_BE_ZERO');
        require(shares <= balanceOf(msg.sender), 'WooStakingVault: shares exceed balance');

        uint256 currentReserveAmount = shares.mulFloor(getPricePerFullShare()); // calculate reserveAmount before _burn
        uint256 poolBalance = balance();
        if (poolBalance < currentReserveAmount) {
            // in case reserve amount exceeds pool balance
            currentReserveAmount = poolBalance;
        }
        _burn(msg.sender, shares);

        totalReserveAmount = totalReserveAmount.add(currentReserveAmount);

        UserInfo storage user = userInfo[msg.sender];
        user.reserveAmount = user.reserveAmount.add(currentReserveAmount);
        user.lastReserveWithdrawTime = block.timestamp;

        emit ReserveWithdraw(msg.sender, currentReserveAmount, shares);
    }

    function withdraw() external nonReentrant {
        UserInfo storage user = userInfo[msg.sender];

        uint256 withdrawAmount = user.reserveAmount;
        require(withdrawAmount > 0, 'WooStakingVault: withdrawAmount_CAN_NOT_BE_ZERO');

        uint256 fee = 0;
        if (block.timestamp < user.lastReserveWithdrawTime.add(withdrawFeePeriod)) {
            fee = withdrawAmount.mul(withdrawFee).div(10000);
            if (fee > 0) {
                TransferHelper.safeTransfer(address(stakedToken), treasury, fee);
            }
        }
        uint256 withdrawAmountAfterFee = withdrawAmount.sub(fee);

        user.reserveAmount = 0;
        totalReserveAmount = totalReserveAmount.sub(withdrawAmount);
        TransferHelper.safeTransfer(address(stakedToken), msg.sender, withdrawAmountAfterFee);

        emit Withdraw(msg.sender, withdrawAmount, fee);
    }

    function instantWithdraw(uint256 shares) external nonReentrant {
        require(shares > 0, 'WooStakingVault: shares_CAN_NOT_BE_ZERO');
        require(shares <= balanceOf(msg.sender), 'WooStakingVault: shares exceed balance');

        uint256 withdrawAmount = shares.mulFloor(getPricePerFullShare());

        uint256 poolBalance = balance();
        if (poolBalance < withdrawAmount) {
            withdrawAmount = poolBalance;
        }

        _burn(msg.sender, shares);

        uint256 fee = wooAccessManager.isZeroFeeVault(msg.sender) ? 0 : withdrawAmount.mul(withdrawFee).div(10000);
        if (fee > 0) {
            TransferHelper.safeTransfer(address(stakedToken), treasury, fee);
        }
        uint256 withdrawAmountAfterFee = withdrawAmount.sub(fee);

        TransferHelper.safeTransfer(address(stakedToken), msg.sender, withdrawAmountAfterFee);

        emit InstantWithdraw(msg.sender, withdrawAmount, fee);
    }

    function addReward(uint256 amount) external whenNotPaused {
        // Note: this method is only for adding Woo reward. Users may not call this method to deposit woo token.
        require(amount > 0, 'WooStakingVault: amount_CAN_NOT_BE_ZERO');
        uint256 balanceBefore = balance();
        uint256 sharePriceBefore = getPricePerFullShare();
        TransferHelper.safeTransferFrom(address(stakedToken), msg.sender, address(this), amount);
        uint256 balanceAfter = balance();
        uint256 sharePriceAfter = getPricePerFullShare();

        emit RewardAdded(msg.sender, balanceBefore, sharePriceBefore, balanceAfter, sharePriceAfter);
    }

    /* ----- Public Functions ----- */

    function getPricePerFullShare() public view returns (uint256) {
        if (totalSupply() == 0) {
            return 1e18;
        }
        return balance().divFloor(totalSupply());
    }

    function balance() public view returns (uint256) {
        return stakedToken.balanceOf(address(this)).sub(totalReserveAmount);
    }

    /* ----- Private Functions ----- */

    function _updateCostSharePrice(uint256 amount, uint256 shares) private {
        uint256 sharesBefore = balanceOf(msg.sender);
        uint256 costBefore = costSharePrice[msg.sender];
        uint256 costAfter = (sharesBefore.mul(costBefore).add(amount.mul(1e18))).div(sharesBefore.add(shares));

        costSharePrice[msg.sender] = costAfter;
    }

    /* ----- Admin Functions ----- */

    /// @notice Sets withdraw fee period
    /// @dev Only callable by the contract owner.
    function setWithdrawFeePeriod(uint256 newWithdrawFeePeriod) external onlyOwner {
        require(
            newWithdrawFeePeriod <= MAX_WITHDRAW_FEE_PERIOD,
            'WooStakingVault: newWithdrawFeePeriod>MAX_WITHDRAW_FEE_PERIOD'
        );
        withdrawFeePeriod = newWithdrawFeePeriod;
    }

    /// @notice Sets withdraw fee
    /// @dev Only callable by the contract owner.
    function setWithdrawFee(uint256 newWithdrawFee) external onlyOwner {
        require(newWithdrawFee <= MAX_WITHDRAW_FEE, 'WooStakingVault: newWithdrawFee>MAX_WITHDRAW_FEE');
        withdrawFee = newWithdrawFee;
    }

    /// @notice Sets treasury address
    /// @dev Only callable by the contract owner.
    function setTreasury(address newTreasury) external onlyOwner {
        require(newTreasury != address(0), 'WooStakingVault: newTreasury_ZERO_ADDR');
        treasury = newTreasury;
    }

    /// @notice Sets WooAccessManager
    /// @dev Only callable by the contract owner.
    function setWooAccessManager(address newWooAccessManager) external onlyOwner {
        require(newWooAccessManager != address(0), 'WooStakingVault: newWooAccessManager_ZERO_ADDR');
        wooAccessManager = IWooAccessManager(newWooAccessManager);
    }

    /**
        @notice Rescues random funds stuck.
        This method only saves the irrelevant tokens just in case users deposited in mistake.
        It cannot transfer any of user staked tokens.
    */
    function inCaseTokensGetStuck(address stuckToken) external onlyOwner {
        require(stuckToken != address(0), 'WooStakingVault: stuckToken_ZERO_ADDR');
        require(stuckToken != address(stakedToken), 'WooStakingVault: stuckToken_CAN_NOT_BE_stakedToken');

        uint256 amount = IERC20(stuckToken).balanceOf(address(this));
        TransferHelper.safeTransfer(stuckToken, msg.sender, amount);
    }

    /// @notice Pause the contract.
    function pause() external onlyOwner {
        super._pause();
    }

    /// @notice Restart the contract.
    function unpause() external onlyOwner {
        super._unpause();
    }
}

File 27 of 106 : WooRebateManager.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

import './libraries/InitializableOwnable.sol';
import './libraries/DecimalMath.sol';
import './interfaces/IWooracle.sol';
import './interfaces/IWooRebateManager.sol';
import './interfaces/IWooGuardian.sol';
import './interfaces/AggregatorV3Interface.sol';
import './interfaces/IWooAccessManager.sol';

import '@openzeppelin/contracts/utils/ReentrancyGuard.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import '@openzeppelin/contracts/utils/EnumerableSet.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

contract WooRebateManager is InitializableOwnable, ReentrancyGuard, IWooRebateManager {
    using SafeMath for uint256;
    using DecimalMath for uint256;
    using SafeERC20 for IERC20;
    using EnumerableSet for EnumerableSet.AddressSet;

    // Note: this is the percent rate of the total swap fee (not the swap volume)
    // decimal: 18; 1e16 = 1%, 1e15 = 0.1%, 1e14 = 0.01%
    //
    // e.g. suppose:
    //   rebateRate = 1e17 (10%), so the rebate amount is total_swap_fee * 10%.
    mapping(address => uint256) public override rebateRate;
    EnumerableSet.AddressSet private rebateAddressSet;

    // pending rebate amount in quote token
    mapping(address => uint256) public pendingRebate;

    IWooPP private wooPP;

    address public immutable override quoteToken; // USDT
    address public rewardToken; // Any Token

    IWooAccessManager public accessManager;

    /* ----- Modifiers ----- */

    modifier onlyAdmin() {
        require(msg.sender == _OWNER_ || accessManager.isRebateAdmin(msg.sender), 'WooRebateManager: NOT_ADMIN');
        _;
    }

    constructor(
        address newQuoteToken,
        address newRewardToken,
        address newAccessManager
    ) public {
        require(newQuoteToken != address(0), 'WooRebateManager: INVALID_QUOTE');
        require(newRewardToken != address(0), 'WooRebateManager: INVALID_REWARD_TOKEN');
        initOwner(msg.sender);
        quoteToken = newQuoteToken;
        rewardToken = newRewardToken;
        accessManager = IWooAccessManager(newAccessManager);
    }

    function pendingRebateInUSDT(address brokerAddr) external view override returns (uint256) {
        require(brokerAddr != address(0), 'WooRebateManager: zero_brokerAddr');
        return pendingRebate[brokerAddr];
    }

    function pendingRebateInWOO(address brokerAddr) external view override returns (uint256) {
        require(brokerAddr != address(0), 'WooRebateManager: zero_brokerAddr');
        return wooPP.querySellQuote(rewardToken, pendingRebate[brokerAddr]);
    }

    function claimRebate() external override nonReentrant {
        require(pendingRebate[msg.sender] > 0, 'WooRebateManager: NO_pending_rebate');

        uint256 quoteAmount = pendingRebate[msg.sender];
        // Note: set the pending rebate early to make external interactions safe.
        pendingRebate[msg.sender] = 0;

        uint256 rewardAmount;
        if (rewardToken == quoteToken) {
            rewardAmount = quoteAmount;
        } else {
            uint256 balanceBefore = IERC20(rewardToken).balanceOf(address(this));
            TransferHelper.safeApprove(quoteToken, address(wooPP), quoteAmount);
            rewardAmount = wooPP.sellQuote(rewardToken, quoteAmount, 0, address(this), address(0));
            uint256 balanceAfter = IERC20(rewardToken).balanceOf(address(this));
            require(balanceAfter.sub(balanceBefore) >= rewardAmount, 'WooRebateManager: woo amount INSUFF');
        }

        if (rewardAmount > 0) {
            TransferHelper.safeTransfer(rewardToken, msg.sender, rewardAmount);
        }

        emit ClaimReward(msg.sender, rewardAmount);
    }

    function allRebateAddresses() external view returns (address[] memory) {
        address[] memory rebateAddresses = new address[](rebateAddressSet.length());
        for (uint256 i = 0; i < rebateAddressSet.length(); ++i) {
            rebateAddresses[i] = rebateAddressSet.at(i);
        }
        return rebateAddresses;
    }

    function allRebateAddressesLength() external view returns (uint256) {
        return rebateAddressSet.length();
    }

    /* ----- Admin Functions ----- */

    function addRebate(address brokerAddr, uint256 amountInUSDT) external override nonReentrant onlyAdmin {
        if (brokerAddr == address(0)) {
            return;
        }
        pendingRebate[brokerAddr] = amountInUSDT.add(pendingRebate[brokerAddr]);
    }

    function setRebateRate(address brokerAddr, uint256 rate) external override onlyAdmin {
        require(brokerAddr != address(0), 'WooRebateManager: brokerAddr_ZERO_ADDR');
        require(rate <= 1e18, 'WooRebateManager: INVALID_USER_REWARD_RATE'); // rate <= 100%
        rebateRate[brokerAddr] = rate;
        if (rate == 0) {
            rebateAddressSet.remove(brokerAddr);
        } else {
            rebateAddressSet.add(brokerAddr);
        }
        emit RebateRateUpdated(brokerAddr, rate);
    }

    function setWooPP(address newWooPP) external onlyAdmin {
        require(newWooPP != address(0), 'WooRebateManager: wooPP_ZERO_ADDR');
        wooPP = IWooPP(newWooPP);
        require(wooPP.quoteToken() == quoteToken, 'WooRebateManager: wooPP_quote_token_INVALID');
    }

    function setAccessManager(address newAccessManager) external onlyOwner {
        require(newAccessManager != address(0), 'WooRebateManager: newAccessManager_ZERO_ADDR');
        accessManager = IWooAccessManager(newAccessManager);
    }

    function setRewardToken(address newRewardToken) external onlyAdmin {
        require(newRewardToken != address(0), 'WooRebateManager: rewardToken_ZERO_ADDR');
        rewardToken = newRewardToken;
    }

    function emergencyWithdraw(address token, address to) public onlyOwner {
        require(token != address(0), 'WooRebateManager: token_ZERO_ADDR');
        require(to != address(0), 'WooRebateManager: to_ZERO_ADDR');
        uint256 amount = IERC20(token).balanceOf(address(this));
        TransferHelper.safeTransfer(token, to, amount);
        emit Withdraw(token, to, amount);
    }
}

File 28 of 106 : IWooRebateManager.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

/// @title Rebate manager interface for WooFi Swap.
/// @notice this is for swap rebate or potential incentive program

interface IWooRebateManager {
    event Withdraw(address indexed token, address indexed to, uint256 amount);
    event RebateRateUpdated(address indexed brokerAddr, uint256 rate);
    event ClaimReward(address indexed brokerAddr, uint256 amount);

    /// @dev Gets the rebate rate for the given broker.
    /// Note: decimal: 18;  1e16 = 1%, 1e15 = 0.1%, 1e14 = 0.01%
    /// @param brokerAddr the address for rebate
    /// @return The rebate rate (decimal: 18; 1e16 = 1%, 1e15 = 0.1%, 1e14 = 0.01%)
    function rebateRate(address brokerAddr) external view returns (uint256);

    /// @dev set the rebate rate
    /// @param brokerAddr the rebate address
    /// @param rate the rebate rate
    function setRebateRate(address brokerAddr, uint256 rate) external;

    /// @dev adds the pending reward for the given user.
    /// @param brokerAddr the address for rebate
    /// @param amountInUSD the pending reward amount
    function addRebate(address brokerAddr, uint256 amountInUSD) external;

    /// @dev Pending amount in $woo.
    /// @param brokerAddr the address for rebate
    function pendingRebateInWOO(address brokerAddr) external view returns (uint256);

    /// @dev Pending amount in $woo.
    /// @param brokerAddr the address for rebate
    function pendingRebateInUSDT(address brokerAddr) external view returns (uint256);

    /// @dev Claims the reward ($woo token will be distributed)
    function claimRebate() external;

    /// @dev get the quote token address
    /// @return address of quote token
    function quoteToken() external view returns (address);
}

File 29 of 106 : LendingVault.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/utils/EnumerableSet.sol';
import '@openzeppelin/contracts/utils/Pausable.sol';
import '@openzeppelin/contracts/utils/ReentrancyGuard.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import './interfaces/ILendingVault.sol';
import './interfaces/IWETH.sol';
import './interfaces/IStrategy.sol';
import './interfaces/IWooAccessManager.sol';

/// @title WOOFi LendingVault.
contract LendingVault is ERC20, Ownable, ReentrancyGuard, Pausable, ILendingVault {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;
    using EnumerableSet for EnumerableSet.AddressSet;

    // *************** //
    // *** STRUCTS *** //
    // *************** //

    struct UserInfo {
        // 在`requestWithdraw()`加, `cancelRequestWithdraw()`和`weeklySettle()`清零
        uint256 requestedShares;
        // 在`weeklySettle()`加, `withdraw()`清零
        uint256 settledAssets;
        // 仅在`deposit()`时触发`_updateCostSharePrice()`来更新
        uint256 costSharePrice;
    }

    struct StrategyCandidate {
        address implementation;
        uint256 proposedTime;
    }

    // ******************************** //
    // *** CONSTANTS AND IMMUTABLES *** //
    // ******************************** //

    address public immutable weth;
    address public immutable override asset;

    uint256 private constant INTEREST_RATE_COEFF = 31536000; // 365 * 24 * 3600
    uint256 public constant MAX_PERCENTAGE = 10000; // 100%

    // ***************** //
    // *** VARIABLES *** //
    // ***************** //

    // 第三方复投策略(如Alpaca)
    address public strategy;
    // 在`instantWithdraw()`时抽取fees并transfer到`treasury`
    address public treasury;
    address public wooAccessManager;

    // `instantWithdraw()`的剩余限额, 每次`instantWithdraw()`都会减少
    // 每日执行`setDailyMaxInstantWithdrawAssets()`(仅)加大限额
    // 每周执行`setWeeklyMaxInstantWithdrawAssets()`重置额度
    uint256 public leftInstantWithdrawAssets;
    // `instantWithdraw()`的最大限额, 用于给前端展示
    // 每日执行`setDailyMaxInstantWithdrawAssets()`(仅)加大上限
    // 每周执行`setWeeklyMaxInstantWithdrawAssets()`更新上限
    uint256 public maxInstantWithdrawAssets;

    // 最大允许Market Maker借用`totalAssets()`的百分比
    uint256 public allowBorrowPercentage = 9000; // 90%
    // `instantWithdraw()`抽取fees的百分比
    uint256 public instantWithdrawFeePercentage = 30; // 0.3%
    // 未来利率百分比, 通过`setInterestRatePercentage()`更新, 更新之前需要`settleInterest()`结算过去的利息
    uint256 public interestRatePercentage; // 由Market Maker调用`setInterestRatePercentage()`更新

    // 总结算金额(这部分不再获得利息收益), 包括未还部分, 在`weeklySettle()`加, `withdraw()`减
    uint256 public totalSettledAssets;
    // Market Maker已还的结算金额, 在`repay()`参数repaySettle为true时表示还debtSettledAssets(联系`totalDebtSettledAssets`),
    // 即在`repay()`处加, 在`withdraw()`减(因为用户取了已经settle的金额)
    uint256 public totalRepaySettledAssets;
    // Market Maker欠的结算金额, 在`weeklySettle()`需要还款时加, `repay`参数repaySettle为true时减,
    // 当`repay()`时还完全部debt(即totalDebtSettledAssets等于0)时, 更新`instantWithdraw()`的每周新限额
    uint256 public totalDebtSettledAssets;
    // Market Maker总借款金额, 在`borrow()`加, `repay()`和`weeklySettle()`需要还款时减
    uint256 public totalBorrowedAssets;
    // 总利息金额(不包括已经结算掉的利率), 仅在`weeklySettle()`加(每周结算完后将没有request的用户利息加上)
    uint256 public totalInterestAssets;
    // 本周利息金额, 在`settleInterest()`加, `weeklySettle()`清零
    uint256 public weeklyInterestAssets;

    // 记录上次执行`settleInterest()`的时间戳
    uint256 public lastSettleInterestTimestamp;
    // 记录上次执行`weeklySettle()`的时间戳
    uint256 public lastWeeklySettleTimestamp;

    uint256 public weeklySettleDiff = 6.5 days; // 561600秒

    uint256 public approvalDelay = 48 hours;

    bool public allowRequestWithdraw = true;

    StrategyCandidate public strategyCandidate;

    EnumerableSet.AddressSet private requestUsers;
    mapping(address => UserInfo) public userInfo;

    // ******************* //
    // *** CONSTRUCTOR *** //
    // ******************* //

    constructor(address _weth, address _asset)
        public
        ERC20(
            string(abi.encodePacked('WOOFi ', ERC20(_asset).name())),
            string(abi.encodePacked('w ', ERC20(_asset).symbol()))
        )
    {
        weth = _weth;
        asset = _asset;
        // solhint-disable-next-line not-rely-on-time
        lastSettleInterestTimestamp = block.timestamp;
        // solhint-disable-next-line not-rely-on-time
        lastWeeklySettleTimestamp = block.timestamp;
    }

    // ***************** //
    // *** MODIFIERS *** //
    // ***************** //

    modifier onlyAdmin() {
        require(
            owner() == msg.sender || IWooAccessManager(wooAccessManager).isVaultAdmin(msg.sender),
            'LendingVault: Not admin'
        );
        _;
    }

    // ************************ //
    // *** PUBLIC FUNCTIONS *** //
    // ************************ //

    /// @inheritdoc ILendingVault
    function totalAssets() public view override returns (uint256 totalManagedAssets) {
        uint256 localAssets_ = localAssets();
        uint256 strategyAssets = IStrategy(strategy).balanceOf();
        uint256 wooPoolAssets = totalBorrowedAssets.add(totalInterestAssets).add(weeklyInterestAssets);

        totalManagedAssets = localAssets_.add(strategyAssets).add(wooPoolAssets);
    }

    /// @inheritdoc ILendingVault
    function localAssets() public view override returns (uint256 assets) {
        assets = IERC20(asset).balanceOf(address(this)).sub(totalRepaySettledAssets);
    }

    /// @inheritdoc ILendingVault
    function getPricePerFullShare() public view override returns (uint256 sharePrice) {
        sharePrice = _convertToAssets(1e18, false);
    }

    /// @inheritdoc ILendingVault
    function convertToShares(uint256 assets) public view override returns (uint256 shares) {
        shares = _convertToShares(assets, false);
    }

    /// @inheritdoc ILendingVault
    function convertToAssets(uint256 shares) public view override returns (uint256 assets) {
        assets = _convertToAssets(shares, false);
    }

    /// @inheritdoc ILendingVault
    function maxDeposit(address) public view override returns (uint256 maxAssets) {
        maxAssets = paused() ? 0 : uint256(-1);
    }

    /// @inheritdoc ILendingVault
    function maxWithdraw(address user) public view override returns (uint256 maxAssets) {
        UserInfo memory userInfo_ = userInfo[user];
        maxAssets = userInfo_.settledAssets;
    }

    /// @inheritdoc ILendingVault
    function maxRequestWithdraw(address user) public view override returns (uint256 maxAssets) {
        maxAssets = _convertToAssets(balanceOf(user), false);
    }

    /// @inheritdoc ILendingVault
    function maxInstantWithdraw(address user) public view override returns (uint256 maxAssets) {
        uint256 assets = _convertToAssets(balanceOf(user), false);
        maxAssets = leftInstantWithdrawAssets > assets ? assets : leftInstantWithdrawAssets;
    }

    /// @inheritdoc ILendingVault
    function isStrategyActive() public view override returns (bool active) {
        active = strategy != address(0) && !IStrategy(strategy).paused();
    }

    // ************************** //
    // *** EXTERNAL FUNCTIONS *** //
    // ************************** //

    /// @inheritdoc ILendingVault
    function deposit(uint256 assets) external payable override nonReentrant whenNotPaused returns (uint256 shares) {
        require((shares = _convertToShares(assets, false)) != 0, 'LendingVault: Zero shares');

        uint256 assetsBefore = localAssets();
        if (asset == weth) {
            require(msg.value == assets, 'LendingVault: msg.value insufficient');
            IWETH(weth).deposit{value: msg.value}();
        } else {
            require(msg.value == 0, 'LendingVault: msg.value invalid');
            TransferHelper.safeTransferFrom(asset, msg.sender, address(this), assets);
        }
        uint256 assetsAfter = localAssets();
        require(assetsAfter.sub(assetsBefore) >= assets, 'LendingVault: assets not enough');

        _updateCostSharePrice(msg.sender, assets, shares);
        _mint(msg.sender, shares);
        _farmAtStrategy();

        emit Deposit(msg.sender, assets, shares);
    }

    /// @inheritdoc ILendingVault
    function withdraw() external override nonReentrant {
        UserInfo storage userInfo_ = userInfo[msg.sender];
        uint256 assets = userInfo_.settledAssets;
        require(totalSettledAssets >= assets, 'LendingVault: Not settle, please wait');
        require(totalRepaySettledAssets >= assets, 'LendingVault: Not repay, please wait');
        userInfo_.settledAssets = 0;
        totalSettledAssets = totalSettledAssets.sub(assets);
        totalRepaySettledAssets = totalRepaySettledAssets.sub(assets);

        if (asset == weth) {
            IWETH(weth).withdraw(assets);
            TransferHelper.safeTransferETH(msg.sender, assets);
        } else {
            TransferHelper.safeTransfer(asset, msg.sender, assets);
        }

        emit Withdraw(msg.sender, assets);
    }

    /// @inheritdoc ILendingVault
    function requestWithdraw(uint256 assets) external override nonReentrant returns (uint256 shares) {
        require(allowRequestWithdraw, 'LendingVault: Not allow yet, please wait');
        require(assets <= maxRequestWithdraw(msg.sender), 'LendingVault: msg.sender assets insufficient');
        require((shares = _convertToShares(assets, true)) != 0, 'LendingVault: Zero shares');

        // 将用户的shares存入Vault, 让Vault暂时保管(不会burn, 在`weeklySettle()`时才会burn)
        TransferHelper.safeTransferFrom(address(this), msg.sender, address(this), shares);

        UserInfo storage userInfo_ = userInfo[msg.sender];
        userInfo_.requestedShares = userInfo_.requestedShares.add(shares);
        requestUsers.add(msg.sender);

        // 此处`assets`仅表示request时存入的shares价值多少assets, 最终得到的assets需在`weeklySettle()`后才准确
        emit RequestWithdraw(msg.sender, assets, shares);
    }

    /// @inheritdoc ILendingVault
    function cancelRequestWithdraw() external override nonReentrant returns (uint256 shares) {
        require(allowRequestWithdraw, 'LendingVault: Not allow yet, please wait');
        UserInfo storage userInfo_ = userInfo[msg.sender];
        require((shares = userInfo_.requestedShares) != 0, 'LendingVault: Zero shares');

        userInfo_.requestedShares = 0;
        requestUsers.remove(msg.sender);

        TransferHelper.safeTransfer(address(this), msg.sender, shares);

        uint256 assets = _convertToAssets(shares, false);
        // `assets` is not the final result, share price will increase until next epoch
        emit CancelRequestWithdraw(msg.sender, assets, shares);
    }

    /// @inheritdoc ILendingVault
    function instantWithdraw(uint256 assets) external override nonReentrant returns (uint256 shares) {
        require(assets <= maxInstantWithdraw(msg.sender), 'LendingVault: msg.sender assets insufficient');
        require((shares = _convertToShares(assets, true)) != 0, 'LendingVault: Zero shares');

        if (isStrategyActive()) {
            IStrategy(strategy).beforeWithdraw();
        }

        _burn(msg.sender, shares);

        _withdrawStrategyIfNeed(assets);
        require(assets <= localAssets(), 'LendingVault: assets exceed');

        leftInstantWithdrawAssets = leftInstantWithdrawAssets.sub(assets);

        uint256 fees;
        if (IWooAccessManager(wooAccessManager).isZeroFeeVault(msg.sender)) {
            fees = 0;
        } else {
            fees = assets.mul(instantWithdrawFeePercentage).div(MAX_PERCENTAGE);
        }

        if (asset == weth) {
            IWETH(weth).withdraw(assets);
            TransferHelper.safeTransferETH(msg.sender, assets.sub(fees));
            if (fees > 0) {
                TransferHelper.safeTransferETH(treasury, fees);
            }
        } else {
            TransferHelper.safeTransfer(asset, msg.sender, assets.sub(fees));
            if (fees > 0) {
                TransferHelper.safeTransfer(asset, treasury, fees);
            }
        }

        emit InstantWithdraw(msg.sender, assets, shares, fees);
    }

    // *********************** //
    // *** ADMIN FUNCTIONS *** //
    // *********************** //

    function settleInterest() public onlyAdmin {
        // solhint-disable-next-line not-rely-on-time
        uint256 currentSettleInterestTimestamp = block.timestamp;
        require(currentSettleInterestTimestamp >= lastSettleInterestTimestamp, 'LendingVault: Timestamp exceed');

        uint256 diff = currentSettleInterestTimestamp.sub(lastSettleInterestTimestamp);
        uint256 rate = diff.mul(1e18).mul(interestRatePercentage).div(INTEREST_RATE_COEFF).div(MAX_PERCENTAGE);
        uint256 interestAssets = totalBorrowedAssets.mul(rate).div(1e18);

        weeklyInterestAssets = weeklyInterestAssets.add(interestAssets);
        lastSettleInterestTimestamp = currentSettleInterestTimestamp;

        emit SettleInterest(msg.sender, diff, rate, interestAssets, weeklyInterestAssets);
    }

    /// @notice 不用程序直接触发, 可以通过`weeklySettle()`和`repay()`顺带触发
    function setWeeklyMaxInstantWithdrawAssets() public onlyAdmin returns (uint256 maxAssets) {
        maxAssets = totalAssets().mul(MAX_PERCENTAGE.sub(allowBorrowPercentage)).div(MAX_PERCENTAGE);
        leftInstantWithdrawAssets = maxAssets;
        maxInstantWithdrawAssets = maxAssets;

        emit SetWeeklyMaxInstantWithdrawAssets(maxAssets);
    }

    function setInterestRatePercentage(uint256 _interestRatePercentage) external onlyAdmin {
        require(_interestRatePercentage <= MAX_PERCENTAGE, 'LendingVault: _interestRatePercentage exceed');
        settleInterest();
        interestRatePercentage = _interestRatePercentage;
    }

    function setWeeklySettleDiff(uint256 _weeklySettleDiff) external onlyAdmin {
        weeklySettleDiff = _weeklySettleDiff;
    }

    /// @notice 程序周一UTC0点触发
    function weeklySettle() external onlyAdmin {
        require(lastWeeklySettleTimestamp.add(weeklySettleDiff) < block.timestamp, 'LendingVault: Not ready to settle');

        if (isStrategyActive()) {
            IStrategy(strategy).beforeWithdraw();
        }
        settleInterest();

        uint256 weeklyBurnShares = 0;
        uint256 weeklySettledAssets = 0;
        uint256 length = requestUsers.length();
        for (uint256 i = 0; i < length; i++) {
            address user = requestUsers.at(0);
            UserInfo storage userInfo_ = userInfo[user];
            uint256 shares = userInfo_.requestedShares;
            weeklyBurnShares = weeklyBurnShares.add(shares);
            uint256 assets = _convertToAssets(shares, false);
            userInfo_.requestedShares = 0;
            userInfo_.settledAssets = userInfo_.settledAssets.add(assets);
            weeklySettledAssets = weeklySettledAssets.add(assets);
            totalSettledAssets = totalSettledAssets.add(assets);
            requestUsers.remove(user);
            emit WeeklySettle(msg.sender, user, assets, shares);
        }
        uint256 totalAssets_ = totalAssets();
        // 将`weeklyInterestAssets`分成两部分, request部分的利息已经在上述for循环里结算给用户(这部分不会再享受到后续利息)
        // 而剩下的部分`leftInterestAssets`将累计到`totalInterestAssets`, 然后清空`weeklyInterestAssets`并burn掉已结算用户的总shares(等于总request的shares)
        uint256 leftInterestAssets = weeklyInterestAssets.mul(totalAssets_.sub(weeklySettledAssets)).div(totalAssets_);
        totalInterestAssets = totalInterestAssets.add(leftInterestAssets);
        weeklyInterestAssets = 0;
        _burn(address(this), weeklyBurnShares);

        totalAssets_ = totalAssets();
        uint256 allowBorrowAssets = totalAssets_.mul(allowBorrowPercentage).div(MAX_PERCENTAGE);

        if (weeklySettledAssets > allowBorrowAssets.sub(totalBorrowedAssets)) {
            uint256 debtSettledAssets = weeklySettledAssets.sub(totalAssets_.sub(totalBorrowedAssets));
            totalBorrowedAssets = totalBorrowedAssets.sub(debtSettledAssets);
            // 需要还款时(有debt)不能执行`setWeeklyMaxInstantWithdrawAssets()`
            totalDebtSettledAssets = totalDebtSettledAssets.add(debtSettledAssets);
        } else {
            // 不需要还款时可以直接执行`setWeeklyMaxInstantWithdrawAssets()`, 因为Vault有足够的金额给用户进行新一周的`instantWithdraw()`
            setWeeklyMaxInstantWithdrawAssets();
        }

        lastWeeklySettleTimestamp = block.timestamp;
    }

    function borrow(uint256 assets) external onlyAdmin {
        if (isStrategyActive()) {
            IStrategy(strategy).beforeWithdraw();
        }

        uint256 allowBorrowAssets = totalAssets().mul(allowBorrowPercentage).div(MAX_PERCENTAGE);
        require(totalBorrowedAssets.add(assets) <= allowBorrowAssets, 'LendingVault: assets exceed');

        _withdrawStrategyIfNeed(assets);
        require(assets <= localAssets(), 'LendingVault: assets exceed');

        settleInterest();
        totalBorrowedAssets = totalBorrowedAssets.add(assets);
        TransferHelper.safeTransfer(asset, msg.sender, assets);

        emit Borrow(assets);
    }

    function repay(uint256 assets, bool repaySettle) external onlyAdmin {
        if (assets > 0) {
            require(
                (!repaySettle && totalBorrowedAssets >= assets) || (repaySettle && totalDebtSettledAssets >= assets),
                'LendingVault: repaySettle error or assets too much'
            );

            uint256 assetsBefore = localAssets();
            TransferHelper.safeTransferFrom(asset, msg.sender, address(this), assets);
            uint256 assetsAfter = localAssets();
            require(assetsAfter.sub(assetsBefore) >= assets, 'LendingVault: assets not enough');

            if (repaySettle) {
                totalRepaySettledAssets = totalRepaySettledAssets.add(assets);
                totalDebtSettledAssets = totalDebtSettledAssets.sub(assets);
                // 还清欠款后需要更新`instantWithdraw()`每周新限额
                if (totalDebtSettledAssets == 0) {
                    setWeeklyMaxInstantWithdrawAssets();
                }
            } else {
                totalBorrowedAssets = totalBorrowedAssets.sub(assets);
                _farmAtStrategy();
            }

            emit Repay(assets, repaySettle);
        }
    }

    function setTreasury(address _treasury) external onlyAdmin {
        require(_treasury != address(0), 'LendingVault: _treasury not set');
        treasury = _treasury;
    }

    function setWooAccessManager(address _wooAccessManager) external onlyAdmin {
        require(_wooAccessManager != address(0), 'LendingVault: _wooAccessManager not set');
        wooAccessManager = _wooAccessManager;
    }

    /// @notice 程序每天UTC0点更新(除了周一UTC0点)
    function setDailyMaxInstantWithdrawAssets() external onlyAdmin returns (uint256 maxAssets) {
        maxAssets = totalAssets().mul(MAX_PERCENTAGE.sub(allowBorrowPercentage)).div(MAX_PERCENTAGE);
        if (maxAssets > maxInstantWithdrawAssets) {
            uint256 increaseAssets = maxAssets.sub(maxInstantWithdrawAssets);
            leftInstantWithdrawAssets = leftInstantWithdrawAssets.add(increaseAssets);
            maxInstantWithdrawAssets = maxAssets;
        }

        emit SetDailyMaxInstantWithdrawAssets(maxInstantWithdrawAssets, leftInstantWithdrawAssets, maxAssets);
    }

    function setAllowBorrowPercentage(uint256 _allowBorrowPercentage) external onlyAdmin {
        require(_allowBorrowPercentage <= MAX_PERCENTAGE, 'LendingVault: _allowBorrowPercentage exceed');
        require(
            totalBorrowedAssets <= localAssets().mul(_allowBorrowPercentage).div(MAX_PERCENTAGE),
            'LendingVault: _allowBorrowPercentage too small'
        );
        allowBorrowPercentage = _allowBorrowPercentage;
    }

    function setInstantWithdrawFeePercentage(uint256 _instantWithdrawFeePercentage) external onlyAdmin {
        require(_instantWithdrawFeePercentage <= MAX_PERCENTAGE, 'LendingVault: _instantWithdrawFeePercentage exceed');
        instantWithdrawFeePercentage = _instantWithdrawFeePercentage;
    }

    function setApprovalDelay(uint256 _approvalDelay) external onlyAdmin {
        approvalDelay = _approvalDelay;
    }

    function setAllowRequestWithdraw(bool _allowRequestWithdraw) external onlyAdmin {
        allowRequestWithdraw = _allowRequestWithdraw;
    }

    function setupStrategy(address _strategy) external onlyAdmin {
        require(_strategy != address(0), 'LendingVault: _strategy not set');
        require(address(strategy) == address(0), 'LendingVault: strategy already set');
        require(address(this) == IStrategy(_strategy).vault(), 'LendingVault: _strategy vault invalid');
        require(asset == IStrategy(_strategy).want(), 'LendingVault: _strategy want invalid');
        strategy = _strategy;

        emit UpgradeStrategy(_strategy);
    }

    function proposeStrat(address _strategy) external onlyAdmin {
        require(address(this) == IStrategy(_strategy).vault(), 'LendingVault: _strategy vault invalid');
        require(asset == IStrategy(_strategy).want(), 'LendingVault: _strategy want invalid');
        // solhint-disable-next-line not-rely-on-time
        strategyCandidate = StrategyCandidate({implementation: _strategy, proposedTime: block.timestamp});

        emit NewStrategyCandidate(_strategy);
    }

    function upgradeStrat() external onlyAdmin {
        require(strategyCandidate.implementation != address(0), 'LendingVault: No candidate');
        // solhint-disable-next-line not-rely-on-time
        require(strategyCandidate.proposedTime.add(approvalDelay) < block.timestamp, 'LendingVault: Time invalid');

        IStrategy(strategy).retireStrat();
        strategy = strategyCandidate.implementation;
        strategyCandidate.implementation = address(0);
        strategyCandidate.proposedTime = 5000000000; // 100+ years to ensure proposedTime check

        _farmAtStrategy();

        emit UpgradeStrategy(strategyCandidate.implementation);
    }

    function inCaseTokensGetStuck(address token) external onlyAdmin {
        require(token != asset, 'LendingVault: token not allow');
        require(token != address(0), 'LendingVault: token not set');
        uint256 tokenBal = IERC20(token).balanceOf(address(this));
        if (tokenBal > 0) {
            TransferHelper.safeTransfer(token, msg.sender, tokenBal);
        }
    }

    function inCaseNativeTokensGetStuck() external onlyAdmin {
        // Vault never needs native tokens to do the yield farming,
        // this native token balance indicates a user's incorrect transfer.
        uint256 nativeBal = address(this).balance;
        if (nativeBal > 0) {
            TransferHelper.safeTransferETH(msg.sender, nativeBal);
        }
    }

    // ************************** //
    // *** INTERNAL FUNCTIONS *** //
    // ************************** //

    function _convertToAssets(uint256 shares, bool roundUp) internal view returns (uint256 assets) {
        uint256 totalSupply_ = totalSupply();
        if (totalSupply_ == 0) {
            assets = shares;
        } else {
            uint256 totalAssets_ = totalAssets();
            assets = shares.mul(totalAssets_).div(totalSupply_);
            if (roundUp && assets.mul(totalSupply_).div(totalAssets_) < shares) {
                assets = assets.add(1);
            }
        }
    }

    function _convertToShares(uint256 assets, bool roundUp) internal view returns (uint256 shares) {
        uint256 totalSupply_ = totalSupply();
        if (totalSupply_ == 0) {
            shares = assets;
        } else {
            uint256 totalAssets_ = totalAssets();
            shares = assets.mul(totalSupply_).div(totalAssets_);
            if (roundUp && shares.mul(totalAssets_).div(totalSupply_) < assets) {
                shares = shares.add(1);
            }
        }
    }

    function _updateCostSharePrice(
        address user,
        uint256 assets,
        uint256 shares
    ) internal {
        uint256 sharesBefore = balanceOf(user);
        UserInfo storage userInfo_ = userInfo[user];
        uint256 costBefore = userInfo_.costSharePrice;

        userInfo_.costSharePrice = (sharesBefore.mul(costBefore).add(assets.mul(1e18))).div(sharesBefore.add(shares));
    }

    function _farmAtStrategy() internal {
        if (isStrategyActive()) {
            uint256 localAssets_ = localAssets();
            TransferHelper.safeTransfer(asset, strategy, localAssets_);
            IStrategy(strategy).deposit();
        }
    }

    function _withdrawStrategyIfNeed(uint256 assets) internal {
        uint256 localAssets_ = localAssets();
        if (localAssets_ < assets) {
            uint256 withdrawAmount = assets.sub(localAssets_);
            require(isStrategyActive(), 'LendingVault: Strategy inactive');
            IStrategy(strategy).withdraw(withdrawAmount);
        }
    }

    // ************************** //
    // *** CALLBACK FUNCTIONS *** //
    // ************************** //

    // solhint-disable-next-line no-empty-blocks
    receive() external payable {}
}

File 30 of 106 : VaultV2.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/ReentrancyGuard.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import '../interfaces/IStrategy.sol';
import '../interfaces/IWETH.sol';
import '../interfaces/IWooAccessManager.sol';
import '../interfaces/IVaultV2.sol';

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

*
* MIT License
* ===========
*
* Copyright (c) 2020 WooTrade
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* 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 SOFTWARE.
*/
contract WOOFiVaultV2 is IVaultV2, ERC20, Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    struct StratCandidate {
        address implementation;
        uint256 proposedTime;
    }

    /* ----- State Variables ----- */

    address public immutable override want;

    IWooAccessManager public immutable accessManager;

    IStrategy public strategy;
    StratCandidate public stratCandidate;

    uint256 public approvalDelay = 48 hours;

    mapping(address => uint256) public costSharePrice;

    event NewStratCandidate(address indexed implementation);
    event UpgradeStrat(address indexed implementation);

    /* ----- Constant Variables ----- */

    // WBNB: https://bscscan.com/token/0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c
    // WAVAX: https://snowtrace.io/address/0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7
    address public immutable override weth;

    constructor(
        address _weth,
        address _want,
        address _accessManager
    )
        public
        ERC20(
            string(abi.encodePacked('WOOFi Earn ', ERC20(_want).name())),
            string(abi.encodePacked('we', ERC20(_want).symbol()))
        )
    {
        require(_weth != address(0), 'WOOFiVaultV2: weth_ZERO_ADDR');
        require(_want != address(0), 'WOOFiVaultV2: want_ZERO_ADDR');
        require(_accessManager != address(0), 'WOOFiVaultV2: accessManager_ZERO_ADDR');

        weth = _weth;
        want = _want;
        accessManager = IWooAccessManager(_accessManager);
    }

    modifier onlyAdmin() {
        require(owner() == msg.sender || accessManager.isVaultAdmin(msg.sender), 'WOOFiVaultV2: NOT_ADMIN');
        _;
    }

    /* ----- External Functions ----- */

    function deposit(uint256 amount) public payable override nonReentrant {
        require(amount > 0, 'WOOFiVaultV2: amount_CAN_NOT_BE_ZERO');

        if (want == weth) {
            require(msg.value == amount, 'WOOFiVaultV2: msg.value_INSUFFICIENT');
        } else {
            require(msg.value == 0, 'WOOFiVaultV2: msg.value_INVALID');
        }

        if (address(strategy) != address(0)) {
            require(!strategy.paused(), 'WOOFiVaultV2: strat_paused');
            strategy.beforeDeposit();
        }

        uint256 balanceBefore = balance();
        if (want == weth) {
            IWETH(weth).deposit{value: msg.value}();
        } else {
            TransferHelper.safeTransferFrom(want, msg.sender, address(this), amount);
        }
        uint256 balanceAfter = balance();
        require(amount <= balanceAfter.sub(balanceBefore), 'WOOFiVaultV2: amount_NOT_ENOUGH');

        uint256 shares = totalSupply() == 0 ? amount : amount.mul(totalSupply()).div(balanceBefore);
        uint256 sharesBefore = balanceOf(msg.sender);
        uint256 costBefore = costSharePrice[msg.sender];
        uint256 costAfter = (sharesBefore.mul(costBefore).add(amount.mul(1e18))).div(sharesBefore.add(shares));
        costSharePrice[msg.sender] = costAfter;

        _mint(msg.sender, shares);

        earn();
    }

    function withdraw(uint256 shares) public override nonReentrant {
        require(shares > 0, 'WOOFiVaultV2: shares_ZERO');
        require(shares <= balanceOf(msg.sender), 'WOOFiVaultV2: shares_NOT_ENOUGH');

        if (address(strategy) != address(0)) {
            strategy.beforeWithdraw();
        }

        uint256 withdrawAmount = shares.mul(balance()).div(totalSupply());
        _burn(msg.sender, shares);

        uint256 balanceBefore = IERC20(want).balanceOf(address(this));
        if (balanceBefore < withdrawAmount) {
            uint256 balanceToWithdraw = withdrawAmount.sub(balanceBefore);
            require(_isStratActive(), 'WOOFiVaultV2: STRAT_INACTIVE');
            strategy.withdraw(balanceToWithdraw);
            uint256 balanceAfter = IERC20(want).balanceOf(address(this));
            if (withdrawAmount > balanceAfter) {
                // NOTE: in case a small amount not counted in, due to the decimal precision.
                withdrawAmount = balanceAfter;
            }
        }

        if (want == weth) {
            IWETH(weth).withdraw(withdrawAmount);
            TransferHelper.safeTransferETH(msg.sender, withdrawAmount);
        } else {
            TransferHelper.safeTransfer(want, msg.sender, withdrawAmount);
        }
    }

    function earn() public override {
        if (_isStratActive()) {
            uint256 balanceAvail = available();
            TransferHelper.safeTransfer(want, address(strategy), balanceAvail);
            strategy.deposit();
        }
    }

    function available() public view override returns (uint256) {
        return IERC20(want).balanceOf(address(this));
    }

    function balance() public view override returns (uint256) {
        return address(strategy) != address(0) ? available().add(strategy.balanceOf()) : available();
    }

    function getPricePerFullShare() public view override returns (uint256) {
        return totalSupply() == 0 ? 1e18 : balance().mul(1e18).div(totalSupply());
    }

    function _isStratActive() internal view returns (bool) {
        return address(strategy) != address(0) && !strategy.paused();
    }

    /* ----- Admin Functions ----- */

    function setupStrat(address _strat) public onlyAdmin {
        require(_strat != address(0), 'WOOFiVaultV2: STRAT_ZERO_ADDR');
        require(address(strategy) == address(0), 'WOOFiVaultV2: STRAT_ALREADY_SET');
        require(address(this) == IStrategy(_strat).vault(), 'WOOFiVaultV2: STRAT_VAULT_INVALID');
        require(want == IStrategy(_strat).want(), 'WOOFiVaultV2: STRAT_WANT_INVALID');
        strategy = IStrategy(_strat);

        emit UpgradeStrat(_strat);
    }

    function proposeStrat(address _implementation) public onlyAdmin {
        require(address(this) == IStrategy(_implementation).vault(), 'WOOFiVaultV2: STRAT_VAULT_INVALID');
        require(want == IStrategy(_implementation).want(), 'WOOFiVaultV2: STRAT_WANT_INVALID');
        stratCandidate = StratCandidate({implementation: _implementation, proposedTime: block.timestamp});

        emit NewStratCandidate(_implementation);
    }

    function upgradeStrat() public onlyAdmin {
        require(stratCandidate.implementation != address(0), 'WOOFiVaultV2: NO_CANDIDATE');
        require(stratCandidate.proposedTime.add(approvalDelay) < block.timestamp, 'WOOFiVaultV2: TIME_INVALID');

        emit UpgradeStrat(stratCandidate.implementation);

        strategy.retireStrat();
        strategy = IStrategy(stratCandidate.implementation);
        stratCandidate.implementation = address(0);
        stratCandidate.proposedTime = 5000000000; // 100+ years to ensure proposedTime check

        earn();
    }

    function setApprovalDelay(uint256 _approvalDelay) external onlyAdmin {
        require(_approvalDelay > 0, 'WOOFiVaultV2: approvalDelay_ZERO');
        approvalDelay = _approvalDelay;
    }

    function inCaseTokensGetStuck(address stuckToken) external onlyAdmin {
        require(stuckToken != want, 'WOOFiVaultV2: stuckToken_NOT_WANT');
        require(stuckToken != address(0), 'WOOFiVaultV2: stuckToken_ZERO_ADDR');
        uint256 amount = IERC20(stuckToken).balanceOf(address(this));
        if (amount > 0) {
            TransferHelper.safeTransfer(stuckToken, msg.sender, amount);
        }
    }

    function inCaseNativeTokensGetStuck() external onlyAdmin {
        // NOTE: vault never needs native tokens to do the yield farming;
        // This native token balance indicates a user's incorrect transfer.
        if (address(this).balance > 0) {
            TransferHelper.safeTransferETH(msg.sender, address(this).balance);
        }
    }

    receive() external payable {}
}

File 31 of 106 : IVaultV2.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface IVaultV2 {
    function want() external view returns (address);

    function weth() external view returns (address);

    function deposit(uint256 amount) external payable;

    function withdraw(uint256 shares) external;

    function earn() external;

    function available() external view returns (uint256);

    function balance() external view returns (uint256);

    function getPricePerFullShare() external view returns (uint256);
}

File 32 of 106 : BaseStrategy.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/Pausable.sol';
import '@openzeppelin/contracts/utils/ReentrancyGuard.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import '../../interfaces/IWooAccessManager.sol';
import '../../interfaces/IStrategy.sol';
import '../../interfaces/IVault.sol';
import '../../interfaces/IVaultV2.sol';

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

/**
 * Base strategy abstract contract for:
 *  - vault and access manager setup
 *  - fees management
 *  - pause / unpause
 */
abstract contract BaseStrategy is Ownable, Pausable, IStrategy, ReentrancyGuard {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    /* ----- State Variables ----- */

    address public override want;
    address public immutable override vault;

    // Default them to 'false' to save the gas cost
    bool public harvestOnDeposit = false;
    // Default them to 'true' to make the system more fair, but cost a bit more gas.
    bool public harvestOnWithdraw = true;

    /* ----- Constant Variables ----- */

    uint256 public constant FEE_MAX = 10000;
    uint256 public performanceFee = 300; // 1 in 10000th: 100: 1%, 300: 3%
    uint256 public withdrawalFee = 0; // 1 in 10000th: 1: 0.01%, 10: 0.1%
    address public performanceTreasury = address(0x4094D7A17a387795838c7aba4687387B0d32BCf3);
    address public withdrawalTreasury = address(0x4094D7A17a387795838c7aba4687387B0d32BCf3);

    IWooAccessManager public accessManager;

    event PerformanceFeeUpdated(uint256 newFee);
    event WithdrawalFeeUpdated(uint256 newFee);

    constructor(address initVault, address initAccessManager) public {
        require(initVault != address(0), 'BaseStrategy: initVault_ZERO_ADDR');
        require(initAccessManager != address(0), 'BaseStrategy: initAccessManager_ZERO_ADDR');
        vault = initVault;
        accessManager = IWooAccessManager(initAccessManager);
        want = IVault(initVault).want();
    }

    modifier onlyAdmin() {
        require(owner() == _msgSender() || accessManager.isVaultAdmin(msg.sender), 'BaseStrategy: NOT_ADMIN');
        _;
    }

    /* ----- Public Functions ----- */

    function beforeDeposit() public virtual override {
        require(msg.sender == address(vault), 'BaseStrategy: NOT_VAULT');
        if (harvestOnDeposit) {
            harvest();
        }
    }

    function beforeWithdraw() public virtual override {
        require(msg.sender == address(vault), 'BaseStrategy: NOT_VAULT');
        if (harvestOnWithdraw) {
            harvest();
        }
    }

    function balanceOf() public view override returns (uint256) {
        return balanceOfWant().add(balanceOfPool());
    }

    function balanceOfWant() public view override returns (uint256) {
        return IERC20(want).balanceOf(address(this));
    }

    /* ----- Internal Functions ----- */

    function chargePerformanceFee(uint256 amount) internal returns (uint256) {
        uint256 fee = amount.mul(performanceFee).div(FEE_MAX);
        if (fee > 0) {
            TransferHelper.safeTransfer(want, performanceTreasury, fee);
        }
        return fee;
    }

    function chargeWithdrawalFee(uint256 amount) internal returns (uint256) {
        uint256 fee = amount.mul(withdrawalFee).div(FEE_MAX);
        if (fee > 0) {
            TransferHelper.safeTransfer(want, withdrawalTreasury, fee);
        }
        return fee;
    }

    /* ----- Abstract Method ----- */

    function balanceOfPool() public view virtual override returns (uint256);

    function deposit() public virtual override;

    function withdraw(uint256 amount) external virtual override;

    function harvest() public virtual override;

    function retireStrat() external virtual override;

    function emergencyExit() external virtual override;

    function _giveAllowances() internal virtual;

    function _removeAllowances() internal virtual;

    /* ----- Admin Functions ----- */

    function setPerformanceFee(uint256 fee) external onlyAdmin {
        require(fee <= FEE_MAX, 'BaseStrategy: fee_EXCCEEDS_MAX');
        performanceFee = fee;
        emit PerformanceFeeUpdated(fee);
    }

    function setWithdrawalFee(uint256 fee) external onlyAdmin {
        require(fee <= FEE_MAX, 'BaseStrategy: fee_EXCCEEDS_MAX');
        require(fee <= 500, 'BaseStrategy: fee_EXCCEEDS_5%'); // less than 5%
        withdrawalFee = fee;
        emit WithdrawalFeeUpdated(fee);
    }

    function setPerformanceTreasury(address treasury) external onlyAdmin {
        require(treasury != address(0), 'BaseStrategy: treasury_ZERO_ADDR');
        performanceTreasury = treasury;
    }

    function setWithdrawalTreasury(address treasury) external onlyAdmin {
        require(treasury != address(0), 'BaseStrategy: treasury_ZERO_ADDR');
        withdrawalTreasury = treasury;
    }

    function setHarvestOnDeposit(bool newHarvestOnDeposit) external onlyAdmin {
        harvestOnDeposit = newHarvestOnDeposit;
    }

    function setHarvestOnWithdraw(bool newHarvestOnWithdraw) external onlyAdmin {
        harvestOnWithdraw = newHarvestOnWithdraw;
    }

    function pause() public onlyAdmin {
        _pause();
        _removeAllowances();
    }

    function unpause() external onlyAdmin {
        _unpause();
        _giveAllowances();
        deposit();
    }

    function paused() public view override(IStrategy, Pausable) returns (bool) {
        return Pausable.paused();
    }

    function inCaseTokensGetStuck(address stuckToken) external override onlyAdmin {
        require(stuckToken != want, 'BaseStrategy: stuckToken_NOT_WANT');
        require(stuckToken != address(0), 'BaseStrategy: stuckToken_ZERO_ADDR');

        uint256 amount = IERC20(stuckToken).balanceOf(address(this));
        if (amount > 0) {
            TransferHelper.safeTransfer(stuckToken, msg.sender, amount);
        }
    }

    function inCaseNativeTokensGetStuck() external onlyAdmin {
        // NOTE: vault never needs native tokens to do the yield farming;
        // This native token balance indicates a user's incorrect transfer.
        if (address(this).balance > 0) {
            TransferHelper.safeTransferETH(msg.sender, address(this).balance);
        }
    }
}

File 33 of 106 : IVault.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface IVault {
    function want() external view returns (address);

    function deposit(uint256 amount) external payable;

    function withdraw(uint256 shares) external;

    function earn() external;

    function available() external view returns (uint256);

    function balance() external view returns (uint256);

    function getPricePerFullShare() external view returns (uint256);
}

File 34 of 106 : VoidStrategy.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/Pausable.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import './BaseStrategy.sol';

contract VoidStrategy is BaseStrategy {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    constructor(address initVault, address initAccessManager) public BaseStrategy(initVault, initAccessManager) {
        _giveAllowances();
    }

    /* ----- External Functions ----- */

    function withdraw(uint256 amount) external override nonReentrant {
        require(msg.sender == vault, 'VoidStrategy: NOT_VAULT');

        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        uint256 withdrawAmount = amount < wantBalance ? amount : wantBalance;

        uint256 fee = chargeWithdrawalFee(withdrawAmount);
        if (withdrawAmount > fee) {
            TransferHelper.safeTransfer(want, vault, withdrawAmount.sub(fee));
        }
    }

    function harvest() public override whenNotPaused {
        require(msg.sender == tx.origin || msg.sender == vault, 'VoidStrategy: EOA_OR_VAULT');
        deposit();
    }

    function deposit() public override whenNotPaused nonReentrant {}

    function balanceOfPool() public view override returns (uint256) {
        return 0;
    }

    /* ----- Private Functions ----- */

    function _giveAllowances() internal override {}

    function _removeAllowances() internal override {}

    function retireStrat() external override {
        require(msg.sender == vault, '!vault');
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            TransferHelper.safeTransfer(want, vault, wantBalance);
        }
    }

    function emergencyExit() external override onlyAdmin {
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            TransferHelper.safeTransfer(want, vault, wantBalance);
        }
    }
}

File 35 of 106 : WooPP.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

import './libraries/InitializableOwnable.sol';
import './libraries/DecimalMath.sol';
import './interfaces/IWooracle.sol';
import './interfaces/IWooPP.sol';
import './interfaces/IWooFeeManager.sol';
import './interfaces/IWooGuardian.sol';
import './interfaces/AggregatorV3Interface.sol';

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/utils/ReentrancyGuard.sol';
import '@openzeppelin/contracts/utils/Pausable.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

/// @title Woo private pool for swaping.
/// @notice the implementation class for interface IWooPP, mainly for query and swap tokens.
contract WooPP is InitializableOwnable, ReentrancyGuard, Pausable, IWooPP {
    /* ----- Type declarations ----- */

    using SafeMath for uint256;
    using DecimalMath for uint256;
    using SafeERC20 for IERC20;

    /* ----- State variables ----- */

    mapping(address => TokenInfo) public tokenInfo;
    mapping(address => bool) public isStrategist;

    /// @inheritdoc IWooPP
    address public immutable override quoteToken;
    address public wooracle;
    IWooGuardian public wooGuardian;
    IWooFeeManager public feeManager;
    string public pairsInfo; // e.g. BNB/ETH/BTCB/WOO-USDT

    /* ----- Modifiers ----- */

    modifier onlyStrategist() {
        require(msg.sender == _OWNER_ || isStrategist[msg.sender], 'WooPP: NOT_STRATEGIST');
        _;
    }

    constructor(
        address newQuoteToken,
        address newWooracle,
        address newFeeManager,
        address newWooGuardian
    ) public {
        require(newQuoteToken != address(0), 'WooPP: INVALID_QUOTE');
        require(newWooracle != address(0), 'WooPP: newWooracle_ZERO_ADDR');
        require(newFeeManager != address(0), 'WooPP: newFeeManager_ZERO_ADDR');
        require(newWooGuardian != address(0), 'WooPP: newWooGuardian_ZERO_ADDR');

        initOwner(msg.sender);
        quoteToken = newQuoteToken;
        wooracle = newWooracle;
        feeManager = IWooFeeManager(newFeeManager);
        require(feeManager.quoteToken() == newQuoteToken, 'WooPP: feeManager_quoteToken_INVALID');
        wooGuardian = IWooGuardian(newWooGuardian);

        TokenInfo storage quoteInfo = tokenInfo[newQuoteToken];
        quoteInfo.isValid = true;
    }

    /* ----- External Functions ----- */

    /// @inheritdoc IWooPP
    function querySellBase(address baseToken, uint256 baseAmount)
        external
        view
        override
        whenNotPaused
        returns (uint256 quoteAmount)
    {
        require(baseToken != address(0), 'WooPP: baseToken_ZERO_ADDR');
        require(baseToken != quoteToken, 'WooPP: baseToken==quoteToken');
        wooGuardian.checkInputAmount(baseToken, baseAmount);

        TokenInfo memory baseInfo = tokenInfo[baseToken];
        require(baseInfo.isValid, 'WooPP: TOKEN_DOES_NOT_EXIST');
        TokenInfo memory quoteInfo = tokenInfo[quoteToken];
        _autoUpdate(baseToken, baseInfo, quoteInfo);

        quoteAmount = getQuoteAmountSellBase(baseToken, baseAmount, baseInfo, quoteInfo);
        wooGuardian.checkSwapAmount(baseToken, quoteToken, baseAmount, quoteAmount);
        uint256 lpFee = quoteAmount.mulCeil(feeManager.feeRate(baseToken));
        quoteAmount = quoteAmount.sub(lpFee);

        require(quoteAmount <= IERC20(quoteToken).balanceOf(address(this)), 'WooPP: INSUFF_QUOTE');
    }

    /// @inheritdoc IWooPP
    function querySellQuote(address baseToken, uint256 quoteAmount)
        external
        view
        override
        whenNotPaused
        returns (uint256 baseAmount)
    {
        require(baseToken != address(0), 'WooPP: baseToken_ZERO_ADDR');
        require(baseToken != quoteToken, 'WooPP: baseToken==quoteToken');
        wooGuardian.checkInputAmount(quoteToken, quoteAmount);

        TokenInfo memory baseInfo = tokenInfo[baseToken];
        require(baseInfo.isValid, 'WooPP: TOKEN_DOES_NOT_EXIST');
        TokenInfo memory quoteInfo = tokenInfo[quoteToken];
        _autoUpdate(baseToken, baseInfo, quoteInfo);

        uint256 lpFee = quoteAmount.mulCeil(feeManager.feeRate(baseToken));
        quoteAmount = quoteAmount.sub(lpFee);
        baseAmount = getBaseAmountSellQuote(baseToken, quoteAmount, baseInfo, quoteInfo);
        wooGuardian.checkSwapAmount(quoteToken, baseToken, quoteAmount, baseAmount);

        require(baseAmount <= IERC20(baseToken).balanceOf(address(this)), 'WooPP: INSUFF_BASE');
    }

    /// @inheritdoc IWooPP
    function sellBase(
        address baseToken,
        uint256 baseAmount,
        uint256 minQuoteAmount,
        address to,
        address rebateTo
    ) external override nonReentrant whenNotPaused returns (uint256 quoteAmount) {
        require(baseToken != address(0), 'WooPP: baseToken_ZERO_ADDR');
        require(to != address(0), 'WooPP: to_ZERO_ADDR');
        require(baseToken != quoteToken, 'WooPP: baseToken==quoteToken');
        wooGuardian.checkInputAmount(baseToken, baseAmount);

        address from = msg.sender;
        TokenInfo memory baseInfo = tokenInfo[baseToken];
        require(baseInfo.isValid, 'WooPP: TOKEN_DOES_NOT_EXIST');
        TokenInfo memory quoteInfo = tokenInfo[quoteToken];
        _autoUpdate(baseToken, baseInfo, quoteInfo);

        TransferHelper.safeTransferFrom(baseToken, from, address(this), baseAmount);

        quoteAmount = getQuoteAmountSellBase(baseToken, baseAmount, baseInfo, quoteInfo);
        wooGuardian.checkSwapAmount(baseToken, quoteToken, baseAmount, quoteAmount);

        uint256 lpFee = quoteAmount.mulCeil(feeManager.feeRate(baseToken));
        quoteAmount = quoteAmount.sub(lpFee);
        require(quoteAmount >= minQuoteAmount, 'WooPP: quoteAmount<minQuoteAmount');

        TransferHelper.safeApprove(quoteToken, address(feeManager), lpFee);
        feeManager.collectFee(lpFee, rebateTo);

        uint256 balanceBefore = IERC20(quoteToken).balanceOf(to);
        TransferHelper.safeTransfer(quoteToken, to, quoteAmount);
        require(IERC20(quoteToken).balanceOf(to).sub(balanceBefore) >= minQuoteAmount, 'WooPP: INSUFF_OUTPUT_AMOUNT');

        _updateReserve(baseToken, baseInfo, quoteInfo);

        tokenInfo[baseToken] = baseInfo;
        tokenInfo[quoteToken] = quoteInfo;

        emit WooSwap(baseToken, quoteToken, baseAmount, quoteAmount, from, to, rebateTo);
    }

    /// @inheritdoc IWooPP
    function sellQuote(
        address baseToken,
        uint256 quoteAmount,
        uint256 minBaseAmount,
        address to,
        address rebateTo
    ) external override nonReentrant whenNotPaused returns (uint256 baseAmount) {
        require(baseToken != address(0), 'WooPP: baseToken_ZERO_ADDR');
        require(to != address(0), 'WooPP: to_ZERO_ADDR');
        require(baseToken != quoteToken, 'WooPP: baseToken==quoteToken');
        wooGuardian.checkInputAmount(quoteToken, quoteAmount);

        address from = msg.sender;
        TokenInfo memory baseInfo = tokenInfo[baseToken];
        require(baseInfo.isValid, 'WooPP: TOKEN_DOES_NOT_EXIST');
        TokenInfo memory quoteInfo = tokenInfo[quoteToken];
        _autoUpdate(baseToken, baseInfo, quoteInfo);

        TransferHelper.safeTransferFrom(quoteToken, from, address(this), quoteAmount);

        uint256 lpFee = quoteAmount.mulCeil(feeManager.feeRate(baseToken));
        quoteAmount = quoteAmount.sub(lpFee);
        baseAmount = getBaseAmountSellQuote(baseToken, quoteAmount, baseInfo, quoteInfo);
        require(baseAmount >= minBaseAmount, 'WooPP: baseAmount<minBaseAmount');

        TransferHelper.safeApprove(quoteToken, address(feeManager), lpFee);
        feeManager.collectFee(lpFee, rebateTo);

        wooGuardian.checkSwapAmount(quoteToken, baseToken, quoteAmount, baseAmount);

        uint256 balanceBefore = IERC20(baseToken).balanceOf(to);
        TransferHelper.safeTransfer(baseToken, to, baseAmount);
        require(IERC20(baseToken).balanceOf(to).sub(balanceBefore) >= minBaseAmount, 'WooPP: INSUFF_OUTPUT_AMOUNT');

        _updateReserve(baseToken, baseInfo, quoteInfo);

        tokenInfo[baseToken] = baseInfo;
        tokenInfo[quoteToken] = quoteInfo;

        emit WooSwap(quoteToken, baseToken, quoteAmount.add(lpFee), baseAmount, from, to, rebateTo);
    }

    /// @dev Set the pairsInfo
    /// @param newPairsInfo the pairs info to set
    function setPairsInfo(string calldata newPairsInfo) external nonReentrant onlyStrategist {
        pairsInfo = newPairsInfo;
    }

    /// @dev Get the pool's balance of token
    /// @param token the token pool to query
    function poolSize(address token) external view returns (uint256) {
        return IERC20(token).balanceOf(address(this));
    }

    /// @dev Set wooracle from newWooracle
    /// @param newWooracle Wooracle address
    function setWooracle(address newWooracle) external nonReentrant onlyStrategist {
        require(newWooracle != address(0), 'WooPP: newWooracle_ZERO_ADDR');
        wooracle = newWooracle;
        emit WooracleUpdated(newWooracle);
    }

    /// @dev Set wooGuardian from newWooGuardian
    /// @param newWooGuardian WooGuardian address
    function setWooGuardian(address newWooGuardian) external nonReentrant onlyStrategist {
        require(newWooGuardian != address(0), 'WooPP: newWooGuardian_ZERO_ADDR');
        wooGuardian = IWooGuardian(newWooGuardian);
        emit WooGuardianUpdated(newWooGuardian);
    }

    /// @dev Set the feeManager.
    /// @param newFeeManager the fee manager
    function setFeeManager(address newFeeManager) external nonReentrant onlyStrategist {
        require(newFeeManager != address(0), 'WooPP: newFeeManager_ZERO_ADDR');
        feeManager = IWooFeeManager(newFeeManager);
        require(feeManager.quoteToken() == quoteToken, 'WooPP: feeManager_quoteToken_INVALID');
        emit FeeManagerUpdated(newFeeManager);
    }

    /// @dev Add the base token for swap
    /// @param baseToken the base token
    /// @param threshold the balance threshold info
    /// @param R the rebalance refactor
    function addBaseToken(
        address baseToken,
        uint256 threshold,
        uint256 R
    ) external nonReentrant onlyStrategist {
        require(baseToken != address(0), 'WooPP: BASE_TOKEN_ZERO_ADDR');
        require(baseToken != quoteToken, 'WooPP: baseToken==quoteToken');
        require(threshold <= type(uint112).max, 'WooPP: THRESHOLD_OUT_OF_RANGE');
        require(R <= 1e18, 'WooPP: R_OUT_OF_RANGE');

        TokenInfo memory info = tokenInfo[baseToken];
        require(!info.isValid, 'WooPP: TOKEN_ALREADY_EXISTS');

        info.threshold = uint112(threshold);
        info.R = uint64(R);
        info.target = max(info.threshold, info.target);
        info.isValid = true;

        tokenInfo[baseToken] = info;

        emit ParametersUpdated(baseToken, threshold, R);
    }

    /// @dev Remove the base token
    /// @param baseToken the base token
    function removeBaseToken(address baseToken) external nonReentrant onlyStrategist {
        require(baseToken != address(0), 'WooPP: BASE_TOKEN_ZERO_ADDR');
        require(tokenInfo[baseToken].isValid, 'WooPP: TOKEN_DOES_NOT_EXIST');
        delete tokenInfo[baseToken];
        emit ParametersUpdated(baseToken, 0, 0);
    }

    /// @dev Tune the token params
    /// @param token the token to tune
    /// @param newThreshold the new balance threshold info
    /// @param newR the new rebalance refactor
    function tuneParameters(
        address token,
        uint256 newThreshold,
        uint256 newR
    ) external nonReentrant onlyStrategist {
        require(token != address(0), 'WooPP: token_ZERO_ADDR');
        require(newThreshold <= type(uint112).max, 'WooPP: THRESHOLD_OUT_OF_RANGE');
        require(newR <= 1e18, 'WooPP: R>1');

        TokenInfo memory info = tokenInfo[token];
        require(info.isValid, 'WooPP: TOKEN_DOES_NOT_EXIST');

        info.threshold = uint112(newThreshold);
        info.R = uint64(newR);
        info.target = max(info.threshold, info.target);

        tokenInfo[token] = info;
        emit ParametersUpdated(token, newThreshold, newR);
    }

    /* ----- Admin Functions ----- */

    /// @dev Pause the contract.
    function pause() external onlyStrategist {
        super._pause();
    }

    /// @dev Restart the contract.
    function unpause() external onlyStrategist {
        super._unpause();
    }

    /// @dev Update the strategist info.
    /// @param strategist the strategist to set
    /// @param flag true or false
    function setStrategist(address strategist, bool flag) external nonReentrant onlyStrategist {
        require(strategist != address(0), 'WooPP: strategist_ZERO_ADDR');
        isStrategist[strategist] = flag;
        emit StrategistUpdated(strategist, flag);
    }

    /// @dev Withdraw the token.
    /// @param token the token to withdraw
    /// @param to the destination address
    /// @param amount the amount to withdraw
    function withdraw(
        address token,
        address to,
        uint256 amount
    ) public nonReentrant onlyOwner {
        require(token != address(0), 'WooPP: token_ZERO_ADDR');
        require(to != address(0), 'WooPP: to_ZERO_ADDR');
        TransferHelper.safeTransfer(token, to, amount);
        emit Withdraw(token, to, amount);
    }

    function withdrawAll(address token, address to) external onlyOwner {
        withdraw(token, to, IERC20(token).balanceOf(address(this)));
    }

    /// @dev Withdraw the token to the OWNER address
    /// @param token the token
    function withdrawAllToOwner(address token) external nonReentrant onlyStrategist {
        require(token != address(0), 'WooPP: token_ZERO_ADDR');
        uint256 amount = IERC20(token).balanceOf(address(this));
        TransferHelper.safeTransfer(token, _OWNER_, amount);
        emit Withdraw(token, _OWNER_, amount);
    }

    /* ----- Private Functions ----- */

    function _autoUpdate(
        address baseToken,
        TokenInfo memory baseInfo,
        TokenInfo memory quoteInfo
    ) private view {
        require(baseToken != address(0), 'WooPP: BASETOKEN_ZERO_ADDR');
        _updateReserve(baseToken, baseInfo, quoteInfo);

        // NOTE: only consider the least 32 bigs integer number is good engouh
        uint32 priceTimestamp = uint32(IWooracle(wooracle).timestamp());
        if (priceTimestamp != baseInfo.lastResetTimestamp) {
            baseInfo.target = max(baseInfo.threshold, baseInfo.reserve);
            baseInfo.lastResetTimestamp = priceTimestamp;
        }
        if (priceTimestamp != quoteInfo.lastResetTimestamp) {
            quoteInfo.target = max(quoteInfo.threshold, quoteInfo.reserve);
            quoteInfo.lastResetTimestamp = priceTimestamp;
        }
    }

    function _updateReserve(
        address baseToken,
        TokenInfo memory baseInfo,
        TokenInfo memory quoteInfo
    ) private view {
        uint256 baseReserve = IERC20(baseToken).balanceOf(address(this));
        uint256 quoteReserve = IERC20(quoteToken).balanceOf(address(this));
        require(baseReserve <= type(uint112).max);
        require(quoteReserve <= type(uint112).max);
        baseInfo.reserve = uint112(baseReserve);
        quoteInfo.reserve = uint112(quoteReserve);
    }

    // When baseSold >= 0 , users sold the base token
    function getQuoteAmountLowQuoteSide(
        uint256 p,
        uint256 k,
        uint256 r,
        uint256 baseAmount
    ) private pure returns (uint256) {
        // priceFactor = 1 + k * baseAmount * p * r;
        uint256 priceFactor = DecimalMath.ONE.add(k.mulCeil(baseAmount).mulCeil(p).mulCeil(r));
        // return baseAmount * p / priceFactor;
        return baseAmount.mulFloor(p).divFloor(priceFactor); // round down
    }

    // When baseSold >= 0
    function getBaseAmountLowQuoteSide(
        uint256 p,
        uint256 k,
        uint256 r,
        uint256 quoteAmount
    ) private pure returns (uint256) {
        // priceFactor = (1 - k * quoteAmount * r);
        uint256 priceFactor = DecimalMath.ONE.sub(k.mulFloor(quoteAmount).mulFloor(r));
        // return quoteAmount / p / priceFactor;
        return quoteAmount.divFloor(p).divFloor(priceFactor);
    }

    // When quoteSold >= 0
    function getBaseAmountLowBaseSide(
        uint256 p,
        uint256 k,
        uint256 r,
        uint256 quoteAmount
    ) private pure returns (uint256) {
        // priceFactor = 1 + k * quoteAmount * r;
        uint256 priceFactor = DecimalMath.ONE.add(k.mulCeil(quoteAmount).mulCeil(r));
        // return quoteAmount / p / priceFactor;
        return quoteAmount.divFloor(p).divFloor(priceFactor); // round down
    }

    // When quoteSold >= 0
    function getQuoteAmountLowBaseSide(
        uint256 p,
        uint256 k,
        uint256 r,
        uint256 baseAmount
    ) private pure returns (uint256) {
        // priceFactor = 1 - k * baseAmount * p * r;
        uint256 priceFactor = DecimalMath.ONE.sub(k.mulFloor(baseAmount).mulFloor(p).mulFloor(r));
        // return baseAmount * p / priceFactor;
        return baseAmount.mulFloor(p).divFloor(priceFactor); // round down
    }

    function getBoughtAmount(
        TokenInfo memory baseInfo,
        TokenInfo memory quoteInfo,
        uint256 p,
        uint256 k,
        bool isSellBase
    ) private pure returns (uint256 baseBought, uint256 quoteBought) {
        uint256 baseSold = 0;
        if (baseInfo.reserve < baseInfo.target) {
            baseBought = uint256(baseInfo.target).sub(uint256(baseInfo.reserve));
        } else {
            baseSold = uint256(baseInfo.reserve).sub(uint256(baseInfo.target));
        }
        uint256 quoteSold = 0;
        if (quoteInfo.reserve < quoteInfo.target) {
            quoteBought = uint256(quoteInfo.target).sub(uint256(quoteInfo.reserve));
        } else {
            quoteSold = uint256(quoteInfo.reserve).sub(uint256(quoteInfo.target));
        }

        if (baseSold.mulCeil(p) > quoteSold) {
            baseSold = baseSold.sub(quoteSold.divFloor(p));
            quoteSold = 0;
        } else {
            quoteSold = quoteSold.sub(baseSold.mulCeil(p));
            baseSold = 0;
        }

        uint256 virtualBaseBought = getBaseAmountLowBaseSide(p, k, DecimalMath.ONE, quoteSold);
        if (isSellBase == (virtualBaseBought < baseBought)) {
            baseBought = virtualBaseBought;
        }
        uint256 virtualQuoteBought = getQuoteAmountLowQuoteSide(p, k, DecimalMath.ONE, baseSold);
        if (isSellBase == (virtualQuoteBought > quoteBought)) {
            quoteBought = virtualQuoteBought;
        }
    }

    function getQuoteAmountSellBase(
        address baseToken,
        uint256 baseAmount,
        TokenInfo memory baseInfo,
        TokenInfo memory quoteInfo
    ) private view returns (uint256 quoteAmount) {
        uint256 p;
        uint256 s;
        uint256 k;
        bool isFeasible;
        (p, s, k, isFeasible) = IWooracle(wooracle).state(baseToken);
        require(isFeasible, 'WooPP: ORACLE_PRICE_NOT_FEASIBLE');

        wooGuardian.checkSwapPrice(p, baseToken, quoteToken);

        // price: p * (1 - s / 2)
        p = p.mulFloor(DecimalMath.ONE.sub(s.divCeil(DecimalMath.TWO)));

        uint256 baseBought;
        uint256 quoteBought;
        (baseBought, quoteBought) = getBoughtAmount(baseInfo, quoteInfo, p, k, true);

        if (baseBought > 0) {
            uint256 quoteSold = getQuoteAmountLowBaseSide(p, k, baseInfo.R, baseBought);
            if (baseAmount > baseBought) {
                uint256 newBaseSold = baseAmount.sub(baseBought);
                quoteAmount = quoteSold.add(getQuoteAmountLowQuoteSide(p, k, DecimalMath.ONE, newBaseSold));
            } else {
                uint256 newBaseBought = baseBought.sub(baseAmount);
                quoteAmount = quoteSold.sub(getQuoteAmountLowBaseSide(p, k, baseInfo.R, newBaseBought));
            }
        } else {
            uint256 baseSold = getBaseAmountLowQuoteSide(p, k, DecimalMath.ONE, quoteBought);
            uint256 newBaseSold = baseAmount.add(baseSold);
            uint256 newQuoteBought = getQuoteAmountLowQuoteSide(p, k, DecimalMath.ONE, newBaseSold);
            quoteAmount = newQuoteBought > quoteBought ? newQuoteBought.sub(quoteBought) : 0;
        }
    }

    function getBaseAmountSellQuote(
        address baseToken,
        uint256 quoteAmount,
        TokenInfo memory baseInfo,
        TokenInfo memory quoteInfo
    ) private view returns (uint256 baseAmount) {
        uint256 p;
        uint256 s;
        uint256 k;
        bool isFeasible;
        (p, s, k, isFeasible) = IWooracle(wooracle).state(baseToken);
        require(isFeasible, 'WooPP: ORACLE_PRICE_NOT_FEASIBLE');

        wooGuardian.checkSwapPrice(p, baseToken, quoteToken);

        // price: p * (1 + s / 2)
        p = p.mulCeil(DecimalMath.ONE.add(s.divCeil(DecimalMath.TWO)));

        uint256 baseBought;
        uint256 quoteBought;
        (baseBought, quoteBought) = getBoughtAmount(baseInfo, quoteInfo, p, k, false);

        if (quoteBought > 0) {
            uint256 baseSold = getBaseAmountLowQuoteSide(p, k, baseInfo.R, quoteBought);
            if (quoteAmount > quoteBought) {
                uint256 newQuoteSold = quoteAmount.sub(quoteBought);
                baseAmount = baseSold.add(getBaseAmountLowBaseSide(p, k, DecimalMath.ONE, newQuoteSold));
            } else {
                uint256 newQuoteBought = quoteBought.sub(quoteAmount);
                baseAmount = baseSold.sub(getBaseAmountLowQuoteSide(p, k, baseInfo.R, newQuoteBought));
            }
        } else {
            uint256 quoteSold = getQuoteAmountLowBaseSide(p, k, DecimalMath.ONE, baseBought);
            uint256 newQuoteSold = quoteAmount.add(quoteSold);
            uint256 newBaseBought = getBaseAmountLowBaseSide(p, k, DecimalMath.ONE, newQuoteSold);
            baseAmount = newBaseBought > baseBought ? newBaseBought.sub(baseBought) : 0;
        }
    }

    function max(uint112 a, uint112 b) private pure returns (uint112) {
        return a >= b ? a : b;
    }
}

File 36 of 106 : IWooFeeManager.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

/// @title Contract to collect transaction fee of Woo private pool.
interface IWooFeeManager {
    /* ----- Events ----- */

    event FeeRateUpdated(address indexed token, uint256 newFeeRate);
    event Withdraw(address indexed token, address indexed to, uint256 amount);

    /* ----- External Functions ----- */

    /// @dev fee rate for the given base token:
    /// NOTE: fee rate decimal 18: 1e16 = 1%, 1e15 = 0.1%, 1e14 = 0.01%
    /// @param token the base token
    /// @return the fee rate
    function feeRate(address token) external view returns (uint256);

    /// @dev Sets the fee rate for the given token
    /// @param token the base token
    /// @param newFeeRate the new fee rate
    function setFeeRate(address token, uint256 newFeeRate) external;

    /// @dev Collects the swap fee to the given brokder address.
    /// @param amount the swap fee amount
    /// @param brokerAddr the broker address to rebate to
    function collectFee(uint256 amount, address brokerAddr) external;

    /// @dev get the quote token address
    /// @return address of quote token
    function quoteToken() external view returns (address);

    /// @dev Collects the fee and distribute to rebate and vault managers.
    function distributeFees() external;
}

File 37 of 106 : WooRouterV2.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

import './interfaces/IWooPP.sol';
import './interfaces/IWETH.sol';
import './interfaces/IWooRouterV2.sol';

import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/Address.sol';
import '@openzeppelin/contracts/utils/ReentrancyGuard.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

/// @title Woo Router implementation.
/// @notice Router for stateless execution of swaps against Woo private pool.
contract WooRouterV2 is IWooRouterV2, Ownable, ReentrancyGuard {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    /* ----- Constant variables ----- */

    // Erc20 placeholder address for native tokens (e.g. eth, bnb, matic, etc)
    address constant ETH_PLACEHOLDER_ADDR = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

    /* ----- State variables ----- */

    // Wrapper for native tokens (e.g. eth, bnb, matic, etc)
    // BSC WBNB: 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c
    address public immutable override WETH;

    IWooPP public override wooPool;

    mapping(address => bool) public isWhitelisted;

    address public quoteToken;

    /* ----- Callback Function ----- */

    receive() external payable {
        // only accept ETH from WETH or whitelisted external swaps.
        assert(msg.sender == WETH || isWhitelisted[msg.sender]);
    }

    /* ----- Query & swap APIs ----- */

    constructor(address _weth, address _pool) public {
        require(_weth != address(0), 'WooRouter: weth_ZERO_ADDR');
        WETH = _weth;
        setPool(_pool);
    }

    /// @inheritdoc IWooRouterV2
    function querySwap(
        address fromToken,
        address toToken,
        uint256 fromAmount
    ) external view override returns (uint256 toAmount) {
        require(fromToken != address(0), 'WooRouter: fromToken_ADDR_ZERO');
        require(toToken != address(0), 'WooRouter: toToken_ADDR_ZERO');
        fromToken = (fromToken == ETH_PLACEHOLDER_ADDR) ? WETH : fromToken;
        toToken = (toToken == ETH_PLACEHOLDER_ADDR) ? WETH : toToken;
        if (fromToken == quoteToken) {
            toAmount = wooPool.querySellQuote(toToken, fromAmount);
        } else if (toToken == quoteToken) {
            toAmount = wooPool.querySellBase(fromToken, fromAmount);
        } else {
            uint256 quoteAmount = wooPool.querySellBase(fromToken, fromAmount);
            toAmount = wooPool.querySellQuote(toToken, quoteAmount);
        }
    }

    /// @inheritdoc IWooRouterV2
    function swap(
        address fromToken,
        address toToken,
        uint256 fromAmount,
        uint256 minToAmount,
        address payable to,
        address rebateTo
    ) external payable override nonReentrant returns (uint256 realToAmount) {
        require(fromToken != address(0), 'WooRouter: fromToken_ADDR_ZERO');
        require(toToken != address(0), 'WooRouter: toToken_ADDR_ZERO');
        require(to != address(0), 'WooRouter: to_ADDR_ZERO');

        bool isFromETH = fromToken == ETH_PLACEHOLDER_ADDR;
        bool isToETH = toToken == ETH_PLACEHOLDER_ADDR;
        fromToken = isFromETH ? WETH : fromToken;
        toToken = isToETH ? WETH : toToken;

        // Step 1: transfer the source tokens to WooRouter
        if (isFromETH) {
            require(fromAmount <= msg.value, 'WooRouter: fromAmount_INVALID');
            IWETH(WETH).deposit{value: msg.value}();
        } else {
            TransferHelper.safeTransferFrom(fromToken, msg.sender, address(this), fromAmount);
        }

        // Step 2: swap and transfer
        TransferHelper.safeApprove(fromToken, address(wooPool), fromAmount);
        if (fromToken == quoteToken) {
            // case 1: quoteToken --> baseToken
            realToAmount = _sellQuoteAndTransfer(isToETH, toToken, fromAmount, minToAmount, to, rebateTo);
        } else if (toToken == quoteToken) {
            // case 2: fromToken --> quoteToken
            realToAmount = wooPool.sellBase(fromToken, fromAmount, minToAmount, to, rebateTo);
        } else {
            // case 3: fromToken --> quoteToken --> toToken
            uint256 quoteAmount = wooPool.sellBase(fromToken, fromAmount, 0, address(this), rebateTo);
            TransferHelper.safeApprove(quoteToken, address(wooPool), quoteAmount);
            realToAmount = _sellQuoteAndTransfer(isToETH, toToken, quoteAmount, minToAmount, to, rebateTo);
        }

        // Step 3: firing event
        emit WooRouterSwap(
            SwapType.WooSwap,
            isFromETH ? ETH_PLACEHOLDER_ADDR : fromToken,
            isToETH ? ETH_PLACEHOLDER_ADDR : toToken,
            fromAmount,
            realToAmount,
            msg.sender,
            to,
            rebateTo
        );
    }

    /// @inheritdoc IWooRouterV2
    function externalSwap(
        address approveTarget,
        address swapTarget,
        address fromToken,
        address toToken,
        uint256 fromAmount,
        uint256 minToAmount,
        address payable to,
        bytes calldata data
    ) public payable override nonReentrant returns (uint256 realToAmount) {
        require(approveTarget != address(0), 'WooRouter: approveTarget_ADDR_ZERO');
        require(swapTarget != address(0), 'WooRouter: swapTarget_ADDR_ZERO');
        require(fromToken != address(0), 'WooRouter: fromToken_ADDR_ZERO');
        require(toToken != address(0), 'WooRouter: toToken_ADDR_ZERO');
        require(to != address(0), 'WooRouter: to_ADDR_ZERO');
        require(isWhitelisted[approveTarget], 'WooRouter: APPROVE_TARGET_NOT_ALLOWED');
        require(isWhitelisted[swapTarget], 'WooRouter: SWAP_TARGET_NOT_ALLOWED');

        uint256 preBalance = _generalBalanceOf(toToken, address(this));
        _internalFallbackSwap(approveTarget, swapTarget, fromToken, fromAmount, data);
        uint256 postBalance = _generalBalanceOf(toToken, address(this));

        require(preBalance <= postBalance, 'WooRouter: balance_ERROR');
        realToAmount = postBalance.sub(preBalance);
        require(realToAmount >= minToAmount && realToAmount > 0, 'WooRouter: realToAmount_NOT_ENOUGH');
        _generalTransfer(toToken, to, realToAmount);

        emit WooRouterSwap(SwapType.DodoSwap, fromToken, toToken, fromAmount, realToAmount, msg.sender, to, address(0));
    }

    /* ----- External Functions ---- */

    /// @dev query the swap price for baseToken -> quoteToken.
    /// @param baseToken the base token to sell
    /// @param baseAmount the amout of base token to sell
    /// @return quoteAmount the amount of swapped quote token
    function querySellBase(address baseToken, uint256 baseAmount) external view returns (uint256 quoteAmount) {
        require(baseToken != address(0), 'WooRouter: baseToken_ADDR_ZERO');
        baseToken = (baseToken == ETH_PLACEHOLDER_ADDR) ? WETH : baseToken;
        quoteAmount = wooPool.querySellBase(baseToken, baseAmount);
    }

    /// @dev query the swap price for quoteToken -> baseToken.
    /// @param baseToken the base token to swap
    /// @param quoteAmount the amount of quote token to swap
    /// @return baseAmount the amount of base token after swap
    function querySellQuote(address baseToken, uint256 quoteAmount) external view returns (uint256 baseAmount) {
        require(baseToken != address(0), 'WooRouter: baseToken_ADDR_ZERO');
        baseToken = (baseToken == ETH_PLACEHOLDER_ADDR) ? WETH : baseToken;
        baseAmount = wooPool.querySellQuote(baseToken, quoteAmount);
    }

    /// @dev swap baseToken -> quoteToken
    /// @param baseToken the base token
    /// @param baseAmount the amount of base token to sell
    /// @param minQuoteAmount the minimum quote amount to receive
    /// @param to the destination address
    /// @param rebateTo the rebate address
    /// @return realQuoteAmount the exact received amount of quote token
    function sellBase(
        address baseToken,
        uint256 baseAmount,
        uint256 minQuoteAmount,
        address to,
        address rebateTo
    ) external nonReentrant returns (uint256 realQuoteAmount) {
        require(baseToken != address(0), 'WooRouter: baseToken_ADDR_ZERO');
        require(to != address(0), 'WooRouter: to_ADDR_ZERO');
        TransferHelper.safeTransferFrom(baseToken, msg.sender, address(this), baseAmount);
        TransferHelper.safeApprove(baseToken, address(wooPool), baseAmount);
        realQuoteAmount = wooPool.sellBase(baseToken, baseAmount, minQuoteAmount, to, rebateTo);
        emit WooRouterSwap(
            SwapType.WooSwap,
            baseToken,
            quoteToken,
            baseAmount,
            realQuoteAmount,
            msg.sender,
            to,
            rebateTo
        );
    }

    /// @dev swap quoteToken -> baseToken
    /// @param baseToken the base token to receive
    /// @param quoteAmount the amount of quote token to sell
    /// @param minBaseAmount the minimum amount of base token for swap
    /// @param to the destination address
    /// @param rebateTo the address for the rebate
    /// @return realBaseAmount the exact received amount of base token to receive
    function sellQuote(
        address baseToken,
        uint256 quoteAmount,
        uint256 minBaseAmount,
        address to,
        address rebateTo
    ) external nonReentrant returns (uint256 realBaseAmount) {
        require(baseToken != address(0), 'WooRouter: baseToken_ADDR_ZERO');
        require(to != address(0), 'WooRouter: to_ADDR_ZERO');
        TransferHelper.safeTransferFrom(quoteToken, msg.sender, address(this), quoteAmount);
        TransferHelper.safeApprove(quoteToken, address(wooPool), quoteAmount);
        realBaseAmount = wooPool.sellQuote(baseToken, quoteAmount, minBaseAmount, to, rebateTo);
        emit WooRouterSwap(
            SwapType.WooSwap,
            quoteToken,
            baseToken,
            quoteAmount,
            realBaseAmount,
            msg.sender,
            to,
            rebateTo
        );
    }

    /* ----- Admin functions ----- */

    /// @dev Rescue the specified funds when stuck happen
    /// @param token token address
    /// @param amount amount of token to rescue
    function rescueFunds(address token, uint256 amount) external nonReentrant onlyOwner {
        require(token != address(0), 'WooRouter: token_ADDR_ZERO');
        TransferHelper.safeTransfer(token, msg.sender, amount);
    }

    /// @dev Rescue the native token funds when stuck happen
    function rescueNativeFunds() external nonReentrant onlyOwner {
        TransferHelper.safeTransferETH(msg.sender, address(this).balance);
    }

    /// @dev Set wooPool from newPool
    /// @param newPool Wooracle address
    function setPool(address newPool) public nonReentrant onlyOwner {
        require(newPool != address(0), 'WooRouter: newPool_ADDR_ZERO');
        wooPool = IWooPP(newPool);
        quoteToken = wooPool.quoteToken();
        require(quoteToken != address(0), 'WooRouter: quoteToken_ADDR_ZERO');
        emit WooPoolChanged(newPool);
    }

    /// @dev Add target address into whitelist
    /// @param target address that approved by WooRouter
    /// @param whitelisted approve to using WooRouter or not
    function setWhitelisted(address target, bool whitelisted) external nonReentrant onlyOwner {
        require(target != address(0), 'WooRouter: target_ADDR_ZERO');
        isWhitelisted[target] = whitelisted;
    }

    /* ----- Private Function ----- */

    function _sellQuoteAndTransfer(
        bool isToETH,
        address toToken,
        uint256 quoteAmount,
        uint256 minToAmount,
        address payable to,
        address rebateTo
    ) private returns (uint256 realToAmount) {
        if (isToETH) {
            realToAmount = wooPool.sellQuote(toToken, quoteAmount, minToAmount, address(this), rebateTo);
            IWETH(WETH).withdraw(realToAmount);
            require(to != address(0), 'WooRouter: to_ZERO_ADDR');
            TransferHelper.safeTransferETH(to, realToAmount);
        } else {
            realToAmount = wooPool.sellQuote(toToken, quoteAmount, minToAmount, to, rebateTo);
        }
    }

    function _internalFallbackSwap(
        address approveTarget,
        address swapTarget,
        address fromToken,
        uint256 fromAmount,
        bytes calldata data
    ) private {
        require(isWhitelisted[approveTarget], 'WooRouter: APPROVE_TARGET_NOT_ALLOWED');
        require(isWhitelisted[swapTarget], 'WooRouter: SWAP_TARGET_NOT_ALLOWED');

        if (fromToken != ETH_PLACEHOLDER_ADDR) {
            TransferHelper.safeTransferFrom(fromToken, msg.sender, address(this), fromAmount);
            TransferHelper.safeApprove(fromToken, approveTarget, fromAmount);
        } else {
            require(fromAmount <= msg.value, 'WooRouter: fromAmount_INVALID');
        }

        (bool success, ) = swapTarget.call{value: fromToken == ETH_PLACEHOLDER_ADDR ? fromAmount : 0}(data);
        require(success, 'WooRouter: FALLBACK_SWAP_FAILED');
    }

    function _generalTransfer(
        address token,
        address payable to,
        uint256 amount
    ) private {
        if (amount > 0) {
            if (token == ETH_PLACEHOLDER_ADDR) {
                TransferHelper.safeTransferETH(to, amount);
            } else {
                TransferHelper.safeTransfer(token, to, amount);
            }
        }
    }

    function _generalBalanceOf(address token, address who) private view returns (uint256) {
        return token == ETH_PLACEHOLDER_ADDR ? who.balance : IERC20(token).balanceOf(who);
    }
}

File 38 of 106 : IWooRouterV2.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

import '../interfaces/IWooPP.sol';

/// @title Woo router interface
/// @notice functions to interface with WooFi swap
interface IWooRouterV2 {
    /* ----- Type declarations ----- */

    enum SwapType {
        WooSwap,
        DodoSwap
    }

    /* ----- Events ----- */

    event WooRouterSwap(
        SwapType swapType,
        address indexed fromToken,
        address indexed toToken,
        uint256 fromAmount,
        uint256 toAmount,
        address from,
        address indexed to,
        address rebateTo
    );

    event WooPoolChanged(address newPool);

    /* ----- Router properties ----- */

    function WETH() external pure returns (address);

    function wooPool() external pure returns (IWooPP);

    /* ----- Main query & swap APIs ----- */

    /// @dev query the amount to swap fromToken -> toToken
    /// @param fromToken the from token
    /// @param toToken the to token
    /// @param fromAmount the amount of fromToken to swap
    /// @return toAmount the predicted amount to receive
    function querySwap(
        address fromToken,
        address toToken,
        uint256 fromAmount
    ) external view returns (uint256 toAmount);

    /// @dev swap fromToken -> toToken
    /// @param fromToken the from token
    /// @param toToken the to token
    /// @param fromAmount the amount of fromToken to swap
    /// @param minToAmount the amount of fromToken to swap
    /// @param to the destination address
    /// @param rebateTo the rebate address (optional, can be 0)
    /// @return realToAmount the amount of toToken to receive
    function swap(
        address fromToken,
        address toToken,
        uint256 fromAmount,
        uint256 minToAmount,
        address payable to,
        address rebateTo
    ) external payable returns (uint256 realToAmount);

    /* ----- 3rd party DEX swap ----- */

    /// @dev swap fromToken -> toToken via an external 3rd swap
    /// @param approveTarget the contract address for token transfer approval
    /// @param swapTarget the contract address for swap
    /// @param fromToken the from token
    /// @param toToken the to token
    /// @param fromAmount the amount of fromToken to swap
    /// @param minToAmount the min amount of swapped toToken
    /// @param to the destination address
    /// @param data call data for external call
    function externalSwap(
        address approveTarget,
        address swapTarget,
        address fromToken,
        address toToken,
        uint256 fromAmount,
        uint256 minToAmount,
        address payable to,
        bytes calldata data
    ) external payable returns (uint256 realToAmount);
}

File 39 of 106 : WooRouter.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

import './interfaces/IWooPP.sol';
import './interfaces/IWETH.sol';
import './interfaces/IWooRouter.sol';

import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/Address.sol';
import '@openzeppelin/contracts/utils/ReentrancyGuard.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

/// @title Woo Router implementation.
/// @notice Router for stateless execution of swaps against Woo private pool.
contract WooRouter is IWooRouter, Ownable, ReentrancyGuard {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    /* ----- Constant variables ----- */

    // Erc20 placeholder address for native tokens (e.g. eth, bnb, matic, etc)
    address constant ETH_PLACEHOLDER_ADDR = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

    /* ----- State variables ----- */

    // Wrapper for native tokens (e.g. eth, bnb, matic, etc)
    // BSC WBNB: 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c
    address public immutable override WETH;

    IWooPP public override wooPool;

    mapping(address => bool) public isWhitelisted;

    address public quoteToken;

    /* ----- Callback Function ----- */

    receive() external payable {
        // only accept ETH from WETH or whitelisted external swaps.
        assert(msg.sender == WETH || isWhitelisted[msg.sender]);
    }

    /* ----- Query & swap APIs ----- */

    constructor(address weth, address newPool) public {
        require(weth != address(0), 'WooRouter: weth_ZERO_ADDR');
        WETH = weth;
        setPool(newPool);
    }

    /// @inheritdoc IWooRouter
    function querySwap(
        address fromToken,
        address toToken,
        uint256 fromAmount
    ) external view override returns (uint256 toAmount) {
        require(fromToken != address(0), 'WooRouter: fromToken_ADDR_ZERO');
        require(toToken != address(0), 'WooRouter: toToken_ADDR_ZERO');
        fromToken = (fromToken == ETH_PLACEHOLDER_ADDR) ? WETH : fromToken;
        toToken = (toToken == ETH_PLACEHOLDER_ADDR) ? WETH : toToken;
        if (fromToken == quoteToken) {
            toAmount = wooPool.querySellQuote(toToken, fromAmount);
        } else if (toToken == quoteToken) {
            toAmount = wooPool.querySellBase(fromToken, fromAmount);
        } else {
            uint256 quoteAmount = wooPool.querySellBase(fromToken, fromAmount);
            toAmount = wooPool.querySellQuote(toToken, quoteAmount);
        }
    }

    /// @inheritdoc IWooRouter
    function swap(
        address fromToken,
        address toToken,
        uint256 fromAmount,
        uint256 minToAmount,
        address payable to,
        address rebateTo
    ) external payable override nonReentrant returns (uint256 realToAmount) {
        require(fromToken != address(0), 'WooRouter: fromToken_ADDR_ZERO');
        require(toToken != address(0), 'WooRouter: toToken_ADDR_ZERO');
        require(to != address(0), 'WooRouter: to_ADDR_ZERO');

        bool isFromETH = fromToken == ETH_PLACEHOLDER_ADDR;
        bool isToETH = toToken == ETH_PLACEHOLDER_ADDR;
        fromToken = isFromETH ? WETH : fromToken;
        toToken = isToETH ? WETH : toToken;

        // Step 1: transfer the source tokens to WooRouter
        if (isFromETH) {
            require(fromAmount <= msg.value, 'WooRouter: fromAmount_INVALID');
            IWETH(WETH).deposit{value: msg.value}();
        } else {
            TransferHelper.safeTransferFrom(fromToken, msg.sender, address(this), fromAmount);
        }

        // Step 2: swap and transfer
        TransferHelper.safeApprove(fromToken, address(wooPool), fromAmount);
        if (fromToken == quoteToken) {
            // case 1: quoteToken --> baseToken
            realToAmount = _sellQuoteAndTransfer(isToETH, toToken, fromAmount, minToAmount, to, rebateTo);
        } else if (toToken == quoteToken) {
            // case 2: fromToken --> quoteToken
            realToAmount = wooPool.sellBase(fromToken, fromAmount, minToAmount, to, rebateTo);
        } else {
            // case 3: fromToken --> quoteToken --> toToken
            uint256 quoteAmount = wooPool.sellBase(fromToken, fromAmount, 0, address(this), rebateTo);
            TransferHelper.safeApprove(quoteToken, address(wooPool), quoteAmount);
            realToAmount = _sellQuoteAndTransfer(isToETH, toToken, quoteAmount, minToAmount, to, rebateTo);
        }

        // Step 3: firing event
        emit WooRouterSwap(
            SwapType.WooSwap,
            isFromETH ? ETH_PLACEHOLDER_ADDR : fromToken,
            isToETH ? ETH_PLACEHOLDER_ADDR : toToken,
            fromAmount,
            realToAmount,
            msg.sender,
            to,
            rebateTo
        );
    }

    /// @inheritdoc IWooRouter
    function externalSwap(
        address approveTarget,
        address swapTarget,
        address fromToken,
        address toToken,
        uint256 fromAmount,
        address payable to,
        bytes calldata data
    ) external payable override {
        externalSwap(approveTarget, swapTarget, fromToken, toToken, fromAmount, 0, to, data);
    }

    /// @inheritdoc IWooRouter
    function externalSwap(
        address approveTarget,
        address swapTarget,
        address fromToken,
        address toToken,
        uint256 fromAmount,
        uint256 minToAmount,
        address payable to,
        bytes calldata data
    ) public payable override nonReentrant returns (uint256 realToAmount) {
        require(approveTarget != address(0), 'WooRouter: approveTarget_ADDR_ZERO');
        require(swapTarget != address(0), 'WooRouter: swapTarget_ADDR_ZERO');
        require(fromToken != address(0), 'WooRouter: fromToken_ADDR_ZERO');
        require(toToken != address(0), 'WooRouter: toToken_ADDR_ZERO');
        require(to != address(0), 'WooRouter: to_ADDR_ZERO');
        require(isWhitelisted[approveTarget], 'WooRouter: APPROVE_TARGET_NOT_ALLOWED');
        require(isWhitelisted[swapTarget], 'WooRouter: SWAP_TARGET_NOT_ALLOWED');

        uint256 preBalance = _generalBalanceOf(toToken, address(this));
        _internalFallbackSwap(approveTarget, swapTarget, fromToken, fromAmount, data);
        uint256 postBalance = _generalBalanceOf(toToken, address(this));

        require(preBalance <= postBalance, 'WooRouter: balance_ERROR');
        realToAmount = postBalance.sub(preBalance);
        require(realToAmount >= minToAmount && realToAmount > 0, 'WooRouter: realToAmount_NOT_ENOUGH');
        _generalTransfer(toToken, to, realToAmount);

        emit WooRouterSwap(SwapType.DodoSwap, fromToken, toToken, fromAmount, realToAmount, msg.sender, to, address(0));
    }

    /* ----- External Functions ---- */

    /// @dev query the swap price for baseToken -> quoteToken.
    /// @param baseToken the base token to sell
    /// @param baseAmount the amout of base token to sell
    /// @return quoteAmount the amount of swapped quote token
    function querySellBase(address baseToken, uint256 baseAmount) external view returns (uint256 quoteAmount) {
        require(baseToken != address(0), 'WooRouter: baseToken_ADDR_ZERO');
        baseToken = (baseToken == ETH_PLACEHOLDER_ADDR) ? WETH : baseToken;
        quoteAmount = wooPool.querySellBase(baseToken, baseAmount);
    }

    /// @dev query the swap price for quoteToken -> baseToken.
    /// @param baseToken the base token to swap
    /// @param quoteAmount the amount of quote token to swap
    /// @return baseAmount the amount of base token after swap
    function querySellQuote(address baseToken, uint256 quoteAmount) external view returns (uint256 baseAmount) {
        require(baseToken != address(0), 'WooRouter: baseToken_ADDR_ZERO');
        baseToken = (baseToken == ETH_PLACEHOLDER_ADDR) ? WETH : baseToken;
        baseAmount = wooPool.querySellQuote(baseToken, quoteAmount);
    }

    /// @dev swap baseToken -> quoteToken
    /// @param baseToken the base token
    /// @param baseAmount the amount of base token to sell
    /// @param minQuoteAmount the minimum quote amount to receive
    /// @param to the destination address
    /// @param rebateTo the rebate address
    /// @return realQuoteAmount the exact received amount of quote token
    function sellBase(
        address baseToken,
        uint256 baseAmount,
        uint256 minQuoteAmount,
        address to,
        address rebateTo
    ) external nonReentrant returns (uint256 realQuoteAmount) {
        require(baseToken != address(0), 'WooRouter: baseToken_ADDR_ZERO');
        require(to != address(0), 'WooRouter: to_ADDR_ZERO');
        TransferHelper.safeTransferFrom(baseToken, msg.sender, address(this), baseAmount);
        TransferHelper.safeApprove(baseToken, address(wooPool), baseAmount);
        realQuoteAmount = wooPool.sellBase(baseToken, baseAmount, minQuoteAmount, to, rebateTo);
        emit WooRouterSwap(
            SwapType.WooSwap,
            baseToken,
            quoteToken,
            baseAmount,
            realQuoteAmount,
            msg.sender,
            to,
            rebateTo
        );
    }

    /// @dev swap quoteToken -> baseToken
    /// @param baseToken the base token to receive
    /// @param quoteAmount the amount of quote token to sell
    /// @param minBaseAmount the minimum amount of base token for swap
    /// @param to the destination address
    /// @param rebateTo the address for the rebate
    /// @return realBaseAmount the exact received amount of base token to receive
    function sellQuote(
        address baseToken,
        uint256 quoteAmount,
        uint256 minBaseAmount,
        address to,
        address rebateTo
    ) external nonReentrant returns (uint256 realBaseAmount) {
        require(baseToken != address(0), 'WooRouter: baseToken_ADDR_ZERO');
        require(to != address(0), 'WooRouter: to_ADDR_ZERO');
        TransferHelper.safeTransferFrom(quoteToken, msg.sender, address(this), quoteAmount);
        TransferHelper.safeApprove(quoteToken, address(wooPool), quoteAmount);
        realBaseAmount = wooPool.sellQuote(baseToken, quoteAmount, minBaseAmount, to, rebateTo);
        emit WooRouterSwap(
            SwapType.WooSwap,
            quoteToken,
            baseToken,
            quoteAmount,
            realBaseAmount,
            msg.sender,
            to,
            rebateTo
        );
    }

    /* ----- Admin functions ----- */

    /// @dev Rescue the specified funds when stuck happen
    /// @param token token address
    /// @param amount amount of token to rescue
    function rescueFunds(address token, uint256 amount) external nonReentrant onlyOwner {
        require(token != address(0), 'WooRouter: token_ADDR_ZERO');
        TransferHelper.safeTransfer(token, msg.sender, amount);
    }

    /// @dev Set wooPool from newPool
    /// @param newPool Wooracle address
    function setPool(address newPool) public nonReentrant onlyOwner {
        require(newPool != address(0), 'WooRouter: newPool_ADDR_ZERO');
        wooPool = IWooPP(newPool);
        quoteToken = wooPool.quoteToken();
        require(quoteToken != address(0), 'WooRouter: quoteToken_ADDR_ZERO');
        emit WooPoolChanged(newPool);
    }

    /// @dev Add target address into whitelist
    /// @param target address that approved by WooRouter
    /// @param whitelisted approve to using WooRouter or not
    function setWhitelisted(address target, bool whitelisted) external nonReentrant onlyOwner {
        require(target != address(0), 'WooRouter: target_ADDR_ZERO');
        isWhitelisted[target] = whitelisted;
    }

    /* ----- Private Function ----- */

    function _sellQuoteAndTransfer(
        bool isToETH,
        address toToken,
        uint256 quoteAmount,
        uint256 minToAmount,
        address payable to,
        address rebateTo
    ) private returns (uint256 realToAmount) {
        if (isToETH) {
            realToAmount = wooPool.sellQuote(toToken, quoteAmount, minToAmount, address(this), rebateTo);
            IWETH(WETH).withdraw(realToAmount);
            require(to != address(0), 'WooRouter: to_ZERO_ADDR');
            TransferHelper.safeTransferETH(to, realToAmount);
        } else {
            realToAmount = wooPool.sellQuote(toToken, quoteAmount, minToAmount, to, rebateTo);
        }
    }

    function _internalFallbackSwap(
        address approveTarget,
        address swapTarget,
        address fromToken,
        uint256 fromAmount,
        bytes calldata data
    ) private {
        require(isWhitelisted[approveTarget], 'WooRouter: APPROVE_TARGET_NOT_ALLOWED');
        require(isWhitelisted[swapTarget], 'WooRouter: SWAP_TARGET_NOT_ALLOWED');

        if (fromToken != ETH_PLACEHOLDER_ADDR) {
            TransferHelper.safeTransferFrom(fromToken, msg.sender, address(this), fromAmount);
            TransferHelper.safeApprove(fromToken, approveTarget, fromAmount);
        } else {
            require(fromAmount <= msg.value, 'WooRouter: fromAmount_INVALID');
        }

        (bool success, ) = swapTarget.call{value: fromToken == ETH_PLACEHOLDER_ADDR ? fromAmount : 0}(data);
        require(success, 'WooRouter: FALLBACK_SWAP_FAILED');
    }

    function _generalTransfer(
        address token,
        address payable to,
        uint256 amount
    ) private {
        if (amount > 0) {
            if (token == ETH_PLACEHOLDER_ADDR) {
                TransferHelper.safeTransferETH(to, amount);
            } else {
                TransferHelper.safeTransfer(token, to, amount);
            }
        }
    }

    function _generalBalanceOf(address token, address who) private view returns (uint256) {
        return token == ETH_PLACEHOLDER_ADDR ? who.balance : IERC20(token).balanceOf(who);
    }
}

File 40 of 106 : IWooRouter.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

import '../interfaces/IWooPP.sol';

/// @title Woo router interface
/// @notice functions to interface with WooFi swap
interface IWooRouter {
    /* ----- Type declarations ----- */

    enum SwapType {
        WooSwap,
        DodoSwap
    }

    /* ----- Events ----- */

    event WooRouterSwap(
        SwapType swapType,
        address indexed fromToken,
        address indexed toToken,
        uint256 fromAmount,
        uint256 toAmount,
        address from,
        address indexed to,
        address rebateTo
    );

    event WooPoolChanged(address newPool);

    /* ----- Router properties ----- */

    function WETH() external pure returns (address);

    function wooPool() external pure returns (IWooPP);

    /* ----- Main query & swap APIs ----- */

    /// @dev query the amount to swap fromToken -> toToken
    /// @param fromToken the from token
    /// @param toToken the to token
    /// @param fromAmount the amount of fromToken to swap
    /// @return toAmount the predicted amount to receive
    function querySwap(
        address fromToken,
        address toToken,
        uint256 fromAmount
    ) external view returns (uint256 toAmount);

    /// @dev swap fromToken -> toToken
    /// @param fromToken the from token
    /// @param toToken the to token
    /// @param fromAmount the amount of fromToken to swap
    /// @param minToAmount the amount of fromToken to swap
    /// @param to the destination address
    /// @param rebateTo the rebate address (optional, can be 0)
    /// @return realToAmount the amount of toToken to receive
    function swap(
        address fromToken,
        address toToken,
        uint256 fromAmount,
        uint256 minToAmount,
        address payable to,
        address rebateTo
    ) external payable returns (uint256 realToAmount);

    /* ----- 3rd party DEX swap ----- */

    /// @dev swap fromToken -> toToken via an external 3rd swap
    /// @param approveTarget the contract address for token transfer approval
    /// @param swapTarget the contract address for swap
    /// @param fromToken the from token
    /// @param toToken the to token
    /// @param fromAmount the amount of fromToken to swap
    /// @param to the destination address
    /// @param data call data for external call
    function externalSwap(
        address approveTarget,
        address swapTarget,
        address fromToken,
        address toToken,
        uint256 fromAmount,
        address payable to,
        bytes calldata data
    ) external payable;

    /// @dev swap fromToken -> toToken via an external 3rd swap
    /// @param approveTarget the contract address for token transfer approval
    /// @param swapTarget the contract address for swap
    /// @param fromToken the from token
    /// @param toToken the to token
    /// @param fromAmount the amount of fromToken to swap
    /// @param minToAmount the min amount of swapped toToken
    /// @param to the destination address
    /// @param data call data for external call
    function externalSwap(
        address approveTarget,
        address swapTarget,
        address fromToken,
        address toToken,
        uint256 fromAmount,
        uint256 minToAmount,
        address payable to,
        bytes calldata data
    ) external payable returns (uint256 realToAmount);
}

File 41 of 106 : WooFeeManager.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

import './libraries/InitializableOwnable.sol';
import './libraries/DecimalMath.sol';
import './interfaces/IWooPP.sol';
import './interfaces/IWooRebateManager.sol';
import './interfaces/IWooFeeManager.sol';
import './interfaces/IWooVaultManager.sol';
import './interfaces/IWooAccessManager.sol';

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/utils/ReentrancyGuard.sol';
import '@openzeppelin/contracts/utils/Pausable.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

/// @title Contract to collect transaction fee of Woo private pool.
contract WooFeeManager is InitializableOwnable, ReentrancyGuard, IWooFeeManager {
    /* ----- Type declarations ----- */

    using SafeMath for uint256;
    using DecimalMath for uint256;
    using SafeERC20 for IERC20;

    /* ----- State variables ----- */

    mapping(address => uint256) public override feeRate; // decimal: 18; 1e16 = 1%, 1e15 = 0.1%, 1e14 = 0.01%
    uint256 public vaultRewardRate; // decimal: 18; 1e16 = 1%, 1e15 = 0.1%, 1e14 = 0.01%

    uint256 public rebateAmount;

    address public immutable override quoteToken;
    IWooRebateManager public rebateManager;
    IWooVaultManager public vaultManager;
    IWooAccessManager public accessManager;

    address public treasury;

    /* ----- Modifiers ----- */

    modifier onlyAdmin() {
        require(msg.sender == _OWNER_ || accessManager.isFeeAdmin(msg.sender), 'WooFeeManager: NOT_ADMIN');
        _;
    }

    constructor(
        address newQuoteToken,
        address newRebateManager,
        address newVaultManager,
        address newAccessManager,
        address newTreasury
    ) public {
        require(newQuoteToken != address(0), 'WooFeeManager: quoteToken_ZERO_ADDR');
        initOwner(msg.sender);
        quoteToken = newQuoteToken;
        rebateManager = IWooRebateManager(newRebateManager);
        vaultManager = IWooVaultManager(newVaultManager);
        vaultRewardRate = 1e18;
        accessManager = IWooAccessManager(newAccessManager);
        treasury = newTreasury;
    }

    /* ----- Public Functions ----- */

    function collectFee(uint256 amount, address brokerAddr) external override nonReentrant {
        TransferHelper.safeTransferFrom(quoteToken, msg.sender, address(this), amount);
        uint256 rebateRate = rebateManager.rebateRate(brokerAddr);
        if (rebateRate > 0) {
            uint256 curRebateAmount = amount.mulFloor(rebateRate);
            rebateManager.addRebate(brokerAddr, curRebateAmount);
            rebateAmount = rebateAmount.add(curRebateAmount);
        }
    }

    /* ----- Admin Functions ----- */

    function distributeFees() external override nonReentrant onlyAdmin {
        uint256 balance = IERC20(quoteToken).balanceOf(address(this));
        require(balance > 0, 'WooFeeManager: balance_ZERO');

        // Step 1: distribute the vault balance. Currently, 80% of fee (2 bps) goes to vault manager.
        uint256 vaultAmount = balance.mulFloor(vaultRewardRate);
        if (vaultAmount > 0) {
            TransferHelper.safeApprove(quoteToken, address(vaultManager), vaultAmount);
            TransferHelper.safeTransfer(quoteToken, address(vaultManager), vaultAmount);
            balance = balance.sub(vaultAmount);
        }

        // Step 2: distribute the rebate balance.
        if (rebateAmount > 0) {
            TransferHelper.safeApprove(quoteToken, address(rebateManager), rebateAmount);
            TransferHelper.safeTransfer(quoteToken, address(rebateManager), rebateAmount);

            // NOTE: if balance not enought: certain rebate rates are set incorrectly.
            balance = balance.sub(rebateAmount);
            rebateAmount = 0;
        }

        // Step 3: balance left for treasury
        TransferHelper.safeApprove(quoteToken, treasury, balance);
        TransferHelper.safeTransfer(quoteToken, treasury, balance);
    }

    function setFeeRate(address token, uint256 newFeeRate) external override onlyAdmin {
        require(newFeeRate <= 1e16, 'WooFeeManager: FEE_RATE>1%');
        feeRate[token] = newFeeRate;
        emit FeeRateUpdated(token, newFeeRate);
    }

    function setRebateManager(address newRebateManager) external onlyAdmin {
        require(newRebateManager != address(0), 'WooFeeManager: rebateManager_ZERO_ADDR');
        rebateManager = IWooRebateManager(newRebateManager);
    }

    function setVaultManager(address newVaultManager) external onlyAdmin {
        require(newVaultManager != address(0), 'WooFeeManager: newVaultManager_ZERO_ADDR');
        vaultManager = IWooVaultManager(newVaultManager);
    }

    function setVaultRewardRate(uint256 newVaultRewardRate) external onlyAdmin {
        require(newVaultRewardRate <= 1e18, 'WooFeeManager: vaultRewardRate_INVALID');
        vaultRewardRate = newVaultRewardRate;
    }

    function setAccessManager(address newAccessManager) external onlyOwner {
        require(newAccessManager != address(0), 'WooFeeManager: newAccessManager_ZERO_ADDR');
        accessManager = IWooAccessManager(newAccessManager);
    }

    function setTreasury(address newTreasury) external onlyOwner {
        require(newTreasury != address(0), 'WooFeeManager: newTreasury_ZERO_ADDR');
        treasury = newTreasury;
    }

    function emergencyWithdraw(address token, address to) external onlyOwner {
        require(token != address(0), 'WooFeeManager: token_ZERO_ADDR');
        require(to != address(0), 'WooFeeManager: to_ZERO_ADDR');
        uint256 amount = IERC20(token).balanceOf(address(this));
        TransferHelper.safeTransfer(token, to, amount);
        emit Withdraw(token, to, amount);
    }
}

File 42 of 106 : WooCrossChainRouter.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

import './interfaces/IWooPP.sol';
import './interfaces/IWETH.sol';
import './interfaces/IWooRouter.sol';

import './interfaces/Stargate/IStargateRouter.sol';
import './interfaces/Stargate/IStargateReceiver.sol';

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/utils/ReentrancyGuard.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

/// @title Woo Router implementation.
/// @notice Router for stateless execution of swaps against Woo private pool.
/// Ref links:
/// chain id: https://stargateprotocol.gitbook.io/stargate/developers/contract-addresses/mainnet
/// poold id: https://stargateprotocol.gitbook.io/stargate/developers/pool-ids
contract WooCrossChainRouter is IStargateReceiver, Ownable, ReentrancyGuard {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    event WooCrossSwapOnSrcChain(
        uint256 indexed refId,
        address indexed sender,
        address indexed to,
        address fromToken,
        uint256 fromAmount,
        uint256 minQuoteAmount,
        uint256 realQuoteAmount
    );

    event WooCrossSwapOnDstChain(
        uint256 indexed refId,
        address indexed sender,
        address indexed to,
        address bridgedToken,
        uint256 bridgedAmount,
        address toToken,
        address realToToken,
        uint256 minToAmount,
        uint256 realToAmount
    );

    address constant ETH_PLACEHOLDER_ADDR = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

    IStargateRouter public stargateRouter;
    IWooPP public wooPool;
    address public quoteToken;
    address public WETH;
    uint256 public bridgeSlippage; // 1 in 10000th: default 1%
    uint256 public dstGasForSwapCall;
    uint256 public dstGasForNoSwapCall;

    mapping(uint16 => address) public wooCrossRouters; // dstChainId => woo router
    mapping(uint16 => uint256) public quotePoolIds; // chainId => woofi_quote_token_pool_id
    mapping(uint16 => address) public wooppQuoteTokens; // dstChainId => wooPP quote token

    receive() external payable {}

    constructor(
        address _weth,
        address _wooPool,
        address _stargateRouter
    ) public {
        WETH = _weth;
        wooPool = IWooPP(_wooPool);
        quoteToken = wooPool.quoteToken();
        stargateRouter = IStargateRouter(_stargateRouter);

        bridgeSlippage = 100;

        // from test result: https://docs.google.com/spreadsheets/d/1TdGKNQ68HAic2jgMs40pKp7kM6AfKGHjIKkyVGwolIU
        dstGasForSwapCall = 360000;
        dstGasForNoSwapCall = 80000;

        // usdc: 1, usdt: 2, busd: 5
        quotePoolIds[1] = 1; // ethereum: usdc
        quotePoolIds[2] = 2; // BSC: usdt
        quotePoolIds[6] = 1; // Avalanche: usdc
        quotePoolIds[9] = 1; // Polygon: usdc
        quotePoolIds[10] = 1; // Arbitrum: usdc
        quotePoolIds[11] = 1; // Optimism: usdc
        quotePoolIds[12] = 1; // Fantom: usdc

        wooppQuoteTokens[2] = address(0x55d398326f99059fF775485246999027B3197955); // bsc_wooPP: usdt
        wooppQuoteTokens[6] = address(0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E); // avax_wooPP: usdc
        wooppQuoteTokens[12] = address(0x04068DA6C83AFCFA0e13ba15A6696662335D5B75); // ftm_wooPP: usdc
    }

    function setWooppQuoteTokens(uint16 _chainId, address _token) public onlyOwner {
        wooppQuoteTokens[_chainId] = _token;
    }

    /*
    https://stargateprotocol.gitbook.io/stargate/developers/contract-addresses/mainnet
        - Chain ID : Chain -
        1: Ether
        2: BSC (BNB Chain)
        6: Avalanche
        9: Polygon
        10: Arbitrum
        11: Optimism
        12: Fantom
    */
    function setWooCrossChainRouter(uint16 _chainId, address _wooCrossRouter) external onlyOwner {
        require(_wooCrossRouter != address(0), 'WooCrossChainRouter: !wooCrossRouter');
        wooCrossRouters[_chainId] = _wooCrossRouter;
    }

    function setStargateRouter(address _stargateRouter) external onlyOwner {
        require(_stargateRouter != address(0), 'WooCrossChainRouter: !stargateRouter');
        stargateRouter = IStargateRouter(_stargateRouter);
    }

    function setWooPool(address _wooPool) external onlyOwner {
        wooPool = IWooPP(_wooPool);
    }

    function setBridgeSlippage(uint256 _bridgeSlippage) external onlyOwner {
        require(_bridgeSlippage <= 10000, 'WooCrossChainRouter: !_bridgeSlippage');
        bridgeSlippage = _bridgeSlippage;
    }

    function setDstGasForSwapCall(uint256 _dstGasForSwapCall) external onlyOwner {
        dstGasForSwapCall = _dstGasForSwapCall;
    }

    function setDstGasForNoSwapCall(uint256 _dstGasForNoSwapCall) external onlyOwner {
        dstGasForNoSwapCall = _dstGasForNoSwapCall;
    }

    function setQuotePoolId(uint16 _chainId, uint256 _quotePoolId) external onlyOwner {
        quotePoolIds[_chainId] = _quotePoolId;
    }

    function crossSwap(
        uint256 refId_,
        address fromToken,
        address toToken,
        uint256 fromAmount,
        uint256 srcMinQuoteAmount,
        uint256 dstMinToAmount,
        uint16 srcChainId,
        uint16 dstChainId,
        address payable to
    ) external payable {
        require(fromToken != address(0), 'WooCrossChainRouter: !fromToken');
        require(toToken != address(0), 'WooCrossChainRouter: !toToken');
        require(to != address(0), 'WooCrossChainRouter: !to');

        uint256 gasValue = msg.value;
        uint256 refId = refId_; // NOTE: to avoid stack too deep issue

        // Step 1: transfer
        {
            bool isFromETH = fromToken == ETH_PLACEHOLDER_ADDR;
            fromToken = isFromETH ? WETH : fromToken;
            if (isFromETH) {
                require(fromAmount <= msg.value, 'WooCrossChainRouter: !fromAmount');
                IWETH(WETH).deposit{value: fromAmount}();
                gasValue -= fromAmount;
            } else {
                TransferHelper.safeTransferFrom(fromToken, msg.sender, address(this), fromAmount);
            }
        }

        // Step 2: local transfer
        uint256 bridgeAmount;
        if (fromToken != quoteToken) {
            TransferHelper.safeApprove(fromToken, address(wooPool), fromAmount);
            bridgeAmount = wooPool.sellBase(fromToken, fromAmount, srcMinQuoteAmount, address(this), to);
        } else {
            bridgeAmount = fromAmount;
        }

        // Step 3: send to stargate
        require(bridgeAmount <= IERC20(quoteToken).balanceOf(address(this)), '!bridgeAmount');
        TransferHelper.safeApprove(quoteToken, address(stargateRouter), bridgeAmount);

        require(to != address(0), 'WooCrossChainRouter: to_ZERO_ADDR'); // NOTE: double check it
        {
            bytes memory payloadData;
            payloadData = abi.encode(
                toToken, // to token
                refId, // reference id
                dstMinToAmount, // minToAmount on destination chain
                to // to address
            );

            bytes memory dstWooCrossRouter = abi.encodePacked(wooCrossRouters[dstChainId]);
            uint256 minBridgeAmount = bridgeAmount.mul(uint256(10000).sub(bridgeSlippage)).div(10000);
            uint256 dstGas = (toToken == wooppQuoteTokens[dstChainId]) ? dstGasForNoSwapCall : dstGasForSwapCall;

            stargateRouter.swap{value: gasValue}(
                dstChainId, // dst chain id
                quotePoolIds[srcChainId], // quote token's pool id on dst chain
                quotePoolIds[dstChainId], // quote token's pool id on src chain
                payable(msg.sender), // rebate address
                bridgeAmount, // swap amount on src chain
                minBridgeAmount, // min received amount on dst chain
                IStargateRouter.lzTxObj(dstGas, 0, '0x'), // config: dstGas, dstNativeToken, dstNativeTokenToAddress
                dstWooCrossRouter, // smart contract to call on dst chain
                payloadData // payload to piggyback
            );
        }

        emit WooCrossSwapOnSrcChain(refId, msg.sender, to, fromToken, fromAmount, srcMinQuoteAmount, bridgeAmount);
    }

    function quoteLayerZeroFee(
        uint16 dstChainId,
        address toToken,
        uint256 refId,
        uint256 dstMinToAmount,
        address to
    ) external view returns (uint256, uint256) {
        bytes memory toAddress = abi.encodePacked(to);
        bytes memory payloadData = abi.encode(
            toToken, // to token
            refId, // reference id
            dstMinToAmount, // minToAmount on destination chain
            to // to address
        );
        uint256 dstGas = (toToken == wooppQuoteTokens[dstChainId]) ? dstGasForNoSwapCall : dstGasForSwapCall;
        return
            stargateRouter.quoteLayerZeroFee(
                dstChainId,
                1, // https://stargateprotocol.gitbook.io/stargate/developers/function-types
                toAddress,
                payloadData,
                IStargateRouter.lzTxObj(dstGas, 0, '0x')
            );
    }

    function sgReceive(
        uint16 _chainId,
        bytes memory _srcAddress,
        uint256 _nonce,
        address _token,
        uint256 amountLD,
        bytes memory payload
    ) external override {
        require(msg.sender == address(stargateRouter), 'WooCrossChainRouter: INVALID_CALLER');

        (address toToken, uint256 refId, uint256 minToAmount, address to) = abi.decode(
            payload,
            (address, uint256, uint256, address)
        );

        if (wooPool.quoteToken() != _token) {
            // NOTE: The bridged token is not WooPP's quote token.
            // So Cannot do the swap; just return it to users.
            TransferHelper.safeTransfer(_token, to, amountLD);
            emit WooCrossSwapOnDstChain(
                refId,
                msg.sender,
                to,
                _token,
                amountLD,
                toToken,
                _token,
                minToAmount,
                amountLD
            );
            return;
        }

        uint256 quoteAmount = amountLD;
        TransferHelper.safeApprove(_token, address(wooPool), quoteAmount);

        if (toToken == ETH_PLACEHOLDER_ADDR) {
            // quoteToken -> WETH -> ETH
            try wooPool.sellQuote(WETH, quoteAmount, minToAmount, address(this), to) returns (uint256 realToAmount) {
                IWETH(WETH).withdraw(realToAmount);
                TransferHelper.safeTransferETH(to, realToAmount);
                emit WooCrossSwapOnDstChain(
                    refId,
                    msg.sender,
                    to,
                    _token,
                    amountLD,
                    toToken,
                    ETH_PLACEHOLDER_ADDR,
                    minToAmount,
                    realToAmount
                );
            } catch {
                // transfer _token/amountLD to msg.sender because the swap failed for some reason.
                // this is not the ideal scenario, but the contract needs to deliver them eth or USDC.
                TransferHelper.safeTransfer(_token, to, amountLD);
                emit WooCrossSwapOnDstChain(
                    refId,
                    msg.sender,
                    to,
                    _token,
                    amountLD,
                    toToken,
                    _token,
                    minToAmount,
                    amountLD
                );
            }
        } else {
            if (_token == toToken) {
                // Stargate bridged token == toToken: NO swap is needed!
                TransferHelper.safeTransfer(toToken, to, amountLD);
                emit WooCrossSwapOnDstChain(
                    refId,
                    msg.sender,
                    to,
                    _token,
                    amountLD,
                    toToken,
                    toToken,
                    minToAmount,
                    amountLD
                );
            } else {
                // swap to the ERC20 token
                try wooPool.sellQuote(toToken, quoteAmount, minToAmount, to, to) returns (uint256 realToAmount) {
                    emit WooCrossSwapOnDstChain(
                        refId,
                        msg.sender,
                        to,
                        _token,
                        amountLD,
                        toToken,
                        toToken,
                        minToAmount,
                        realToAmount
                    );
                } catch {
                    TransferHelper.safeTransfer(_token, to, amountLD);
                    emit WooCrossSwapOnDstChain(
                        refId,
                        msg.sender,
                        to,
                        _token,
                        amountLD,
                        toToken,
                        _token,
                        minToAmount,
                        amountLD
                    );
                }
            }
        }
    }

    function inCaseTokensGetStuck(address stuckToken) external onlyOwner {
        uint256 amount = IERC20(stuckToken).balanceOf(address(this));
        if (amount > 0) {
            TransferHelper.safeTransfer(stuckToken, msg.sender, amount);
        }
    }

    function inCaseNativeTokensGetStuck() external onlyOwner {
        if (address(this).balance > 0) {
            TransferHelper.safeTransferETH(msg.sender, address(this).balance);
        }
    }

    function _generalTransfer(
        address token,
        address payable to,
        uint256 amount
    ) private {
        if (amount > 0) {
            if (token == ETH_PLACEHOLDER_ADDR) {
                TransferHelper.safeTransferETH(to, amount);
            } else {
                TransferHelper.safeTransfer(token, to, amount);
            }
        }
    }

    function _generalBalanceOf(address token, address who) private view returns (uint256) {
        return token == ETH_PLACEHOLDER_ADDR ? who.balance : IERC20(token).balanceOf(who);
    }
}

File 43 of 106 : IStargateRouter.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

interface IStargateRouter {
    struct lzTxObj {
        uint256 dstGasForCall;
        uint256 dstNativeAmount;
        bytes dstNativeAddr;
    }

    function addLiquidity(
        uint256 _poolId,
        uint256 _amountLD,
        address _to
    ) external;

    function swap(
        uint16 _dstChainId,
        uint256 _srcPoolId,
        uint256 _dstPoolId,
        address payable _refundAddress,
        uint256 _amountLD,
        uint256 _minAmountLD,
        lzTxObj memory _lzTxParams,
        bytes calldata _to,
        bytes calldata _payload
    ) external payable;

    function redeemRemote(
        uint16 _dstChainId,
        uint256 _srcPoolId,
        uint256 _dstPoolId,
        address payable _refundAddress,
        uint256 _amountLP,
        uint256 _minAmountLD,
        bytes calldata _to,
        lzTxObj memory _lzTxParams
    ) external payable;

    function instantRedeemLocal(
        uint16 _srcPoolId,
        uint256 _amountLP,
        address _to
    ) external returns (uint256);

    function redeemLocal(
        uint16 _dstChainId,
        uint256 _srcPoolId,
        uint256 _dstPoolId,
        address payable _refundAddress,
        uint256 _amountLP,
        bytes calldata _to,
        lzTxObj memory _lzTxParams
    ) external payable;

    function sendCredits(
        uint16 _dstChainId,
        uint256 _srcPoolId,
        uint256 _dstPoolId,
        address payable _refundAddress
    ) external payable;

    function quoteLayerZeroFee(
        uint16 _dstChainId,
        uint8 _functionType,
        bytes calldata _toAddress,
        bytes calldata _transferAndCallPayload,
        lzTxObj memory _lzTxParams
    ) external view returns (uint256, uint256);
}

File 44 of 106 : IStargateReceiver.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;

interface IStargateReceiver {
    function sgReceive(
        uint16 _chainId,
        bytes memory _srcAddress,
        uint256 _nonce,
        address _token,
        uint256 amountLD,
        bytes memory payload
    ) external;
}

File 45 of 106 : WooAccessManager.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/Pausable.sol';
import './interfaces/IWooAccessManager.sol';

contract WooAccessManager is IWooAccessManager, Ownable, Pausable {
    /* ----- State variables ----- */

    mapping(address => bool) public override isFeeAdmin;
    mapping(address => bool) public override isVaultAdmin;
    mapping(address => bool) public override isRebateAdmin;
    mapping(address => bool) public override isZeroFeeVault;

    /* ----- Admin Functions ----- */

    /// @inheritdoc IWooAccessManager
    function setFeeAdmin(address feeAdmin, bool flag) external override onlyOwner whenNotPaused {
        require(feeAdmin != address(0), 'WooAccessManager: feeAdmin_ZERO_ADDR');
        isFeeAdmin[feeAdmin] = flag;
        emit FeeAdminUpdated(feeAdmin, flag);
    }

    /// @inheritdoc IWooAccessManager
    function batchSetFeeAdmin(address[] calldata feeAdmins, bool[] calldata flags)
        external
        override
        onlyOwner
        whenNotPaused
    {
        require(feeAdmins.length == flags.length, 'WooAccessManager: length_INVALID');

        for (uint256 i = 0; i < feeAdmins.length; i++) {
            require(feeAdmins[i] != address(0), 'WooAccessManager: feeAdmin_ZERO_ADDR');
            isFeeAdmin[feeAdmins[i]] = flags[i];
            emit FeeAdminUpdated(feeAdmins[i], flags[i]);
        }
    }

    /// @inheritdoc IWooAccessManager
    function setVaultAdmin(address vaultAdmin, bool flag) external override onlyOwner whenNotPaused {
        require(vaultAdmin != address(0), 'WooAccessManager: vaultAdmin_ZERO_ADDR');
        isVaultAdmin[vaultAdmin] = flag;
        emit VaultAdminUpdated(vaultAdmin, flag);
    }

    /// @inheritdoc IWooAccessManager
    function batchSetVaultAdmin(address[] calldata vaultAdmins, bool[] calldata flags)
        external
        override
        onlyOwner
        whenNotPaused
    {
        require(vaultAdmins.length == flags.length, 'WooAccessManager: length_INVALID');

        for (uint256 i = 0; i < vaultAdmins.length; i++) {
            require(vaultAdmins[i] != address(0), 'WooAccessManager: vaultAdmin_ZERO_ADDR');
            isVaultAdmin[vaultAdmins[i]] = flags[i];
            emit VaultAdminUpdated(vaultAdmins[i], flags[i]);
        }
    }

    /// @inheritdoc IWooAccessManager
    function setRebateAdmin(address rebateAdmin, bool flag) external override onlyOwner whenNotPaused {
        require(rebateAdmin != address(0), 'WooAccessManager: rebateAdmin_ZERO_ADDR');
        isRebateAdmin[rebateAdmin] = flag;
        emit RebateAdminUpdated(rebateAdmin, flag);
    }

    /// @inheritdoc IWooAccessManager
    function batchSetRebateAdmin(address[] calldata rebateAdmins, bool[] calldata flags)
        external
        override
        onlyOwner
        whenNotPaused
    {
        require(rebateAdmins.length == flags.length, 'WooAccessManager: length_INVALID');

        for (uint256 i = 0; i < rebateAdmins.length; i++) {
            require(rebateAdmins[i] != address(0), 'WooAccessManager: rebateAdmin_ZERO_ADDR');
            isRebateAdmin[rebateAdmins[i]] = flags[i];
            emit RebateAdminUpdated(rebateAdmins[i], flags[i]);
        }
    }

    /// @inheritdoc IWooAccessManager
    function setZeroFeeVault(address vault, bool flag) external override onlyOwner whenNotPaused {
        require(vault != address(0), 'WooAccessManager: vault_ZERO_ADDR');
        isZeroFeeVault[vault] = flag;
        emit ZeroFeeVaultUpdated(vault, flag);
    }

    /// @inheritdoc IWooAccessManager
    function batchSetZeroFeeVault(address[] calldata vaults, bool[] calldata flags)
        external
        override
        onlyOwner
        whenNotPaused
    {
        require(vaults.length == flags.length, 'WooAccessManager: length_INVALID');

        for (uint256 i = 0; i < vaults.length; i++) {
            require(vaults[i] != address(0), 'WooAccessManager: vault_ZERO_ADDR');
            isZeroFeeVault[vaults[i]] = flags[i];
            emit ZeroFeeVaultUpdated(vaults[i], flags[i]);
        }
    }

    /// @notice Pause the contract.
    function pause() external onlyOwner {
        super._pause();
    }

    /// @notice Restart the contract.
    function unpause() external onlyOwner {
        super._unpause();
    }
}

File 46 of 106 : TestErc20Token.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.6.12;

import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import '@openzeppelin/contracts/access/Ownable.sol';

contract TestToken is ERC20('TestToken', 'TT'), Ownable {
    using SafeMath for uint256;

    function mint(address _to, uint256 _amount) public onlyOwner {
        _mint(_to, _amount);
    }
}

File 47 of 106 : VaultV2_Vector.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/ReentrancyGuard.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import '../interfaces/IStrategy.sol';
import '../interfaces/IWETH.sol';
import '../interfaces/IWooAccessManager.sol';
import '../interfaces/IVault.sol';

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

*
* MIT License
* ===========
*
* Copyright (c) 2020 WooTrade
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* 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 SOFTWARE.
*/
contract WOOFiVaultV2Vector is IVault, ERC20, Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    struct StratCandidate {
        address implementation;
        uint256 proposedTime;
    }

    /* ----- State Variables ----- */

    address public immutable override want;

    IWooAccessManager public immutable accessManager;

    IStrategy public strategy;
    StratCandidate public stratCandidate;

    uint256 public approvalDelay = 48 hours;
    uint256 public earnThreshold = 3000;

    mapping(address => uint256) public costSharePrice;

    event NewStratCandidate(address indexed implementation);
    event UpgradeStrat(address indexed implementation);

    /* ----- Constant Variables ----- */

    // WBNB: https://bscscan.com/token/0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c
    // WAVAX: https://snowtrace.io/address/0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7
    address public immutable weth;

    constructor(
        address _weth,
        address _want,
        address _accessManager
    )
        public
        ERC20(
            string(abi.encodePacked('WOOFi Earn ', ERC20(_want).name())),
            string(abi.encodePacked('we', ERC20(_want).symbol()))
        )
    {
        require(_weth != address(0), 'WOOFiVaultV2: weth_ZERO_ADDR');
        require(_want != address(0), 'WOOFiVaultV2: want_ZERO_ADDR');
        require(_accessManager != address(0), 'WOOFiVaultV2: accessManager_ZERO_ADDR');

        weth = _weth;
        want = _want;
        accessManager = IWooAccessManager(_accessManager);
    }

    modifier onlyAdmin() {
        require(owner() == msg.sender || accessManager.isVaultAdmin(msg.sender), 'WOOFiVaultV2: NOT_ADMIN');
        _;
    }

    /* ----- External Functions ----- */

    function deposit(uint256 amount) public payable override nonReentrant {
        require(amount > 0, 'WOOFiVaultV2: amount_CAN_NOT_BE_ZERO');

        if (want == weth) {
            require(msg.value == amount, 'WOOFiVaultV2: msg.value_INSUFFICIENT');
        } else {
            require(msg.value == 0, 'WOOFiVaultV2: msg.value_INVALID');
        }

        if (address(strategy) != address(0)) {
            require(!strategy.paused(), 'WOOFiVaultV2: strat_paused');
            strategy.beforeDeposit();
        }

        uint256 balanceBefore = balance();
        if (want == weth) {
            IWETH(weth).deposit{value: msg.value}();
        } else {
            TransferHelper.safeTransferFrom(want, msg.sender, address(this), amount);
        }
        uint256 balanceAfter = balance();
        require(amount <= balanceAfter.sub(balanceBefore), 'WOOFiVaultV2: amount_NOT_ENOUGH');

        uint256 shares = totalSupply() == 0 ? amount : amount.mul(totalSupply()).div(balanceBefore);
        uint256 sharesBefore = balanceOf(msg.sender);
        uint256 costBefore = costSharePrice[msg.sender];
        uint256 costAfter = (sharesBefore.mul(costBefore).add(amount.mul(1e18))).div(sharesBefore.add(shares));
        costSharePrice[msg.sender] = costAfter;

        _mint(msg.sender, shares);

        if (amount >= earnThreshold * (10**uint256(ERC20(want).decimals()))) {
            earn();
        }
    }

    function withdraw(uint256 shares) public override nonReentrant {
        require(shares > 0, 'WOOFiVaultV2: shares_ZERO');
        require(shares <= balanceOf(msg.sender), 'WOOFiVaultV2: shares_NOT_ENOUGH');

        if (address(strategy) != address(0)) {
            strategy.beforeWithdraw();
        }

        uint256 withdrawAmount = shares.mul(balance()).div(totalSupply());
        _burn(msg.sender, shares);

        uint256 balanceBefore = IERC20(want).balanceOf(address(this));
        if (balanceBefore < withdrawAmount) {
            uint256 balanceToWithdraw = withdrawAmount.sub(balanceBefore);
            require(_isStratActive(), 'WOOFiVaultV2: STRAT_INACTIVE');
            strategy.withdraw(balanceToWithdraw);
            uint256 balanceAfter = IERC20(want).balanceOf(address(this));
            if (withdrawAmount > balanceAfter) {
                // NOTE: in case a small amount not counted in, due to the decimal precision.
                withdrawAmount = balanceAfter;
            }
        }

        if (want == weth) {
            IWETH(weth).withdraw(withdrawAmount);
            TransferHelper.safeTransferETH(msg.sender, withdrawAmount);
        } else {
            TransferHelper.safeTransfer(want, msg.sender, withdrawAmount);
        }
    }

    function earn() public override {
        if (_isStratActive()) {
            uint256 balanceAvail = available();
            TransferHelper.safeTransfer(want, address(strategy), balanceAvail);
            strategy.deposit();
        }
    }

    function available() public view override returns (uint256) {
        return IERC20(want).balanceOf(address(this));
    }

    function balance() public view override returns (uint256) {
        return address(strategy) != address(0) ? available().add(strategy.balanceOf()) : available();
    }

    function getPricePerFullShare() public view override returns (uint256) {
        return totalSupply() == 0 ? 1e18 : balance().mul(1e18).div(totalSupply());
    }

    function _isStratActive() internal view returns (bool) {
        return address(strategy) != address(0) && !strategy.paused();
    }

    /* ----- Admin Functions ----- */

    function setupStrat(address _strat) public onlyAdmin {
        require(_strat != address(0), 'WOOFiVaultV2: STRAT_ZERO_ADDR');
        require(address(strategy) == address(0), 'WOOFiVaultV2: STRAT_ALREADY_SET');
        require(address(this) == IStrategy(_strat).vault(), 'WOOFiVaultV2: STRAT_VAULT_INVALID');
        require(want == IStrategy(_strat).want(), 'WOOFiVaultV2: STRAT_WANT_INVALID');
        strategy = IStrategy(_strat);

        emit UpgradeStrat(_strat);
    }

    function proposeStrat(address _implementation) public onlyAdmin {
        require(address(this) == IStrategy(_implementation).vault(), 'WOOFiVaultV2: STRAT_VAULT_INVALID');
        require(want == IStrategy(_implementation).want(), 'WOOFiVaultV2: STRAT_WANT_INVALID');
        stratCandidate = StratCandidate({implementation: _implementation, proposedTime: block.timestamp});

        emit NewStratCandidate(_implementation);
    }

    function upgradeStrat() public onlyAdmin {
        require(stratCandidate.implementation != address(0), 'WOOFiVaultV2: NO_CANDIDATE');
        require(stratCandidate.proposedTime.add(approvalDelay) < block.timestamp, 'WOOFiVaultV2: TIME_INVALID');

        emit UpgradeStrat(stratCandidate.implementation);

        strategy.retireStrat();
        strategy = IStrategy(stratCandidate.implementation);
        stratCandidate.implementation = address(0);
        stratCandidate.proposedTime = 5000000000; // 100+ years to ensure proposedTime check

        earn();
    }

    function setApprovalDelay(uint256 _approvalDelay) external onlyAdmin {
        require(_approvalDelay > 0, 'WOOFiVaultV2: approvalDelay_ZERO');
        approvalDelay = _approvalDelay;
    }

    function setEarnThreshold(uint256 _earnThreshold) external onlyAdmin {
        earnThreshold = _earnThreshold;
    }

    function inCaseTokensGetStuck(address stuckToken) external onlyAdmin {
        require(stuckToken != want, 'WOOFiVaultV2: stuckToken_NOT_WANT');
        require(stuckToken != address(0), 'WOOFiVaultV2: stuckToken_ZERO_ADDR');
        uint256 amount = IERC20(stuckToken).balanceOf(address(this));
        if (amount > 0) {
            TransferHelper.safeTransfer(stuckToken, msg.sender, amount);
        }
    }

    function inCaseNativeTokensGetStuck() external onlyAdmin {
        // NOTE: vault never needs native tokens to do the yield farming;
        // This native token balance indicates a user's incorrect transfer.
        if (address(this).balance > 0) {
            TransferHelper.safeTransferETH(msg.sender, address(this).balance);
        }
    }

    receive() external payable {}
}

File 48 of 106 : VaultErc20.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/ReentrancyGuard.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import '../interfaces/IStrategy.sol';
import '../interfaces/IWETH.sol';
import '../interfaces/IWooAccessManager.sol';
import '../interfaces/IVault.sol';

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

contract VaultErc20 is IVault, ERC20, Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    struct StratCandidate {
        address implementation;
        uint256 proposedTime;
    }

    /* ----- State Variables ----- */

    address public immutable override want;

    IWooAccessManager public immutable accessManager;

    IStrategy public strategy;
    StratCandidate public stratCandidate;

    uint256 public approvalDelay = 48 hours;

    mapping(address => uint256) public costSharePrice;

    event NewStratCandidate(address indexed implementation);
    event UpgradeStrat(address indexed implementation);

    constructor(address initWant, address initAccessManager)
        public
        ERC20(
            string(abi.encodePacked('WOOFi Earn ', ERC20(initWant).name())),
            string(abi.encodePacked('we', ERC20(initWant).symbol()))
        )
    {
        require(initWant != address(0), 'Vault: initWant_ZERO_ADDR');
        require(initAccessManager != address(0), 'Vault: initAccessManager_ZERO_ADDR');

        want = initWant;
        accessManager = IWooAccessManager(initAccessManager);
    }

    modifier onlyAdmin() {
        require(owner() == _msgSender() || accessManager.isVaultAdmin(msg.sender), 'Vault: NOT_ADMIN');
        _;
    }

    /* ----- External Functions ----- */

    function deposit(uint256 amount) public payable override nonReentrant {
        require(amount > 0, 'Vault: amount_CAN_NOT_BE_ZERO');

        // STEP 0: strategy's routing work before deposit.
        if (address(strategy) != address(0)) {
            require(!strategy.paused(), 'Vault: strat_paused');
            strategy.beforeDeposit();
        }

        // STEP 1: check the deposit amount
        uint256 balanceBefore = balance();
        TransferHelper.safeTransferFrom(want, msg.sender, address(this), amount);
        uint256 balanceAfter = balance();
        require(amount <= balanceAfter.sub(balanceBefore), 'Vault: amount_NOT_ENOUGH');

        // STEP 2: issues the shares and update the cost basis
        uint256 shares = totalSupply() == 0 ? amount : amount.mul(totalSupply()).div(balanceBefore);
        uint256 sharesBefore = balanceOf(msg.sender);
        uint256 costBefore = costSharePrice[msg.sender];
        uint256 costAfter = (sharesBefore.mul(costBefore).add(amount.mul(1e18))).div(sharesBefore.add(shares));
        costSharePrice[msg.sender] = costAfter;
        _mint(msg.sender, shares);

        // STEP 3
        earn();
    }

    function withdraw(uint256 shares) public override nonReentrant {
        require(shares > 0, 'Vault: shares_ZERO');
        require(shares <= balanceOf(msg.sender), 'Vault: shares_NOT_ENOUGH');

        // STEP 0: burn the user's shares to start the withdrawal process.
        uint256 withdrawAmount = shares.mul(balance()).div(totalSupply());
        _burn(msg.sender, shares);

        // STEP 1: withdraw the token from strategy if needed
        uint256 balanceBefore = IERC20(want).balanceOf(address(this));
        if (balanceBefore < withdrawAmount) {
            uint256 balanceToWithdraw = withdrawAmount.sub(balanceBefore);
            require(_isStratActive(), 'Vault: STRAT_INACTIVE');
            strategy.withdraw(balanceToWithdraw);
            uint256 balanceAfter = IERC20(want).balanceOf(address(this));
            require(balanceAfter.sub(balanceBefore) > 0, 'Vault: Strat_WITHDRAW_ERROR');
            if (withdrawAmount > balanceAfter) {
                // NOTE: Tiny diff is accepted due to the decimal precision.
                withdrawAmount = balanceAfter;
            }
        }

        // STEP 3
        TransferHelper.safeTransfer(want, msg.sender, withdrawAmount);
    }

    function earn() public override {
        if (_isStratActive()) {
            uint256 balanceAvail = available();
            if (balanceAvail > 0) {
                TransferHelper.safeTransfer(want, address(strategy), balanceAvail);
                strategy.deposit();
            }
        }
    }

    function available() public view override returns (uint256) {
        return IERC20(want).balanceOf(address(this));
    }

    function balance() public view override returns (uint256) {
        return address(strategy) != address(0) ? available().add(strategy.balanceOf()) : available();
    }

    function getPricePerFullShare() public view override returns (uint256) {
        return totalSupply() == 0 ? 1e18 : balance().mul(1e18).div(totalSupply());
    }

    function _isStratActive() internal view returns (bool) {
        return address(strategy) != address(0) && !strategy.paused();
    }

    /* ----- Admin Functions ----- */

    function setupStrat(address _strat) public onlyAdmin {
        require(_strat != address(0), 'Vault: STRAT_ZERO_ADDR');
        require(address(strategy) == address(0), 'Vault: STRAT_ALREADY_SET');
        require(address(this) == IStrategy(_strat).vault(), 'Vault: STRAT_VAULT_INVALID');
        require(want == IStrategy(_strat).want(), 'Vault: STRAT_WANT_INVALID');
        strategy = IStrategy(_strat);

        emit UpgradeStrat(_strat);
    }

    function proposeStrat(address _implementation) public onlyAdmin {
        require(address(this) == IStrategy(_implementation).vault(), 'Vault: STRAT_VAULT_INVALID');
        require(want == IStrategy(_implementation).want(), 'Vault: STRAT_WANT_INVALID');
        stratCandidate = StratCandidate({implementation: _implementation, proposedTime: block.timestamp});

        emit NewStratCandidate(_implementation);
    }

    function upgradeStrat() public onlyAdmin {
        require(stratCandidate.implementation != address(0), 'Vault: NO_CANDIDATE');
        require(stratCandidate.proposedTime.add(approvalDelay) < block.timestamp, 'Vault: TIME_INVALID');

        emit UpgradeStrat(stratCandidate.implementation);

        strategy.retireStrat();
        strategy = IStrategy(stratCandidate.implementation);
        stratCandidate.implementation = address(0);
        stratCandidate.proposedTime = 5000000000; // 100+ years to ensure proposedTime check

        earn();
    }

    function setApprovalDelay(uint256 newApprovalDelay) external onlyAdmin {
        require(newApprovalDelay > 0, 'Vault: newApprovalDelay_ZERO');
        approvalDelay = newApprovalDelay;
    }

    function inCaseTokensGetStuck(address stuckToken) external onlyAdmin {
        // NOTE: vault never allowed to access users' `want` token
        require(stuckToken != want, 'Vault: stuckToken_NOT_WANT');
        require(stuckToken != address(0), 'Vault: stuckToken_ZERO_ADDR');
        uint256 amount = IERC20(stuckToken).balanceOf(address(this));
        if (amount > 0) {
            TransferHelper.safeTransfer(stuckToken, msg.sender, amount);
        }
    }
}

File 49 of 106 : VaultAvax.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/ReentrancyGuard.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import '../interfaces/IStrategy.sol';
import '../interfaces/IWETH.sol';
import '../interfaces/IWooAccessManager.sol';
import '../interfaces/IVault.sol';

contract VaultAvax is IVault, ERC20, Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    struct StratCandidate {
        address implementation;
        uint256 proposedTime;
    }

    /* ----- State Variables ----- */

    address public immutable override want;

    IWooAccessManager public immutable accessManager;

    IStrategy public strategy;
    StratCandidate public stratCandidate;

    mapping(address => uint256) public costSharePrice;

    event NewStratCandidate(address indexed implementation);
    event UpgradeStrat(address indexed implementation);

    /* ----- Constant Variables ----- */

    // WAVAX: https://snowtrace.io/address/0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7
    address public constant wrappedEther = 0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7;

    constructor(address initWant, address initAccessManager)
        public
        ERC20(
            string(abi.encodePacked('WOOFi Earn ', ERC20(initWant).name())),
            string(abi.encodePacked('we', ERC20(initWant).symbol()))
        )
    {
        require(initWant != address(0), 'Vault: initWant_ZERO_ADDR');
        require(initAccessManager != address(0), 'Vault: initAccessManager_ZERO_ADDR');

        want = initWant;
        accessManager = IWooAccessManager(initAccessManager);
    }

    modifier onlyAdmin() {
        require(owner() == _msgSender() || accessManager.isVaultAdmin(msg.sender), 'Vault: NOT_ADMIN');
        _;
    }

    /* ----- External Functions ----- */

    function deposit(uint256 amount) public payable override nonReentrant {
        require(amount > 0, 'Vault: amount_CAN_NOT_BE_ZERO');

        if (want == wrappedEther) {
            require(msg.value == amount, 'Vault: msg.value_INSUFFICIENT');
        } else {
            require(msg.value == 0, 'Vault: msg.value_INVALID');
        }

        if (address(strategy) != address(0)) {
            require(!strategy.paused(), 'Vault: strat_paused');
            strategy.beforeDeposit();
        }

        uint256 balanceBefore = balance();
        if (want == wrappedEther) {
            IWETH(wrappedEther).deposit{value: msg.value}();
        } else {
            TransferHelper.safeTransferFrom(want, msg.sender, address(this), amount);
        }
        uint256 balanceAfter = balance();
        require(amount <= balanceAfter.sub(balanceBefore), 'Vault: amount_NOT_ENOUGH');

        uint256 shares = totalSupply() == 0 ? amount : amount.mul(totalSupply()).div(balanceBefore);
        uint256 sharesBefore = balanceOf(msg.sender);
        uint256 costBefore = costSharePrice[msg.sender];
        uint256 costAfter = (sharesBefore.mul(costBefore).add(amount.mul(1e18))).div(sharesBefore.add(shares));
        costSharePrice[msg.sender] = costAfter;

        _mint(msg.sender, shares);

        earn();
    }

    function withdraw(uint256 shares) public override nonReentrant {
        require(shares > 0, 'Vault: shares_ZERO');
        require(shares <= balanceOf(msg.sender), 'Vault: shares_NOT_ENOUGH');

        uint256 withdrawAmount = shares.mul(balance()).div(totalSupply());
        _burn(msg.sender, shares);

        uint256 balanceBefore = IERC20(want).balanceOf(address(this));
        if (balanceBefore < withdrawAmount) {
            uint256 balanceToWithdraw = withdrawAmount.sub(balanceBefore);
            require(_isStratActive(), 'Vault: STRAT_INACTIVE');
            strategy.withdraw(balanceToWithdraw);
            uint256 balanceAfter = IERC20(want).balanceOf(address(this));
            if (withdrawAmount > balanceAfter) {
                // NOTE: in case a small amount not counted in, due to the decimal precision.
                withdrawAmount = balanceAfter;
            }
        }

        if (want == wrappedEther) {
            IWETH(wrappedEther).withdraw(withdrawAmount);
            TransferHelper.safeTransferETH(msg.sender, withdrawAmount);
        } else {
            TransferHelper.safeTransfer(want, msg.sender, withdrawAmount);
        }
    }

    function earn() public override {
        if (_isStratActive()) {
            uint256 balanceAvail = available();
            TransferHelper.safeTransfer(want, address(strategy), balanceAvail);
            strategy.deposit();
        }
    }

    function available() public view override returns (uint256) {
        return IERC20(want).balanceOf(address(this));
    }

    function balance() public view override returns (uint256) {
        return address(strategy) != address(0) ? available().add(strategy.balanceOf()) : available();
    }

    function getPricePerFullShare() public view override returns (uint256) {
        return totalSupply() == 0 ? 1e18 : balance().mul(1e18).div(totalSupply());
    }

    function _isStratActive() internal view returns (bool) {
        return address(strategy) != address(0) && !strategy.paused();
    }

    /* ----- Admin Functions ----- */

    function setupStrat(address _strat) public onlyAdmin {
        require(_strat != address(0), 'Vault: STRAT_ZERO_ADDR');
        require(address(strategy) == address(0), 'Vault: STRAT_ALREADY_SET');
        require(address(this) == IStrategy(_strat).vault(), 'Vault: STRAT_VAULT_INVALID');
        require(want == IStrategy(_strat).want(), 'Vault: STRAT_WANT_INVALID');
        strategy = IStrategy(_strat);

        emit UpgradeStrat(_strat);
    }

    function proposeStrat(address _implementation) public onlyAdmin {
        require(address(this) == IStrategy(_implementation).vault(), 'Vault: STRAT_VAULT_INVALID');
        require(want == IStrategy(_implementation).want(), 'Vault: STRAT_WANT_INVALID');
        stratCandidate = StratCandidate({implementation: _implementation, proposedTime: block.timestamp});

        emit NewStratCandidate(_implementation);
    }

    function upgradeStrat() public onlyAdmin {
        require(stratCandidate.implementation != address(0), 'Vault: NO_CANDIDATE');
        require(stratCandidate.proposedTime.add(48 hours) < block.timestamp, 'Vault: TIME_INVALID');

        emit UpgradeStrat(stratCandidate.implementation);

        strategy.retireStrat();
        strategy = IStrategy(stratCandidate.implementation);
        stratCandidate.implementation = address(0);
        stratCandidate.proposedTime = 5000000000; // 100+ years to ensure proposedTime check

        earn();
    }

    function inCaseTokensGetStuck(address stuckToken) external onlyAdmin {
        require(stuckToken != address(0), 'Vault: stuckToken_ZERO_ADDR');
        require(stuckToken != want, 'Vault: stuckToken_NOT_WANT');

        uint256 amount = IERC20(stuckToken).balanceOf(address(this));
        if (amount > 0) {
            TransferHelper.safeTransfer(stuckToken, msg.sender, amount);
        }
    }

    function inCaseNativeTokensGetStuck() external onlyAdmin {
        // NOTE: vault never needs native tokens to do the yield farming;
        // This native token balance indicates a user's incorrect transfer.
        if (address(this).balance > 0) {
            TransferHelper.safeTransferETH(msg.sender, address(this).balance);
        }
    }

    receive() external payable {}
}

File 50 of 106 : Vault.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/ReentrancyGuard.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import '../interfaces/IStrategy.sol';
import '../interfaces/IWETH.sol';
import '../interfaces/IWooAccessManager.sol';
import '../interfaces/IVault.sol';

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

*
* MIT License
* ===========
*
* Copyright (c) 2020 WooTrade
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* 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 SOFTWARE.
*/
contract Vault is IVault, ERC20, Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    struct StratCandidate {
        address implementation;
        uint256 proposedTime;
    }

    /* ----- State Variables ----- */

    address public immutable override want;

    IWooAccessManager public immutable accessManager;

    IStrategy public strategy;
    StratCandidate public stratCandidate;

    uint256 public approvalDelay = 48 hours;

    mapping(address => uint256) public costSharePrice;

    event NewStratCandidate(address indexed implementation);
    event UpgradeStrat(address indexed implementation);

    /* ----- Constant Variables ----- */

    // WBNB: https://bscscan.com/token/0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c
    address public constant wrappedEther = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c;

    constructor(address initWant, address initAccessManager)
        public
        ERC20(
            string(abi.encodePacked('WOOFi Earn ', ERC20(initWant).name())),
            string(abi.encodePacked('we', ERC20(initWant).symbol()))
        )
    {
        require(initWant != address(0), 'Vault: initWant_ZERO_ADDR');
        require(initAccessManager != address(0), 'Vault: initAccessManager_ZERO_ADDR');

        want = initWant;
        accessManager = IWooAccessManager(initAccessManager);
    }

    modifier onlyAdmin() {
        require(owner() == _msgSender() || accessManager.isVaultAdmin(msg.sender), 'Vault: NOT_ADMIN');
        _;
    }

    /* ----- External Functions ----- */

    function deposit(uint256 amount) public payable override nonReentrant {
        require(amount > 0, 'Vault: amount_CAN_NOT_BE_ZERO');

        if (want == wrappedEther) {
            require(msg.value == amount, 'Vault: msg.value_INSUFFICIENT');
        } else {
            require(msg.value == 0, 'Vault: msg.value_INVALID');
        }

        if (address(strategy) != address(0)) {
            require(!strategy.paused(), 'Vault: strat_paused');
            strategy.beforeDeposit();
        }

        uint256 balanceBefore = balance();
        if (want == wrappedEther) {
            IWETH(wrappedEther).deposit{value: msg.value}();
        } else {
            TransferHelper.safeTransferFrom(want, msg.sender, address(this), amount);
        }
        uint256 balanceAfter = balance();
        require(amount <= balanceAfter.sub(balanceBefore), 'Vault: amount_NOT_ENOUGH');

        uint256 shares = totalSupply() == 0 ? amount : amount.mul(totalSupply()).div(balanceBefore);
        uint256 sharesBefore = balanceOf(msg.sender);
        uint256 costBefore = costSharePrice[msg.sender];
        uint256 costAfter = (sharesBefore.mul(costBefore).add(amount.mul(1e18))).div(sharesBefore.add(shares));
        costSharePrice[msg.sender] = costAfter;

        _mint(msg.sender, shares);

        earn();
    }

    function withdraw(uint256 shares) public override nonReentrant {
        require(shares > 0, 'Vault: shares_ZERO');
        require(shares <= balanceOf(msg.sender), 'Vault: shares_NOT_ENOUGH');

        uint256 withdrawAmount = shares.mul(balance()).div(totalSupply());
        _burn(msg.sender, shares);

        uint256 balanceBefore = IERC20(want).balanceOf(address(this));
        if (balanceBefore < withdrawAmount) {
            uint256 balanceToWithdraw = withdrawAmount.sub(balanceBefore);
            require(_isStratActive(), 'Vault: STRAT_INACTIVE');
            strategy.withdraw(balanceToWithdraw);
            uint256 balanceAfter = IERC20(want).balanceOf(address(this));
            if (withdrawAmount > balanceAfter) {
                // NOTE: in case a small amount not counted in, due to the decimal precision.
                withdrawAmount = balanceAfter;
            }
        }

        if (want == wrappedEther) {
            IWETH(wrappedEther).withdraw(withdrawAmount);
            TransferHelper.safeTransferETH(msg.sender, withdrawAmount);
        } else {
            TransferHelper.safeTransfer(want, msg.sender, withdrawAmount);
        }
    }

    function earn() public override {
        if (_isStratActive()) {
            uint256 balanceAvail = available();
            TransferHelper.safeTransfer(want, address(strategy), balanceAvail);
            strategy.deposit();
        }
    }

    function available() public view override returns (uint256) {
        return IERC20(want).balanceOf(address(this));
    }

    function balance() public view override returns (uint256) {
        return address(strategy) != address(0) ? available().add(strategy.balanceOf()) : available();
    }

    function getPricePerFullShare() public view override returns (uint256) {
        return totalSupply() == 0 ? 1e18 : balance().mul(1e18).div(totalSupply());
    }

    function _isStratActive() internal view returns (bool) {
        return address(strategy) != address(0) && !strategy.paused();
    }

    /* ----- Admin Functions ----- */

    function setupStrat(address _strat) public onlyAdmin {
        require(_strat != address(0), 'Vault: STRAT_ZERO_ADDR');
        require(address(strategy) == address(0), 'Vault: STRAT_ALREADY_SET');
        require(address(this) == IStrategy(_strat).vault(), 'Vault: STRAT_VAULT_INVALID');
        require(want == IStrategy(_strat).want(), 'Vault: STRAT_WANT_INVALID');
        strategy = IStrategy(_strat);

        emit UpgradeStrat(_strat);
    }

    function proposeStrat(address _implementation) public onlyAdmin {
        require(address(this) == IStrategy(_implementation).vault(), 'Vault: STRAT_VAULT_INVALID');
        require(want == IStrategy(_implementation).want(), 'Vault: STRAT_WANT_INVALID');
        stratCandidate = StratCandidate({implementation: _implementation, proposedTime: block.timestamp});

        emit NewStratCandidate(_implementation);
    }

    function upgradeStrat() public onlyAdmin {
        require(stratCandidate.implementation != address(0), 'Vault: NO_CANDIDATE');
        require(stratCandidate.proposedTime.add(approvalDelay) < block.timestamp, 'Vault: TIME_INVALID');

        emit UpgradeStrat(stratCandidate.implementation);

        strategy.retireStrat();
        strategy = IStrategy(stratCandidate.implementation);
        stratCandidate.implementation = address(0);
        stratCandidate.proposedTime = 5000000000; // 100+ years to ensure proposedTime check

        earn();
    }

    function setApprovalDelay(uint256 newApprovalDelay) external onlyAdmin {
        require(newApprovalDelay > 0, 'Vault: newApprovalDelay_ZERO');
        approvalDelay = newApprovalDelay;
    }

    function inCaseTokensGetStuck(address stuckToken) external onlyAdmin {
        require(stuckToken != want, 'Vault: stuckToken_NOT_WANT');
        require(stuckToken != address(0), 'Vault: stuckToken_ZERO_ADDR');
        uint256 amount = IERC20(stuckToken).balanceOf(address(this));
        if (amount > 0) {
            TransferHelper.safeTransfer(stuckToken, msg.sender, amount);
        }
    }

    function inCaseNativeTokensGetStuck() external onlyAdmin {
        // NOTE: vault never needs native tokens to do the yield farming;
        // This native token balance indicates a user's incorrect transfer.
        if (address(this).balance > 0) {
            TransferHelper.safeTransferETH(msg.sender, address(this).balance);
        }
    }

    receive() external payable {}
}

File 51 of 106 : StrategyScream.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/Pausable.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import '../../../interfaces/TraderJoe/IUniswapRouter.sol';
import '../../../interfaces/Scream/IVToken.sol';
import '../../../interfaces/Scream/IComptroller.sol';
import '../../../interfaces/IWooAccessManager.sol';
import '../../../interfaces/IWETH.sol';
import '../BaseStrategy.sol';

contract StrategyScream is BaseStrategy {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    /* ----- State Variables ----- */

    // scFUSDT: https://ftmscan.com/address/0x02224765bc8d54c21bb51b0951c80315e1c263f9
    address public iToken;
    address[] public rewardToWantRoute;
    uint256 public lastHarvest;
    uint256 public supplyBal;

    /* ----- Constant Variables ----- */

    address public constant wrappedEther = address(0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83); // WFTM
    address public constant reward = address(0xe0654C8e6fd4D733349ac7E09f6f23DA256bF475); // SCREAM
    address public constant uniRouter = address(0xF491e7B69E4244ad4002BC14e878a34207E38c29); // SpookySwapRouter
    address public constant comptroller = address(0x260E596DAbE3AFc463e75B6CC05d8c46aCAcFB09); // Unitroller that implement Comptroller

    /* ----- Events ----- */

    event StratHarvest(address indexed harvester, uint256 wantHarvested, uint256 tvl);
    event Deposit(uint256 tvl);
    event Withdraw(uint256 tvl);

    constructor(
        address _vault,
        address _accessManager,
        address _iToken,
        address[] memory _rewardToWantRoute
    ) public BaseStrategy(_vault, _accessManager) {
        iToken = _iToken;
        rewardToWantRoute = _rewardToWantRoute;

        _giveAllowances();
    }

    /* ----- External Functions ----- */

    function beforeDeposit() public override {
        super.beforeDeposit();
        updateSupplyBal();
    }

    function rewardToWant() external view returns (address[] memory) {
        return rewardToWantRoute;
    }

    /* ----- Public Functions ----- */

    function harvest() public override whenNotPaused {
        require(msg.sender == tx.origin || msg.sender == address(vault), 'StrategyScream: EOA_or_vault');

        // When pendingImplementation not zero address, means there is a new implement ready to replace.
        if (IComptroller(comptroller).pendingComptrollerImplementation() == address(0)) {
            uint256 beforeBal = balanceOfWant();

            _harvestAndSwap(rewardToWantRoute);

            uint256 wantHarvested = balanceOfWant().sub(beforeBal);
            uint256 fee = chargePerformanceFee(wantHarvested);
            deposit();

            lastHarvest = block.timestamp;
            emit StratHarvest(msg.sender, wantHarvested.sub(fee), balanceOf());
        } else {
            _withdrawAll();
            pause();
        }
    }

    function deposit() public override whenNotPaused nonReentrant {
        uint256 wantBal = balanceOfWant();

        if (wantBal > 0) {
            IVToken(iToken).mint(wantBal);
            updateSupplyBal();
            emit Deposit(balanceOf());
        }
    }

    function withdraw(uint256 amount) public override nonReentrant {
        require(msg.sender == vault, 'StrategyScream: !vault');
        require(amount > 0, 'StrategyScream: !amount');

        uint256 wantBal = balanceOfWant();

        if (wantBal < amount) {
            IVToken(iToken).redeemUnderlying(amount.sub(wantBal));
            updateSupplyBal();
            uint256 newWantBal = IERC20(want).balanceOf(address(this));
            require(newWantBal > wantBal, 'StrategyScream: !newWantBal');
            wantBal = newWantBal;
        }

        uint256 withdrawAmt = amount < wantBal ? amount : wantBal;

        uint256 fee = chargeWithdrawalFee(withdrawAmt);
        if (withdrawAmt > fee) {
            TransferHelper.safeTransfer(want, vault, withdrawAmt.sub(fee));
        }
        emit Withdraw(balanceOf());
    }

    function updateSupplyBal() public {
        supplyBal = IVToken(iToken).balanceOfUnderlying(address(this));
    }

    function balanceOfPool() public view override returns (uint256) {
        return supplyBal;
    }

    /* ----- Internal Functions ----- */

    function _giveAllowances() internal override {
        TransferHelper.safeApprove(want, iToken, 0);
        TransferHelper.safeApprove(want, iToken, uint256(-1));
        TransferHelper.safeApprove(reward, uniRouter, 0);
        TransferHelper.safeApprove(reward, uniRouter, uint256(-1));
        TransferHelper.safeApprove(wrappedEther, uniRouter, 0);
        TransferHelper.safeApprove(wrappedEther, uniRouter, uint256(-1));
    }

    function _removeAllowances() internal override {
        TransferHelper.safeApprove(want, iToken, 0);
        TransferHelper.safeApprove(reward, uniRouter, 0);
        TransferHelper.safeApprove(wrappedEther, uniRouter, 0);
    }

    function _withdrawAll() internal {
        uint256 iTokenBal = IERC20(iToken).balanceOf(address(this));
        if (iTokenBal > 0) {
            IVToken(iToken).redeem(iTokenBal);
        }
        updateSupplyBal();
    }

    /* ----- Private Functions ----- */

    function _harvestAndSwap(address[] memory _route) private {
        address[] memory markets = new address[](1);
        markets[0] = iToken;
        IComptroller(comptroller).claimComp(address(this), markets);

        // in case of reward token is native token (ETH/BNB/AVAX/FTM)
        uint256 toWrapBal = address(this).balance;
        if (toWrapBal > 0) {
            IWETH(wrappedEther).deposit{value: toWrapBal}();
        }

        uint256 rewardBal = IERC20(reward).balanceOf(address(this));

        // rewardBal == 0: means the current token reward ended
        // reward == want: no need to swap
        if (rewardBal > 0 && reward != want) {
            require(_route.length > 0, 'StrategyScream: SWAP_ROUTE_INVALID');
            IUniswapRouter(uniRouter).swapExactTokensForTokens(rewardBal, 0, _route, address(this), now);
        }
    }

    /* ----- Admin Functions ----- */

    function retireStrat() external override {
        require(msg.sender == vault, 'StrategyScream: !vault');
        _withdrawAll();
        uint256 wantBal = IERC20(want).balanceOf(address(this));
        if (wantBal > 0) {
            TransferHelper.safeTransfer(want, vault, wantBal);
        }
    }

    function emergencyExit() external override onlyAdmin {
        _withdrawAll();
        uint256 wantBal = IERC20(want).balanceOf(address(this));
        if (wantBal > 0) {
            TransferHelper.safeTransfer(want, vault, wantBal);
        }
    }

    receive() external payable {}
}

File 52 of 106 : IUniswapRouter.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface IUniswapRouter {
    function factory() external pure returns (address);

    function WBNB() external pure returns (address);

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint256 amountADesired,
        uint256 amountBDesired,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline
    )
        external
        returns (
            uint256 amountA,
            uint256 amountB,
            uint256 liquidity
        );

    function addLiquidityBNB(
        address token,
        uint256 amountTokenDesired,
        uint256 amountTokenMin,
        uint256 amountBNBMin,
        address to,
        uint256 deadline
    )
        external
        payable
        returns (
            uint256 amountToken,
            uint256 amountBNB,
            uint256 liquidity
        );

    function removeLiquidity(
        address tokenA,
        address tokenB,
        uint256 liquidity,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountA, uint256 amountB);

    function removeLiquidityBNB(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountBNBMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountToken, uint256 amountBNB);

    function removeLiquidityWithPermit(
        address tokenA,
        address tokenB,
        uint256 liquidity,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint256 amountA, uint256 amountB);

    function removeLiquidityBNBWithPermit(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountBNBMin,
        address to,
        uint256 deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint256 amountToken, uint256 amountBNB);

    function removeLiquidityBNBSupportingFeeOnTransferTokens(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountBNBMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountBNB);

    function removeLiquidityBNBWithPermitSupportingFeeOnTransferTokens(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountBNBMin,
        address to,
        uint256 deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint256 amountBNB);

    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function swapTokensForExactTokens(
        uint256 amountOut,
        uint256 amountInMax,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external;

    function swapExactBNBForTokensSupportingFeeOnTransferTokens(
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external payable;

    function swapExactTokensForBNBSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external;

    function swapExactBNBForTokens(
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external payable returns (uint256[] memory amounts);

    function swapTokensForExactBNB(
        uint256 amountOut,
        uint256 amountInMax,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function swapExactTokensForBNB(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function swapBNBForExactTokens(
        uint256 amountOut,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external payable returns (uint256[] memory amounts);

    function quote(
        uint256 amountA,
        uint256 reserveA,
        uint256 reserveB
    ) external pure returns (uint256 amountB);

    function getAmountOut(
        uint256 amountIn,
        uint256 reserveIn,
        uint256 reserveOut
    ) external pure returns (uint256 amountOut);

    function getAmountIn(
        uint256 amountOut,
        uint256 reserveIn,
        uint256 reserveOut
    ) external pure returns (uint256 amountIn);

    function getAmountsOut(uint256 amountIn, address[] calldata path) external view returns (uint256[] memory amounts);

    function getAmountsIn(uint256 amountOut, address[] calldata path) external view returns (uint256[] memory amounts);
}

File 53 of 106 : IVToken.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';

interface IVToken is IERC20 {
    function underlying() external returns (address);

    function mint(uint256 mintAmount) external returns (uint256);

    function redeem(uint256 redeemTokens) external returns (uint256);

    function redeemUnderlying(uint256 redeemAmount) external returns (uint256);

    function borrow(uint256 borrowAmount) external returns (uint256);

    function repayBorrow(uint256 repayAmount) external returns (uint256);

    function balanceOfUnderlying(address owner) external returns (uint256);

    function borrowBalanceCurrent(address account) external returns (uint256);

    function comptroller() external returns (address);
}

File 54 of 106 : IComptroller.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface IComptroller {
    function claimComp(address holder, address[] calldata _iTokens) external;

    function claimComp(address holder) external;

    function enterMarkets(address[] memory _iTokens) external;

    function pendingComptrollerImplementation() external view returns (address implementation);
}

File 55 of 106 : StrategyTraderJoeLP.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/Pausable.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import '../../../interfaces/PancakeSwap/IMasterChef.sol';
import '../../../interfaces/TraderJoe/IUniswapPair.sol';
import '../../../interfaces/TraderJoe/IUniswapRouter.sol';
import '../../../interfaces/IWooAccessManager.sol';
import '../../../interfaces/IStrategy.sol';

import '../BaseStrategy.sol';

contract StrategyTraderJoeLP is BaseStrategy {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    /* ----- State Variables ----- */

    uint256 public immutable pid;

    address[] public rewardToLP0Route;
    address[] public rewardToLP1Route;

    address public lpToken0;
    address public lpToken1;

    address public constant reward = address(0x6e84a6216eA6dACC71eE8E6b0a5B7322EEbC0fDd);
    address public constant uniRouter = address(0x60aE616a2155Ee3d9A68541Ba4544862310933d4);
    address public constant masterChef = address(0xd6a4F121CA35509aF06A0Be99093d08462f53052);

    constructor(
        address initVault,
        address initAccessManager,
        uint256 initPid,
        address[] memory initRewardToLP0Route,
        address[] memory initRewardToLP1Route
    ) public BaseStrategy(initVault, initAccessManager) {
        pid = initPid;
        rewardToLP0Route = initRewardToLP0Route;
        rewardToLP1Route = initRewardToLP1Route;

        if (initRewardToLP0Route.length == 0) {
            lpToken0 = reward;
        } else {
            require(initRewardToLP0Route[0] == reward);
            lpToken0 = initRewardToLP0Route[initRewardToLP0Route.length - 1];
        }
        if (initRewardToLP1Route.length == 0) {
            lpToken1 = reward;
        } else {
            require(initRewardToLP1Route[0] == reward);
            lpToken1 = initRewardToLP1Route[initRewardToLP1Route.length - 1];
        }

        require(
            IUniswapV2Pair(want).token0() == lpToken0 || IUniswapV2Pair(want).token0() == lpToken1,
            'StrategyLP: LP_token0_INVALID'
        );
        require(
            IUniswapV2Pair(want).token1() == lpToken0 || IUniswapV2Pair(want).token1() == lpToken1,
            'StrategyLP: LP_token1_INVALID'
        );

        (address lpToken, , , ) = IMasterChef(masterChef).poolInfo(initPid);
        require(lpToken == want, 'StrategyLP: wrong_initPid');

        _giveAllowances();
    }

    /* ----- External Functions ----- */

    function withdraw(uint256 amount) external override nonReentrant {
        require(msg.sender == vault, 'StrategyLP: NOT_VAULT');

        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance < amount) {
            IMasterChef(masterChef).withdraw(pid, amount.sub(wantBalance));
            wantBalance = IERC20(want).balanceOf(address(this));
        }

        // just in case the decimal precision for the very left staking amount
        uint256 withdrawAmount = amount < wantBalance ? amount : wantBalance;

        uint256 fee = chargeWithdrawalFee(withdrawAmount);
        if (withdrawAmount > fee) {
            TransferHelper.safeTransfer(want, vault, withdrawAmount.sub(fee));
        }
    }

    function harvest() public override whenNotPaused {
        require(msg.sender == tx.origin || msg.sender == vault, 'StrategyLP: EOA_OR_VAULT');

        IMasterChef(masterChef).deposit(pid, 0);
        uint256 rewardAmount = IERC20(reward).balanceOf(address(this));
        if (rewardAmount > 0) {
            uint256 wantBefore = IERC20(want).balanceOf(address(this));
            _addLiquidity();
            uint256 wantAfter = IERC20(want).balanceOf(address(this));
            uint256 perfAmount = wantAfter.sub(wantBefore);
            chargePerformanceFee(perfAmount);
        }
        deposit();
    }

    function deposit() public override whenNotPaused nonReentrant {
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            IMasterChef(masterChef).deposit(pid, wantBalance);
        }
    }

    function balanceOfPool() public view override returns (uint256) {
        (uint256 amount, ) = IMasterChef(masterChef).userInfo(pid, address(this));
        return amount;
    }

    /* ----- Private Functions ----- */

    function _giveAllowances() internal override {
        TransferHelper.safeApprove(want, masterChef, 0);
        TransferHelper.safeApprove(want, masterChef, uint256(-1));

        TransferHelper.safeApprove(reward, uniRouter, 0);
        TransferHelper.safeApprove(reward, uniRouter, uint256(-1));

        TransferHelper.safeApprove(lpToken0, uniRouter, 0);
        TransferHelper.safeApprove(lpToken0, uniRouter, uint256(-1));

        TransferHelper.safeApprove(lpToken1, uniRouter, 0);
        TransferHelper.safeApprove(lpToken1, uniRouter, uint256(-1));
    }

    function _removeAllowances() internal override {
        TransferHelper.safeApprove(want, masterChef, 0);
        TransferHelper.safeApprove(reward, uniRouter, 0);
        TransferHelper.safeApprove(lpToken0, uniRouter, 0);
        TransferHelper.safeApprove(lpToken1, uniRouter, 0);
    }

    function _addLiquidity() private {
        uint256 rewardHalf = IERC20(reward).balanceOf(address(this)).div(2);

        if (lpToken0 != reward) {
            IUniswapRouter(uniRouter).swapExactTokensForTokens(rewardHalf, 0, rewardToLP0Route, address(this), now);
        }

        if (lpToken1 != reward) {
            IUniswapRouter(uniRouter).swapExactTokensForTokens(rewardHalf, 0, rewardToLP1Route, address(this), now);
        }

        uint256 lp0Balance = IERC20(lpToken0).balanceOf(address(this));
        uint256 lp1Balance = IERC20(lpToken1).balanceOf(address(this));
        IUniswapRouter(uniRouter).addLiquidity(lpToken0, lpToken1, lp0Balance, lp1Balance, 0, 0, address(this), now);
    }

    function retireStrat() external override {
        require(msg.sender == vault, '!vault');
        IMasterChef(masterChef).emergencyWithdraw(pid);
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            TransferHelper.safeTransfer(want, vault, wantBalance);
        }
    }

    function emergencyExit() external override onlyAdmin {
        IMasterChef(masterChef).emergencyWithdraw(pid);
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            TransferHelper.safeTransfer(want, vault, wantBalance);
        }
    }
}

File 56 of 106 : IMasterChef.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface IMasterChef {
    function deposit(uint256 pid, uint256 amount) external;

    function withdraw(uint256 pid, uint256 amount) external;

    function enterStaking(uint256 amount) external;

    function leaveStaking(uint256 amount) external;

    function emergencyWithdraw(uint256 pid) external;

    function pendingCake(uint256 pid, address user) external view returns (uint256);

    function poolInfo(uint256 pid)
        external
        view
        returns (
            address,
            uint256,
            uint256,
            uint256
        );

    function userInfo(uint256 pid, address user) external view returns (uint256, uint256);
}

File 57 of 106 : IUniswapPair.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface IUniswapV2Pair {
    function factory() external view returns (address);

    function token0() external view returns (address);

    function token1() external view returns (address);

    function burn(address to) external returns (uint256 amount0, uint256 amount1);

    function getReserves()
        external
        view
        returns (
            uint112 reserve0,
            uint112 reserve1,
            uint32 blockTimestampLast
        );
}

File 58 of 106 : StrategyTraderJoeDualLP.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/Pausable.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import '../../../interfaces/PancakeSwap/IMasterChef.sol';
import '../../../interfaces/TraderJoe/IUniswapPair.sol';
import '../../../interfaces/TraderJoe/IUniswapRouter.sol';
import '../../../interfaces/IWooAccessManager.sol';
import '../../../interfaces/IStrategy.sol';

import '../BaseStrategy.sol';

contract StrategyTraderJoeDualLP is BaseStrategy {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    /* ----- State Variables ----- */

    uint256 public immutable pid;

    address[] public rewardToLP0Route;
    address[] public rewardToLP1Route;
    address[] public secondRewardToLP0Route;
    address[] public secondRewardToLP1Route;

    address public lpToken0;
    address public lpToken1;

    address public constant reward = address(0x6e84a6216eA6dACC71eE8E6b0a5B7322EEbC0fDd); // JOE
    address public constant secondReward = address(0x8729438EB15e2C8B576fCc6AeCdA6A148776C0F5); // QI
    address public constant uniRouter = address(0x60aE616a2155Ee3d9A68541Ba4544862310933d4);
    address public constant masterChef = address(0x188bED1968b795d5c9022F6a0bb5931Ac4c18F00);

    constructor(
        address initVault,
        address initAccessManager,
        uint256 initPid,
        address[] memory initRewardToLP0Route,
        address[] memory initRewardToLP1Route,
        address[] memory initSecondRewardToLP0Route,
        address[] memory initSecondRewardToLP1Route
    ) public BaseStrategy(initVault, initAccessManager) {
        pid = initPid;
        rewardToLP0Route = initRewardToLP0Route;
        rewardToLP1Route = initRewardToLP1Route;
        secondRewardToLP0Route = initSecondRewardToLP0Route;
        secondRewardToLP1Route = initSecondRewardToLP1Route;

        if (initRewardToLP0Route.length == 0) {
            lpToken0 = reward;
        } else {
            require(initRewardToLP0Route[0] == reward);
            lpToken0 = initRewardToLP0Route[initRewardToLP0Route.length - 1];
        }
        if (initRewardToLP1Route.length == 0) {
            lpToken1 = reward;
        } else {
            require(initRewardToLP1Route[0] == reward);
            lpToken1 = initRewardToLP1Route[initRewardToLP1Route.length - 1];
        }
        require(initSecondRewardToLP0Route[0] == secondReward);
        require(initSecondRewardToLP1Route[0] == secondReward);

        require(
            IUniswapV2Pair(want).token0() == lpToken0 || IUniswapV2Pair(want).token0() == lpToken1,
            'StrategyLP: LP_token0_INVALID'
        );
        require(
            IUniswapV2Pair(want).token1() == lpToken0 || IUniswapV2Pair(want).token1() == lpToken1,
            'StrategyLP: LP_token1_INVALID'
        );

        (address lpToken, , , ) = IMasterChef(masterChef).poolInfo(initPid);
        require(lpToken == want, 'StrategyLP: wrong_initPid');

        _giveAllowances();
    }

    /* ----- External Functions ----- */

    function withdraw(uint256 amount) external override nonReentrant {
        require(msg.sender == vault, 'StrategyLP: NOT_VAULT');

        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance < amount) {
            IMasterChef(masterChef).withdraw(pid, amount.sub(wantBalance));
            wantBalance = IERC20(want).balanceOf(address(this));
        }

        // just in case the decimal precision for the very left staking amount
        uint256 withdrawAmount = amount < wantBalance ? amount : wantBalance;

        uint256 fee = chargeWithdrawalFee(withdrawAmount);
        if (withdrawAmount > fee) {
            TransferHelper.safeTransfer(want, vault, withdrawAmount.sub(fee));
        }
    }

    function harvest() public override whenNotPaused {
        require(msg.sender == tx.origin || msg.sender == vault, 'StrategyLP: EOA_OR_VAULT');

        IMasterChef(masterChef).deposit(pid, 0);
        uint256 rewardAmount = IERC20(reward).balanceOf(address(this));
        uint256 secondRewardAmount = IERC20(secondReward).balanceOf(address(this));
        if (rewardAmount > 0 || secondRewardAmount > 0) {
            uint256 wantBefore = IERC20(want).balanceOf(address(this));
            _addLiquidity();
            uint256 wantAfter = IERC20(want).balanceOf(address(this));
            uint256 perfAmount = wantAfter.sub(wantBefore);
            chargePerformanceFee(perfAmount);
        }
        deposit();
    }

    function deposit() public override whenNotPaused nonReentrant {
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            IMasterChef(masterChef).deposit(pid, wantBalance);
        }
    }

    function balanceOfPool() public view override returns (uint256) {
        (uint256 amount, ) = IMasterChef(masterChef).userInfo(pid, address(this));
        return amount;
    }

    /* ----- Private Functions ----- */

    function _giveAllowances() internal override {
        TransferHelper.safeApprove(want, masterChef, 0);
        TransferHelper.safeApprove(want, masterChef, uint256(-1));

        TransferHelper.safeApprove(reward, uniRouter, 0);
        TransferHelper.safeApprove(reward, uniRouter, uint256(-1));

        TransferHelper.safeApprove(secondReward, uniRouter, 0);
        TransferHelper.safeApprove(secondReward, uniRouter, uint256(-1));

        TransferHelper.safeApprove(lpToken0, uniRouter, 0);
        TransferHelper.safeApprove(lpToken0, uniRouter, uint256(-1));

        TransferHelper.safeApprove(lpToken1, uniRouter, 0);
        TransferHelper.safeApprove(lpToken1, uniRouter, uint256(-1));
    }

    function _removeAllowances() internal override {
        TransferHelper.safeApprove(want, masterChef, 0);
        TransferHelper.safeApprove(reward, uniRouter, 0);
        TransferHelper.safeApprove(secondReward, uniRouter, 0);
        TransferHelper.safeApprove(lpToken0, uniRouter, 0);
        TransferHelper.safeApprove(lpToken1, uniRouter, 0);
    }

    function _addLiquidity() private {
        uint256 rewardHalf = IERC20(reward).balanceOf(address(this)).div(2);
        uint256 secondRewardHalf = IERC20(secondReward).balanceOf(address(this)).div(2);

        if (lpToken0 != reward) {
            IUniswapRouter(uniRouter).swapExactTokensForTokens(rewardHalf, 0, rewardToLP0Route, address(this), now);
        }

        if (lpToken1 != reward) {
            IUniswapRouter(uniRouter).swapExactTokensForTokens(rewardHalf, 0, rewardToLP1Route, address(this), now);
        }

        if (lpToken0 != secondReward) {
            IUniswapRouter(uniRouter).swapExactTokensForTokens(
                secondRewardHalf,
                0,
                secondRewardToLP0Route,
                address(this),
                now
            );
        }

        if (lpToken1 != secondReward) {
            IUniswapRouter(uniRouter).swapExactTokensForTokens(
                secondRewardHalf,
                0,
                secondRewardToLP1Route,
                address(this),
                now
            );
        }

        uint256 lp0Balance = IERC20(lpToken0).balanceOf(address(this));
        uint256 lp1Balance = IERC20(lpToken1).balanceOf(address(this));
        IUniswapRouter(uniRouter).addLiquidity(lpToken0, lpToken1, lp0Balance, lp1Balance, 0, 0, address(this), now);
    }

    function retireStrat() external override {
        require(msg.sender == vault, '!vault');
        IMasterChef(masterChef).emergencyWithdraw(pid);
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            TransferHelper.safeTransfer(want, vault, wantBalance);
        }
    }

    function emergencyExit() external override onlyAdmin {
        IMasterChef(masterChef).emergencyWithdraw(pid);
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            TransferHelper.safeTransfer(want, vault, wantBalance);
        }
    }
}

File 59 of 106 : StrategyLP.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/Pausable.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import '../../../interfaces/PancakeSwap/IMasterChef.sol';
import '../../../interfaces/PancakeSwap/IPancakePair.sol';
import '../../../interfaces/PancakeSwap/IPancakeRouter.sol';
import '../../../interfaces/IWooAccessManager.sol';
import '../../../interfaces/IStrategy.sol';

import '../BaseStrategy.sol';

contract StrategySpookySwapLP is BaseStrategy {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    /* ----- State Variables ----- */

    uint256 public immutable pid;

    address[] public rewardToLP0Route;
    address[] public rewardToLP1Route;

    address public lpToken0;
    address public lpToken1;

    address public constant reward = address(0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE); // BOO
    address public constant uniRouter = address(0xF491e7B69E4244ad4002BC14e878a34207E38c29);
    address public constant masterChef = address(0x2b2929E785374c651a81A63878Ab22742656DcDd);

    constructor(
        address initVault,
        address initAccessManager,
        uint256 initPid,
        address[] memory initRewardToLP0Route,
        address[] memory initRewardToLP1Route
    ) public BaseStrategy(initVault, initAccessManager) {
        pid = initPid;
        rewardToLP0Route = initRewardToLP0Route;
        rewardToLP1Route = initRewardToLP1Route;

        if (initRewardToLP0Route.length == 0) {
            lpToken0 = reward;
        } else {
            require(initRewardToLP0Route[0] == reward);
            lpToken0 = initRewardToLP0Route[initRewardToLP0Route.length - 1];
        }
        if (initRewardToLP1Route.length == 0) {
            lpToken1 = reward;
        } else {
            require(initRewardToLP1Route[0] == reward);
            lpToken1 = initRewardToLP1Route[initRewardToLP1Route.length - 1];
        }

        require(
            IPancakePair(want).token0() == lpToken0 || IPancakePair(want).token0() == lpToken1,
            'StrategySpookySwapLP: LP_token0_INVALID'
        );
        require(
            IPancakePair(want).token1() == lpToken0 || IPancakePair(want).token1() == lpToken1,
            'StrategySpookySwapLP: LP_token1_INVALID'
        );

        (address lpToken, , , ) = IMasterChef(masterChef).poolInfo(initPid);
        require(lpToken == want, 'StrategySpookySwapLP: wrong_initPid');

        _giveAllowances();
    }

    /* ----- External Functions ----- */

    function withdraw(uint256 amount) external override nonReentrant {
        require(msg.sender == vault, 'StrategySpookySwapLP: NOT_VAULT');

        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance < amount) {
            IMasterChef(masterChef).withdraw(pid, amount.sub(wantBalance));
            wantBalance = IERC20(want).balanceOf(address(this));
        }

        // just in case the decimal precision for the very left staking amount
        uint256 withdrawAmount = amount < wantBalance ? amount : wantBalance;

        uint256 fee = chargeWithdrawalFee(withdrawAmount);
        if (withdrawAmount > fee) {
            TransferHelper.safeTransfer(want, vault, withdrawAmount.sub(fee));
        }
    }

    function harvest() public override whenNotPaused {
        require(msg.sender == tx.origin || msg.sender == vault, 'StrategySpookySwapLP: EOA_OR_VAULT');

        IMasterChef(masterChef).deposit(pid, 0);
        uint256 rewardAmount = IERC20(reward).balanceOf(address(this));
        if (rewardAmount > 0) {
            uint256 wantBefore = IERC20(want).balanceOf(address(this));
            _addLiquidity();
            uint256 wantAfter = IERC20(want).balanceOf(address(this));
            uint256 perfAmount = wantAfter.sub(wantBefore);
            chargePerformanceFee(perfAmount);
        }
        deposit();
    }

    function deposit() public override whenNotPaused nonReentrant {
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            IMasterChef(masterChef).deposit(pid, wantBalance);
        }
    }

    function balanceOfPool() public view override returns (uint256) {
        (uint256 amount, ) = IMasterChef(masterChef).userInfo(pid, address(this));
        return amount;
    }

    /* ----- Private Functions ----- */

    function _giveAllowances() internal override {
        TransferHelper.safeApprove(want, masterChef, 0);
        TransferHelper.safeApprove(want, masterChef, uint256(-1));

        TransferHelper.safeApprove(reward, uniRouter, 0);
        TransferHelper.safeApprove(reward, uniRouter, uint256(-1));

        TransferHelper.safeApprove(lpToken0, uniRouter, 0);
        TransferHelper.safeApprove(lpToken0, uniRouter, uint256(-1));

        TransferHelper.safeApprove(lpToken1, uniRouter, 0);
        TransferHelper.safeApprove(lpToken1, uniRouter, uint256(-1));
    }

    function _removeAllowances() internal override {
        TransferHelper.safeApprove(want, masterChef, 0);
        TransferHelper.safeApprove(reward, uniRouter, 0);
        TransferHelper.safeApprove(lpToken0, uniRouter, 0);
        TransferHelper.safeApprove(lpToken1, uniRouter, 0);
    }

    function _addLiquidity() private {
        uint256 rewardHalf = IERC20(reward).balanceOf(address(this)).div(2);

        if (lpToken0 != reward) {
            IPancakeRouter(uniRouter).swapExactTokensForTokens(rewardHalf, 0, rewardToLP0Route, address(this), now);
        }

        if (lpToken1 != reward) {
            IPancakeRouter(uniRouter).swapExactTokensForTokens(rewardHalf, 0, rewardToLP1Route, address(this), now);
        }

        uint256 lp0Balance = IERC20(lpToken0).balanceOf(address(this));
        uint256 lp1Balance = IERC20(lpToken1).balanceOf(address(this));
        IPancakeRouter(uniRouter).addLiquidity(lpToken0, lpToken1, lp0Balance, lp1Balance, 0, 0, address(this), now);
    }

    function retireStrat() external override {
        require(msg.sender == vault, '!vault');
        IMasterChef(masterChef).emergencyWithdraw(pid);
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            TransferHelper.safeTransfer(want, vault, wantBalance);
        }
    }

    function emergencyExit() external override onlyAdmin {
        IMasterChef(masterChef).emergencyWithdraw(pid);
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            TransferHelper.safeTransfer(want, vault, wantBalance);
        }
    }
}

File 60 of 106 : IPancakePair.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface IPancakePair {
    event Approval(address indexed owner, address indexed spender, uint256 value);
    event Transfer(address indexed from, address indexed to, uint256 value);

    function name() external pure returns (string memory);

    function symbol() external pure returns (string memory);

    function decimals() external pure returns (uint8);

    function totalSupply() external view returns (uint256);

    function balanceOf(address owner) external view returns (uint256);

    function allowance(address owner, address spender) external view returns (uint256);

    function approve(address spender, uint256 value) external returns (bool);

    function transfer(address to, uint256 value) external returns (bool);

    function transferFrom(
        address from,
        address to,
        uint256 value
    ) external returns (bool);

    function DOMAIN_SEPARATOR() external view returns (bytes32);

    function PERMIT_TYPEHASH() external pure returns (bytes32);

    function nonces(address owner) external view returns (uint256);

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    event Mint(address indexed sender, uint256 amount0, uint256 amount1);
    event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to);
    event Swap(
        address indexed sender,
        uint256 amount0In,
        uint256 amount1In,
        uint256 amount0Out,
        uint256 amount1Out,
        address indexed to
    );
    event Sync(uint112 reserve0, uint112 reserve1);

    function MINIMUM_LIQUIDITY() external pure returns (uint256);

    function factory() external view returns (address);

    function token0() external view returns (address);

    function token1() external view returns (address);

    function getReserves()
        external
        view
        returns (
            uint112 reserve0,
            uint112 reserve1,
            uint32 blockTimestampLast
        );

    function price0CumulativeLast() external view returns (uint256);

    function price1CumulativeLast() external view returns (uint256);

    function kLast() external view returns (uint256);

    function mint(address to) external returns (uint256 liquidity);

    function burn(address to) external returns (uint256 amount0, uint256 amount1);

    function swap(
        uint256 amount0Out,
        uint256 amount1Out,
        address to,
        bytes calldata data
    ) external;

    function skim(address to) external;

    function sync() external;

    function initialize(address, address) external;
}

File 61 of 106 : IPancakeRouter.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface IPancakeRouter {
    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint256 amountADesired,
        uint256 amountBDesired,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline
    )
        external
        returns (
            uint256 amountA,
            uint256 amountB,
            uint256 liquidity
        );
}

File 62 of 106 : StrategyLPV2.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import '../../../interfaces/PancakeSwap/IMasterChefV2.sol';
import '../../../interfaces/PancakeSwap/IPancakePair.sol';
import '../../../interfaces/PancakeSwap/IPancakeRouter.sol';
import '../../../interfaces/IWooAccessManager.sol';
import '../../../interfaces/IStrategy.sol';

import '../BaseStrategy.sol';

contract StrategyLPV2 is BaseStrategy {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    /* ----- State Variables ----- */

    uint256 public immutable pid;

    address[] public rewardToLP0Route;
    address[] public rewardToLP1Route;

    address public lpToken0;
    address public lpToken1;

    address public constant reward = address(0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82);
    address public constant uniRouter = address(0x10ED43C718714eb63d5aA57B78B54704E256024E);
    address public constant masterChefV2 = address(0xa5f8C5Dbd5F286960b9d90548680aE5ebFf07652);

    constructor(
        address initVault,
        address initAccessManager,
        uint256 initPid,
        address[] memory initRewardToLP0Route,
        address[] memory initRewardToLP1Route
    ) public BaseStrategy(initVault, initAccessManager) {
        pid = initPid;
        rewardToLP0Route = initRewardToLP0Route;
        rewardToLP1Route = initRewardToLP1Route;

        if (initRewardToLP0Route.length == 0) {
            lpToken0 = reward;
        } else {
            require(initRewardToLP0Route[0] == reward);
            lpToken0 = initRewardToLP0Route[initRewardToLP0Route.length - 1];
        }
        if (initRewardToLP1Route.length == 0) {
            lpToken1 = reward;
        } else {
            require(initRewardToLP1Route[0] == reward);
            lpToken1 = initRewardToLP1Route[initRewardToLP1Route.length - 1];
        }

        require(
            IPancakePair(want).token0() == lpToken0 || IPancakePair(want).token0() == lpToken1,
            'StrategyLP: LP_token0_INVALID'
        );
        require(
            IPancakePair(want).token1() == lpToken0 || IPancakePair(want).token1() == lpToken1,
            'StrategyLP: LP_token1_INVALID'
        );

        address lpToken = IMasterChefV2(masterChefV2).lpToken(initPid);
        require(lpToken == want, 'StrategyLP: wrong_initPid');

        _giveAllowances();
    }

    /* ----- External Functions ----- */

    function withdraw(uint256 amount) external override nonReentrant {
        require(msg.sender == vault, 'StrategyLP: NOT_VAULT');

        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance < amount) {
            IMasterChefV2(masterChefV2).withdraw(pid, amount.sub(wantBalance));
            wantBalance = IERC20(want).balanceOf(address(this));
        }

        // just in case the decimal precision for the very left staking amount
        uint256 withdrawAmount = amount < wantBalance ? amount : wantBalance;

        uint256 fee = chargeWithdrawalFee(withdrawAmount);
        if (withdrawAmount > fee) {
            TransferHelper.safeTransfer(want, vault, withdrawAmount.sub(fee));
        }
    }

    function harvest() public override whenNotPaused {
        require(msg.sender == tx.origin || msg.sender == vault, 'StrategyLP: EOA_OR_VAULT');

        IMasterChefV2(masterChefV2).deposit(pid, 0);
        uint256 rewardAmount = IERC20(reward).balanceOf(address(this));
        if (rewardAmount > 0) {
            uint256 wantBefore = IERC20(want).balanceOf(address(this));
            _addLiquidity();
            uint256 wantAfter = IERC20(want).balanceOf(address(this));
            uint256 perfAmount = wantAfter.sub(wantBefore);
            chargePerformanceFee(perfAmount);
        }
        deposit();
    }

    function deposit() public override whenNotPaused nonReentrant {
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            IMasterChefV2(masterChefV2).deposit(pid, wantBalance);
        }
    }

    function balanceOfPool() public view override returns (uint256) {
        (uint256 amount, , ) = IMasterChefV2(masterChefV2).userInfo(pid, address(this));
        return amount;
    }

    /* ----- Private Functions ----- */

    function _giveAllowances() internal override {
        TransferHelper.safeApprove(want, masterChefV2, 0);
        TransferHelper.safeApprove(want, masterChefV2, uint256(-1));

        TransferHelper.safeApprove(reward, uniRouter, 0);
        TransferHelper.safeApprove(reward, uniRouter, uint256(-1));

        TransferHelper.safeApprove(lpToken0, uniRouter, 0);
        TransferHelper.safeApprove(lpToken0, uniRouter, uint256(-1));

        TransferHelper.safeApprove(lpToken1, uniRouter, 0);
        TransferHelper.safeApprove(lpToken1, uniRouter, uint256(-1));
    }

    function _removeAllowances() internal override {
        TransferHelper.safeApprove(want, masterChefV2, 0);
        TransferHelper.safeApprove(reward, uniRouter, 0);
        TransferHelper.safeApprove(lpToken0, uniRouter, 0);
        TransferHelper.safeApprove(lpToken1, uniRouter, 0);
    }

    function _addLiquidity() private {
        uint256 rewardHalf = IERC20(reward).balanceOf(address(this)).div(2);

        if (lpToken0 != reward) {
            IPancakeRouter(uniRouter).swapExactTokensForTokens(rewardHalf, 0, rewardToLP0Route, address(this), now);
        }

        if (lpToken1 != reward) {
            IPancakeRouter(uniRouter).swapExactTokensForTokens(rewardHalf, 0, rewardToLP1Route, address(this), now);
        }

        uint256 lp0Balance = IERC20(lpToken0).balanceOf(address(this));
        uint256 lp1Balance = IERC20(lpToken1).balanceOf(address(this));
        IPancakeRouter(uniRouter).addLiquidity(lpToken0, lpToken1, lp0Balance, lp1Balance, 0, 0, address(this), now);
    }

    function retireStrat() external override {
        require(msg.sender == vault, '!vault');
        IMasterChefV2(masterChefV2).emergencyWithdraw(pid);
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            TransferHelper.safeTransfer(want, vault, wantBalance);
        }
    }

    function emergencyExit() external override onlyAdmin {
        IMasterChefV2(masterChefV2).emergencyWithdraw(pid);
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            TransferHelper.safeTransfer(want, vault, wantBalance);
        }
    }

    function setRewardToLP0Route(address[] memory _rewardToLP0Route) external onlyAdmin {
        require(_rewardToLP0Route.length > 0, 'StrategyLPV2: _rewardToLP0Route_LENGTH_ZERO');
        rewardToLP0Route = _rewardToLP0Route;
    }

    function setRewardToLP1Route(address[] memory _rewardToLP1Route) external onlyAdmin {
        require(_rewardToLP1Route.length > 0, 'StrategyLPV2: _rewardToLP1Route_LENGTH_ZERO');
        rewardToLP1Route = _rewardToLP1Route;
    }
}

File 63 of 106 : IMasterChefV2.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface IMasterChefV2 {
    function deposit(uint256 _pid, uint256 _amount) external;

    function withdraw(uint256 _pid, uint256 _amount) external;

    function pendingCake(uint256 _pid, address _user) external view returns (uint256);

    function poolInfo(uint256 _pid)
        external
        view
        returns (
            uint256,
            uint256,
            uint256,
            uint256,
            bool
        );

    function lpToken(uint256 _pid) external view returns (address);

    function userInfo(uint256 _pid, address _user)
        external
        view
        returns (
            uint256,
            uint256,
            uint256
        );

    function emergencyWithdraw(uint256 _pid) external;
}

File 64 of 106 : StrategyLP.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/Pausable.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import '../../../interfaces/PancakeSwap/IMasterChef.sol';
import '../../../interfaces/PancakeSwap/IPancakePair.sol';
import '../../../interfaces/PancakeSwap/IPancakeRouter.sol';
import '../../../interfaces/IWooAccessManager.sol';
import '../../../interfaces/IStrategy.sol';

import '../BaseStrategy.sol';

contract StrategyLP is BaseStrategy {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    /* ----- State Variables ----- */

    uint256 public immutable pid;

    address[] public rewardToLP0Route;
    address[] public rewardToLP1Route;

    address public lpToken0;
    address public lpToken1;

    address public constant reward = address(0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82);
    address public constant uniRouter = address(0x10ED43C718714eb63d5aA57B78B54704E256024E);
    address public constant masterChef = address(0x73feaa1eE314F8c655E354234017bE2193C9E24E);

    constructor(
        address initVault,
        address initAccessManager,
        uint256 initPid,
        address[] memory initRewardToLP0Route,
        address[] memory initRewardToLP1Route
    ) public BaseStrategy(initVault, initAccessManager) {
        pid = initPid;
        rewardToLP0Route = initRewardToLP0Route;
        rewardToLP1Route = initRewardToLP1Route;

        if (initRewardToLP0Route.length == 0) {
            lpToken0 = reward;
        } else {
            require(initRewardToLP0Route[0] == reward);
            lpToken0 = initRewardToLP0Route[initRewardToLP0Route.length - 1];
        }
        if (initRewardToLP1Route.length == 0) {
            lpToken1 = reward;
        } else {
            require(initRewardToLP1Route[0] == reward);
            lpToken1 = initRewardToLP1Route[initRewardToLP1Route.length - 1];
        }

        require(
            IPancakePair(want).token0() == lpToken0 || IPancakePair(want).token0() == lpToken1,
            'StrategyLP: LP_token0_INVALID'
        );
        require(
            IPancakePair(want).token1() == lpToken0 || IPancakePair(want).token1() == lpToken1,
            'StrategyLP: LP_token1_INVALID'
        );

        (address lpToken, , , ) = IMasterChef(masterChef).poolInfo(initPid);
        require(lpToken == want, 'StrategyLP: wrong_initPid');

        _giveAllowances();
    }

    /* ----- External Functions ----- */

    function withdraw(uint256 amount) external override nonReentrant {
        require(msg.sender == vault, 'StrategyLP: NOT_VAULT');

        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance < amount) {
            IMasterChef(masterChef).withdraw(pid, amount.sub(wantBalance));
            wantBalance = IERC20(want).balanceOf(address(this));
        }

        // just in case the decimal precision for the very left staking amount
        uint256 withdrawAmount = amount < wantBalance ? amount : wantBalance;

        uint256 fee = chargeWithdrawalFee(withdrawAmount);
        if (withdrawAmount > fee) {
            TransferHelper.safeTransfer(want, vault, withdrawAmount.sub(fee));
        }
    }

    function harvest() public override whenNotPaused {
        require(msg.sender == tx.origin || msg.sender == vault, 'StrategyLP: EOA_OR_VAULT');

        IMasterChef(masterChef).deposit(pid, 0);
        uint256 rewardAmount = IERC20(reward).balanceOf(address(this));
        if (rewardAmount > 0) {
            uint256 wantBefore = IERC20(want).balanceOf(address(this));
            _addLiquidity();
            uint256 wantAfter = IERC20(want).balanceOf(address(this));
            uint256 perfAmount = wantAfter.sub(wantBefore);
            chargePerformanceFee(perfAmount);
        }
        deposit();
    }

    function deposit() public override whenNotPaused nonReentrant {
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            IMasterChef(masterChef).deposit(pid, wantBalance);
        }
    }

    function balanceOfPool() public view override returns (uint256) {
        (uint256 amount, ) = IMasterChef(masterChef).userInfo(pid, address(this));
        return amount;
    }

    /* ----- Private Functions ----- */

    function _giveAllowances() internal override {
        TransferHelper.safeApprove(want, masterChef, 0);
        TransferHelper.safeApprove(want, masterChef, uint256(-1));

        TransferHelper.safeApprove(reward, uniRouter, 0);
        TransferHelper.safeApprove(reward, uniRouter, uint256(-1));

        TransferHelper.safeApprove(lpToken0, uniRouter, 0);
        TransferHelper.safeApprove(lpToken0, uniRouter, uint256(-1));

        TransferHelper.safeApprove(lpToken1, uniRouter, 0);
        TransferHelper.safeApprove(lpToken1, uniRouter, uint256(-1));
    }

    function _removeAllowances() internal override {
        TransferHelper.safeApprove(want, masterChef, 0);
        TransferHelper.safeApprove(reward, uniRouter, 0);
        TransferHelper.safeApprove(lpToken0, uniRouter, 0);
        TransferHelper.safeApprove(lpToken1, uniRouter, 0);
    }

    function _addLiquidity() private {
        uint256 rewardHalf = IERC20(reward).balanceOf(address(this)).div(2);

        if (lpToken0 != reward) {
            IPancakeRouter(uniRouter).swapExactTokensForTokens(rewardHalf, 0, rewardToLP0Route, address(this), now);
        }

        if (lpToken1 != reward) {
            IPancakeRouter(uniRouter).swapExactTokensForTokens(rewardHalf, 0, rewardToLP1Route, address(this), now);
        }

        uint256 lp0Balance = IERC20(lpToken0).balanceOf(address(this));
        uint256 lp1Balance = IERC20(lpToken1).balanceOf(address(this));
        IPancakeRouter(uniRouter).addLiquidity(lpToken0, lpToken1, lp0Balance, lp1Balance, 0, 0, address(this), now);
    }

    function retireStrat() external override {
        require(msg.sender == vault, '!vault');
        IMasterChef(masterChef).emergencyWithdraw(pid);
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            TransferHelper.safeTransfer(want, vault, wantBalance);
        }
    }

    function emergencyExit() external override onlyAdmin {
        IMasterChef(masterChef).emergencyWithdraw(pid);
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            TransferHelper.safeTransfer(want, vault, wantBalance);
        }
    }
}

File 65 of 106 : StrategyLP.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/Pausable.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import '../../../interfaces/PancakeSwap/IMasterChef.sol';
import '../../../interfaces/PancakeSwap/IPancakePair.sol';
import '../../../interfaces/PancakeSwap/IPancakeRouter.sol';
import '../../../interfaces/IWooAccessManager.sol';
import '../../../interfaces/IStrategy.sol';

import '../BaseStrategy.sol';

contract StrategyBiswapLP is BaseStrategy {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    /* ----- State Variables ----- */

    uint256 public immutable pid;

    address[] public rewardToLP0Route;
    address[] public rewardToLP1Route;

    address public lpToken0;
    address public lpToken1;

    address public constant reward = address(0x965F527D9159dCe6288a2219DB51fc6Eef120dD1);
    address public constant uniRouter = address(0x3a6d8cA21D1CF76F653A67577FA0D27453350dD8);
    address public constant masterChef = address(0xDbc1A13490deeF9c3C12b44FE77b503c1B061739);

    constructor(
        address initVault,
        address initAccessManager,
        uint256 initPid,
        address[] memory initRewardToLP0Route,
        address[] memory initRewardToLP1Route
    ) public BaseStrategy(initVault, initAccessManager) {
        pid = initPid;
        rewardToLP0Route = initRewardToLP0Route;
        rewardToLP1Route = initRewardToLP1Route;

        if (initRewardToLP0Route.length == 0) {
            lpToken0 = reward;
        } else {
            require(initRewardToLP0Route[0] == reward);
            lpToken0 = initRewardToLP0Route[initRewardToLP0Route.length - 1];
        }
        if (initRewardToLP1Route.length == 0) {
            lpToken1 = reward;
        } else {
            require(initRewardToLP1Route[0] == reward);
            lpToken1 = initRewardToLP1Route[initRewardToLP1Route.length - 1];
        }

        require(
            IPancakePair(want).token0() == lpToken0 || IPancakePair(want).token0() == lpToken1,
            'StrategyLP: LP_token0_INVALID'
        );
        require(
            IPancakePair(want).token1() == lpToken0 || IPancakePair(want).token1() == lpToken1,
            'StrategyLP: LP_token1_INVALID'
        );

        (address lpToken, , , ) = IMasterChef(masterChef).poolInfo(initPid);
        require(lpToken == want, 'StrategyLP: wrong_initPid');

        _giveAllowances();
    }

    /* ----- External Functions ----- */

    function withdraw(uint256 amount) external override nonReentrant {
        require(msg.sender == vault, 'StrategyLP: NOT_VAULT');

        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance < amount) {
            IMasterChef(masterChef).withdraw(pid, amount.sub(wantBalance));
            wantBalance = IERC20(want).balanceOf(address(this));
        }

        // just in case the decimal precision for the very left staking amount
        uint256 withdrawAmount = amount < wantBalance ? amount : wantBalance;

        uint256 fee = chargeWithdrawalFee(withdrawAmount);
        if (withdrawAmount > fee) {
            TransferHelper.safeTransfer(want, vault, withdrawAmount.sub(fee));
        }
    }

    function harvest() public override whenNotPaused {
        require(msg.sender == tx.origin || msg.sender == vault, 'StrategyLP: EOA_OR_VAULT');

        IMasterChef(masterChef).deposit(pid, 0);
        uint256 rewardAmount = IERC20(reward).balanceOf(address(this));
        if (rewardAmount > 0) {
            uint256 wantBefore = IERC20(want).balanceOf(address(this));
            _addLiquidity();
            uint256 wantAfter = IERC20(want).balanceOf(address(this));
            uint256 perfAmount = wantAfter.sub(wantBefore);
            chargePerformanceFee(perfAmount);
        }
        deposit();
    }

    function deposit() public override whenNotPaused nonReentrant {
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            IMasterChef(masterChef).deposit(pid, wantBalance);
        }
    }

    function balanceOfPool() public view override returns (uint256) {
        (uint256 amount, ) = IMasterChef(masterChef).userInfo(pid, address(this));
        return amount;
    }

    /* ----- Private Functions ----- */

    function _giveAllowances() internal override {
        TransferHelper.safeApprove(want, masterChef, 0);
        TransferHelper.safeApprove(want, masterChef, uint256(-1));

        TransferHelper.safeApprove(reward, uniRouter, 0);
        TransferHelper.safeApprove(reward, uniRouter, uint256(-1));

        TransferHelper.safeApprove(lpToken0, uniRouter, 0);
        TransferHelper.safeApprove(lpToken0, uniRouter, uint256(-1));

        TransferHelper.safeApprove(lpToken1, uniRouter, 0);
        TransferHelper.safeApprove(lpToken1, uniRouter, uint256(-1));
    }

    function _removeAllowances() internal override {
        TransferHelper.safeApprove(want, masterChef, 0);
        TransferHelper.safeApprove(reward, uniRouter, 0);
        TransferHelper.safeApprove(lpToken0, uniRouter, 0);
        TransferHelper.safeApprove(lpToken1, uniRouter, 0);
    }

    function _addLiquidity() private {
        uint256 rewardHalf = IERC20(reward).balanceOf(address(this)).div(2);

        if (lpToken0 != reward) {
            IPancakeRouter(uniRouter).swapExactTokensForTokens(rewardHalf, 0, rewardToLP0Route, address(this), now);
        }

        if (lpToken1 != reward) {
            IPancakeRouter(uniRouter).swapExactTokensForTokens(rewardHalf, 0, rewardToLP1Route, address(this), now);
        }

        uint256 lp0Balance = IERC20(lpToken0).balanceOf(address(this));
        uint256 lp1Balance = IERC20(lpToken1).balanceOf(address(this));
        IPancakeRouter(uniRouter).addLiquidity(lpToken0, lpToken1, lp0Balance, lp1Balance, 0, 0, address(this), now);
    }

    function retireStrat() external override {
        require(msg.sender == vault, '!vault');
        IMasterChef(masterChef).emergencyWithdraw(pid);
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            TransferHelper.safeTransfer(want, vault, wantBalance);
        }
    }

    function emergencyExit() external override onlyAdmin {
        IMasterChef(masterChef).emergencyWithdraw(pid);
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            TransferHelper.safeTransfer(want, vault, wantBalance);
        }
    }
}

File 66 of 106 : StrategyAlpaca.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/Pausable.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import '../../../interfaces/Alpaca/IAlpacaVault.sol';
import '../../../interfaces/Alpaca/IFairLaunch.sol';
import '../../../interfaces/PancakeSwap/IPancakeRouter.sol';
import '../../../interfaces/IWooAccessManager.sol';
import '../../../interfaces/IStrategy.sol';
import '../../../interfaces/IWETH.sol';
import '../BaseStrategy.sol';

contract StrategyAlpaca is BaseStrategy {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    /* ----- State Variables ----- */

    address public alpacaVault;
    address public fairLaunch;
    uint256 public immutable pid;

    address[] public rewardToWantRoute;

    /* ----- Constant Variables ----- */

    address public constant wrappedEther = address(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c);
    address public constant reward = address(0x8F0528cE5eF7B51152A59745bEfDD91D97091d2F);
    address public constant uniRouter = address(0x10ED43C718714eb63d5aA57B78B54704E256024E);

    constructor(
        address initVault,
        address initAccessManager,
        address initAlpacaVault,
        address initFairLaunch,
        uint256 initPid,
        address[] memory initRewardToWantRoute
    ) public BaseStrategy(initVault, initAccessManager) {
        (address stakeToken, , , , ) = IFairLaunch(initFairLaunch).poolInfo(initPid);
        require(stakeToken == initAlpacaVault, 'StrategyAlpaca: wrong_initPid');
        alpacaVault = initAlpacaVault;
        fairLaunch = initFairLaunch;
        pid = initPid;
        rewardToWantRoute = initRewardToWantRoute;

        _giveAllowances();
    }

    /* ----- Public Functions ----- */

    function harvest() public override whenNotPaused {
        require(msg.sender == tx.origin || msg.sender == address(vault), 'StrategyAlpaca: EOA_or_vault');

        (uint256 amount, , , ) = IFairLaunch(fairLaunch).userInfo(pid, address(this));
        if (amount > 0) {
            IFairLaunch(fairLaunch).harvest(pid);
        }

        uint256 rewardBalance = IERC20(reward).balanceOf(address(this));
        if (rewardBalance > 0) {
            uint256 wantBalBefore = IERC20(want).balanceOf(address(this));
            IPancakeRouter(uniRouter).swapExactTokensForTokens(
                rewardBalance,
                0,
                rewardToWantRoute,
                address(this),
                now.add(600)
            );
            uint256 wantBalAfter = IERC20(want).balanceOf(address(this));
            uint256 perfAmount = wantBalAfter.sub(wantBalBefore);
            chargePerformanceFee(perfAmount);
        }

        deposit();
    }

    function deposit() public override whenNotPaused nonReentrant {
        uint256 wantBalance = IERC20(want).balanceOf(address(this));

        if (wantBalance > 0) {
            if (want == wrappedEther) {
                IWETH(wrappedEther).withdraw(wantBalance);
                IAlpacaVault(alpacaVault).deposit{value: wantBalance}(wantBalance);
            } else {
                IAlpacaVault(alpacaVault).deposit(wantBalance);
            }
            IFairLaunch(fairLaunch).deposit(address(this), pid, IAlpacaVault(alpacaVault).balanceOf(address(this)));
        }
    }

    function withdraw(uint256 amount) public override nonReentrant {
        require(msg.sender == address(vault), 'StrategyAlpaca: not_vault');
        require(amount > 0, 'StrategyAlpaca: amount_ZERO');

        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance < amount) {
            uint256 ibAmount = amount.mul(IAlpacaVault(alpacaVault).totalSupply()).div(
                IAlpacaVault(alpacaVault).totalToken()
            );
            IFairLaunch(fairLaunch).withdraw(address(this), pid, ibAmount);
            IAlpacaVault(alpacaVault).withdraw(IERC20(alpacaVault).balanceOf(address(this)));
            if (want == wrappedEther) {
                _wrapEther();
            }
            wantBalance = IERC20(want).balanceOf(address(this));
        }

        // just in case the decimal precision for the very left staking amount
        uint256 withdrawAmount = amount < wantBalance ? amount : wantBalance;

        uint256 fee = chargeWithdrawalFee(withdrawAmount);
        if (withdrawAmount > fee) {
            TransferHelper.safeTransfer(want, vault, withdrawAmount.sub(fee));
        }
    }

    function balanceOfPool() public view override returns (uint256) {
        (uint256 amount, , , ) = IFairLaunch(fairLaunch).userInfo(pid, address(this));

        return amount.mul(IAlpacaVault(alpacaVault).totalToken()).div(IAlpacaVault(alpacaVault).totalSupply());
    }

    /* ----- Private Functions ----- */

    function _giveAllowances() internal override {
        TransferHelper.safeApprove(reward, uniRouter, 0);
        TransferHelper.safeApprove(reward, uniRouter, uint256(-1));
        TransferHelper.safeApprove(want, alpacaVault, 0);
        TransferHelper.safeApprove(want, alpacaVault, uint256(-1));
        TransferHelper.safeApprove(alpacaVault, fairLaunch, 0);
        TransferHelper.safeApprove(alpacaVault, fairLaunch, uint256(-1));
    }

    function _removeAllowances() internal override {
        TransferHelper.safeApprove(reward, uniRouter, 0);
        TransferHelper.safeApprove(want, alpacaVault, 0);
        TransferHelper.safeApprove(alpacaVault, fairLaunch, 0);
    }

    function _withdrawAll() private {
        uint256 amount = balanceOfPool();
        uint256 ibAmount = amount.mul(IAlpacaVault(alpacaVault).totalSupply()).div(
            IAlpacaVault(alpacaVault).totalToken()
        );
        IFairLaunch(fairLaunch).withdraw(address(this), pid, ibAmount);
        IAlpacaVault(alpacaVault).withdraw(IERC20(alpacaVault).balanceOf(address(this)));
        if (want == wrappedEther) {
            _wrapEther();
        }
    }

    function _wrapEther() private {
        // NOTE: alpaca vault withdrawal returns the native BNB token; so wrapEther is required.
        uint256 etherBalance = address(this).balance;
        if (etherBalance > 0) {
            IWETH(wrappedEther).deposit{value: etherBalance}();
        }
    }

    /* ----- Admin Functions ----- */

    function retireStrat() external override {
        require(msg.sender == vault, '!vault');
        _withdrawAll();
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            TransferHelper.safeTransfer(want, vault, wantBalance);
        }
    }

    function emergencyExit() external override onlyAdmin {
        _withdrawAll();
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            TransferHelper.safeTransfer(want, vault, wantBalance);
        }
    }

    receive() external payable {}
}

File 67 of 106 : IAlpacaVault.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface IAlpacaVault {
    function balanceOf(address account) external view returns (uint256);

    function totalToken() external view returns (uint256);

    function totalSupply() external view returns (uint256);

    function deposit(uint256 amountToken) external payable;

    function withdraw(uint256 share) external;
}

File 68 of 106 : IFairLaunch.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface IFairLaunch {
    function deposit(
        address _for,
        uint256 pid,
        uint256 _amount
    ) external; // staking

    function withdraw(
        address _for,
        uint256 _pid,
        uint256 _amount
    ) external; // unstaking

    function harvest(uint256 _pid) external;

    function pendingAlpaca(uint256 _pid, address _user) external returns (uint256);

    function userInfo(uint256, address)
        external
        view
        returns (
            uint256 amount,
            uint256 rewardDebt,
            uint256 bonusDebt,
            uint256 fundedBy
        );

    function poolInfo(uint256)
        external
        view
        returns (
            address stakeToken,
            uint256 allocPoint,
            uint256 lastRewardBlock,
            uint256 accAlpacaPerShare,
            uint256 accAlpacaPerShareTilBonusEnd
        );
}

File 69 of 106 : StrategyCake.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/Pausable.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import '../../../interfaces/PancakeSwap/IMasterChef.sol';
import '../BaseStrategy.sol';

contract StrategyCake is BaseStrategy {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    /* ----- Constant Variables ----- */

    address public constant masterChef = address(0x73feaa1eE314F8c655E354234017bE2193C9E24E);

    constructor(address initVault, address initAccessManager) public BaseStrategy(initVault, initAccessManager) {
        _giveAllowances();
    }

    /* ----- External Functions ----- */

    function withdraw(uint256 amount) external override nonReentrant {
        require(msg.sender == address(vault), 'StrategyCake: NOT_VAULT');

        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance < amount) {
            IMasterChef(masterChef).leaveStaking(amount.sub(wantBalance));
            wantBalance = IERC20(want).balanceOf(address(this));
        }

        // just in case the decimal precision for the very left staking amount
        uint256 withdrawAmount = amount < wantBalance ? amount : wantBalance;

        uint256 fee = chargeWithdrawalFee(withdrawAmount);
        if (withdrawAmount > fee) {
            TransferHelper.safeTransfer(want, vault, withdrawAmount.sub(fee));
        }
    }

    function harvest() public override whenNotPaused {
        require(msg.sender == tx.origin || msg.sender == address(vault), 'StrategyCake: EOA_or_vault');

        uint256 balanceBefore = IERC20(want).balanceOf(address(this));
        IMasterChef(masterChef).leaveStaking(0);
        uint256 balanceAfter = IERC20(want).balanceOf(address(this));

        uint256 perfAmount = balanceAfter.sub(balanceBefore);
        chargePerformanceFee(perfAmount);
        deposit();
    }

    function deposit() public override whenNotPaused nonReentrant {
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            IMasterChef(masterChef).enterStaking(wantBalance);
        }
    }

    function balanceOfPool() public view override returns (uint256) {
        (uint256 amount, ) = IMasterChef(masterChef).userInfo(0, address(this));
        return amount;
    }

    function _giveAllowances() internal override {
        TransferHelper.safeApprove(want, masterChef, 0);
        TransferHelper.safeApprove(want, masterChef, uint256(-1));
    }

    function _removeAllowances() internal override {
        TransferHelper.safeApprove(want, masterChef, 0);
    }

    /* ----- Admin Functions ----- */

    function retireStrat() external override {
        require(msg.sender == vault, '!vault');
        IMasterChef(masterChef).emergencyWithdraw(0);
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            TransferHelper.safeTransfer(want, vault, wantBalance);
        }
    }

    function emergencyExit() external override onlyAdmin {
        IMasterChef(masterChef).emergencyWithdraw(0);
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            TransferHelper.safeTransfer(want, vault, wantBalance);
        }
    }
}

File 70 of 106 : StrategyGeist.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/Pausable.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import '../../../interfaces/TraderJoe/IUniswapRouter.sol';
import '../../../interfaces/Geist/IDataProvider.sol';
import '../../../interfaces/Geist/IIncentivesController.sol';
import '../../../interfaces/Geist/ILendingPool.sol';
import '../../../interfaces/Geist/IMultiFeeDistribution.sol';
import '../../../interfaces/IWooAccessManager.sol';
import '../../../interfaces/IWETH.sol';
import '../BaseStrategy.sol';

contract StrategyGeist is BaseStrategy {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    struct TokenAddresses {
        address token; // Deposit Token
        address gToken; // Token that minted by lend
    }

    /* ----- State Variables ----- */

    TokenAddresses public wantToken;
    TokenAddresses[] public rewards;

    address[] public rewardToWNativeRoute;
    address[] public wNativeToWantRoute;
    address[][] public extraRewardToWNativeRoutes;

    uint256 public lastHarvest;

    /* ----- Constant Variables ----- */

    address public constant wNative = address(0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83); // WFTM
    address public constant reward = address(0xd8321AA83Fb0a4ECd6348D4577431310A6E0814d); // GEIST
    address public constant uniRouter = address(0xF491e7B69E4244ad4002BC14e878a34207E38c29); // SpookySwapRouter

    address public dataProvider = address(0xf3B0611e2E4D2cd6aB4bb3e01aDe211c3f42A8C3);
    address public lendingPool = address(0x9FAD24f572045c7869117160A571B2e50b10d068);
    address public multiFeeDistribution = address(0x49c93a95dbcc9A6A4D8f77E59c038ce5020e82f8);
    address public incentivesController = address(0x297FddC5c33Ef988dd03bd13e162aE084ea1fE57);

    /* ----- Events ----- */

    event StratHarvest(address indexed harvester, uint256 wantHarvested, uint256 tvl);
    event Deposit(uint256 tvl);
    event Withdraw(uint256 tvl);

    constructor(
        address _vault,
        address _accessManager,
        address _want,
        address[] memory _rewardToWNativeRoute,
        address[] memory _wNativeToWantRoute,
        address[][] memory _extraRewardToWNativeRoutes
    ) public BaseStrategy(_vault, _accessManager) {
        (address gToken, , ) = IDataProvider(dataProvider).getReserveTokensAddresses(_want);
        wantToken = TokenAddresses(_want, gToken);

        rewardToWNativeRoute = _rewardToWNativeRoute;
        wNativeToWantRoute = _wNativeToWantRoute;
        extraRewardToWNativeRoutes = _extraRewardToWNativeRoutes;

        for (uint256 i; i < extraRewardToWNativeRoutes.length; i++) {
            address _token = extraRewardToWNativeRoutes[i][0];
            (address _gToken, , ) = IDataProvider(dataProvider).getReserveTokensAddresses(_token);
            rewards.push(TokenAddresses(_token, _gToken));
        }

        _giveAllowances();
    }

    /* ----- External Functions ----- */

    function rewardToWNative() external view returns (address[] memory) {
        return rewardToWNativeRoute;
    }

    /* ----- Public Functions ----- */

    function harvest() public override whenNotPaused {
        require(msg.sender == tx.origin || msg.sender == address(vault), 'StrategyGeist: EOA_or_vault');

        uint256 reserveWantGTokenBal = IERC20(wantToken.gToken).balanceOf(address(this));
        address[] memory tokens = new address[](1);
        tokens[0] = wantToken.gToken;
        // Claim pending rewards for one or more pools.
        // Rewards are not received directly, they are minted by the rewardMinter.
        IIncentivesController(incentivesController).claim(address(this), tokens);
        // Withdraw full unlocked balance and claim pending rewards
        IMultiFeeDistribution(multiFeeDistribution).exit();

        uint256 rewardBal = IERC20(reward).balanceOf(address(this));
        if (rewardBal > 0) {
            uint256 beforeBal = balanceOfWant();
            _swapRewards(reserveWantGTokenBal);
            uint256 wantHarvested = balanceOfWant().sub(beforeBal);
            uint256 fee = chargePerformanceFee(wantHarvested);
            deposit();

            lastHarvest = block.timestamp;
            emit StratHarvest(msg.sender, wantHarvested.sub(fee), balanceOf());
        }

        uint256 wantGTokenBalAfter = IERC20(wantToken.gToken).balanceOf(address(this));
        require(wantGTokenBalAfter >= reserveWantGTokenBal, 'StrategyGeist: gTokenBalError');
    }

    function deposit() public override whenNotPaused nonReentrant {
        uint256 wantBal = balanceOfWant();

        if (wantBal > 0) {
            ILendingPool(lendingPool).deposit(want, wantBal, address(this), 0);
            emit Deposit(balanceOf());
        }
    }

    function withdraw(uint256 amount) public override nonReentrant {
        require(msg.sender == vault, 'StrategyGeist: !vault');
        require(amount > 0, 'StrategyGeist: !amount');

        uint256 wantBal = balanceOfWant();

        if (wantBal < amount) {
            ILendingPool(lendingPool).withdraw(want, amount.sub(wantBal), address(this));
            uint256 newWantBal = IERC20(want).balanceOf(address(this));
            require(newWantBal > wantBal, 'StrategyGeist: !newWantBal');
            wantBal = newWantBal;
        }

        uint256 withdrawAmt = amount < wantBal ? amount : wantBal;

        uint256 fee = chargeWithdrawalFee(withdrawAmt);
        if (withdrawAmt > fee) {
            TransferHelper.safeTransfer(want, vault, withdrawAmt.sub(fee));
        }
        emit Withdraw(balanceOf());
    }

    function userReserves() public view returns (uint256, uint256) {
        (uint256 supplyBal, , uint256 borrowBal, , , , , , ) = IDataProvider(dataProvider).getUserReserveData(
            want,
            address(this)
        );
        return (supplyBal, borrowBal);
    }

    function balanceOfPool() public view override returns (uint256) {
        (uint256 supplyBal, uint256 borrowBal) = userReserves();
        return supplyBal.sub(borrowBal);
    }

    /* ----- Internal Functions ----- */

    function _giveAllowances() internal override {
        TransferHelper.safeApprove(want, lendingPool, uint256(-1));

        TransferHelper.safeApprove(reward, uniRouter, uint256(-1));
        for (uint256 i; i < rewards.length; i++) {
            TransferHelper.safeApprove(rewards[i].token, uniRouter, uint256(-1));
        }
    }

    function _removeAllowances() internal override {
        TransferHelper.safeApprove(want, lendingPool, 0);

        TransferHelper.safeApprove(reward, uniRouter, 0);
        for (uint256 i; i < rewards.length; i++) {
            TransferHelper.safeApprove(rewards[i].token, uniRouter, 0);
        }
    }

    /* ----- Private Functions ----- */

    function _swapRewards(uint256 reserveWantGTokenBal) private {
        // reward to wNative
        uint256 rewardBal = IERC20(reward).balanceOf(address(this));
        IUniswapRouter(uniRouter).swapExactTokensForTokens(rewardBal, 0, rewardToWNativeRoute, address(this), now);

        for (uint256 i; i < rewards.length; i++) {
            uint256 gTokenToWithdraw = IERC20(rewards[i].gToken).balanceOf(address(this));

            // if reward is wantToken, we have to substrate the reserved gToken balance.
            if (rewards[i].gToken == wantToken.gToken) {
                gTokenToWithdraw = gTokenToWithdraw.sub(reserveWantGTokenBal);
            }

            if (gTokenToWithdraw > 0) {
                // gToken to the underlying asset
                ILendingPool(lendingPool).withdraw(rewards[i].token, gTokenToWithdraw, address(this));

                if (rewards[i].token != wNative && rewards[i].token != wantToken.token) {
                    uint256 tokenToSwap = IERC20(rewards[i].token).balanceOf(address(this));
                    IUniswapRouter(uniRouter).swapExactTokensForTokens(
                        tokenToSwap,
                        0,
                        extraRewardToWNativeRoutes[i],
                        address(this),
                        now
                    );
                }
            }
        }

        uint256 wNativeBal = IERC20(wNative).balanceOf(address(this));
        if (wNative != want && wNativeBal > 0) {
            IUniswapRouter(uniRouter).swapExactTokensForTokens(wNativeBal, 0, wNativeToWantRoute, address(this), now);
        }
    }

    /* ----- Admin Functions ----- */

    function addRewardToNativeRoute(address[] memory _rewardToWNativeRoute) external onlyAdmin {
        address _token = _rewardToWNativeRoute[0];
        (address _gToken, , ) = IDataProvider(dataProvider).getReserveTokensAddresses(_token);

        rewards.push(TokenAddresses(_token, _gToken));
        extraRewardToWNativeRoutes.push(_rewardToWNativeRoute);

        TransferHelper.safeApprove(_token, uniRouter, uint256(-1));
    }

    function removeRewardToWNativeRoute() external onlyAdmin {
        IERC20(rewards[rewards.length - 1].token).safeApprove(uniRouter, 0);

        rewards.pop();
        extraRewardToWNativeRoutes.pop();
    }

    function retireStrat() external override {
        require(msg.sender == vault, 'StrategyGeist: !vault');
        ILendingPool(lendingPool).withdraw(want, type(uint256).max, address(this));
        uint256 wantBal = IERC20(want).balanceOf(address(this));
        if (wantBal > 0) {
            TransferHelper.safeTransfer(want, vault, wantBal);
        }
    }

    function emergencyExit() external override onlyAdmin {
        ILendingPool(lendingPool).withdraw(want, type(uint256).max, address(this));
        uint256 wantBal = IERC20(want).balanceOf(address(this));
        if (wantBal > 0) {
            TransferHelper.safeTransfer(want, vault, wantBal);
        }
    }

    receive() external payable {}
}

File 71 of 106 : IDataProvider.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface IDataProvider {
    function getReserveTokensAddresses(address asset)
        external
        view
        returns (
            address aTokenAddress,
            address stableDebtTokenAddress,
            address variableDebtTokenAddress
        );

    function getUserReserveData(address asset, address user)
        external
        view
        returns (
            uint256 currentATokenBalance,
            uint256 currentStableDebt,
            uint256 currentVariableDebt,
            uint256 principalStableDebt,
            uint256 scaledVariableDebt,
            uint256 stableBorrowRate,
            uint256 liquidityRate,
            uint40 stableRateLastUpdated,
            bool usageAsCollateralEnabled
        );
}

File 72 of 106 : IIncentivesController.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface IIncentivesController {
    function claimableReward(address _user, address[] calldata _tokens) external view returns (uint256[] memory);

    function claim(address _user, address[] calldata _tokens) external;
}

File 73 of 106 : ILendingPool.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface ILendingPool {
    function deposit(
        address asset,
        uint256 amount,
        address onBehalfOf,
        uint16 referralCode
    ) external;

    function borrow(
        address asset,
        uint256 amount,
        uint256 interestRateMode,
        uint16 referralCode,
        address onBehalfOf
    ) external;

    function repay(
        address asset,
        uint256 amount,
        uint256 rateMode,
        address onBehalfOf
    ) external returns (uint256);

    function withdraw(
        address asset,
        uint256 amount,
        address to
    ) external returns (uint256);

    function getUserAccountData(address user)
        external
        view
        returns (
            uint256 totalCollateralETH,
            uint256 totalDebtETH,
            uint256 availableBorrowsETH,
            uint256 currentLiquidationThreshold,
            uint256 ltv,
            uint256 healthFactor
        );
}

File 74 of 106 : IMultiFeeDistribution.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

interface IMultiFeeDistribution {
    struct RewardData {
        address token;
        uint256 amount;
    }

    function claimableRewards(address account) external view returns (RewardData[] memory rewards);

    function exit() external;
}

File 75 of 106 : StrategyCurveLP.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/Pausable.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import '../../../interfaces/TraderJoe/IUniswapRouter.sol';
import '../../../interfaces/Curve/ICurveSwap.sol';
import '../../../interfaces/Curve/IRewardsGauge.sol';
import '../BaseStrategy.sol';

contract StrategyCurveLP is BaseStrategy {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    /* ----- State Variables ----- */

    // Tokens used
    address public crv;
    address public coReward; // cooperation reward
    address public wNative; // wrapped native
    address public depositToken;

    // Third party contracts
    address public rewardsGauge;
    address public pool;
    uint256 public immutable poolSize;
    uint256 public depositIndex;
    bool public useUnderlying;

    // Routes
    address[] public crvToWNativeRoute;
    address[] public coRewardToWNativeRoute;
    address[] public wNativeToDepositRoute;

    address public crvRouter;
    address public uniRouter;

    uint256 public lastHarvest;

    /* ----- Events ----- */

    event StratHarvest(address indexed harvester, uint256 wantHarvested, uint256 tvl);
    event Deposit(uint256 tvl);
    event Withdraw(uint256 tvl);

    constructor(
        address initVault,
        address initAccessManager,
        address initRewardsGauge,
        address initPool,
        uint256 initPoolSize,
        uint256 initDepositIndex,
        bool initUseUnderLying,
        address[] memory initCrvToWNativeRoute,
        address[] memory initCoRewardToWNativeRoute,
        address[] memory initWNativeToDepositRoute,
        address initUniRouter
    ) public BaseStrategy(initVault, initAccessManager) {
        rewardsGauge = initRewardsGauge;
        pool = initPool;
        poolSize = initPoolSize;
        depositIndex = initDepositIndex;
        useUnderlying = initUseUnderLying;

        crv = initCrvToWNativeRoute[0];
        coReward = initCoRewardToWNativeRoute[0];
        wNative = initCrvToWNativeRoute[initCrvToWNativeRoute.length - 1];
        crvToWNativeRoute = initCrvToWNativeRoute;
        coRewardToWNativeRoute = initCoRewardToWNativeRoute;
        crvRouter = initUniRouter;

        require(initWNativeToDepositRoute[0] == wNative, 'StrategyCurveLP: initWNativeToDepositRoute[0] != wNative');
        depositToken = initWNativeToDepositRoute[initWNativeToDepositRoute.length - 1];
        wNativeToDepositRoute = initWNativeToDepositRoute;
        uniRouter = initUniRouter;

        _giveAllowances();
    }

    /* ----- External Functions ----- */

    function crvToWNative() external view returns (address[] memory) {
        return crvToWNativeRoute;
    }

    function coRewardToWNative() external view returns (address[] memory) {
        return coRewardToWNativeRoute;
    }

    function wNativeToDeposit() external view returns (address[] memory) {
        return wNativeToDepositRoute;
    }

    /* ----- Public Functions ----- */

    function harvest() public override whenNotPaused {
        require(msg.sender == tx.origin || msg.sender == vault, 'StrategyCurveLP: !EOA or !Vault');

        IRewardsGauge(rewardsGauge).claim_rewards(address(this));
        uint256 crvBal = IERC20(crv).balanceOf(address(this));
        uint256 coRewardBal = 0;
        if (coReward != address(0)) {
            coRewardBal = IERC20(coReward).balanceOf(address(this));
        }
        uint256 wNativeBal = IERC20(wNative).balanceOf(address(this));
        if (wNativeBal > 0 || crvBal > 0 || coRewardBal > 0) {
            uint256 beforeBal = balanceOfWant();
            _addLiquidity();
            uint256 wantHarvested = balanceOfWant().sub(beforeBal);
            uint256 fee = chargePerformanceFee(wantHarvested);
            deposit();
            lastHarvest = block.timestamp;
            emit StratHarvest(msg.sender, wantHarvested.sub(fee), balanceOf());
        }
    }

    function deposit() public override whenNotPaused nonReentrant {
        uint256 wantBal = balanceOfWant();

        if (wantBal > 0) {
            IRewardsGauge(rewardsGauge).deposit(wantBal);
            emit Deposit(balanceOf());
        }
    }

    function withdraw(uint256 amount) public override nonReentrant {
        require(msg.sender == vault, 'StrategyCurveLP: !Vault');
        require(amount > 0, 'StrategyCurveLP: !amount');

        uint256 wantBal = balanceOfWant();

        if (wantBal < amount) {
            IRewardsGauge(rewardsGauge).withdraw(amount.sub(wantBal));
            wantBal = IERC20(want).balanceOf(address(this));
        }

        uint256 withdrawAmt = amount < wantBal ? amount : wantBal;

        uint256 fee = chargeWithdrawalFee(withdrawAmt);
        if (withdrawAmt > fee) {
            TransferHelper.safeTransfer(want, vault, withdrawAmt.sub(fee));
        }
        emit Withdraw(balanceOf());
    }

    function balanceOfPool() public view override returns (uint256) {
        return IRewardsGauge(rewardsGauge).balanceOf(address(this));
    }

    /* ----- Internal Functions ----- */

    function _giveAllowances() internal override {
        TransferHelper.safeApprove(want, rewardsGauge, 0);
        TransferHelper.safeApprove(want, rewardsGauge, uint256(-1));
        TransferHelper.safeApprove(crv, crvRouter, 0);
        TransferHelper.safeApprove(crv, crvRouter, uint256(-1));
        if (coReward != address(0)) {
            TransferHelper.safeApprove(coReward, uniRouter, 0);
            TransferHelper.safeApprove(coReward, uniRouter, uint256(-1));
        }
        TransferHelper.safeApprove(wNative, uniRouter, 0);
        TransferHelper.safeApprove(wNative, uniRouter, uint256(-1));
        TransferHelper.safeApprove(depositToken, pool, 0);
        TransferHelper.safeApprove(depositToken, pool, uint256(-1));
    }

    function _removeAllowances() internal override {
        TransferHelper.safeApprove(want, rewardsGauge, 0);
        if (coReward != address(0)) {
            TransferHelper.safeApprove(coReward, uniRouter, 0);
        }
        TransferHelper.safeApprove(wNative, uniRouter, 0);
        TransferHelper.safeApprove(crv, crvRouter, 0);
        TransferHelper.safeApprove(depositToken, pool, 0);
    }

    function _addLiquidity() internal {
        uint256 crvBal = IERC20(crv).balanceOf(address(this));
        if (crvBal > 0) {
            IUniswapRouter(crvRouter).swapExactTokensForTokens(
                crvBal,
                0,
                crvToWNativeRoute,
                address(this),
                block.timestamp
            );
        }

        if (coReward != address(0)) {
            uint256 coRewardBal = IERC20(coReward).balanceOf(address(this));
            if (coRewardBal > 0) {
                IUniswapRouter(uniRouter).swapExactTokensForTokens(
                    coRewardBal,
                    0,
                    coRewardToWNativeRoute,
                    address(this),
                    block.timestamp
                );
            }
        }

        uint256 wNativeBal = IERC20(wNative).balanceOf(address(this));
        if (depositToken != wNative) {
            IUniswapRouter(uniRouter).swapExactTokensForTokens(
                wNativeBal,
                0,
                wNativeToDepositRoute,
                address(this),
                block.timestamp
            );
        }

        uint256 depositBal = IERC20(depositToken).balanceOf(address(this));

        if (poolSize == 2) {
            uint256[2] memory amounts;
            amounts[depositIndex] = depositBal;
            if (useUnderlying) ICurveSwap2(pool).add_liquidity(amounts, 0, true);
            else ICurveSwap2(pool).add_liquidity(amounts, 0);
        } else if (poolSize == 3) {
            uint256[3] memory amounts;
            amounts[depositIndex] = depositBal;
            if (useUnderlying) ICurveSwap3(pool).add_liquidity(amounts, 0, true);
            else ICurveSwap3(pool).add_liquidity(amounts, 0);
        } else if (poolSize == 4) {
            uint256[4] memory amounts;
            amounts[depositIndex] = depositBal;
            ICurveSwap4(pool).add_liquidity(amounts, 0);
        } else if (poolSize == 5) {
            uint256[5] memory amounts;
            amounts[depositIndex] = depositBal;
            ICurveSwap5(pool).add_liquidity(amounts, 0);
        }
    }

    /* ----- Admin Functions ----- */

    function setCrvRoute(address newCrvRouter, address[] memory newCrvToWNativeRoute) external onlyAdmin {
        require(newCrvToWNativeRoute[0] == crv, 'StrategyCurveLP: !crv');
        require(newCrvToWNativeRoute[newCrvToWNativeRoute.length - 1] == wNative, 'StrategyCurveLP: !wNative');

        _removeAllowances();
        crvToWNativeRoute = newCrvToWNativeRoute;
        crvRouter = newCrvRouter;
        _giveAllowances();
    }

    function retireStrat() external override {
        require(msg.sender == vault, 'StrategyCurveLP: !Vault');
        IRewardsGauge(rewardsGauge).withdraw(balanceOfPool());
        uint256 wantBal = IERC20(want).balanceOf(address(this));
        if (wantBal > 0) {
            TransferHelper.safeTransfer(want, vault, wantBal);
        }
    }

    function emergencyExit() external override onlyAdmin {
        IRewardsGauge(rewardsGauge).withdraw(balanceOfPool());
        uint256 wantBal = IERC20(want).balanceOf(address(this));
        if (wantBal > 0) {
            TransferHelper.safeTransfer(want, vault, wantBal);
        }
    }

    receive() external payable {}
}

File 76 of 106 : ICurveSwap.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface ICurveSwap2 {
    function add_liquidity(uint256[2] memory amounts, uint256 min_mint_amount) external;

    function add_liquidity(
        uint256[2] memory amounts,
        uint256 min_mint_amount,
        bool _use_underlying
    ) external;
}

interface ICurveSwap3 {
    function add_liquidity(uint256[3] memory amounts, uint256 min_mint_amount) external;

    function add_liquidity(
        uint256[3] memory amounts,
        uint256 min_mint_amount,
        bool _use_underlying
    ) external;
}

interface ICurveSwap4 {
    function add_liquidity(uint256[4] memory amounts, uint256 min_mint_amount) external;
}

interface ICurveSwap5 {
    function add_liquidity(uint256[5] memory amounts, uint256 min_mint_amount) external;
}

File 77 of 106 : IRewardsGauge.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface IRewardsGauge {
    function balanceOf(address account) external view returns (uint256);

    function claimable_reward(address user, address token) external view returns (uint256);

    function claim_rewards(address user) external;

    function deposit(uint256 value) external;

    function withdraw(uint256 value) external;
}

File 78 of 106 : StrategyBenqi.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/Pausable.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import '../../../interfaces/BankerJoe/IJoeRouter.sol';
import '../../../interfaces/BankerJoe/IVToken.sol';
import '../../../interfaces/BankerJoe/IComptroller.sol';
import '../../../interfaces/IWooAccessManager.sol';
import '../../../interfaces/IWETH.sol';
import '../BaseStrategy.sol';

contract StrategyBenqi is BaseStrategy {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    /* ----- State Variables ----- */

    // list of benqi markets
    // qiAvax:  0x5C0401e81Bc07Ca70fAD469b451682c0d747Ef1c  https://snowtrace.io/address/0x5c0401e81bc07ca70fad469b451682c0d747ef1c
    // qiBTC:   0xe194c4c5aC32a3C9ffDb358d9Bfd523a0B6d1568  https://snowtrace.io/address/0xe194c4c5ac32a3c9ffdb358d9bfd523a0b6d1568
    // qiETH:   0x334AD834Cd4481BB02d09615E7c11a00579A7909  https://snowtrace.io/address/0x334ad834cd4481bb02d09615e7c11a00579a7909
    // qiUSDT:  0xc9e5999b8e75C3fEB117F6f73E664b9f3C8ca65C  https://snowtrace.io/address/0xc9e5999b8e75c3feb117f6f73e664b9f3c8ca65c
    // qiLink:  0x4e9f683A27a6BdAD3FC2764003759277e93696e6  https://snowtrace.io/address/0x4e9f683a27a6bdad3fc2764003759277e93696e6
    // qiDai:   0x835866d37AFB8CB8F8334dCCdaf66cf01832Ff5D  https://snowtrace.io/address/0x835866d37AFB8CB8F8334dCCdaf66cf01832Ff5D
    // qiUSDC:  0xBEb5d47A3f720Ec0a390d04b4d41ED7d9688bC7F  https://snowtrace.io/address/0xbeb5d47a3f720ec0a390d04b4d41ed7d9688bc7f
    // qiQi:    0x35Bd6aedA81a7E5FC7A7832490e71F757b0cD9Ce  https://snowtrace.io/address/0x35bd6aeda81a7e5fc7a7832490e71f757b0cd9ce
    address public qiToken;

    address[] public reward1ToWantRoute;
    address[] public reward2ToWantRoute;
    uint256 public lastHarvest;
    uint256 public supplyBal;

    /* ----- Constant Variables ----- */

    address public constant wrappedEther = address(0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7); // WAVAX
    address public constant reward1 = address(0x8729438EB15e2C8B576fCc6AeCdA6A148776C0F5); // Qi token
    address public constant reward2 = wrappedEther; // Wavax token
    address public constant uniRouter = address(0x60aE616a2155Ee3d9A68541Ba4544862310933d4); // JoeRouter
    address public constant comptroller = address(0x486Af39519B4Dc9a7fCcd318217352830E8AD9b4); // to claim reward

    /* ----- Events ----- */

    event StratHarvest(address indexed harvester, uint256 wantHarvested, uint256 tvl);
    event Deposit(uint256 tvl);
    event Withdraw(uint256 tvl);

    constructor(
        address initVault,
        address initAccessManager,
        address initQiToken,
        address[] memory initReward1ToWantRoute,
        address[] memory initReward2ToWantRoute
    ) public BaseStrategy(initVault, initAccessManager) {
        qiToken = initQiToken;
        reward1ToWantRoute = initReward1ToWantRoute;
        reward2ToWantRoute = initReward2ToWantRoute;

        _giveAllowances();
    }

    /* ----- External Functions ----- */

    function beforeDeposit() public override {
        super.beforeDeposit();
        updateSupplyBal();
    }

    function reward1ToWant() external view returns (address[] memory) {
        return reward1ToWantRoute;
    }

    function reward2ToWant() external view returns (address[] memory) {
        return reward2ToWantRoute;
    }

    /* ----- Public Functions ----- */

    function harvest() public override whenNotPaused {
        require(msg.sender == tx.origin || msg.sender == address(vault), 'StrategyBenqi: EOA_or_vault');

        // When pendingImplementation not zero address, means there is a new implement ready to replace.
        if (IComptroller(comptroller).pendingComptrollerImplementation() == address(0)) {
            uint256 beforeBal = balanceOfWant();

            _harvestAndSwap(0, reward1, reward1ToWantRoute);
            _harvestAndSwap(1, reward2, reward2ToWantRoute);

            uint256 wantHarvested = balanceOfWant().sub(beforeBal);
            uint256 fee = chargePerformanceFee(wantHarvested);
            deposit();

            lastHarvest = block.timestamp;
            emit StratHarvest(msg.sender, wantHarvested.sub(fee), balanceOf());
        } else {
            _withdrawAll();
            pause();
        }
    }

    function _harvestAndSwap(
        uint8 index,
        address reward,
        address[] memory route
    ) private {
        address[] memory markets = new address[](1);
        markets[0] = qiToken;
        IComptroller(comptroller).claimReward(index, address(this), markets);

        // in case of reward token is native token (ETH/BNB/Avax)
        uint256 toWrapBal = address(this).balance;
        if (toWrapBal > 0) {
            IWETH(wrappedEther).deposit{value: toWrapBal}();
        }

        uint256 rewardBal = IERC20(reward).balanceOf(address(this));

        // rewardBal == 0: means the current token reward ended
        // reward == want: no need to swap
        if (rewardBal > 0 && reward != want) {
            require(route.length > 0, 'StrategyBenqi: SWAP_ROUTE_INVALID');
            IJoeRouter(uniRouter).swapExactTokensForTokens(rewardBal, 0, route, address(this), now);
        }
    }

    function deposit() public override whenNotPaused nonReentrant {
        uint256 wantBal = balanceOfWant();

        if (wantBal > 0) {
            IVToken(qiToken).mint(wantBal);
            updateSupplyBal();
            emit Deposit(balanceOf());
        }
    }

    function withdraw(uint256 amount) public override nonReentrant {
        require(msg.sender == vault, 'StrategyBenqi: !vault');
        require(amount > 0, 'StrategyBenqi: !amount');

        uint256 wantBal = balanceOfWant();

        if (wantBal < amount) {
            IVToken(qiToken).redeemUnderlying(amount.sub(wantBal));
            updateSupplyBal();
            uint256 newWantBal = IERC20(want).balanceOf(address(this));
            require(newWantBal > wantBal, 'StrategyBenqi: !newWantBal');
            wantBal = newWantBal;
        }

        uint256 withdrawAmt = amount < wantBal ? amount : wantBal;

        uint256 fee = chargeWithdrawalFee(withdrawAmt);
        if (withdrawAmt > fee) {
            TransferHelper.safeTransfer(want, vault, withdrawAmt.sub(fee));
        }
        emit Withdraw(balanceOf());
    }

    function updateSupplyBal() public {
        supplyBal = IVToken(qiToken).balanceOfUnderlying(address(this));
    }

    function balanceOfPool() public view override returns (uint256) {
        return supplyBal;
    }

    /* ----- Internal Functions ----- */

    function _giveAllowances() internal override {
        TransferHelper.safeApprove(want, qiToken, 0);
        TransferHelper.safeApprove(want, qiToken, uint256(-1));
        TransferHelper.safeApprove(reward1, uniRouter, 0);
        TransferHelper.safeApprove(reward1, uniRouter, uint256(-1));
        TransferHelper.safeApprove(reward2, uniRouter, 0);
        TransferHelper.safeApprove(reward2, uniRouter, uint256(-1));
        TransferHelper.safeApprove(wrappedEther, uniRouter, 0);
        TransferHelper.safeApprove(wrappedEther, uniRouter, uint256(-1));
    }

    function _removeAllowances() internal override {
        TransferHelper.safeApprove(want, qiToken, 0);
        TransferHelper.safeApprove(reward1, uniRouter, 0);
        TransferHelper.safeApprove(reward2, uniRouter, 0);
        TransferHelper.safeApprove(wrappedEther, uniRouter, 0);
    }

    function _withdrawAll() internal {
        uint256 qiTokenBal = IERC20(qiToken).balanceOf(address(this));
        if (qiTokenBal > 0) {
            IVToken(qiToken).redeem(qiTokenBal);
        }
        updateSupplyBal();
    }

    /* ----- Admin Functions ----- */

    function retireStrat() external override {
        require(msg.sender == vault, 'StrategyBenqi: !vault');
        _withdrawAll();
        uint256 wantBal = IERC20(want).balanceOf(address(this));
        if (wantBal > 0) {
            TransferHelper.safeTransfer(want, vault, wantBal);
        }
    }

    function emergencyExit() external override onlyAdmin {
        _withdrawAll();
        uint256 wantBal = IERC20(want).balanceOf(address(this));
        if (wantBal > 0) {
            TransferHelper.safeTransfer(want, vault, wantBal);
        }
    }

    receive() external payable {}
}

File 79 of 106 : IJoeRouter.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface IJoeRouter {
    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint256 amountADesired,
        uint256 amountBDesired,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline
    )
        external
        returns (
            uint256 amountA,
            uint256 amountB,
            uint256 liquidity
        );
}

File 80 of 106 : IVToken.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';

interface IVToken is IERC20 {
    function underlying() external returns (address);

    function mint(uint256 mintAmount) external returns (uint256);

    function redeem(uint256 redeemTokens) external returns (uint256);

    function redeemUnderlying(uint256 redeemAmount) external returns (uint256);

    function borrow(uint256 borrowAmount) external returns (uint256);

    function repayBorrow(uint256 repayAmount) external returns (uint256);

    function balanceOfUnderlying(address owner) external returns (uint256);

    function borrowBalanceCurrent(address account) external returns (uint256);

    function comptroller() external returns (address);
}

File 81 of 106 : IComptroller.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface IComptroller {
    function claimComp(address holder, address[] calldata _iTokens) external;

    function claimComp(address holder) external;

    function compAccrued(address holder) external view returns (uint256 comp);

    function enterMarkets(address[] memory _iTokens) external;

    function pendingImplementation() external view returns (address implementation);

    function claimReward(uint8 rewardType, address payable holder) external;

    function claimReward(
        uint8 rewardType,
        address payable holder,
        address[] memory _iTokens
    ) external;

    // Benqi comptroller admin method
    function pendingComptrollerImplementation() external view returns (address implementation);
}

File 82 of 106 : StrategyBankerJoe.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/Pausable.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import '../../../interfaces/BankerJoe/IJoeRouter.sol';
import '../../../interfaces/BankerJoe/IVToken.sol';
import '../../../interfaces/BankerJoe/IComptroller.sol';
import '../../../interfaces/IWooAccessManager.sol';
import '../../../interfaces/IWETH.sol';
import '../BaseStrategy.sol';

contract StrategyBankerJoe is BaseStrategy {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    /* ----- State Variables ----- */

    address public iToken;
    address[] public reward1ToWantRoute;
    address[] public reward2ToWantRoute;
    uint256 public lastHarvest;
    uint256 public supplyBal;

    /* ----- Constant Variables ----- */

    address public constant wrappedEther = address(0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7); // WAVAX
    address public constant reward1 = address(0x6e84a6216eA6dACC71eE8E6b0a5B7322EEbC0fDd); // JOE
    address public constant reward2 = wrappedEther; // WAVAX
    address public constant uniRouter = address(0x60aE616a2155Ee3d9A68541Ba4544862310933d4); // JoeRouter
    address public constant comptroller = address(0xdc13687554205E5b89Ac783db14bb5bba4A1eDaC);

    /* ----- Events ----- */

    event StratHarvest(address indexed harvester, uint256 wantHarvested, uint256 tvl);
    event Deposit(uint256 tvl);
    event Withdraw(uint256 tvl);

    constructor(
        address initVault,
        address initAccessManager,
        address initIToken,
        address[] memory initReward1ToWantRoute,
        address[] memory initReward2ToWantRoute
    ) public BaseStrategy(initVault, initAccessManager) {
        iToken = initIToken;
        reward1ToWantRoute = initReward1ToWantRoute;
        reward2ToWantRoute = initReward2ToWantRoute;

        _giveAllowances();
    }

    /* ----- External Functions ----- */

    function beforeDeposit() public override {
        super.beforeDeposit();
        updateSupplyBal();
    }

    function reward1ToWant() external view returns (address[] memory) {
        return reward1ToWantRoute;
    }

    function reward2ToWant() external view returns (address[] memory) {
        return reward2ToWantRoute;
    }

    /* ----- Public Functions ----- */

    function harvest() public override whenNotPaused {
        require(msg.sender == tx.origin || msg.sender == address(vault), 'StrategyBankerJoe: EOA_or_vault');

        // When pendingImplementation not zero address, means there is a new implement ready to replace.
        if (IComptroller(comptroller).pendingImplementation() == address(0)) {
            uint256 beforeBal = balanceOfWant();

            _harvestAndSwap(0, reward1, reward1ToWantRoute);
            _harvestAndSwap(1, reward2, reward2ToWantRoute);

            uint256 wantHarvested = balanceOfWant().sub(beforeBal);
            uint256 fee = chargePerformanceFee(wantHarvested);
            deposit();

            lastHarvest = block.timestamp;
            emit StratHarvest(msg.sender, wantHarvested.sub(fee), balanceOf());
        } else {
            _withdrawAll();
            pause();
        }
    }

    function deposit() public override whenNotPaused nonReentrant {
        uint256 wantBal = balanceOfWant();

        if (wantBal > 0) {
            IVToken(iToken).mint(wantBal);
            updateSupplyBal();
            emit Deposit(balanceOf());
        }
    }

    function withdraw(uint256 amount) public override nonReentrant {
        require(msg.sender == vault, 'StrategyBankerJoe: !vault');
        require(amount > 0, 'StrategyBankerJoe: !amount');

        uint256 wantBal = balanceOfWant();

        if (wantBal < amount) {
            IVToken(iToken).redeemUnderlying(amount.sub(wantBal));
            updateSupplyBal();
            uint256 newWantBal = IERC20(want).balanceOf(address(this));
            require(newWantBal > wantBal, 'StrategyBankerJoe: !newWantBal');
            wantBal = newWantBal;
        }

        uint256 withdrawAmt = amount < wantBal ? amount : wantBal;

        uint256 fee = chargeWithdrawalFee(withdrawAmt);
        if (withdrawAmt > fee) {
            TransferHelper.safeTransfer(want, vault, withdrawAmt.sub(fee));
        }
        emit Withdraw(balanceOf());
    }

    function updateSupplyBal() public {
        supplyBal = IVToken(iToken).balanceOfUnderlying(address(this));
    }

    function balanceOfPool() public view override returns (uint256) {
        return supplyBal;
    }

    /* ----- Internal Functions ----- */

    function _giveAllowances() internal override {
        TransferHelper.safeApprove(want, iToken, 0);
        TransferHelper.safeApprove(want, iToken, uint256(-1));
        TransferHelper.safeApprove(reward1, uniRouter, 0);
        TransferHelper.safeApprove(reward1, uniRouter, uint256(-1));
        TransferHelper.safeApprove(reward2, uniRouter, 0);
        TransferHelper.safeApprove(reward2, uniRouter, uint256(-1));
        TransferHelper.safeApprove(wrappedEther, uniRouter, 0);
        TransferHelper.safeApprove(wrappedEther, uniRouter, uint256(-1));
    }

    function _removeAllowances() internal override {
        TransferHelper.safeApprove(want, iToken, 0);
        TransferHelper.safeApprove(reward1, uniRouter, 0);
        TransferHelper.safeApprove(reward2, uniRouter, 0);
        TransferHelper.safeApprove(wrappedEther, uniRouter, 0);
    }

    function _withdrawAll() internal {
        uint256 iTokenBal = IERC20(iToken).balanceOf(address(this));
        if (iTokenBal > 0) {
            IVToken(iToken).redeem(iTokenBal);
        }
        updateSupplyBal();
    }

    /* ----- Private Functions ----- */

    function _harvestAndSwap(
        uint8 index,
        address reward,
        address[] memory route
    ) private {
        address[] memory markets = new address[](1);
        markets[0] = iToken;
        IComptroller(comptroller).claimReward(index, address(this), markets);

        // in case of reward token is native token (ETH/BNB/AVAX)
        uint256 toWrapBal = address(this).balance;
        if (toWrapBal > 0) {
            IWETH(wrappedEther).deposit{value: toWrapBal}();
        }

        uint256 rewardBal = IERC20(reward).balanceOf(address(this));

        // rewardBal == 0: means the current token reward ended
        // reward == want: no need to swap
        if (rewardBal > 0 && reward != want) {
            require(route.length > 0, 'StrategyBenqi: SWAP_ROUTE_INVALID');
            IJoeRouter(uniRouter).swapExactTokensForTokens(rewardBal, 0, route, address(this), now);
        }
    }

    /* ----- Admin Functions ----- */

    function retireStrat() external override {
        require(msg.sender == vault, 'StrategyBankerJoe: !vault');
        _withdrawAll();
        uint256 wantBal = IERC20(want).balanceOf(address(this));
        if (wantBal > 0) {
            TransferHelper.safeTransfer(want, vault, wantBal);
        }
    }

    function emergencyExit() external override onlyAdmin {
        _withdrawAll();
        uint256 wantBal = IERC20(want).balanceOf(address(this));
        if (wantBal > 0) {
            TransferHelper.safeTransfer(want, vault, wantBal);
        }
    }

    receive() external payable {}
}

File 83 of 106 : StrategySJoe.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import '../../../interfaces/TraderJoe/IStableJoeStaking.sol';
import '../../../interfaces/BankerJoe/IJoeRouter.sol';
import '../BaseStrategy.sol';

contract StrategySJoe is BaseStrategy {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    /* ----- Constant Variables ----- */

    address public constant staking = address(0x1a731B2299E22FbAC282E7094EdA41046343Cb51); // Joe staking contract
    address public constant uniRouter = address(0x60aE616a2155Ee3d9A68541Ba4544862310933d4); // JoeRouter02
    address public constant reward = address(0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E); // USDC

    address[] public rewardToWantRoute; // usdc -> joe

    constructor(address initVault, address initAccessManager) public BaseStrategy(initVault, initAccessManager) {
        _giveAllowances();
    }

    /* ----- External Functions ----- */

    function deposit() public override whenNotPaused nonReentrant {
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            IStableJoeStaking(staking).deposit(wantBalance);
        }
    }

    function withdraw(uint256 amount) external override nonReentrant {
        require(msg.sender == address(vault), 'StrategySJoe: NOT_VAULT');

        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance < amount) {
            IStableJoeStaking(staking).withdraw(amount.sub(wantBalance));
            wantBalance = IERC20(want).balanceOf(address(this));
        }

        require(wantBalance >= amount.mul(9999).div(10000), 'StrategySJoe: WITHDRAW_INSUFF_AMOUNT');
        uint256 withdrawAmount = amount < wantBalance ? amount : wantBalance;

        uint256 fee = chargeWithdrawalFee(withdrawAmount);
        if (withdrawAmount > fee) {
            TransferHelper.safeTransfer(want, vault, withdrawAmount.sub(fee));
        }
    }

    function harvest() public override whenNotPaused {
        require(msg.sender == tx.origin || msg.sender == address(vault), 'StrategySJoe: EOA_or_vault');

        uint256 balanceBefore = IERC20(want).balanceOf(address(this));

        IStableJoeStaking(staking).deposit(0); // to harvest the reward

        uint256 rewardBal = IERC20(reward).balanceOf(address(this));
        if (rewardBal > 0 && reward != want) {
            IJoeRouter(uniRouter).swapExactTokensForTokens(rewardBal, 0, rewardToWantRoute, address(this), now);
        }

        uint256 balanceAfter = IERC20(want).balanceOf(address(this));

        uint256 perfAmount = balanceAfter.sub(balanceBefore);
        chargePerformanceFee(perfAmount);
        deposit();
    }

    function balanceOfPool() public view override returns (uint256) {
        (uint256 amount, ) = IStableJoeStaking(staking).getUserInfo(address(this), reward);
        return amount;
    }

    function _giveAllowances() internal override {
        TransferHelper.safeApprove(want, staking, 0);
        TransferHelper.safeApprove(want, staking, uint256(-1));
        TransferHelper.safeApprove(reward, uniRouter, 0);
        TransferHelper.safeApprove(reward, uniRouter, uint256(-1));
    }

    function _removeAllowances() internal override {
        TransferHelper.safeApprove(want, staking, 0);
        TransferHelper.safeApprove(reward, uniRouter, 0);
    }

    /* ----- Admin Functions ----- */

    function setRewardToWantRoute(address[] memory _rewardToWantRoute) external onlyAdmin {
        rewardToWantRoute = _rewardToWantRoute;
    }

    function retireStrat() external override {
        require(msg.sender == vault, '!vault');
        IStableJoeStaking(staking).emergencyWithdraw();
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            TransferHelper.safeTransfer(want, vault, wantBalance);
        }
    }

    function emergencyExit() external override onlyAdmin {
        IStableJoeStaking(staking).emergencyWithdraw();
        uint256 wantBalance = IERC20(want).balanceOf(address(this));
        if (wantBalance > 0) {
            TransferHelper.safeTransfer(want, vault, wantBalance);
        }
    }
}

File 84 of 106 : IStableJoeStaking.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface IStableJoeStaking {
    function deposit(uint256 _amount) external;

    function getUserInfo(address _user, address _rewardToken) external view returns (uint256, uint256);

    function rewardTokensLength() external view returns (uint256);

    function pendingReward(address _user, address _token) external view returns (uint256);

    function withdraw(uint256 _amount) external;

    function emergencyWithdraw() external;

    function updateReward(address _token) external;
}

File 85 of 106 : StratStargateStableCompound.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import '../../../interfaces/Stargate/ILPStaking.sol';
import '../../../interfaces/Stargate/IStargateRouter.sol';
import '../../../interfaces/Stargate/IStargatePool.sol';
import '../../../interfaces/BankerJoe/IJoeRouter.sol';
import '../BaseStrategy.sol';

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

*
* MIT License
* ===========
*
* Copyright (c) 2020 WooTrade
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* 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 SOFTWARE.
*/
contract StratStargateStableCompound is BaseStrategy {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    /* ----- State Variables ----- */

    address public wrappedEther;
    address public uniRouter;

    // DepositPool list:
    // usdc.e pool helper: 0x257D69AA678e0A8DA6DFDA6A16CdF2052A460b45
    IStargateRouter public router;

    uint256 public stakingPid;

    uint8 public balanceSafeRate = 5;

    IStargatePool public pool;

    // Stake LP token to earn $STG
    // BNB chain: https://bscscan.com/address/0x3052a0f6ab15b4ae1df39962d5ddefaca86dab47#code
    ILPStaking public staking;

    address public wantLPToken; // S*BUSD:  deposit busd into pool to get S*BUSD LP token, then further stakes this LP token into LPRouter to get $STG reward

    address public reward; // STG

    address[] public rewardToWantRoute;

    uint256 public lastHarvest;

    uint16 public dstChainId;
    uint256 public srcPoolId;
    uint256 public dstPoolId;
    bool public instantRedeemOnly = true;

    /* ----- Events ----- */

    event StratHarvest(address indexed harvester, uint256 wantHarvested, uint256 tvl);
    event Deposit(uint256 tvl);
    event Withdraw(uint256 tvl);

    constructor(
        address _vault,
        address _accessManager,
        address _uniRouter, // swap router
        address _pool, // pool
        address _staking, // lp staking - Masterchef
        uint256 _stakingPid, // _pid for staking
        address _reward, // $stg
        address[] memory _rewardToWantRoute // $stg -> xxx -> want
    ) public BaseStrategy(_vault, _accessManager) {
        wrappedEther = IVaultV2(_vault).weth();
        uniRouter = _uniRouter;
        pool = IStargatePool(_pool);
        wantLPToken = _pool; // NOTE: pool is LPErc20Token for staking
        router = IStargateRouter(IStargatePool(_pool).router());
        staking = ILPStaking(_staking);
        stakingPid = _stakingPid;
        reward = _reward;
        rewardToWantRoute = _rewardToWantRoute;

        require(pool.token() == want, 'StratStargateStableCompound: !pool_token');

        require(
            rewardToWantRoute.length > 0 &&
                rewardToWantRoute[0] == reward &&
                rewardToWantRoute[rewardToWantRoute.length - 1] == want,
            'StratStargateStableCompound: !route'
        );

        _giveAllowances();
    }

    /* ----- External Functions ----- */

    function rewardToWant() external view returns (address[] memory) {
        return rewardToWantRoute;
    }

    /* ----- Public Functions ----- */

    function harvest() public override whenNotPaused {
        require(msg.sender == tx.origin || msg.sender == address(vault), 'StratStargateStableCompound: EOA_or_vault');

        // NOTE: pool's local available balance
        if (IERC20(want).balanceOf(address(pool)) < balanceOfPool().mul(balanceSafeRate)) {
            _withdrawAll();
            pause();
            return;
        }

        uint256 beforeBal = balanceOfWant();

        staking.deposit(stakingPid, 0); // harvest STG token

        uint256 rewardBal = IERC20(reward).balanceOf(address(this));
        if (rewardBal > 0 && reward != want) {
            IJoeRouter(uniRouter).swapExactTokensForTokens(rewardBal, 0, rewardToWantRoute, address(this), now);
        }

        uint256 wantHarvested = balanceOfWant().sub(beforeBal);
        uint256 fee = chargePerformanceFee(wantHarvested);
        deposit();

        lastHarvest = block.timestamp;
        emit StratHarvest(msg.sender, wantHarvested.sub(fee), balanceOf());
    }

    function deposit() public override whenNotPaused nonReentrant {
        uint256 wantBal = balanceOfWant();
        if (wantBal > 0) {
            router.addLiquidity(pool.poolId(), wantBal, address(this));
            staking.deposit(stakingPid, IERC20(wantLPToken).balanceOf(address(this)));
            emit Deposit(balanceOf());
        }
    }

    function withdraw(uint256 amount) public override nonReentrant {
        require(msg.sender == vault, 'StratStargateStableCompound: !vault');
        require(amount > 0, 'StratStargateStableCompound: !amount');

        uint256 wantBal = balanceOfWant();

        if (wantBal < amount) {
            // local amount usd converted to LP token amount
            uint256 lptokenAmountToWithdraw = _amountLDtoLP(amount.sub(wantBal));

            // lp token unstaked from LPStaking, and then parked here in this strat
            staking.withdraw(stakingPid, lptokenAmountToWithdraw);

            // redeem all the want LP tokens out
            _redeemLocalWantLP();

            uint256 newWantBal = IERC20(want).balanceOf(address(this));
            require(newWantBal > wantBal, 'StratStargateStableCompound: !newWantBal');
            wantBal = newWantBal;
        }

        require(wantBal >= amount.mul(9999).div(10000), 'StratStargateStableCompound: !withdraw');
        uint256 withdrawAmt = amount < wantBal ? amount : wantBal;
        uint256 fee = chargeWithdrawalFee(withdrawAmt);
        if (withdrawAmt > fee) {
            TransferHelper.safeTransfer(want, vault, withdrawAmt.sub(fee));
        }

        emit Withdraw(balanceOf());
    }

    function _redeemLocalWantLP() internal {
        address thisAddr = address(this);
        uint256 lpAmount = IERC20(wantLPToken).balanceOf(thisAddr);

        if (instantRedeemOnly) {
            router.instantRedeemLocal(uint16(pool.poolId()), lpAmount, thisAddr);
            return;
        }

        uint256 capLpAmount = _amountSDtoLP(pool.deltaCredit());
        // check the redeemed amount with the capped local instant redeem amount
        if (lpAmount <= capLpAmount) {
            // NOTE: this means capable of local instant redemption
            router.instantRedeemLocal(uint16(pool.poolId()), lpAmount, thisAddr);
        } else {
            bytes memory to = abi.encodePacked(thisAddr);
            router.redeemLocal(
                dstChainId,
                srcPoolId,
                dstPoolId,
                payable(thisAddr),
                lpAmount,
                to,
                IStargateRouter.lzTxObj(0, 0, to)
            );
        }
    }

    function balanceOfPool() public view override returns (uint256) {
        (uint256 lpAmount, ) = staking.userInfo(stakingPid, address(this));
        return _amountLPtoLD(lpAmount); // lp token amount -> usd local decimal amount
    }

    function maxInstantRedeemLpAmount() public view returns (uint256) {
        return _amountSDtoLP(pool.deltaCredit());
    }

    function canInstantRedeemLocalNow() external view returns (bool) {
        (uint256 lpStakeAmount, ) = staking.userInfo(stakingPid, address(this));
        uint256 capLpAmount = maxInstantRedeemLpAmount();
        return lpStakeAmount <= capLpAmount;
    }

    /* ----- Internal Functions ----- */

    // NOTE: convert from LD (local decimal) to LP token.
    // Follows the logic here: https://bscscan.com/address/0x98a5737749490856b401db5dc27f522fc314a4e1#code
    function _amountLDtoLP(uint256 _amountLD) internal view returns (uint256 amountLP) {
        uint256 totalLiquidity = pool.totalLiquidity();
        uint256 totalSupply = pool.totalSupply();
        require(totalLiquidity > 0, 'Stargate: totalLiquidity_ZERO');
        uint256 amountSD = _amountLD.div(pool.convertRate());
        amountLP = amountSD.mul(totalSupply).div(totalLiquidity); // amountSD / (totalLiquidity / totalSupply)
    }

    function _amountLPtoLD(uint256 _amountLP) internal view returns (uint256 amountLD) {
        uint256 totalLiquidity = pool.totalLiquidity();
        uint256 totalSupply = pool.totalSupply();
        require(totalLiquidity > 0, 'Stargate: cant convert LPtoSD when totalSupply == 0');
        uint256 amountSD = _amountLP.mul(totalLiquidity).div(totalSupply);
        amountLD = amountSD.mul(pool.convertRate());
    }

    function _amountSDtoLP(uint256 _amountSD) internal view returns (uint256) {
        uint256 totalLiquidity = pool.totalLiquidity();
        uint256 totalSupply = pool.totalSupply();
        require(totalLiquidity > 0, 'Stargate: cant convert SDtoLP when totalLiq == 0');
        return _amountSD.mul(totalSupply).div(totalLiquidity);
    }

    function _amountLDtoSD(uint256 _amountLD) internal view returns (uint256 amountSD) {
        amountSD = _amountLD.div(pool.convertRate());
    }

    function _amountSDtoLD(uint256 _amountSD) internal view returns (uint256 amountLD) {
        amountLD = _amountSD.mul(pool.convertRate());
    }

    function _giveAllowances() internal override {
        TransferHelper.safeApprove(want, address(router), 0);
        TransferHelper.safeApprove(want, address(router), uint256(-1));
        TransferHelper.safeApprove(wantLPToken, address(staking), 0);
        TransferHelper.safeApprove(wantLPToken, address(staking), uint256(-1));
        TransferHelper.safeApprove(reward, uniRouter, 0);
        TransferHelper.safeApprove(reward, uniRouter, uint256(-1));
    }

    function _removeAllowances() internal override {
        TransferHelper.safeApprove(want, address(router), 0);
        TransferHelper.safeApprove(wantLPToken, address(staking), 0);
        TransferHelper.safeApprove(reward, uniRouter, 0);
    }

    function _withdrawAll() internal {
        (uint256 lpStakeAmount, ) = staking.userInfo(stakingPid, address(this));
        if (lpStakeAmount > 0) {
            // unstake the LP token from LPStaking to this strat
            staking.withdraw(stakingPid, lpStakeAmount);
            // redeem out all the LP tokens
            _redeemLocalWantLP();
        }
        emit Withdraw(balanceOf());
    }

    /* ----- Admin Functions ----- */

    function setRedeemParams(
        uint16 _dstChainId,
        uint256 _srcPoolId,
        uint256 _dstPoolId
    ) external onlyAdmin {
        dstChainId = _dstChainId;
        srcPoolId = _srcPoolId;
        dstPoolId = _dstPoolId;
    }

    function setInstantRedeemOnly(bool _instantRedeemOnly) external onlyAdmin {
        instantRedeemOnly = _instantRedeemOnly;
    }

    function setBalanceSafeRate(uint8 _balanceSafeRate) external onlyAdmin {
        balanceSafeRate = _balanceSafeRate;
    }

    function setRewardToWantRoute(address[] calldata _rewardToWantRoute) external onlyAdmin {
        rewardToWantRoute = _rewardToWantRoute;
    }

    function setUniRouter(address _uniRouter) external onlyAdmin {
        uniRouter = _uniRouter;
    }

    function retireStrat() external override {
        require(msg.sender == vault, 'StratStargateStableCompound: !vault');
        // call harvest explicitly if needed
        _withdrawAll();
        uint256 wantBal = IERC20(want).balanceOf(address(this));
        if (wantBal > 0) {
            TransferHelper.safeTransfer(want, vault, wantBal);
        }
    }

    function emergencyExit() external override onlyAdmin {
        _withdrawAll();
        uint256 wantBal = IERC20(want).balanceOf(address(this));
        if (wantBal > 0) {
            TransferHelper.safeTransfer(want, vault, wantBal);
        }
    }

    function emergencyExit1(
        uint16 _dstChainId,
        uint256 _srcPoolId,
        uint256 _dstPoolId,
        bytes calldata _to,
        IStargateRouter.lzTxObj memory _lzTxParams
    ) external onlyAdmin {
        (uint256 lpStakeAmount, ) = staking.userInfo(stakingPid, address(this));
        staking.withdraw(stakingPid, lpStakeAmount);
        uint256 wantLPAmount = IERC20(wantLPToken).balanceOf(address(this));
        router.redeemLocal(
            _dstChainId, // dstChainId
            _srcPoolId, // srcPoolId
            _dstPoolId, // dstPoolId
            payable(vault), // refund address
            wantLPAmount, // lp token amount
            _to,
            _lzTxParams
        );
    }

    function emergencyExit2(
        uint16 _dstChainId,
        uint256 _srcPoolId,
        uint256 _dstPoolId,
        bytes calldata _to,
        IStargateRouter.lzTxObj memory _lzTxParams
    ) external onlyAdmin {
        (uint256 lpStakeAmount, ) = staking.userInfo(stakingPid, address(this));
        staking.withdraw(stakingPid, lpStakeAmount);
        uint256 wantLPAmount = IERC20(wantLPToken).balanceOf(address(this));
        router.redeemLocal(
            _dstChainId, // dstChainId
            _srcPoolId, // srcPoolId
            _dstPoolId, // dstPoolId
            payable(address(this)), // refund address
            wantLPAmount, // lp amount
            _to,
            _lzTxParams
        );
        TransferHelper.safeTransfer(want, vault, IERC20(want).balanceOf(address(this)));
    }

    receive() external payable {}
}

File 86 of 106 : ILPStaking.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface ILPStaking {
    function deposit(uint256 pid, uint256 amount) external;

    function withdraw(uint256 pid, uint256 amount) external;

    function enterStaking(uint256 amount) external;

    function leaveStaking(uint256 amount) external;

    function emergencyWithdraw(uint256 pid) external;

    function pendingStargate(uint256 _pid, address _user) external view returns (uint256);

    function poolInfo(uint256 pid)
        external
        view
        returns (
            address,
            uint256,
            uint256,
            uint256
        );

    function userInfo(uint256 pid, address user) external view returns (uint256, uint256);
}

File 87 of 106 : IStargatePool.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;

interface IStargatePool {
    /**
     * @dev shared id between chains to represent same pool.
     */
    function poolId() external view returns (uint256);

    /**
     * @dev the shared decimals (lowest common decimals between chains);
     *   e.g. typically, decimal = 6
     */
    function sharedDecimals() external view returns (uint256);

    /**
     * @dev the decimals for the underlying asset token (e.g. busd, usdt, usdt.e, usdc, etc)
     */
    function localDecimals() external view returns (uint256);

    /**
     * @dev the token for the pool.
     */
    function token() external view returns (address);

    /**
     * @dev the router for the pool.
     */
    function router() external view returns (address);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev the total amount of tokens added on this side of the chain (fees + deposits - withdrawals)
     */
    function totalLiquidity() external view returns (uint256);

    /**
     * @dev convertRate = 10 ^ (localDecimals - sharedDecimals)
     */
    function convertRate() external view returns (uint256);

    /**
     * @dev total weight for pool percentages
     */
    function totalWeight() external view returns (uint256);

    /**
     * @dev credits accumulated from txn
     */
    function deltaCredit() external view returns (uint256);
}

File 88 of 106 : StrategyPlatypusVector.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import '../../../interfaces/VectorFinance/IPoolHelper.sol';
import '../../../interfaces/VectorFinance/IMainStaking.sol';
import '../../../interfaces/BankerJoe/IJoeRouter.sol';
import '../BaseStrategy.sol';

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

*
* MIT License
* ===========
*
* Copyright (c) 2020 WooTrade
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* 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 SOFTWARE.
*/
contract StrategyPlatypusVector is BaseStrategy {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    /* ----- State Variables ----- */

    // DepositPool list:
    // usdc.e pool helper: 0x257D69AA678e0A8DA6DFDA6A16CdF2052A460b45
    IPoolHelper public poolHelper;
    address public mainStaking = address(0x8B3d9F0017FA369cD8C164D0Cc078bf4cA588aE5);

    address[] public reward1ToWantRoute;
    address[] public reward2ToWantRoute;
    uint256 public lastHarvest;
    uint256 public slippage = 10; // 100 = 1%; 10 = 0.1%; 1 = 0.01%; default: 0.1%

    /* ----- Constant Variables ----- */

    address public constant wrappedEther = address(0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7); // WAVAX
    address public constant reward1 = address(0xe6E7e03b60c0F8DaAE5Db98B03831610A60FfE1B); // VTX
    address public constant reward2 = address(0x22d4002028f537599bE9f666d1c4Fa138522f9c8); // PTP
    address public constant uniRouter = address(0x60aE616a2155Ee3d9A68541Ba4544862310933d4); // JoeRouter02

    /* ----- Events ----- */

    event StratHarvest(address indexed harvester, uint256 wantHarvested, uint256 tvl);
    event Deposit(uint256 tvl);
    event Withdraw(uint256 tvl);

    constructor(
        address _vault,
        address _accessManager,
        address _poolHelper,
        address[] memory _reward1ToWantRoute,
        address[] memory _reward2ToWantRoute
    ) public BaseStrategy(_vault, _accessManager) {
        poolHelper = IPoolHelper(_poolHelper);
        reward1ToWantRoute = _reward1ToWantRoute;
        reward2ToWantRoute = _reward2ToWantRoute;

        require(IVault(_vault).want() == poolHelper.depositToken(), 'StrategyPlatypusVector: !poolHelper');
        require(
            reward1ToWantRoute.length > 0 && reward1ToWantRoute[reward1ToWantRoute.length - 1] == want,
            'StrategyPlatypusVector: !route'
        );
        require(
            reward2ToWantRoute.length > 0 && reward2ToWantRoute[reward2ToWantRoute.length - 1] == want,
            'StrategyPlatypusVector: !route'
        );

        _giveAllowances();
    }

    /* ----- External Functions ----- */

    function reward1ToWant() external view returns (address[] memory) {
        return reward1ToWantRoute;
    }

    function reward2ToWant() external view returns (address[] memory) {
        return reward2ToWantRoute;
    }

    /* ----- Public Functions ----- */

    function harvest() public override whenNotPaused {
        require(msg.sender == tx.origin || msg.sender == address(vault), 'StrategyPlatypusVector: EOA_or_vault');

        // NOTE: in case of upgrading, withdraw all the funds and pause the strategy.
        if (IMainStaking(mainStaking).nextImplementation() != address(0)) {
            _withdrawAll();
            pause();
            return;
        }

        uint256 beforeBal = balanceOfWant();

        poolHelper.getReward(); // Harvest VTX and PTP rewards

        _swapRewardToWant(reward1, reward1ToWantRoute);
        _swapRewardToWant(reward2, reward2ToWantRoute);

        uint256 wantHarvested = balanceOfWant().sub(beforeBal);
        uint256 fee = chargePerformanceFee(wantHarvested);
        deposit();

        lastHarvest = block.timestamp;
        emit StratHarvest(msg.sender, wantHarvested.sub(fee), balanceOf());
    }

    function _swapRewardToWant(address reward, address[] memory route) private {
        uint256 rewardBal = IERC20(reward).balanceOf(address(this));

        // rewardBal == 0: means the current token reward ended
        // reward == want: no need to swap
        if (rewardBal > 0 && reward != want) {
            require(route.length > 0, 'StrategyPlatypusVector: SWAP_ROUTE_INVALID');
            IJoeRouter(uniRouter).swapExactTokensForTokens(rewardBal, 0, route, address(this), now);
        }
    }

    function deposit() public override whenNotPaused nonReentrant {
        uint256 wantBal = balanceOfWant();
        if (wantBal > 0) {
            poolHelper.deposit(wantBal);
            emit Deposit(balanceOf());
        }
    }

    function withdraw(uint256 amount) public override nonReentrant {
        require(msg.sender == vault, 'StrategyPlatypusVector: !vault');
        require(amount > 0, 'StrategyPlatypusVector: !amount');

        uint256 wantBal = balanceOfWant();

        if (wantBal < amount) {
            uint256 amountToWithdraw = amount.sub(wantBal);
            // minAmount with slippage
            uint256 minAmount = amountToWithdraw.mul(uint256(10000).sub(slippage)).div(10000);
            poolHelper.withdraw(amountToWithdraw, minAmount);
            uint256 newWantBal = IERC20(want).balanceOf(address(this));
            require(newWantBal > wantBal, 'StrategyPlatypusVector: !newWantBal');
            wantBal = newWantBal;
        }

        uint256 withdrawAmt = amount < wantBal ? amount : wantBal;
        uint256 fee = chargeWithdrawalFee(withdrawAmt);
        if (withdrawAmt > fee) {
            TransferHelper.safeTransfer(want, vault, withdrawAmt.sub(fee));
        }

        emit Withdraw(balanceOf());
    }

    function balanceOfPool() public view override returns (uint256) {
        return poolHelper.depositTokenBalance();
    }

    /* ----- Internal Functions ----- */

    function _giveAllowances() internal override {
        TransferHelper.safeApprove(want, mainStaking, 0);
        TransferHelper.safeApprove(want, mainStaking, uint256(-1));
        TransferHelper.safeApprove(reward1, uniRouter, 0);
        TransferHelper.safeApprove(reward1, uniRouter, uint256(-1));
        TransferHelper.safeApprove(reward2, uniRouter, 0);
        TransferHelper.safeApprove(reward2, uniRouter, uint256(-1));
        TransferHelper.safeApprove(wrappedEther, uniRouter, 0);
        TransferHelper.safeApprove(wrappedEther, uniRouter, uint256(-1));
    }

    function _removeAllowances() internal override {
        TransferHelper.safeApprove(want, mainStaking, 0);
        TransferHelper.safeApprove(reward1, uniRouter, 0);
        TransferHelper.safeApprove(reward2, uniRouter, 0);
        TransferHelper.safeApprove(wrappedEther, uniRouter, 0);
    }

    function _withdrawAll() internal {
        uint256 stakingBal = balanceOfPool();
        if (stakingBal > 0) {
            // minAmount with slippage
            uint256 minAmount = stakingBal.mul(uint256(10000).sub(slippage)).div(10000);
            poolHelper.withdraw(stakingBal, minAmount);
        }
    }

    /* ----- Admin Functions ----- */

    function setPoolHelper(address newPoolHelper) external onlyAdmin {
        require(newPoolHelper != address(0), 'StrategyPlatypusVector: !newPoolHelper');
        poolHelper = IPoolHelper(newPoolHelper);
    }

    function setSlippage(uint256 newSlippage) external onlyAdmin {
        slippage = newSlippage;
    }

    function retireStrat() external override {
        require(msg.sender == vault, 'StrategyPlatypusVector: !vault');
        // call harvest explicitly if needed
        _withdrawAll();
        uint256 wantBal = IERC20(want).balanceOf(address(this));
        if (wantBal > 0) {
            TransferHelper.safeTransfer(want, vault, wantBal);
        }
    }

    function emergencyExit() external override onlyAdmin {
        _withdrawAll();
        uint256 wantBal = IERC20(want).balanceOf(address(this));
        if (wantBal > 0) {
            TransferHelper.safeTransfer(want, vault, wantBal);
        }
    }

    receive() external payable {}
}

File 89 of 106 : IPoolHelper.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface IPoolHelper {
    function balance(address _address) external view returns (uint256);

    function depositToken() external view returns (address);

    function depositTokenBalance() external view returns (uint256);

    function rewardPerToken(address token) external view returns (uint256);

    function update() external;

    function deposit(uint256 amount) external;

    function stake(uint256 _amount) external;

    function withdraw(uint256 amount, uint256 minAmount) external;

    /// @notice Harvest VTX and PTP rewards for msg.sender
    function getReward() external;

    function mainStaking() external view returns (address);
}

File 90 of 106 : IMainStaking.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface IMainStaking {
    function setXPTP(address _xPTP) external;

    function addFee(
        uint256 max,
        uint256 min,
        uint256 value,
        address to,
        bool isPTP,
        bool isAddress
    ) external;

    function setFee(uint256 index, uint256 value) external;

    function setCallerFee(uint256 value) external;

    function deposit(
        address token,
        uint256 amount,
        address sender
    ) external;

    function harvest(address token, bool isUser) external;

    function withdraw(
        address token,
        uint256 _amount,
        uint256 _slippage,
        address sender
    ) external;

    function stakePTP(uint256 amount) external;

    function stakeAllPtp() external;

    function claimVePTP() external;

    function getStakedPtp() external;

    function getVePtp() external;

    function unstakePTP() external;

    function pendingPtpForPool(address _token) external view returns (uint256 pendingPtp);

    function masterPlatypus() external view returns (address);

    function getLPTokensForShares(uint256 amount, address token) external view returns (uint256);

    function getSharesForDepositTokens(uint256 amount, address token) external view returns (uint256);

    function getDepositTokensForShares(uint256 amount, address token) external view returns (uint256);

    function registerPool(
        uint256 _pid,
        address _token,
        address _lpAddress,
        string memory receiptName,
        string memory receiptSymbol,
        uint256 allocpoints
    ) external;

    function getPoolInfo(address _address)
        external
        view
        returns (
            uint256 pid,
            bool isActive,
            address token,
            address lp,
            uint256 sizeLp,
            address receipt,
            uint256 size,
            address rewards_addr,
            address helper
        );

    function removePool(address token) external;

    function nextImplementation() external view returns (address);

    function timelockLength() external view returns (uint256);

    function timelockEndForUpgrade() external view returns (uint256);

    function timelockEndForTimelock() external view returns (uint256);
}

File 91 of 106 : Wooracle_BSC.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

import './libraries/InitializableOwnable.sol';
import './interfaces/IWooracle.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';

/// @title Wooracle implementation in BSC
/// @notice Will be maintained and updated periodically by Woo.network in multichains.
contract Wooracle is InitializableOwnable, IWooracle {
    /* ----- State variables ----- */

    // 128 + 64 + 64 = 256 bits (slot size)
    struct TokenInfo {
        uint128 price; // 18 - base_decimal + quote_decimal
        uint64 coeff; // 18. coeff <= 1e18    (2^64 = 1.84e19)
        uint64 spread; // 18. spread <= 2e18   (2^64 = 1.84e19)
    }

    mapping(address => TokenInfo) public infos;

    address public override quoteToken;
    uint256 public override timestamp;

    uint256 public staleDuration;

    constructor() public {
        initOwner(msg.sender);
        staleDuration = uint256(300);
    }

    /* ----- External Functions ----- */

    /// @dev Set the quote token address.
    /// @param newQuoteToken token address
    function setQuoteToken(address newQuoteToken) external onlyOwner {
        quoteToken = newQuoteToken;
    }

    /// @dev Set the staleDuration.
    /// @param newStaleDuration the new stale duration
    function setStaleDuration(uint256 newStaleDuration) external onlyOwner {
        staleDuration = newStaleDuration;
    }

    /// @dev Update the base token prices.
    /// @param base the baseToken address
    /// @param newPrice the new prices for the base token
    function postPrice(address base, uint128 newPrice) external onlyOwner {
        infos[base].price = newPrice;
        timestamp = block.timestamp;
    }

    /// @dev batch update baseTokens prices
    /// @param bases list of baseToken address
    /// @param newPrices the updated prices list
    function postPriceList(address[] calldata bases, uint128[] calldata newPrices) external onlyOwner {
        uint256 length = bases.length;
        require(length == newPrices.length, 'Wooracle: length_INVALID');

        for (uint256 i = 0; i < length; i++) {
            infos[bases[i]].price = newPrices[i];
        }

        timestamp = block.timestamp;
    }

    /// @dev update the spreads info.
    /// @param base baseToken address
    /// @param newSpread the new spreads
    function postSpread(address base, uint64 newSpread) external onlyOwner {
        infos[base].spread = newSpread;
        timestamp = block.timestamp;
    }

    /// @dev batch update the spreads info.
    /// @param bases list of baseToken address
    /// @param newSpreads list of spreads info
    function postSpreadList(address[] calldata bases, uint64[] calldata newSpreads) external onlyOwner {
        uint256 length = bases.length;
        require(length == newSpreads.length, 'Wooracle: length_INVALID');

        for (uint256 i = 0; i < length; i++) {
            infos[bases[i]].spread = newSpreads[i];
        }

        timestamp = block.timestamp;
    }

    /// @dev update the state of the given base token.
    /// @param base baseToken address
    /// @param newPrice the new prices
    /// @param newSpread the new spreads
    /// @param newCoeff the new slippage coefficent
    function postState(
        address base,
        uint128 newPrice,
        uint64 newSpread,
        uint64 newCoeff
    ) external onlyOwner {
        _setState(base, newPrice, newSpread, newCoeff);
        timestamp = block.timestamp;
    }

    /// @dev batch update the prices, spreads and slipagge coeffs info.
    /// @param bases list of baseToken address
    /// @param newPrices the prices list
    /// @param newSpreads the spreads list
    /// @param newCoeffs the slippage coefficent list
    function postStateList(
        address[] calldata bases,
        uint128[] calldata newPrices,
        uint64[] calldata newSpreads,
        uint64[] calldata newCoeffs
    ) external onlyOwner {
        uint256 length = bases.length;
        require(
            length == newPrices.length && length == newSpreads.length && length == newCoeffs.length,
            'Wooracle: length_INVALID'
        );

        for (uint256 i = 0; i < length; i++) {
            _setState(bases[i], newPrices[i], newSpreads[i], newCoeffs[i]);
        }
        timestamp = block.timestamp;
    }

    /// @inheritdoc IWooracle
    function price(address base) external view override returns (uint256 priceNow, bool feasible) {
        priceNow = uint256(infos[base].price);
        feasible = priceNow != 0 && block.timestamp <= (timestamp + staleDuration * 1 seconds);
    }

    function getPrice(address base) external view override returns (uint256) {
        return uint256(infos[base].price);
    }

    function getSpread(address base) external view override returns (uint256) {
        return uint256(infos[base].spread);
    }

    function getCoeff(address base) external view override returns (uint256) {
        return uint256(infos[base].coeff);
    }

    /// @inheritdoc IWooracle
    function state(address base)
        external
        view
        override
        returns (
            uint256 priceNow,
            uint256 spreadNow,
            uint256 coeffNow,
            bool feasible
        )
    {
        TokenInfo storage info = infos[base];
        priceNow = uint256(info.price);
        spreadNow = uint256(info.spread);
        coeffNow = uint256(info.coeff);
        feasible = priceNow != 0 && block.timestamp <= (timestamp + staleDuration * 1 seconds);
    }

    function isFeasible(address base) public view override returns (bool) {
        return infos[base].price != 0 && block.timestamp <= (timestamp + staleDuration * 1 seconds);
    }

    /* ----- Private Functions ----- */

    function _setState(
        address base,
        uint128 newPrice,
        uint64 newSpread,
        uint64 newCoeff
    ) private {
        TokenInfo storage info = infos[base];
        info.price = newPrice;
        info.spread = newSpread;
        info.coeff = newCoeff;
    }
}

File 92 of 106 : Wooracle_avax_ftm.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

import './libraries/InitializableOwnable.sol';
import './interfaces/IWooracle.sol';

/// @title Wooracle implementation in Fantom and Avalanche chain.
/// @notice Will be maintained and updated periodically by Woo.network in multichains.
contract Wooracle is InitializableOwnable, IWooracle {
    /* ----- State variables ----- */

    struct TokenInfo {
        uint256 price; // 18 - base_decimal + quote_decimal
        uint256 coeff; // 36 - quote
        uint256 spread; // 18
    }

    mapping(address => TokenInfo) public infos;

    address public override quoteToken;
    uint256 public override timestamp;

    uint256 public staleDuration;

    constructor() public {
        initOwner(msg.sender);
        staleDuration = uint256(300);
    }

    /* ----- External Functions ----- */

    /// @dev Set the quote token address.
    /// @param newQuoteToken token address
    function setQuoteToken(address newQuoteToken) external onlyOwner {
        quoteToken = newQuoteToken;
    }

    /// @dev Set the staleDuration.
    /// @param newStaleDuration the new stale duration
    function setStaleDuration(uint256 newStaleDuration) external onlyOwner {
        staleDuration = newStaleDuration;
    }

    /// @dev Update the base token prices.
    /// @param base the baseToken address
    /// @param newPrice the new prices for the base token
    function postPrice(address base, uint256 newPrice) external onlyOwner {
        infos[base].price = newPrice;
        timestamp = block.timestamp;
    }

    /// @dev batch update baseTokens prices
    /// @param bases list of baseToken address
    /// @param newPrices the updated prices list
    function postPriceList(address[] calldata bases, uint256[] calldata newPrices) external onlyOwner {
        uint256 length = bases.length;
        require(length == newPrices.length, 'Wooracle: length_INVALID');

        for (uint256 i = 0; i < length; i++) {
            infos[bases[i]].price = newPrices[i];
        }

        timestamp = block.timestamp;
    }

    /// @dev update the spreads info.
    /// @param base baseToken address
    /// @param newSpread the new spreads
    function postSpread(address base, uint256 newSpread) external onlyOwner {
        infos[base].spread = newSpread;
        timestamp = block.timestamp;
    }

    /// @dev batch update the spreads info.
    /// @param bases list of baseToken address
    /// @param newSpreads list of spreads info
    function postSpreadList(address[] calldata bases, uint256[] calldata newSpreads) external onlyOwner {
        uint256 length = bases.length;
        require(length == newSpreads.length, 'Wooracle: length_INVALID');

        for (uint256 i = 0; i < length; i++) {
            infos[bases[i]].spread = newSpreads[i];
        }

        timestamp = block.timestamp;
    }

    /// @dev update the state of the given base token.
    /// @param base baseToken address
    /// @param newPrice the new prices
    /// @param newSpread the new spreads
    /// @param newCoeff the new slippage coefficent
    function postState(
        address base,
        uint256 newPrice,
        uint256 newSpread,
        uint256 newCoeff
    ) external onlyOwner {
        _setState(base, newPrice, newSpread, newCoeff);
        timestamp = block.timestamp;
    }

    /// @dev batch update the prices, spreads and slipagge coeffs info.
    /// @param bases list of baseToken address
    /// @param newPrices the prices list
    /// @param newSpreads the spreads list
    /// @param newCoeffs the slippage coefficent list
    function postStateList(
        address[] calldata bases,
        uint256[] calldata newPrices,
        uint256[] calldata newSpreads,
        uint256[] calldata newCoeffs
    ) external onlyOwner {
        uint256 length = bases.length;
        require(
            length == newPrices.length && length == newSpreads.length && length == newCoeffs.length,
            'Wooracle: length_INVALID'
        );

        for (uint256 i = 0; i < length; i++) {
            _setState(bases[i], newPrices[i], newSpreads[i], newCoeffs[i]);
        }
        timestamp = block.timestamp;
    }

    /// @inheritdoc IWooracle
    function price(address base) external view override returns (uint256 priceNow, bool feasible) {
        priceNow = infos[base].price;
        feasible = priceNow != 0 && block.timestamp <= (timestamp + staleDuration * 1 seconds);
    }

    function getPrice(address base) external view override returns (uint256) {
        return infos[base].price;
    }

    function getSpread(address base) external view override returns (uint256) {
        return infos[base].spread;
    }

    function getCoeff(address base) external view override returns (uint256) {
        return infos[base].coeff;
    }

    /// @inheritdoc IWooracle
    function state(address base)
        external
        view
        override
        returns (
            uint256 priceNow,
            uint256 spreadNow,
            uint256 coeffNow,
            bool feasible
        )
    {
        TokenInfo storage info = infos[base];
        priceNow = info.price;
        spreadNow = info.spread;
        coeffNow = info.coeff;
        feasible = priceNow != 0 && block.timestamp <= (timestamp + staleDuration * 1 seconds);
    }

    function isFeasible(address base) public view override returns (bool) {
        return infos[base].price != 0 && block.timestamp <= (timestamp + staleDuration * 1 seconds);
    }

    /* ----- Private Functions ----- */

    function _setState(
        address base,
        uint256 newPrice,
        uint256 newSpread,
        uint256 newCoeff
    ) private {
        TokenInfo storage info = infos[base];
        info.price = newPrice;
        info.spread = newSpread;
        info.coeff = newCoeff;
    }
}

File 93 of 106 : BaseMaxiVault.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';
import '@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol';
import '@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol';

import '../../interfaces/IWooAccessManager.sol';

import '../../libraries/PausableUpgradeable.sol';

abstract contract BaseMaxiVault is PausableUpgradeable, ReentrancyGuardUpgradeable {
    using SafeMathUpgradeable for uint256;

    /* ----- State Variables ----- */

    address public depositToken;
    address public rewardToken;

    address public wooAccessManager;

    uint256 public performanceFee;
    uint256 public withdrawalFee;
    address public performanceTreasury;
    address public withdrawalTreasury;

    /* ----- Constant Variables ----- */

    uint256 public constant FEE_DENOMINATOR = 10000;

    /* ----- Event ----- */

    event PerformanceFeeUpdated(uint256 fee);
    event WithdrawalFeeUpdated(uint256 fee);

    /* ----- Initializer ----- */

    function __BaseMaxiVault_init(address _wooAccessManager) internal initializer {
        require(_wooAccessManager != address(0), 'BaseMaxiVault: _wooAccessManager_ZERO_ADDRESS');

        __PausableUpgradeable_init();
        __ReentrancyGuard_init();

        wooAccessManager = _wooAccessManager;

        performanceFee = 300; // 1 in 10000th -> 100: 1%, 300: 3%
        withdrawalFee = 0; // 1 in 10000th -> 1: 0.01%, 10: 0.1%
        performanceTreasury = 0x4094D7A17a387795838c7aba4687387B0d32BCf3;
        withdrawalTreasury = 0x4094D7A17a387795838c7aba4687387B0d32BCf3;
    }

    /* ----- Modifier ----- */

    modifier onlyAdmin() {
        require(
            owner() == msg.sender || IWooAccessManager(wooAccessManager).isVaultAdmin(msg.sender),
            'WOOFiMaximizerVault: NOT_ADMIN'
        );
        _;
    }

    /* ----- Internal Functions ----- */

    function _chargePerformanceFee(address _token, uint256 _amount) internal returns (uint256) {
        uint256 fee = _amount.mul(performanceFee).div(FEE_DENOMINATOR);
        if (fee > 0) TransferHelper.safeTransfer(_token, performanceTreasury, fee);
        return fee;
    }

    function _chargeWithdrawalFee(address _token, uint256 _amount) internal returns (uint256) {
        uint256 fee = _amount.mul(performanceFee).div(FEE_DENOMINATOR);
        if (fee > 0) TransferHelper.safeTransfer(_token, withdrawalTreasury, fee);
        return fee;
    }

    /* ----- Abstract Method ----- */

    function emergencyExit() external virtual;

    function depositAll() external virtual;

    function withdrawAll() external virtual;

    function deposit(uint256 _amount) public virtual;

    function withdraw(uint256 _amount) public virtual;

    function harvest() public virtual;

    function depositToPool() public virtual; // admin function

    function withdrawFromPool() public virtual; // admin function

    function _giveAllowances() internal virtual;

    function _removeAllowances() internal virtual;

    /* ----- Admin Functions ----- */

    function setPerformanceFee(uint256 _fee) external onlyAdmin {
        require(_fee <= FEE_DENOMINATOR, 'BaseMaxiVault: _fee_EXCEEDS_FEE_DENOMINATOR');
        performanceFee = _fee;
        emit PerformanceFeeUpdated(_fee);
    }

    function setWithdrawalFee(uint256 _fee) external onlyAdmin {
        require(_fee <= 500, 'BaseMaxiVault: fee_EXCEEDS_5%'); // less than 5%
        withdrawalFee = _fee;
        emit WithdrawalFeeUpdated(_fee);
    }

    function setPerformanceTreasury(address _treasury) external onlyAdmin {
        require(_treasury != address(0), 'BaseMaxiVault: _treasury_ZERO_ADDRESS');
        performanceTreasury = _treasury;
    }

    function setWithdrawalTreasury(address _treasury) external onlyAdmin {
        require(_treasury != address(0), 'BaseMaxiVault: _treasury_ZERO_ADDRESS');
        withdrawalTreasury = _treasury;
    }

    function setWooAccessManager(address _wooAccessManager) external onlyAdmin {
        require(_wooAccessManager != address(0), 'BaseMaxiVault: _wooAccessManager_ZERO_ADDRESS');
        wooAccessManager = _wooAccessManager;
    }

    function pause() external onlyAdmin {
        setPaused(true);
        _removeAllowances();
    }

    function unpause() external onlyAdmin {
        setPaused(false);
        _giveAllowances();
        depositToPool();
    }

    function inCaseTokensGetStuck(address _stuckToken) external onlyAdmin {
        require(_stuckToken != address(0), 'BaseMaxiVault: _stuckToken_ZERO_ADDRESS');
        require(_stuckToken != depositToken, 'BaseMaxiVault: _stuckToken_MUST_NOT_depositToken');
        require(_stuckToken != rewardToken, 'BaseMaxiVault: _stuckToken_MUST_NOT_rewardToken');

        uint256 bal = IERC20(_stuckToken).balanceOf(address(this));
        if (bal > 0) TransferHelper.safeTransfer(_stuckToken, msg.sender, bal);
    }

    function inCaseNativeTokensGetStuck() external onlyAdmin {
        // NOTE: vault never needs native tokens to do the yield farming;
        // This native token balance indicates a user's incorrect transfer.
        if (address(this).balance > 0) TransferHelper.safeTransferETH(msg.sender, address(this).balance);
    }
}

File 94 of 106 : ReentrancyGuardUpgradeable.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;
import "../proxy/Initializable.sol";

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuardUpgradeable is Initializable {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    function __ReentrancyGuard_init() internal initializer {
        __ReentrancyGuard_init_unchained();
    }

    function __ReentrancyGuard_init_unchained() internal initializer {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and make it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        // On the first call to nonReentrant, _notEntered will be true
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;

        _;

        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
    uint256[49] private __gap;
}

File 95 of 106 : SafeMathUpgradeable.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

/**
 * @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 SafeMathUpgradeable {
    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        uint256 c = a + b;
        if (c < a) return (false, 0);
        return (true, c);
    }

    /**
     * @dev Returns the substraction of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        if (b > a) return (false, 0);
        return (true, a - b);
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, 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 (true, 0);
        uint256 c = a * b;
        if (c / a != b) return (false, 0);
        return (true, c);
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        if (b == 0) return (false, 0);
        return (true, a / b);
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        if (b == 0) return (false, 0);
        return (true, a % b);
    }

    /**
     * @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) {
        require(b <= a, "SafeMath: subtraction overflow");
        return a - b;
    }

    /**
     * @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) {
        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, reverting 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) {
        require(b > 0, "SafeMath: division by zero");
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting 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) {
        require(b > 0, "SafeMath: modulo by zero");
        return a % b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {trySub}.
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        return a - b;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryDiv}.
     *
     * 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, string memory errorMessage) internal pure returns (uint256) {
        require(b > 0, errorMessage);
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting with custom message when dividing by zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryMod}.
     *
     * 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, string memory errorMessage) internal pure returns (uint256) {
        require(b > 0, errorMessage);
        return a % b;
    }
}

File 96 of 106 : PausableUpgradeable.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol';

abstract contract PausableUpgradeable is OwnableUpgradeable {
    uint256 public lastPauseTime;
    bool public paused;

    event PauseChanged(bool isPaused);

    modifier notPaused() {
        require(!paused, 'PausableUpgradeable: cannot be performed while the contract is paused');
        _;
    }

    function __PausableUpgradeable_init() internal initializer {
        __Ownable_init();
        require(owner() != address(0), 'PausableUpgradeable: owner must be set');
    }

    function setPaused(bool _paused) public onlyOwner {
        if (_paused == paused) {
            return;
        }

        paused = _paused;
        if (paused) {
            lastPauseTime = now;
        }

        emit PauseChanged(paused);
    }

    uint256[50] private __gap;
}

File 97 of 106 : Initializable.sol
// SPDX-License-Identifier: MIT

// solhint-disable-next-line compiler-version
pragma solidity >=0.4.24 <0.8.0;

import "../utils/AddressUpgradeable.sol";

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {UpgradeableProxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 */
abstract contract Initializable {

    /**
     * @dev Indicates that the contract has been initialized.
     */
    bool private _initialized;

    /**
     * @dev Indicates that the contract is in the process of being initialized.
     */
    bool private _initializing;

    /**
     * @dev Modifier to protect an initializer function from being invoked twice.
     */
    modifier initializer() {
        require(_initializing || _isConstructor() || !_initialized, "Initializable: contract is already initialized");

        bool isTopLevelCall = !_initializing;
        if (isTopLevelCall) {
            _initializing = true;
            _initialized = true;
        }

        _;

        if (isTopLevelCall) {
            _initializing = false;
        }
    }

    /// @dev Returns true if and only if the function is running in the constructor
    function _isConstructor() private view returns (bool) {
        return !AddressUpgradeable.isContract(address(this));
    }
}

File 98 of 106 : AddressUpgradeable.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.2 <0.8.0;

/**
 * @dev Collection of functions related to the address type
 */
library AddressUpgradeable {
    /**
     * @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) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.

        uint256 size;
        // solhint-disable-next-line no-inline-assembly
        assembly { size := extcodesize(account) }
        return size > 0;
    }

    /**
     * @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].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
        (bool success, ) = recipient.call{ value: amount }("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain`call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
      return functionCall(target, data, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        require(isContract(target), "Address: call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.call{ value: value }(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
        require(isContract(target), "Address: static call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.staticcall(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
        if (success) {
            return returndata;
        } else {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly

                // solhint-disable-next-line no-inline-assembly
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

File 99 of 106 : OwnableUpgradeable.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

import "../utils/ContextUpgradeable.sol";
import "../proxy/Initializable.sol";
/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    function __Ownable_init() internal initializer {
        __Context_init_unchained();
        __Ownable_init_unchained();
    }

    function __Ownable_init_unchained() internal initializer {
        address msgSender = _msgSender();
        _owner = msgSender;
        emit OwnershipTransferred(address(0), msgSender);
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
    uint256[49] private __gap;
}

File 100 of 106 : ContextUpgradeable.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;
import "../proxy/Initializable.sol";

/*
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with GSN meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract ContextUpgradeable is Initializable {
    function __Context_init() internal initializer {
        __Context_init_unchained();
    }

    function __Context_init_unchained() internal initializer {
    }
    function _msgSender() internal view virtual returns (address payable) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes memory) {
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
        return msg.data;
    }
    uint256[50] private __gap;
}

File 101 of 106 : MaxiVault.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

/*

░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝

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

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';
import '@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol';
import '@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol';

import '../BaseMaxiVault.sol';

import '../../../interfaces/Trisolaris/IMasterChef.sol';
import '../../../interfaces/Trisolaris/ITriBar.sol';

contract WOOFiMaxiVaultTrisolaris is ERC20Upgradeable, BaseMaxiVault {
    using SafeMathUpgradeable for uint256;

    // Info of each user.
    struct UserInfo {
        uint256 amount; // How many deposit tokens the user has provided.
        uint256 rewardDebt; // Reward debt. See explanation below.
        //
        // We do some fancy math here. Basically, any point in time, the amount of reward tokens
        // entitled to a user but is pending to be distributed is:
        //
        //   pending reward = (user.amount * pool.accRewardPerShare) - user.rewardDebt
        //
        // Whenever a user deposits or withdraws deposit tokens to a pool. Here's what happens:
        //   1. The pool's `accRewardPerShare` 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 ----- */

    mapping(address => UserInfo) public userInfo;

    /* ----- State Variables ----- */

    uint256 public pid;
    uint256 public accRewardPerShare;

    // Contract Address
    address public masterChef; // LP Farm
    address public rewardBar; // TriBar

    /* ----- Event ----- */

    event Deposit(address indexed user, uint256 amount);
    event Withdraw(address indexed user, uint256 amount);
    event ClaimReward(address indexed user, uint256 amount);
    event Harvest(address indexed user, uint256 amount);
    event EmergencyWithdraw(address indexed user, uint256 depositAmount, uint256 rewardAmount);

    /* ----- Initializer ----- */

    function initialize(
        uint256 _pid,
        address _masterChef,
        address _rewardBar,
        address _depositToken,
        address _rewardToken,
        address _wooAccessManager
    ) external initializer {
        require(_masterChef != address(0), 'WOOFiMaximizerVault: _masterChef_ZERO_ADDRESS');
        require(_rewardBar != address(0), 'WOOFiMaximizerVault: _rewardBar_ZERO_ADDRESS');
        require(_depositToken != address(0), 'WOOFiMaximizerVault: _depositToken_ZERO_ADDRESS');
        require(_rewardToken != address(0), 'WOOFiMaximizerVault: _rewardToken_ZERO_ADDRESS');
        require(_wooAccessManager != address(0), 'WOOFiMaximizerVault: _wooAccessManager_ZERO_ADDRESS');

        __BaseMaxiVault_init(_wooAccessManager);
        __ERC20_init(
            string(abi.encodePacked('WOOFi Earn Maxi ', ERC20Upgradeable(_depositToken).name())),
            string(abi.encodePacked('wem', ERC20Upgradeable(_depositToken).symbol()))
        );

        pid = _pid;
        masterChef = _masterChef;
        rewardBar = _rewardBar;
        depositToken = _depositToken; // LP Token
        rewardToken = _rewardToken; // TRI

        _giveAllowances();
    }

    /* ----- Modifier ----- */

    modifier fairUpdate() {
        harvest(); // only way to update accRewardPerShare
        _;
    }

    /* ----- External Functions ----- */

    function pendingReward(address _user) external view returns (uint256) {
        uint256 pendingInMasterChef = IMasterChef(masterChef).pendingTri(pid, address(this));
        uint256 calAccRewardPerShare = balance() > 0
            ? accRewardPerShare.add(pendingInMasterChef.mul(1e12).div(balance()))
            : accRewardPerShare;
        UserInfo memory user = userInfo[_user];
        return user.amount.mul(calAccRewardPerShare).div(1e12).sub(user.rewardDebt);
    }

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

    function withdrawAll() external override {
        withdraw(IERC20(address(this)).balanceOf(msg.sender));
    }

    /* ----- Public Functions ----- */

    function deposit(uint256 _amount) public override nonReentrant notPaused fairUpdate {
        require(_amount > 0, 'WOOFiMaxiVault: _amount_ZERO');

        UserInfo storage user = userInfo[msg.sender];
        uint256 pendingRewards = user.amount.mul(accRewardPerShare).div(1e12).sub(user.rewardDebt);
        TransferHelper.safeTransferFrom(depositToken, msg.sender, address(this), _amount);
        IMasterChef(masterChef).deposit(pid, _amount);
        user.amount = user.amount.add(_amount);
        user.rewardDebt = user.amount.mul(accRewardPerShare).div(1e12).sub(pendingRewards);
        _mint(msg.sender, _amount);
        emit Deposit(msg.sender, _amount);
    }

    function withdraw(uint256 _amount) public override nonReentrant notPaused fairUpdate {
        UserInfo storage user = userInfo[msg.sender];
        require(user.amount >= _amount, 'WOOFiMaxiVault: _amount_EXCEEDS_USER_BALANCE');

        IMasterChef(masterChef).withdraw(pid, _amount);
        _claimReward(); // update user.rewardDebt
        user.amount = user.amount.sub(_amount);
        uint256 fee = _chargeWithdrawalFee(depositToken, _amount);

        _burn(msg.sender, _amount);
        TransferHelper.safeTransfer(depositToken, msg.sender, _amount.sub(fee));
        emit Withdraw(msg.sender, _amount.sub(fee));
    }

    function claimReward() public nonReentrant notPaused fairUpdate {
        _claimReward();
    }

    function harvest() public override notPaused {
        uint256 rewardBalBefore = IERC20(rewardToken).balanceOf(address(this));
        IMasterChef(masterChef).harvest(pid);
        uint256 rewardBalAfter = IERC20(rewardToken).balanceOf(address(this));

        if (rewardBalAfter == rewardBalBefore) return;

        uint256 rewardsHarvested = rewardBalAfter.sub(rewardBalBefore);
        uint256 fee = _chargePerformanceFee(rewardToken, rewardsHarvested);

        accRewardPerShare = balanceOfReward().mul(1e12).div(balance());
        ITriBar(rewardBar).enter(IERC20(rewardToken).balanceOf(address(this)));

        emit Harvest(msg.sender, rewardsHarvested.sub(fee));
    }

    function balance() public view returns (uint256) {
        (uint256 amount, ) = IMasterChef(masterChef).userInfo(pid, address(this));
        return amount;
    }

    function balanceOfReward() public view returns (uint256) {
        uint256 shares = IERC20(rewardBar).balanceOf(address(this));
        uint256 barSharePrice = getBarPricePerFullShare();

        return IERC20(rewardToken).balanceOf(address(this)).add(shares.mul(barSharePrice).div(1e18));
    }

    function getBarPricePerFullShare() public view returns (uint256) {
        uint256 rewardBarBal = IERC20(rewardToken).balanceOf(rewardBar); // TRI balance in TriBar
        uint256 rewardBarShares = IERC20(rewardBar).totalSupply(); // xTri total supply

        return rewardBarShares == 0 ? 1e18 : rewardBarBal.mul(1e18).div(rewardBarShares);
    }

    /* ----- Internal Functions ----- */

    function _giveAllowances() internal override {
        TransferHelper.safeApprove(depositToken, masterChef, uint256(-1));
        TransferHelper.safeApprove(rewardToken, rewardBar, uint256(-1));
    }

    function _removeAllowances() internal override {
        TransferHelper.safeApprove(depositToken, masterChef, 0);
        TransferHelper.safeApprove(rewardToken, rewardBar, 0);
    }

    /* ----- Private Functions ----- */

    function _claimReward() private {
        UserInfo storage user = userInfo[msg.sender];
        uint256 pendingRewards = user.amount.mul(accRewardPerShare).div(1e12).sub(user.rewardDebt);
        uint256 rewardBal = IERC20(rewardToken).balanceOf(address(this));
        if (rewardBal < pendingRewards) {
            uint256 leaveAmt = pendingRewards.sub(rewardBal);
            uint256 leaveShares = leaveAmt.mul(1e18).div(getBarPricePerFullShare());
            ITriBar(rewardBar).leave(leaveShares);

            uint256 rewardBalAfter = IERC20(rewardToken).balanceOf(address(this));
            require(rewardBalAfter > rewardBal, 'WOOFiMaxiVault: rewardBar_LEAVE_ERROR');
            rewardBal = rewardBalAfter;
        }

        uint256 claimAmt = pendingRewards < rewardBal ? pendingRewards : rewardBal;
        if (claimAmt > 0) {
            TransferHelper.safeTransfer(rewardToken, msg.sender, claimAmt);
        }

        user.rewardDebt = user.amount.mul(accRewardPerShare).div(1e12);
        emit ClaimReward(msg.sender, claimAmt);
    }

    /* ----- Admin Functions ----- */

    function emergencyExit() external override onlyAdmin {
        IMasterChef(masterChef).emergencyWithdraw(pid);
        ITriBar(rewardBar).leave(IERC20(rewardBar).balanceOf(address(this)));

        emit EmergencyWithdraw(
            msg.sender,
            IERC20(depositToken).balanceOf(address(this)),
            IERC20(rewardToken).balanceOf(address(this))
        );
    }

    function depositToPool() public override onlyAdmin {
        if (balance() > 0) harvest();
        uint256 depositBal = IERC20(depositToken).balanceOf(address(this));
        if (depositBal > 0) IMasterChef(masterChef).deposit(pid, depositBal);
    }

    function withdrawFromPool() public override onlyAdmin {
        if (balance() > 0) {
            harvest();
            IMasterChef(masterChef).withdraw(pid, balance());
        }
    }
}

File 102 of 106 : ERC20Upgradeable.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

import "../../utils/ContextUpgradeable.sol";
import "./IERC20Upgradeable.sol";
import "../../math/SafeMathUpgradeable.sol";
import "../../proxy/Initializable.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * We have followed general OpenZeppelin guidelines: functions revert instead
 * of returning `false` on failure. This behavior is nonetheless conventional
 * and does not conflict with the expectations of ERC20 applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20Upgradeable {
    using SafeMathUpgradeable for uint256;

    mapping (address => uint256) private _balances;

    mapping (address => mapping (address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;
    uint8 private _decimals;

    /**
     * @dev Sets the values for {name} and {symbol}, initializes {decimals} with
     * a default value of 18.
     *
     * To select a different value for {decimals}, use {_setupDecimals}.
     *
     * All three of these values are immutable: they can only be set once during
     * construction.
     */
    function __ERC20_init(string memory name_, string memory symbol_) internal initializer {
        __Context_init_unchained();
        __ERC20_init_unchained(name_, symbol_);
    }

    function __ERC20_init_unchained(string memory name_, string memory symbol_) internal initializer {
        _name = name_;
        _symbol = symbol_;
        _decimals = 18;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5,05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
     * called.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual returns (uint8) {
        return _decimals;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `recipient` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * Requirements:
     *
     * - `sender` and `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     * - the caller must have allowance for ``sender``'s tokens of at least
     * `amount`.
     */
    function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
        return true;
    }

    /**
     * @dev Moves tokens `amount` from `sender` to `recipient`.
     *
     * This is internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `sender` cannot be the zero address.
     * - `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     */
    function _transfer(address sender, address recipient, uint256 amount) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(sender, recipient, amount);

        _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
        _balances[recipient] = _balances[recipient].add(amount);
        emit Transfer(sender, recipient, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply = _totalSupply.add(amount);
        _balances[account] = _balances[account].add(amount);
        emit Transfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
        _totalSupply = _totalSupply.sub(amount);
        emit Transfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @dev Sets {decimals} to a value other than the default one of 18.
     *
     * WARNING: This function should only be called from the constructor. Most
     * applications that interact with token contracts will not expect
     * {decimals} to ever change, and may work incorrectly if it does.
     */
    function _setupDecimals(uint8 decimals_) internal virtual {
        _decimals = decimals_;
    }

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be to transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
    uint256[44] private __gap;
}

File 103 of 106 : IMasterChef.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface IMasterChef {
    function deposit(uint256 pid, uint256 amount) external;

    function withdraw(uint256 pid, uint256 amount) external;

    function harvest(uint256 pid) external returns (address);

    function emergencyWithdraw(uint256 pid) external;

    function pendingTri(uint256 pid, address user) external view returns (uint256);

    function poolInfo(uint256 pid)
        external
        view
        returns (
            address,
            uint256,
            uint256,
            uint256
        );

    function userInfo(uint256 pid, address user) external view returns (uint256, uint256);
}

File 104 of 106 : ITriBar.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface ITriBar {
    function enter(uint256 triAmount) external;

    function leave(uint256 xTriAmount) external;
}

File 105 of 106 : IERC20Upgradeable.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20Upgradeable {
    /**
     * @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);
}

File 106 of 106 : DecimalMathTest.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.6.12;

import '../libraries/DecimalMath.sol';

contract DecimalMathTest {
    function mulFloor(uint256 target, uint256 d) external pure returns (uint256) {
        return DecimalMath.mulFloor(target, d);
    }

    function mulCeil(uint256 target, uint256 d) external pure returns (uint256) {
        return DecimalMath.mulCeil(target, d);
    }

    function divFloor(uint256 target, uint256 d) external pure returns (uint256) {
        return DecimalMath.divFloor(target, d);
    }

    function divCeil(uint256 target, uint256 d) external pure returns (uint256) {
        return DecimalMath.divCeil(target, d);
    }

    function reciprocalFloor(uint256 target) external pure returns (uint256) {
        return DecimalMath.reciprocalFloor(target);
    }

    function reciprocalCeil(uint256 target) external pure returns (uint256) {
        return DecimalMath.reciprocalCeil(target);
    }
}

Settings
{
  "evmVersion": "istanbul",
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {}
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"address","name":"_weth","type":"address"},{"internalType":"address","name":"_pool","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newPool","type":"address"}],"name":"WooPoolChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"enum IWooRouterV2.SwapType","name":"swapType","type":"uint8"},{"indexed":true,"internalType":"address","name":"fromToken","type":"address"},{"indexed":true,"internalType":"address","name":"toToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"fromAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"toAmount","type":"uint256"},{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"address","name":"rebateTo","type":"address"}],"name":"WooRouterSwap","type":"event"},{"inputs":[],"name":"WETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"approveTarget","type":"address"},{"internalType":"address","name":"swapTarget","type":"address"},{"internalType":"address","name":"fromToken","type":"address"},{"internalType":"address","name":"toToken","type":"address"},{"internalType":"uint256","name":"fromAmount","type":"uint256"},{"internalType":"uint256","name":"minToAmount","type":"uint256"},{"internalType":"address payable","name":"to","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"externalSwap","outputs":[{"internalType":"uint256","name":"realToAmount","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"isWhitelisted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"baseToken","type":"address"},{"internalType":"uint256","name":"baseAmount","type":"uint256"}],"name":"querySellBase","outputs":[{"internalType":"uint256","name":"quoteAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"baseToken","type":"address"},{"internalType":"uint256","name":"quoteAmount","type":"uint256"}],"name":"querySellQuote","outputs":[{"internalType":"uint256","name":"baseAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"fromToken","type":"address"},{"internalType":"address","name":"toToken","type":"address"},{"internalType":"uint256","name":"fromAmount","type":"uint256"}],"name":"querySwap","outputs":[{"internalType":"uint256","name":"toAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"quoteToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"rescueFunds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rescueNativeFunds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"baseToken","type":"address"},{"internalType":"uint256","name":"baseAmount","type":"uint256"},{"internalType":"uint256","name":"minQuoteAmount","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"address","name":"rebateTo","type":"address"}],"name":"sellBase","outputs":[{"internalType":"uint256","name":"realQuoteAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"baseToken","type":"address"},{"internalType":"uint256","name":"quoteAmount","type":"uint256"},{"internalType":"uint256","name":"minBaseAmount","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"address","name":"rebateTo","type":"address"}],"name":"sellQuote","outputs":[{"internalType":"uint256","name":"realBaseAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newPool","type":"address"}],"name":"setPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bool","name":"whitelisted","type":"bool"}],"name":"setWhitelisted","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"fromToken","type":"address"},{"internalType":"address","name":"toToken","type":"address"},{"internalType":"uint256","name":"fromAmount","type":"uint256"},{"internalType":"uint256","name":"minToAmount","type":"uint256"},{"internalType":"address payable","name":"to","type":"address"},{"internalType":"address","name":"rebateTo","type":"address"}],"name":"swap","outputs":[{"internalType":"uint256","name":"realToAmount","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"wooPool","outputs":[{"internalType":"contract IWooPP","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]

60a06040523480156200001157600080fd5b5060405162002d3b38038062002d3b8339810160408190526200003491620002d8565b600062000040620000e5565b600080546001600160a01b0319166001600160a01b0383169081178255604051929350917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908290a350600180556001600160a01b038216620000c05760405162461bcd60e51b8152600401620000b790620003cd565b60405180910390fd5b6001600160601b0319606083901b16608052620000dd81620000e9565b505062000454565b3390565b600260015414156200010f5760405162461bcd60e51b8152600401620000b79062000396565b60026001556200011e620000e5565b6001600160a01b03166200013162000299565b6001600160a01b0316146200015a5760405162461bcd60e51b8152600401620000b79062000361565b6001600160a01b038116620001835760405162461bcd60e51b8152600401620000b7906200032a565b600280546001600160a01b0319166001600160a01b03838116919091179182905560408051630217a4b760e41b81529051929091169163217a4b7091600480820192602092909190829003018186803b158015620001e057600080fd5b505afa158015620001f5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200021b9190620002a8565b600480546001600160a01b0319166001600160a01b03928316179081905516620002595760405162461bcd60e51b8152600401620000b79062000404565b7f4577a21bd8e55848c574b7582f8e6cc6a7cf1c1958f36a9751eab6329d656b1e816040516200028a919062000316565b60405180910390a15060018055565b6000546001600160a01b031690565b600060208284031215620002ba578081fd5b81516001600160a01b0381168114620002d1578182fd5b9392505050565b60008060408385031215620002eb578081fd5b8251620002f8816200043b565b60208401519092506200030b816200043b565b809150509250929050565b6001600160a01b0391909116815260200190565b6020808252601c908201527f576f6f526f757465723a206e6577506f6f6c5f414444525f5a45524f00000000604082015260600190565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b6020808252601f908201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604082015260600190565b60208082526019908201527f576f6f526f757465723a20776574685f5a45524f5f4144445200000000000000604082015260600190565b6020808252601f908201527f576f6f526f757465723a2071756f7465546f6b656e5f414444525f5a45524f00604082015260600190565b6001600160a01b03811681146200045157600080fd5b50565b60805160601c61289c6200049f6000398061011d52806108505280610bf15280610d145280610d435280610d8d528061112852806111c452806112125280611d0c525061289c6000f3fe60806040526004361061010d5760003560e01c806379a0487611610095578063a739460311610064578063a7394603146102ef578063ad5c464814610304578063e94803f414610319578063f2fde38b14610339578063f3287c2f1461035957610160565b806379a04876146102875780637dc20382146102a75780638da5cb5b146102ba5780639281aa0b146102cf57610160565b80634437152a116100dc5780634437152a146101f257806366410a21146102125780636846fb5014610232578063715018a61461025257806378e3214f1461026757610160565b8063063c27f814610165578063199b83fa1461017a578063217a4b70146101a35780633af32abf146101c557610160565b3661016057336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016148061015857503360009081526003602052604090205460ff165b61015e57fe5b005b600080fd5b34801561017157600080fd5b5061015e610379565b61018d610188366004611e77565b6103f9565b60405161019a9190612807565b60405180910390f35b3480156101af57600080fd5b506101b861063e565b60405161019a9190612142565b3480156101d157600080fd5b506101e56101e0366004611e3f565b61064d565b60405161019a91906121c5565b3480156101fe57600080fd5b5061015e61020d366004611e3f565b610662565b34801561021e57600080fd5b5061018d61022d36600461203c565b6107fc565b34801561023e57600080fd5b5061018d61024d366004612067565b6108fa565b34801561025e57600080fd5b5061015e610a74565b34801561027357600080fd5b5061015e61028236600461203c565b610afd565b34801561029357600080fd5b5061018d6102a236600461203c565b610b9d565b61018d6102b5366004611f95565b610c44565b3480156102c657600080fd5b506101b861104c565b3480156102db57600080fd5b5061015e6102ea366004612004565b61105b565b3480156102fb57600080fd5b506101b8611117565b34801561031057600080fd5b506101b8611126565b34801561032557600080fd5b5061018d610334366004611f55565b61114a565b34801561034557600080fd5b5061015e610354366004611e3f565b61142b565b34801561036557600080fd5b5061018d610374366004612067565b6114eb565b600260015414156103a55760405162461bcd60e51b815260040161039c90612799565b60405180910390fd5b60026001556103b2611661565b6001600160a01b03166103c361104c565b6001600160a01b0316146103e95760405162461bcd60e51b815260040161039c906125f0565b6103f33347611665565b60018055565b60006002600154141561041e5760405162461bcd60e51b815260040161039c90612799565b60026001556001600160a01b038a166104495760405162461bcd60e51b815260040161039c90612509565b6001600160a01b03891661046f5760405162461bcd60e51b815260040161039c90612242565b6001600160a01b0388166104955760405162461bcd60e51b815260040161039c90612762565b6001600160a01b0387166104bb5760405162461bcd60e51b815260040161039c9061244b565b6001600160a01b0384166104e15760405162461bcd60e51b815260040161039c9061254b565b6001600160a01b038a1660009081526003602052604090205460ff166105195760405162461bcd60e51b815260040161039c90612482565b6001600160a01b03891660009081526003602052604090205460ff166105515760405162461bcd60e51b815260040161039c906123d2565b600061055d88306116f7565b905061056d8b8b8b8a88886117ae565b600061057989306116f7565b90508082111561059b5760405162461bcd60e51b815260040161039c90612670565b6105a5818361192d565b92508683101580156105b75750600083115b6105d35760405162461bcd60e51b815260040161039c906124c7565b6105de898785611955565b856001600160a01b0316896001600160a01b03168b6001600160a01b031660008051602061284783398151915260018c883360006040516106239594939291906121d0565b60405180910390a45050600180559998505050505050505050565b6004546001600160a01b031681565b60036020526000908152604090205460ff1681565b600260015414156106855760405162461bcd60e51b815260040161039c90612799565b6002600155610692611661565b6001600160a01b03166106a361104c565b6001600160a01b0316146106c95760405162461bcd60e51b815260040161039c906125f0565b6001600160a01b0381166106ef5760405162461bcd60e51b815260040161039c906122bf565b600280546001600160a01b0319166001600160a01b03838116919091179182905560408051630217a4b760e41b81529051929091169163217a4b7091600480820192602092909190829003018186803b15801561074b57600080fd5b505afa15801561075f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107839190611e5b565b600480546001600160a01b0319166001600160a01b039283161790819055166107be5760405162461bcd60e51b815260040161039c906127d0565b7f4577a21bd8e55848c574b7582f8e6cc6a7cf1c1958f36a9751eab6329d656b1e816040516107ed9190612142565b60405180910390a15060018055565b60006001600160a01b0383166108245760405162461bcd60e51b815260040161039c90612582565b6001600160a01b03831673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee1461084e5782610870565b7f00000000000000000000000000000000000000000000000000000000000000005b6002546040516366410a2160e01b81529194506001600160a01b0316906366410a21906108a3908690869060040161217a565b60206040518083038186803b1580156108bb57600080fd5b505afa1580156108cf573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108f391906120e1565b9392505050565b60006002600154141561091f5760405162461bcd60e51b815260040161039c90612799565b60026001556001600160a01b03861661094a5760405162461bcd60e51b815260040161039c90612582565b6001600160a01b0383166109705760405162461bcd60e51b815260040161039c9061254b565b61097c8633308861199a565b6002546109949087906001600160a01b031687611a8b565b6002546040516306846fb560e41b81526001600160a01b0390911690636846fb50906109cc9089908990899089908990600401612193565b602060405180830381600087803b1580156109e657600080fd5b505af11580156109fa573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a1e91906120e1565b6004546040519192506001600160a01b0380861692918116919089169060008051602061284783398151915290610a5f906000908b90889033908b906121d0565b60405180910390a46001805595945050505050565b610a7c611661565b6001600160a01b0316610a8d61104c565b6001600160a01b031614610ab35760405162461bcd60e51b815260040161039c906125f0565b600080546040516001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600080546001600160a01b0319169055565b60026001541415610b205760405162461bcd60e51b815260040161039c90612799565b6002600155610b2d611661565b6001600160a01b0316610b3e61104c565b6001600160a01b031614610b645760405162461bcd60e51b815260040161039c906125f0565b6001600160a01b038216610b8a5760405162461bcd60e51b815260040161039c9061220b565b610b95823383611b79565b505060018055565b60006001600160a01b038316610bc55760405162461bcd60e51b815260040161039c90612582565b6001600160a01b03831673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee14610bef5782610c11565b7f00000000000000000000000000000000000000000000000000000000000000005b600254604051633cd0243b60e11b81529194506001600160a01b0316906379a04876906108a3908690869060040161217a565b600060026001541415610c695760405162461bcd60e51b815260040161039c90612799565b60026001556001600160a01b038716610c945760405162461bcd60e51b815260040161039c90612762565b6001600160a01b038616610cba5760405162461bcd60e51b815260040161039c9061244b565b6001600160a01b038316610ce05760405162461bcd60e51b815260040161039c9061254b565b6001600160a01b0387811673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee9081149188161481610d125788610d34565b7f00000000000000000000000000000000000000000000000000000000000000005b985080610d415787610d63565b7f00000000000000000000000000000000000000000000000000000000000000005b97508115610e045734871115610d8b5760405162461bcd60e51b815260040161039c906125b9565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b158015610de657600080fd5b505af1158015610dfa573d6000803e3d6000fd5b5050505050610e10565b610e108933308a61199a565b600254610e28908a906001600160a01b031689611a8b565b6004546001600160a01b038a811691161415610e5357610e4c818989898989611c60565b9250610fb0565b6004546001600160a01b0389811691161415610ef3576002546040516306846fb560e41b81526001600160a01b0390911690636846fb5090610ea1908c908b908b908b908b90600401612193565b602060405180830381600087803b158015610ebb57600080fd5b505af1158015610ecf573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e4c91906120e1565b6002546040516306846fb560e41b81526000916001600160a01b031690636846fb5090610f2c908d908c90869030908c90600401612193565b602060405180830381600087803b158015610f4657600080fd5b505af1158015610f5a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f7e91906120e1565b600454600254919250610f9e916001600160a01b03918216911683611a8b565b610fac828a838a8a8a611c60565b9350505b846001600160a01b031681610fc55788610fdb565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee5b6001600160a01b031683610fef578a611005565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee5b6001600160a01b031660008051602061284783398151915260008b88338b6040516110349594939291906121d0565b60405180910390a45050600180559695505050505050565b6000546001600160a01b031690565b6002600154141561107e5760405162461bcd60e51b815260040161039c90612799565b600260015561108b611661565b6001600160a01b031661109c61104c565b6001600160a01b0316146110c25760405162461bcd60e51b815260040161039c906125f0565b6001600160a01b0382166110e85760405162461bcd60e51b815260040161039c9061272b565b6001600160a01b03919091166000908152600360205260409020805460ff191691151591909117905560018055565b6002546001600160a01b031681565b7f000000000000000000000000000000000000000000000000000000000000000081565b60006001600160a01b0384166111725760405162461bcd60e51b815260040161039c90612762565b6001600160a01b0383166111985760405162461bcd60e51b815260040161039c9061244b565b6001600160a01b03841673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee146111c257836111e4565b7f00000000000000000000000000000000000000000000000000000000000000005b93506001600160a01b03831673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee146112105782611232565b7f00000000000000000000000000000000000000000000000000000000000000005b6004549093506001600160a01b03858116911614156112d4576002546040516366410a2160e01b81526001600160a01b03909116906366410a219061127d908690869060040161217a565b60206040518083038186803b15801561129557600080fd5b505afa1580156112a9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112cd91906120e1565b90506108f3565b6004546001600160a01b038481169116141561131c57600254604051633cd0243b60e11b81526001600160a01b03909116906379a048769061127d908790869060040161217a565b600254604051633cd0243b60e11b81526000916001600160a01b0316906379a048769061134f908890879060040161217a565b60206040518083038186803b15801561136757600080fd5b505afa15801561137b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061139f91906120e1565b6002546040516366410a2160e01b81529192506001600160a01b0316906366410a21906113d2908790859060040161217a565b60206040518083038186803b1580156113ea57600080fd5b505afa1580156113fe573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061142291906120e1565b95945050505050565b611433611661565b6001600160a01b031661144461104c565b6001600160a01b03161461146a5760405162461bcd60e51b815260040161039c906125f0565b6001600160a01b0381166114905760405162461bcd60e51b815260040161039c90612279565b600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b6000600260015414156115105760405162461bcd60e51b815260040161039c90612799565b60026001556001600160a01b03861661153b5760405162461bcd60e51b815260040161039c90612582565b6001600160a01b0383166115615760405162461bcd60e51b815260040161039c9061254b565b600454611579906001600160a01b031633308861199a565b600454600254611596916001600160a01b03908116911687611a8b565b60025460405163f3287c2f60e01b81526001600160a01b039091169063f3287c2f906115ce9089908990899089908990600401612193565b602060405180830381600087803b1580156115e857600080fd5b505af11580156115fc573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061162091906120e1565b6004546040519192506001600160a01b0380861692898216929091169060008051602061284783398151915290610a5f906000908b90889033908b906121d0565b3390565b604080516000808252602082019092526001600160a01b03841690839060405161168f9190612109565b60006040518083038185875af1925050503d80600081146116cc576040519150601f19603f3d011682016040523d82523d6000602084013e6116d1565b606091505b50509050806116f25760405162461bcd60e51b815260040161039c9061237e565b505050565b60006001600160a01b03831673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee1461179e576040516370a0823160e01b81526001600160a01b038416906370a0823190611749908590600401612142565b60206040518083038186803b15801561176157600080fd5b505afa158015611775573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061179991906120e1565b6108f3565b506001600160a01b031631919050565b6001600160a01b03861660009081526003602052604090205460ff166117e65760405162461bcd60e51b815260040161039c90612482565b6001600160a01b03851660009081526003602052604090205460ff1661181e5760405162461bcd60e51b815260040161039c906123d2565b6001600160a01b03841673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee1461185e5761184e8433308661199a565b611859848785611a8b565b61187e565b3483111561187e5760405162461bcd60e51b815260040161039c906125b9565b60006001600160a01b0380871690861673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee146118af5760006118b1565b845b84846040516118c19291906120f9565b60006040518083038185875af1925050503d80600081146118fe576040519150601f19603f3d011682016040523d82523d6000602084013e611903565b606091505b50509050806119245760405162461bcd60e51b815260040161039c906122f6565b50505050505050565b60008282111561194f5760405162461bcd60e51b815260040161039c90612414565b50900390565b80156116f2576001600160a01b03831673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee141561198f5761198a8282611665565b6116f2565b6116f2838383611b79565b60006060856001600160a01b03166323b872dd8686866040516024016119c293929190612156565b6040516020818303038152906040529060e01b6020820180516001600160e01b0383818316178352505050506040516119fb9190612109565b6000604051808303816000865af19150503d8060008114611a38576040519150601f19603f3d011682016040523d82523d6000602084013e611a3d565b606091505b5091509150818015611a67575080511580611a67575080806020019051810190611a6791906120c5565b611a835760405162461bcd60e51b815260040161039c9061232d565b505050505050565b60006060846001600160a01b031663095ea7b38585604051602401611ab192919061217a565b6040516020818303038152906040529060e01b6020820180516001600160e01b038381831617835250505050604051611aea9190612109565b6000604051808303816000865af19150503d8060008114611b27576040519150601f19603f3d011682016040523d82523d6000602084013e611b2c565b606091505b5091509150818015611b56575080511580611b56575080806020019051810190611b5691906120c5565b611b725760405162461bcd60e51b815260040161039c90612625565b5050505050565b60006060846001600160a01b031663a9059cbb8585604051602401611b9f92919061217a565b6040516020818303038152906040529060e01b6020820180516001600160e01b038381831617835250505050604051611bd89190612109565b6000604051808303816000865af19150503d8060008114611c15576040519150601f19603f3d011682016040523d82523d6000602084013e611c1a565b606091505b5091509150818015611c44575080511580611c44575080806020019051810190611c4491906120c5565b611b725760405162461bcd60e51b815260040161039c906126de565b60008615611da85760025460405163f3287c2f60e01b81526001600160a01b039091169063f3287c2f90611ca09089908990899030908990600401612193565b602060405180830381600087803b158015611cba57600080fd5b505af1158015611cce573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611cf291906120e1565b604051632e1a7d4d60e01b81529091506001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690632e1a7d4d90611d41908490600401612807565b600060405180830381600087803b158015611d5b57600080fd5b505af1158015611d6f573d6000803e3d6000fd5b505050506001600160a01b038316611d995760405162461bcd60e51b815260040161039c906126a7565b611da38382611665565b611e35565b60025460405163f3287c2f60e01b81526001600160a01b039091169063f3287c2f90611de09089908990899089908990600401612193565b602060405180830381600087803b158015611dfa57600080fd5b505af1158015611e0e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e3291906120e1565b90505b9695505050505050565b600060208284031215611e50578081fd5b81356108f381612820565b600060208284031215611e6c578081fd5b81516108f381612820565b60008060008060008060008060006101008a8c031215611e95578485fd5b8935611ea081612820565b985060208a0135611eb081612820565b975060408a0135611ec081612820565b965060608a0135611ed081612820565b955060808a0135945060a08a0135935060c08a0135611eee81612820565b925060e08a013567ffffffffffffffff80821115611f0a578384fd5b818c0191508c601f830112611f1d578384fd5b813581811115611f2b578485fd5b8d6020828501011115611f3c578485fd5b6020830194508093505050509295985092959850929598565b600080600060608486031215611f69578283fd5b8335611f7481612820565b92506020840135611f8481612820565b929592945050506040919091013590565b60008060008060008060c08789031215611fad578182fd5b8635611fb881612820565b95506020870135611fc881612820565b945060408701359350606087013592506080870135611fe681612820565b915060a0870135611ff681612820565b809150509295509295509295565b60008060408385031215612016578182fd5b823561202181612820565b9150602083013561203181612838565b809150509250929050565b6000806040838503121561204e578182fd5b823561205981612820565b946020939093013593505050565b600080600080600060a0868803121561207e578081fd5b853561208981612820565b9450602086013593506040860135925060608601356120a781612820565b915060808601356120b781612820565b809150509295509295909350565b6000602082840312156120d6578081fd5b81516108f381612838565b6000602082840312156120f2578081fd5b5051919050565b6000828483379101908152919050565b60008251815b81811015612129576020818601810151858301520161210f565b818111156121375782828501525b509190910192915050565b6001600160a01b0391909116815260200190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b03929092168252602082015260400190565b6001600160a01b0395861681526020810194909452604084019290925283166060830152909116608082015260a00190565b901515815260200190565b60a081016121dd87612810565b8252602082019590955260408101939093526001600160a01b03918216606084015216608090910152919050565b6020808252601a908201527f576f6f526f757465723a20746f6b656e5f414444525f5a45524f000000000000604082015260600190565b6020808252601f908201527f576f6f526f757465723a20737761705461726765745f414444525f5a45524f00604082015260600190565b60208082526026908201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160408201526564647265737360d01b606082015260800190565b6020808252601c908201527f576f6f526f757465723a206e6577506f6f6c5f414444525f5a45524f00000000604082015260600190565b6020808252601f908201527f576f6f526f757465723a2046414c4c4241434b5f535741505f4641494c454400604082015260600190565b60208082526031908201527f5472616e7366657248656c7065723a3a7472616e7366657246726f6d3a207472604082015270185b9cd9995c919c9bdb4819985a5b1959607a1b606082015260800190565b60208082526034908201527f5472616e7366657248656c7065723a3a736166655472616e736665724554483a60408201527308115512081d1c985b9cd9995c8819985a5b195960621b606082015260800190565b60208082526022908201527f576f6f526f757465723a20535741505f5441524745545f4e4f545f414c4c4f57604082015261115160f21b606082015260800190565b6020808252601e908201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604082015260600190565b6020808252601c908201527f576f6f526f757465723a20746f546f6b656e5f414444525f5a45524f00000000604082015260600190565b60208082526025908201527f576f6f526f757465723a20415050524f56455f5441524745545f4e4f545f414c6040820152641313d5d15160da1b606082015260800190565b60208082526022908201527f576f6f526f757465723a207265616c546f416d6f756e745f4e4f545f454e4f5560408201526108e960f31b606082015260800190565b60208082526022908201527f576f6f526f757465723a20617070726f76655461726765745f414444525f5a45604082015261524f60f01b606082015260800190565b60208082526017908201527f576f6f526f757465723a20746f5f414444525f5a45524f000000000000000000604082015260600190565b6020808252601e908201527f576f6f526f757465723a2062617365546f6b656e5f414444525f5a45524f0000604082015260600190565b6020808252601d908201527f576f6f526f757465723a2066726f6d416d6f756e745f494e56414c4944000000604082015260600190565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b6020808252602b908201527f5472616e7366657248656c7065723a3a73616665417070726f76653a2061707060408201526a1c9bdd994819985a5b195960aa1b606082015260800190565b60208082526018908201527f576f6f526f757465723a2062616c616e63655f4552524f520000000000000000604082015260600190565b60208082526017908201527f576f6f526f757465723a20746f5f5a45524f5f41444452000000000000000000604082015260600190565b6020808252602d908201527f5472616e7366657248656c7065723a3a736166655472616e736665723a20747260408201526c185b9cd9995c8819985a5b1959609a1b606082015260800190565b6020808252601b908201527f576f6f526f757465723a207461726765745f414444525f5a45524f0000000000604082015260600190565b6020808252601e908201527f576f6f526f757465723a2066726f6d546f6b656e5f414444525f5a45524f0000604082015260600190565b6020808252601f908201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604082015260600190565b6020808252601f908201527f576f6f526f757465723a2071756f7465546f6b656e5f414444525f5a45524f00604082015260600190565b90815260200190565b806002811061281b57fe5b919050565b6001600160a01b038116811461283557600080fd5b50565b801515811461283557600080fdfe27c98e911efdd224f4002f6cd831c3ad0d2759ee176f9ee8466d95826af22a1ca2646970667358221220514cd453f88c937c474a7a701bfae34bc992314b63695eeb330cf6d40df28c5064736f6c634300060c00330000000000000000000000000d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000000000000000000007400b665c8f4f3a951a99f1ee9872efb8778723d

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

0000000000000000000000000d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000000000000000000007400b665c8f4f3a951a99f1ee9872efb8778723d

-----Decoded View---------------
Arg [0] : _weth (address): 0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270
Arg [1] : _pool (address): 0x7400b665c8f4f3a951a99f1ee9872efb8778723d

-----Encoded View---------------
2 Constructor Arguments found :
Arg [0] : 0000000000000000000000000d500b1d8e8ef31e21c99d1db9a6444d3adf1270
Arg [1] : 0000000000000000000000007400b665c8f4f3a951a99f1ee9872efb8778723d


Block Transaction Gas Used Reward
Age Block Fee Address BC Fee Address Voting Power Jailed Incoming
Block Uncle Number Difficulty Gas Used Reward
Loading
Loading
Make sure to use the "Vote Down" button for any spammy posts, and the "Vote Up" for interesting conversations.