FRAX Price: $0.99 (+2.26%)

Contract

0xC4ff88102DDe74F9bdEDB292F1B7F58bf15bEd46

Overview

FRAX Balance | FXTL Balance

0 FRAX | 688 FXTL

FRAX Value

$0.00

Token Holdings

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Block
From
To
Harvest230795942025-07-20 0:04:59188 days ago1752969899IN
0xC4ff8810...bf15bEd46
0 FRAX0.000046630.00100025
Harvest228212652025-07-14 0:34:01194 days ago1752453241IN
0xC4ff8810...bf15bEd46
0 FRAX0.000046980.00100025
Harvest224802192025-07-06 3:05:49202 days ago1751771149IN
0xC4ff8810...bf15bEd46
0 FRAX0.000022260.00100025
Harvest165581102025-02-19 1:02:11339 days ago1739926931IN
0xC4ff8810...bf15bEd46
0 FRAX0.000000670.00100025
Harvest163880022025-02-15 2:31:55343 days ago1739586715IN
0xC4ff8810...bf15bEd46
0 FRAX0.000000670.00100025
Harvest160837442025-02-08 1:29:59350 days ago1738978199IN
0xC4ff8810...bf15bEd46
0 FRAX0.000000680.00100025
Harvest160419052025-02-07 2:15:21351 days ago1738894521IN
0xC4ff8810...bf15bEd46
0 FRAX0.000000690.00100025
Harvest154314272025-01-23 23:06:05365 days ago1737673565IN
0xC4ff8810...bf15bEd46
0 FRAX0.00000120.00100025
Harvest140041422024-12-21 22:09:55398 days ago1734818995IN
0xC4ff8810...bf15bEd46
0 FRAX0.000001030.00100025
Harvest137843942024-12-16 20:04:59403 days ago1734379499IN
0xC4ff8810...bf15bEd46
0 FRAX0.000002480.00100025
Harvest134472682024-12-09 0:47:27411 days ago1733705247IN
0xC4ff8810...bf15bEd46
0 FRAX0.000002340.00100025
Harvest132608242024-12-04 17:12:39416 days ago1733332359IN
0xC4ff8810...bf15bEd46
0 FRAX0.000003690.00100025
Harvest118973362024-11-03 3:43:03447 days ago1730605383IN
0xC4ff8810...bf15bEd46
0 FRAX0.000000890.00100025
Harvest118562882024-11-02 4:54:47448 days ago1730523287IN
0xC4ff8810...bf15bEd46
0 FRAX0.000000880.00100025
Harvest117252562024-10-30 4:07:03451 days ago1730261223IN
0xC4ff8810...bf15bEd46
0 FRAX0.000001490.00100025
Harvest115975192024-10-27 5:09:09454 days ago1730005749IN
0xC4ff8810...bf15bEd46
0 FRAX0.000000890.00100025
Harvest114152642024-10-22 23:53:59458 days ago1729641239IN
0xC4ff8810...bf15bEd46
0 FRAX0.000001050.00100025
Harvest112449682024-10-19 1:17:27462 days ago1729300647IN
0xC4ff8810...bf15bEd46
0 FRAX0.000001470.00100025
Harvest111679092024-10-17 6:28:49464 days ago1729146529IN
0xC4ff8810...bf15bEd46
0 FRAX0.000001450.00100025
Harvest109043372024-10-11 4:03:05470 days ago1728619385IN
0xC4ff8810...bf15bEd46
0 FRAX0.000002090.00100025
Harvest106875492024-10-06 3:36:49475 days ago1728185809IN
0xC4ff8810...bf15bEd46
0 FRAX0.000000890.00100025
Harvest105191272024-10-02 6:02:45479 days ago1727848965IN
0xC4ff8810...bf15bEd46
0 FRAX0.000001080.00100025
Harvest104268052024-09-30 2:45:21481 days ago1727664321IN
0xC4ff8810...bf15bEd46
0 FRAX0.000001550.00100025
Harvest102036972024-09-24 22:48:25486 days ago1727218105IN
0xC4ff8810...bf15bEd46
0 FRAX0.000001780.00100025
Harvest99963522024-09-20 3:36:55491 days ago1726803415IN
0xC4ff8810...bf15bEd46
0 FRAX0.000001040.00100025
View all transactions

View more zero value Internal Transactions in Advanced View mode

Advanced mode:

Cross-Chain Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
RaStrategy

Compiler Version
v0.6.12+commit.27d51765

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "../../../main/strategies/base/BaseStrategy.sol";
import "../../../main/interfaces/IERCFund.sol";
import "./interfaces/IRaGauge.sol";
import "./interfaces/IRaRouter.sol";

contract RaStrategy is BaseStrategy {

    uint256 public constant FEE_DENOMINATOR = 10000;

    //Tokens
    address public tokenA;
    address public tokenB;
    address private constant FXS = 0xFc00000000000000000000000000000000000002;
    address private constant ROUTER = 0xAAA45c8F5ef92a000a121d102F4e89278a711Faa; //this is different than the "Swap router"

    address[] public reward_a_path;
    address[] public a_b_path;

    constructor(
        address _rewards,
        address _want,
        address _tokenA,
        address _tokenB,
        address _betweenToken //token between reward token and A in swap if necessary
    )
        public
        BaseStrategy(
            _want,
            0x8b36504F2c52f59a57EE6c84d6f1b82e7698EcF0, //Fraxtal ERCFund
            FXS,
            ROUTER,
            _rewards
        )
    {
        tokenA = _tokenA;
        tokenB = _tokenB;

        if(_betweenToken == address(0)) {
            reward_a_path = [FXS, _tokenA];
            a_b_path = [_tokenA, _tokenB];
        }
        else {
            reward_a_path = [FXS, _betweenToken, _tokenA];
            a_b_path = [_tokenA, _tokenB];
        }

        IERC20(_want).safeApprove(_rewards, uint256(-1));
        if(FXS != _tokenA && FXS != _tokenB) IERC20(FXS).safeApprove(currentRouter, uint256(-1));
        IERC20(_tokenA).safeApprove(currentRouter, uint256(-1));
        IERC20(_tokenB).safeApprove(currentRouter, uint256(-1));
    }

    // **** Views ****

    function balanceOfPool() public override view returns (uint256) {
        return IRaGauge(rewards).balanceOf(address(this));
    }

    function getHarvestable() external override view returns (uint256) {
        return 0;
    }

    function getFeeDistToken() public override view returns (address) {
        return harvestedToken;
    }

    /* **** Mutative functions **** */

    function harvest() public override onlyHumanOrWhitelisted nonReentrant {
        _getReward();

        //distribute fee
        uint256 amountHarvested = IERC20(harvestedToken).balanceOf(address(this));
        if (amountHarvested > 0) {
            uint256 feeAmount = amountHarvested.mul(IERCFund(strategist).getFee()).div(FEE_DENOMINATOR);
            IERC20(harvestedToken).safeTransfer(strategist, feeAmount);
        }

        //Swap remaining harvested tokens for tokenA if the harvested token isn't tokenA
        if(harvestedToken != tokenA) {
            uint256 remainingHarvested = IERC20(harvestedToken).balanceOf(address(this));
            IRaRouter.route[] memory routes;

            if(reward_a_path.length == 2) {
                routes = new IRaRouter.route[](1);
                routes[0] = IRaRouter.route(reward_a_path[0], reward_a_path[1], false);
            }
            else if(reward_a_path.length == 3) {
                routes = new IRaRouter.route[](2);
                routes[0] = IRaRouter.route(reward_a_path[0], reward_a_path[1], false);
                routes[1] = IRaRouter.route(reward_a_path[1], reward_a_path[2], false);
            }

            IRaRouter(currentRouter).swapExactTokensForTokens(
                remainingHarvested,
                0,
                routes,
                address(this),
                now.add(60)
            );
        }

        //Swap 1/2 of tokenA for tokenB
        uint256 _balanceA = IERC20(tokenA).balanceOf(address(this));
        if (_balanceA > 0) {
            IRaRouter.route[] memory routes = new IRaRouter.route[](1);
            routes[0] = IRaRouter.route(a_b_path[0], a_b_path[1], false);

            IRaRouter(currentRouter).swapExactTokensForTokens(
                _balanceA.div(2),
                0,
                routes,
                address(this),
                now.add(60)
            );
        }

        //Add liquidity
        uint256 aBalance = IERC20(tokenA).balanceOf(address(this));
        uint256 bBalance = IERC20(tokenB).balanceOf(address(this));
        if (aBalance > 0 && bBalance > 0) {
            IRaRouter(currentRouter).addLiquidity(
                tokenA, tokenB,
                false,
                aBalance, bBalance,
                0, 0,
                address(this),
                now + 60
            );
        }

        _distributePerformanceFeesAndDeposit();
    }

    function deposit() public override {
        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            IRaGauge(rewards).deposit(_want);
        }
    }

    function _withdrawSome(uint256 _amount)
        internal
        override
        returns (uint256)
    {
        IRaGauge(rewards).withdraw(_amount);
        return _amount;
    }

    function _getReward() internal {
        address[] memory _tokens = new address[](1);
        _tokens[0] = harvestedToken;
        IRaGauge(rewards).getReward(address(this), _tokens);
        IRaGauge(rewards).claimFees();
    }

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

    // Added to support recovering LP Rewards from other systems to be distributed to holders
    function salvage(address recipient, address token, uint256 amount) public onlyOwner {
        require(token != want, "cannot salvage want");
        IERC20(token).safeTransfer(recipient, amount);
    }
}

// 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;
    }
}

// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a >= b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow, so we distribute
        return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2);
    }
}

// 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;
    }
}

// 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 { }
}

// 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);
}

// 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");
        }
    }
}

// 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);
            }
        }
    }
}

// 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 10 of 98 : 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;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "../../../main/interfaces/IStrategy.sol";
import "../../../main/interfaces/IVault.sol";
import "./interfaces/ICurveHelper.sol";

struct RewardData {
    address token;
    uint256 rewardRate;
    uint256 periodFinish;
}

contract CurveMulticall is Ownable {

    struct VaultData {
        uint256 amountUserStaked;
        uint256 totalStaked;
        uint256 lpTokenApproved;
        uint256 userLpBalance;

        //uint256 lastDepositTime;
        //uint256 boost;
        //bool boostable;

        //uint256 rewardAllocation;
        //uint256 rewardMultiplier;
        //uint256 totalPendingReward;
        //uint256 withdrawPenaltyTime;
        //uint256 withdrawPenalty;

        uint256 lpSupply;
        uint256 lpStaked;
        uint256 ratio;

        ICurveHelper.CurvePoolData curvePoolData;
    }

    address private constant twoPoolHelper = 0xd4b6b08efe59efA0e564A8e2F147455F618C0a03;

    mapping(address => address) public helpers;

    constructor() public {
    }

    function getVaultInfoFor(address user, IVault jar) public view
        returns (
            VaultData memory
        )
    {
        uint256 ratio = 0;
        if(jar.balance() > 0 && jar.totalShares() > 0) {
            ratio = jar.getRatio();
        }

        IStrategy strategy = IStrategy(jar.strategy());
        address stakingRewards = strategy.rewards();
        address lpTokenAddress = strategy.want();

        return VaultData({
            amountUserStaked: jar.balanceOf(user),
            totalStaked: jar.balance(),
            lpTokenApproved: IERC20(jar.token()).allowance(user, address(jar)),
            userLpBalance: IERC20(jar.token()).balanceOf(user),

            lpSupply: ERC20(lpTokenAddress).totalSupply(),
            lpStaked: ERC20(lpTokenAddress).balanceOf(stakingRewards),
            ratio: ratio,

            curvePoolData: getCurvePoolData(lpTokenAddress, stakingRewards)
        });
    }

    function getInfoForVaults(address user, IVault[] memory vaults) public view
        returns (
            VaultData[] memory vaultData
        )
    {
        uint256 length = vaults.length;
        vaultData = new VaultData[](length);

        for (uint256 i = 0; i < length; i++) {
            vaultData[i] = getVaultInfoFor(user, vaults[i]);
        }
    }

    function getCurvePoolData(address lpTokenAddress, address stakingRewards) public view returns (ICurveHelper.CurvePoolData memory) {
        if(helpers[stakingRewards] != address(0)) {
            return ICurveHelper(helpers[stakingRewards]).getPoolInfo(lpTokenAddress, stakingRewards);
        }
        //most pools are two token pools
        try ICurveHelper(twoPoolHelper).getPoolInfo(lpTokenAddress, stakingRewards) {
            return ICurveHelper(twoPoolHelper).getPoolInfo(lpTokenAddress, stakingRewards);
        }
        catch {}
    }

    function setHelper(address stakingRewards, address helper) public onlyOwner {
        helpers[stakingRewards] = helper;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../interfaces/ICurveHelper.sol";

interface IRewardStream {
    function reward_data(address _token) external view returns (address distributor, uint256 period_finish, uint256 rate, uint256 last_update, uint256 integral);
    function reward_count() external view returns (uint256);
    function reward_tokens(uint256 num) external view returns (address);
}
interface _ICurvePool {
    function get_virtual_price() external view returns (uint256);

    function price_oracle(uint256 k) external view returns (uint256);
    function coins(uint256 i) external view returns (address);
}

contract Curve2PoolHelper is ICurveHelper {

    function getPoolInfo(address _pool, address _rewardStream) public override view returns (CurvePoolData memory) {
        return CurvePoolData({
            virtualPrice: _ICurvePool(_pool).get_virtual_price(),

            rewardData: getRewardData(_rewardStream),
            tokenData: getUnderlyingTokenData(_pool, 2)
        });
    }

    function getRewardData(address stream) public view returns (RewardData[] memory data) {
        data = new RewardData[](IRewardStream(stream).reward_count());

        for (uint256 i = 0; i < IRewardStream(stream).reward_count(); i++) {
            address token = IRewardStream(stream).reward_tokens(i);
            (,uint256 finish, uint256 rate,,) = IRewardStream(stream).reward_data(token);

            data[i] = RewardData({
                token: token,
                rewardRate: rate,
                periodFinish: finish
            });
        }
    }

    function getUnderlyingTokenData(address pool, uint256 num) public view returns (TokenData[] memory data) {
        data = new TokenData[](num);

        for (uint256 i = 0; i < num; i++) {
            data[i] = TokenData({
                token: _ICurvePool(pool).coins(i),
                balance: IERC20(_ICurvePool(pool).coins(i)).balanceOf(pool),
                decimals: ERC20(_ICurvePool(pool).coins(i)).decimals(),
                price: 0
            });
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../interfaces/ICurveHelper.sol";

interface IRewardStream {
    function reward_data(address _token) external view returns (address distributor, uint256 period_finish, uint256 rate, uint256 last_update, uint256 integral);
    function reward_count() external view returns (uint256);
    function reward_tokens(uint256 num) external view returns (address);
}
interface _ICurvePool {
    function get_virtual_price() external view returns (uint256);

    function price_oracle(uint256 k) external view returns (uint256);
    function coins(uint256 i) external view returns (address);
}

contract CurveTriHelper is ICurveHelper {

    function getPoolInfo(address _pool, address _rewardStream) public override view returns (CurvePoolData memory) {
        return CurvePoolData({
            virtualPrice: _ICurvePool(_pool).get_virtual_price(),

            rewardData: getRewardData(_rewardStream),
            tokenData: getUnderlyingTokenData(_pool, 3)
        });
    }

    function getRewardData(address stream) public view returns (RewardData[] memory data) {
        data = new RewardData[](IRewardStream(stream).reward_count());

        for (uint256 i = 0; i < IRewardStream(stream).reward_count(); i++) {
            address token = IRewardStream(stream).reward_tokens(i);
            (,uint256 finish, uint256 rate,,) = IRewardStream(stream).reward_data(token);

            data[i] = RewardData({
                token: token,
                rewardRate: rate,
                periodFinish: finish
            });
        }
    }

    function getUnderlyingTokenData(address pool, uint256 num) public view returns (TokenData[] memory data) {
        data = new TokenData[](num);

        for (uint256 i = 0; i < num; i++) {
            data[i] = TokenData({
                token: _ICurvePool(pool).coins(i),
                balance: IERC20(_ICurvePool(pool).coins(i)).balanceOf(pool),
                decimals: ERC20(_ICurvePool(pool).coins(i)).decimals(),
                price: 0
            });
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

interface ICurveHelper {
    struct RewardData {
        address token;
        uint256 rewardRate;
        uint256 periodFinish;
    }

    struct TokenData {
        address token;
        uint256 decimals;
        uint256 balance;
        uint256 price;
    }

    struct CurvePoolData {
        uint256 virtualPrice;

        RewardData[] rewardData;
        TokenData[] tokenData;
    }

    function getPoolInfo(address _pool, address _rewardStream) external view returns (CurvePoolData memory);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

struct RewardData {
    address token;
    uint256 amount; //amount staked, used for pools that distribute the token being staked
    uint256 duration;
    uint256 periodFinish;
    uint256 rewardRate;
    //used to be `harvestable`
    uint256 decimals; //# of decimals the reward token has
}

interface IStakingRewardsHelper {
    function getRewardData(address _rewards) external view returns (RewardData[] memory);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

struct TokenData {
    address token;
    uint256 decimals;
    uint256 balance;
    uint256 price;
}

interface ITokenHelper {
    function getTokenData(address _strategy) external view returns (TokenData[] memory data);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../../interfaces/IStakingRewardsHelper.sol";
import "../../../../main/interfaces/IStakingRewards.sol";

interface __IFraxFarm {
    function rewardRates(uint256 index) external view returns (uint256);
}

contract FraxFarmRewardHelper is IStakingRewardsHelper {

    function getRewardData(address _rewards) public override view returns (RewardData[] memory data) {
        data = new RewardData[](1);

        data[0].periodFinish = IStakingRewards(_rewards).periodFinish();
        data[0].rewardRate = __IFraxFarm(_rewards).rewardRates(0);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../../interfaces/IStakingRewardsHelper.sol";
import "../../../../main/interfaces/IStakingRewards.sol";

contract GenericStakingRewardHelper is IStakingRewardsHelper {

    function getRewardData(address _rewards) public override view returns (RewardData[] memory data) {
        data = new RewardData[](1);

        data[0].periodFinish = IStakingRewards(_rewards).periodFinish();
        data[0].rewardRate = IStakingRewards(_rewards).rewardRate();
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "../../main/interfaces/IMaximizerVault.sol";
import "../../main/interfaces/IVault.sol";
import "../../main/interfaces/IStrategy.sol";
import "../../main/interfaces/IStakingRewards.sol";
import "../../main/interfaces/uniswap/IUniswapV2Pair.sol";
import "./interfaces/IStakingRewardsHelper.sol";
import "./interfaces/ITokenHelper.sol";

contract StakingRewardsMulticall is Ownable {

    struct VaultData {
        uint256 amountUserStaked;
        uint256 totalStaked;
        uint256 lpTokenApproved;
        uint256 userLpBalance;

        uint256 pendingReward; // Pending reward for current user
        uint256 pendingHarvested; // Pending harvested tokens for current user (for maximizer vaults)
        uint256 lastDepositTime;
        //uint256 boost;
        //bool boostable;

        //uint256 rewardMultiplier;
        //uint256 rewardAllocation;
        //uint256 totalPendingReward; // The total amount of pending rewards available for stakers to claim
        uint256 totalPendingHarvested;
        //uint256 withdrawPenaltyTime;
        //uint256 withdrawPenalty;

        uint256 lpSupply;
        uint256 lpStaked;
        uint256 ratio;

        address stakingRewards;
        RewardData[] rewardData;
        TokenData[] tokenData;
        uint256 lastHarvestTime;

        address token0;
        address token1;
        uint256 token0Bal;
        uint256 token1Bal;
        uint256 decimals0;
        uint256 decimals1;
    }

    //staking reward contract addresses -> helper contracts that return data OR
    //reward token addresses -> helper contracts that return data
    mapping(address => address) public dataContracts;
    mapping(address => address) public tokenHelpers; //strategy addresses -> helper contracts that return token data for tokens that aren't Uniswap LP tokens

    //if true, `getLpStaked` will always use the balance of the staking token in the staking contract instead of trying to use `totalSupply`
    //added because the Solace staking contract uses `totalSupply` to return the # of NFTs in existence
    mapping(address => bool) public useBalance;

    constructor() public {
    }

    function getVaultInfoFor(address user, IVault jar) public view
        returns (
            VaultData memory
        )
    {
        uint256 ratio = 0;
        if(jar.balance() > 0 && jar.totalShares() > 0) {
            ratio = jar.getRatio();
        }

        IStrategy strategy = IStrategy(jar.strategy());
        address lpTokenAddress = strategy.want();
        address token0;
        address token1;

        try IUniswapV2Pair(lpTokenAddress).token0() {
            token0 = IUniswapV2Pair(lpTokenAddress).token0();
            token1 = IUniswapV2Pair(lpTokenAddress).token1();
        }
        catch {
            token0 = lpTokenAddress;
            token1 = lpTokenAddress;
        }

        return VaultData({
            amountUserStaked: jar.balanceOf(user),
            totalStaked: jar.balance(),
            lpTokenApproved: IERC20(jar.token()).allowance(user, address(jar)),
            userLpBalance: IERC20(jar.token()).balanceOf(user),

            pendingReward: getPendingReward(address(jar), user),
            pendingHarvested: getPendingHarvested(address(jar), user),
            lastDepositTime: getLastDepositTime(address(jar), user),
            //boost: getBoost(jar, user),
            //boostable: isBoosted(address(jar)),

            //rewardMultiplier: rewardMultiplier(address(jar)),
            //rewardAllocation: rewardAllocation(address(jar)),
            //totalPendingReward: totalPendingReward(address(jar)),
            totalPendingHarvested: totalPendingHarvested(address(jar)),
            //withdrawPenaltyTime: withdrawPenaltyTime(address(jar)),
            //withdrawPenalty: withdrawPenalty(address(jar)),

            lpSupply: ERC20(lpTokenAddress).totalSupply(),
            lpStaked: getLpStaked(strategy.rewards(), lpTokenAddress),
            ratio: ratio,

            stakingRewards: strategy.rewards(),
            rewardData: getRewardData(strategy),
            tokenData: getTokenData(address(strategy)),
            lastHarvestTime: strategy.lastHarvestTime(),

            token0: token0,
            token1: token1,
            token0Bal: ERC20(token0).balanceOf(lpTokenAddress),
            token1Bal: ERC20(token1).balanceOf(lpTokenAddress),
            decimals0: ERC20(token0).decimals(),
            decimals1: ERC20(token1).decimals()
        });
    }

    function getInfoForVaults(address user, IVault[] memory vaults) public view
        returns (
            VaultData[] memory vaultData
        )
    {
        uint256 length = vaults.length;
        vaultData = new VaultData[](length);

        for (uint256 i = 0; i < length; i++) {
            vaultData[i] = getVaultInfoFor(user, vaults[i]);
        }
    }

    // **** Helper View Functions ****

    function getRewardData(IStrategy strategy) public view returns (RewardData[] memory) {
        //in case there are multiple types of pools that emit the same reward
        if(dataContracts[strategy.rewards()] != address(0)) {
            return IStakingRewardsHelper(dataContracts[strategy.rewards()]).getRewardData(strategy.rewards());
        }
        //for strategy contracts that migrate
        else if(dataContracts[address(strategy)] != address(0)) {
            return IStakingRewardsHelper(dataContracts[address(strategy)]).getRewardData(strategy.rewards());
        }
        //use the harvested token if possible in order to avoid having to define a helper for each reward pool contract
        else {
            try strategy.getHarvestedToken() {
                if(dataContracts[strategy.getHarvestedToken()] != address(0)) {
                    return IStakingRewardsHelper(dataContracts[strategy.getHarvestedToken()]).getRewardData(strategy.rewards());
                }
            }
            catch {}
        }
        //else, use default code
        RewardData[] memory data = new RewardData[](1);
        data[0].periodFinish = getPeriodFinish(strategy);
        data[0].rewardRate = getRewardRate(strategy);
        return data;
    }

    function getTokenData(address strategy) public view returns (TokenData[] memory) {
        if(tokenHelpers[strategy] != address(0)) {
            return ITokenHelper(tokenHelpers[strategy]).getTokenData(strategy);
        }
        try IStrategy(strategy).getHarvestedToken() {
            if(tokenHelpers[IStrategy(strategy).getHarvestedToken()] != address(0)) {
                return ITokenHelper(tokenHelpers[IStrategy(strategy).getHarvestedToken()]).getTokenData(strategy);
            }
        }
        catch {}
    }

    function getLpStaked(address _rewards, address _lpToken) internal view returns (uint256 lpStaked) {
        if(useBalance[_rewards]) {
            lpStaked = ERC20(_lpToken).balanceOf(_rewards);
        }
        else {
            try IStakingRewards(_rewards).totalSupply() {
                lpStaked = IStakingRewards(_rewards).totalSupply();
            }
            catch {
                lpStaked = ERC20(_lpToken).balanceOf(_rewards);
            }
        }
    }

    function getRewardRate(IStrategy strategy) internal view returns (uint256 rewardRate) {
        try IStakingRewards(strategy.rewards()).rewardRate() {
            rewardRate = IStakingRewards(strategy.rewards()).rewardRate();
        }
        catch {
            return 0;
        }
    }

    function getPeriodFinish(IStrategy strategy) internal view returns (uint256) {
        try IStakingRewards(strategy.rewards()).periodFinish() {
            return IStakingRewards(strategy.rewards()).periodFinish();
        }
        catch {
            return 0;
        }
    }

    function getHarvestable(IStrategy strategy) internal view returns (uint256) {
        try strategy.getHarvestable() {
            return strategy.getHarvestable();
        }
        catch {
            return 0;
        }
    }

    function getPendingHarvested(address _vault, address _user) internal view returns (uint256) {
        try IMaximizerVault(_vault).getPendingHarvested(_user) {
            return IMaximizerVault(_vault).getPendingHarvested(_user);
        }
        catch {
            return 0;
        }
    }

    function totalPendingHarvested(address _vault) internal view returns (uint256) {
        try IMaximizerVault(_vault).totalPendingHarvested() {
            return IMaximizerVault(_vault).totalPendingHarvested();
        }
        catch {
            return 0;
        }
    }

    //Support for functions that were removed from the new Jar contract
    function getPendingReward(address _vault, address _user) internal view returns (uint256) {
        if(IVault(_vault).balanceOf(_user) > 0) {
            try IVault(_vault).getPendingReward(_user) {
                return IVault(_vault).getPendingReward(_user);
            }
            catch {
                return 0;
            }
        }
    }

    function getLastDepositTime(address _vault, address _user) internal view returns (uint256) {
        try IVault(_vault).getLastDepositTime(_user) {
            return IVault(_vault).getLastDepositTime(_user);
        }
        catch {
            return 0;
        }
    }

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

    function setHelper(address _rewards, address _helper) public onlyOwner {
        dataContracts[_rewards] = _helper;
    }

    function setTokenHelper(address _strategy, address _helper) public onlyOwner {
        tokenHelpers[_strategy] = _helper;
    }

    function setUseBalance(address _rewards, bool _value) public onlyOwner {
        useBalance[_rewards] = _value;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../interfaces/ITokenHelper.sol";
import "../../../main/interfaces/IStrategy.sol";

interface __IFraxFarm {
    /// @notice Total 'balance' used for calculating the percent of the pool the account owns
    /// @return uint256 The combined weight. Takes into account the locked stake time multiplier and veFXS multiplier
    function totalCombinedWeight() external view returns (uint256);
}

contract FraxFarmTokenHelper is ITokenHelper {
    using SafeMath for uint256;

    function getTokenData(address _strategy) external override view returns (TokenData[] memory data) {
        data = new TokenData[](1);

        data[0].balance = __IFraxFarm(IStrategy(_strategy).rewards()).totalCombinedWeight();
    }
}

// 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/access/Ownable.sol";

//Contract where the fees are sent to before they are converted
//No fee distribution contract currently exists for Fraxtal
contract ERCFundStandalone is Ownable {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    uint256 public constant feeMAX = 3000;

    uint256 public fee = 500; //5%
    bool public feeSharingEnabled = true;
    mapping(address => bool) public converters;

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

    function feeShareEnabled() external view returns (bool) {
        return feeSharingEnabled;
    }

    function getFee() external view returns (uint256) {
        return fee;
    }

    /* ========== SETTER FUNCTIONS ========== */

    function setFeeSharingEnabled(bool enabled) public onlyOwner {
        feeSharingEnabled = enabled;
    }

    function setFee(uint256 _fee) public onlyOwner {
        require(_fee <= feeMAX);
        fee = _fee;
    }

    function setConverter(address _converter, bool allowed) public onlyOwner {
        converters[_converter] = allowed;
    }

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

    //Used by the fee converter
    function recover(address token) public {
        require(converters[msg.sender], "not allowed");

        uint256 _token = IERC20(token).balanceOf(address(this));
        if (_token > 0) {
            IERC20(token).safeTransfer(msg.sender, _token);
            emit Recovered(token, _token);
        }
    }

    // **** Events ****
    event Recovered(address indexed tokenWithdrew, uint256 _amount);
    event Notified(address indexed tokenDeposited);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "../../../main/strategies/base/BaseStrategy.sol";
import "../../../main/interfaces/curve/ICurveGauge.sol";
import "../../../main/interfaces/IERCFund.sol";
import "../../../main/interfaces/IGenericVault.sol";

interface ICurvePool {
    /// @notice Deposit coins into the pool
    /// @param _amounts List of amounts of coins to deposit
    /// @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit
    function add_liquidity(uint256[3] calldata _amounts, uint256 _min_mint_amount) external;
}
interface ICurveRouter {
    function exchange(
        address[11] calldata _route,
        uint256[5][5] calldata _swap_params,
        uint256 _amount,
        uint256 _expected,
        address[5] calldata _pools,
        address _receiver
    ) external;
}

//Vaults with only FXS rewards that aren't paired with FRAX or wfrxETH
contract FraxtalCurveBase2 is BaseStrategy {

    uint256 public constant keepMax = 10000;

    address private constant FXS = 0xFc00000000000000000000000000000000000002;
    address public CRV = 0x11cDb42B0EB46D95f990BeDD4695A6e3fA034978;

    address private constant TRIFXS_POOL = 0xa0D3911349e701A1F49C1Ba2dDA34b4ce9636569;

    address[] public crv_weth_path;

    constructor(
        address _rewards,
        address _want,
        address _tokenA,
        address _tokenB,
        address _betweenToken //token between reward token and A in swap if necessary (only for pools)
    )
        public
        BaseStrategy(
            _want,
            0x8b36504F2c52f59a57EE6c84d6f1b82e7698EcF0, //Fraxtal ERCFund
            FXS,
            0x39cd4db6460d8B5961F73E997E86DdbB7Ca4D5F6, //FraxswapRouter PLACEHOLDER
            _rewards
        )
    {
        /*tokenA = _tokenA;
        tokenB = _tokenB;

        if(_betweenToken == address(0)) {
            reward_a_path = [FXS, _tokenA];
            a_b_path = [_tokenA, _tokenB];
        }
        else {
            reward_a_path = [FXS, _betweenToken, _tokenA];
            a_b_path = [_tokenA, _tokenB];
        }

        IERC20(_want).safeApprove(_rewards, uint256(-1));
        if(FXS != _tokenA && FXS != _tokenB) IERC20(FXS).safeApprove(currentRouter, uint256(-1));
        IERC20(_tokenA).safeApprove(currentRouter, uint256(-1));
        IERC20(_tokenB).safeApprove(currentRouter, uint256(-1));*/
    }


    // **** Views ****

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

    //Calling claimable_reward via a transaction = claim available reward tokens
    function getHarvestable() external override view returns (uint256) {
        return 0; //ICurveGauge(rewards).claimable_reward(address(this), harvestedToken);
    }

    function getFeeDistToken() public override view returns (address) {
        return FXS;
    }

    /* **** Mutative functions **** */

    function harvest() public override onlyHumanOrWhitelisted nonReentrant {
        //Transfer any WETH that may already be in the contract to the fee dist fund
        /*

        //Calculate the amount of tokens harvested
        uint256 crv_balance_before = IERC20(CRV).balanceOf(address(this));
        _getReward();

        //Convert all harvested CRV to WETH
        uint256 crv_harvested = IERC20(CRV).balanceOf(address(this)).sub(crv_balance_before);
        if(crv_harvested > 0) {
            _swapUniswapWithPathPreapproved(crv_weth_path, crv_harvested);
            //Deduct fee from the converted CRV
            uint256 convertedBal = IERC20(WETH).balanceOf(address(this));
            if (convertedBal > 0) {
                uint256 feeAmount = convertedBal.mul(IERCFund(strategist).getFee()).div(keepMax);
                _notifyJar(feeAmount);
                IERC20(WETH).safeTransfer(strategist, feeAmount);
            }
        }

        //Deposit WETH in pool to get atricrypto LP tokens
        uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));
        if (_wethBalance > 0) {
            ICurvePool(pool).add_liquidity([0, 0, _wethBalance], 0);
        }*/
    }

    function deposit() public override {
        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            ICurveGauge(rewards).deposit(_want, address(this), false);
        }
    }

    function _withdrawSome(uint256 _amount)
        internal
        override
        returns (uint256)
    {
        ICurveGauge(rewards).withdraw(_amount, false);
        return _amount;
    }

    function _getReward() internal {
        ICurveGauge(rewards).claim_rewards();
    }

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

    // Added to support recovering LP Rewards from other systems to be distributed to holders
    function salvage(address recipient, address token, uint256 amount) public onlyOwner {
        // Admin cannot withdraw the staking or harvested token from the contract
        require(token != want, "cannot salvage want");
        require(token != harvestedToken, "cannot salvage harvestedToken");
        require(token != rewards, "cannot salvage gauge token");
        IERC20(token).safeTransfer(recipient, amount);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "../../../main/strategies/base/BaseStrategy.sol";
import "../../../main/interfaces/curve/ICurveGauge.sol";
import "../../../main/interfaces/IERCFund.sol";
import "../../../main/interfaces/IGenericVault.sol";

interface ICurveStableSwapNG is IERC20 {
    /// @notice Adds liquidity into the pool.
    /// @param _amounts Amounts of each coin to add.
    /// @param _min_mint_amount Minimum amount of LP to mint.
    /// @return uint256 Amount of LP tokens received by the `receiver`
    function add_liquidity(uint256[] calldata _amounts, uint256 _min_mint_amount) external returns (uint256);

    /// `nonReentrant`
    /// @notice Exchange using wrapped native token by default
    /// @param i Index value for the input coin
    /// @param j Index value for the output coin
    /// @param dx Amount of input coin being swapped in
    /// @param min_dy Minimum amount of output coin to receive
    /// @return uint256 Amount of tokens at index j received by the `receiver
    function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external returns (uint256);

    function coins(uint256 index) external view returns (address);
}
interface __ICurveTricryptoOptimized {
    /// `nonReentrant`
    /// @dev i and j are uint256s, unlike `ICurveStableSwapNG`
    /// @notice Exchange using wrapped native token by default
    /// @param i Index value for the input coin
    /// @param j Index value for the output coin
    /// @param dx Amount of input coin being swapped in
    /// @param min_dy Minimum amount of output coin to receive
    /// @return uint256 Amount of tokens at index j received by the `receiver
    function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external returns (uint256);
}

//Pools with two tokens that have only FXS rewards
//Don't use for TriFXS
contract FraxtalCurveTwoPoolStrategy is BaseStrategy {

    uint256 public constant FEE_DENOMINATOR = 10000;

    address private constant FXS = 0xFc00000000000000000000000000000000000002;
    address private constant TRIFXS_POOL = 0xa0D3911349e701A1F49C1Ba2dDA34b4ce9636569;
    address public underlying;
    uint256 private index = 0;

    constructor(
        address _rewards,
        address _want,
        address _underlying
    )
        public
        BaseStrategy(
            _want,
            0x8b36504F2c52f59a57EE6c84d6f1b82e7698EcF0, //Fraxtal ERCFund
            FXS,
            TRIFXS_POOL,
            _rewards
        )
    {
        underlying = _underlying;

        //frax = 0
        //frxETH = 1
        //fxs = 2
        if (underlying == 0xFC00000000000000000000000000000000000006) {
            index = 1;
        }

        IERC20(_want).safeApprove(_rewards, uint256(-1));
        IERC20(FXS).safeApprove(currentRouter, uint256(-1));
        IERC20(_underlying).safeApprove(_want, uint256(-1));
    }


    // **** Views ****

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

    //Calling claimable_reward via a transaction = claim available reward tokens
    function getHarvestable() external override view returns (uint256) {
        return 0; //ICurveGauge(rewards).claimable_reward(address(this), harvestedToken);
    }

    function getFeeDistToken() public override view returns (address) {
        return FXS;
    }

    /* **** Mutative functions **** */

    function harvest() public override onlyHumanOrWhitelisted nonReentrant {
        _getReward();

        //distribute fee
        uint256 _amountHarvested = IERC20(harvestedToken).balanceOf(address(this));
        if (_amountHarvested > 0) {
            uint256 _feeAmount = _amountHarvested.mul(IERCFund(strategist).getFee()).div(FEE_DENOMINATOR);
            IERC20(harvestedToken).safeTransfer(strategist, _feeAmount);
        }

        uint256 _remainingHarvested = IERC20(harvestedToken).balanceOf(address(this));
        if (_remainingHarvested > 0) {
            __ICurveTricryptoOptimized(currentRouter).exchange(2, index, _remainingHarvested, 1);
        }

        uint256 _underlyingBalance = IERC20(underlying).balanceOf(address(this));
        if (_underlyingBalance > 0) {
            uint256[] memory _balances = new uint256[](2);
            //strategy is only for pool with two tokens
            if (ICurveStableSwapNG(want).coins(0) == underlying) {
                _balances[0] = _underlyingBalance;
            }
            else {
                _balances[1] = _underlyingBalance;
            }
            ICurveStableSwapNG(want).add_liquidity(_balances, 0);

            _distributePerformanceFeesAndDeposit();
        }
    }

    function deposit() public override {
        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            ICurveGauge(rewards).deposit(_want, address(this));
        }
    }

    function _withdrawSome(uint256 _amount)
        internal
        override
        returns (uint256)
    {
        ICurveGauge(rewards).withdraw(_amount);
        return _amount;
    }

    function _getReward() internal {
        ICurveGauge(rewards).claim_rewards();
    }

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

    // Added to support recovering LP Rewards from other systems to be distributed to holders
    function salvage(address recipient, address token, uint256 amount) public onlyOwner {
        // Admin cannot withdraw the staking or harvested token from the contract
        require(token != want, "cannot salvage want");
        require(token != harvestedToken, "cannot salvage harvestedToken");
        require(token != rewards, "cannot salvage gauge token");
        IERC20(token).safeTransfer(recipient, amount);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/math/Math.sol";
import "../../../main/interfaces/IStrategy.sol";
import "../../../main/interfaces/IERCFund.sol";

interface ITwocryptoStrategy {
    function salvage(address recipient, address token, uint256 amount) external;
}
interface __ICurveTwocryptoOptimized is IERC20 {
    /// `nonReentrant`
    /// @dev i and j are uint256s
    /// @notice Exchange using wrapped native token by default
    /// @param i Index value for the input coin
    /// @param j Index value for the output coin
    /// @param dx Amount of input coin being swapped in
    /// @param min_dy Minimum amount of output coin to receive
    /// @return uint256 Amount of tokens at index j received by the `receiver
    function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external returns (uint256);

    function coins(uint256 index) external view returns (address);

    /// @notice Deposit coins into the pool
    /// @param _amounts List of amounts of coins to deposit
    /// @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit
    function add_liquidity(uint256[2] calldata _amounts, uint256 _min_mint_amount) external;
}
interface __ICurveTricryptoOptimized is IERC20 {
    /// `nonReentrant`
    /// @notice Exchange using wrapped native token by default
    /// @param i Index value for the input coin
    /// @param j Index value for the output coin
    /// @param dx Amount of input coin being swapped in
    /// @param min_dy Minimum amount of output coin to receive
    /// @return uint256 Amount of tokens at index j received by the `receiver
    function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external returns (uint256);

    function coins(uint256 index) external view returns (address);
}

contract FxsRewardHandler is Ownable {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    uint256 public constant FEE_DENOMINATOR = 10000;

    address private constant strategist = 0x8b36504F2c52f59a57EE6c84d6f1b82e7698EcF0;
    address private constant FXS = 0xFc00000000000000000000000000000000000002;
    address private constant TRIFXS_POOL = 0xa0D3911349e701A1F49C1Ba2dDA34b4ce9636569;
    address private constant frxETH = 0xFC00000000000000000000000000000000000006; //frxETH
    uint256 private trifxs_index = 1; //convert FXS to WETH
    uint256 private eth_index = 0;

    address public want;
    address public strategy;

    constructor(address _strategy) public
    {
        strategy = _strategy;
        want = IStrategy(_strategy).want();
        IERC20(FXS).safeApprove(TRIFXS_POOL, uint256(-1));
        IERC20(frxETH).safeApprove(want, uint256(-1));

        if (__ICurveTwocryptoOptimized(want).coins(1) == frxETH) {
            eth_index = 1;
        }
    }

    //Check token balances beforehand so we don't waste gas
    function harvest() public {
        uint256 _amountHarvested_fxs = IERC20(FXS).balanceOf(strategy);
        if (_amountHarvested_fxs > 0) {
            ITwocryptoStrategy(strategy).salvage(address(this), FXS, _amountHarvested_fxs);

            uint256 _feeAmount = _amountHarvested_fxs.mul(IERCFund(strategist).getFee()).div(FEE_DENOMINATOR);
            IERC20(FXS).safeTransfer(strategist, _feeAmount);
            uint256 _remaining_fxs = _amountHarvested_fxs.sub(_feeAmount);
            __ICurveTricryptoOptimized(TRIFXS_POOL).exchange(2, trifxs_index, _remaining_fxs, 1);

            uint256 _underlyingBalance2 = IERC20(frxETH).balanceOf(address(this));
            if (_underlyingBalance2 > 0) {
                if (eth_index == 1) {
                    __ICurveTwocryptoOptimized(want).add_liquidity([0, _underlyingBalance2], 0);
                }
                else {
                    __ICurveTwocryptoOptimized(want).add_liquidity([_underlyingBalance2, 0], 0);
                }
            }

            uint256 _wantBalance = IERC20(want).balanceOf(address(this));
            if(_wantBalance > 0) {
                IERC20(want).transfer(strategy, _wantBalance);
            }
        }
    }

    /* ========== ADMIN FUNCTIONS NOT DIRECTLY RELATED TO CONVERTING ========== */

    //No user tokens should be in this contract
    function salvage(address _recipient, address _token) public onlyOwner {
        uint256 _amount = IERC20(_token).balanceOf(address(this));
        IERC20(_token).transfer(_recipient, _amount);
    }

    //Return ownership of the contract to me
    //Renamed from `restoreOwnership` because it was right next to `renounceOwnership`
    function acquireOwnership() public onlyOwner {
        Ownable(strategy).transferOwnership(owner());
    }

    function acquireOwnership(address _address) public onlyOwner {
        Ownable(_address).transferOwnership(owner());
    }

    function executeTransaction(address target, uint value, string memory signature, bytes memory data) public payable onlyOwner returns (bytes memory) {
        bytes memory callData;

        if (bytes(signature).length == 0) {
            callData = data;
        } else {
            callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
        }

        // solium-disable-next-line security/no-call-value
        // XXX: Using ".value(...)" is deprecated. Use "{value: ...}" instead.
        (bool success, bytes memory returnData) = target.call{value : value}(callData);
        require(success, "Timelock::executeTransaction: Transaction execution reverted.");

        return returnData;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "../../../main/strategies/base/BaseStrategy.sol";
import "../../../main/interfaces/curve/ICurveGauge.sol";
import "../../../main/interfaces/IERCFund.sol";
import "../../../main/interfaces/IGenericVault.sol";

interface __ICurveTricryptoOptimized is IERC20 {
    /// @notice Deposit coins into the pool
    /// @param _amounts List of amounts of coins to deposit
    /// @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit
    function add_liquidity(uint256[3] calldata _amounts, uint256 _min_mint_amount) external;

    /// `nonReentrant`
    /// @notice Exchange using wrapped native token by default
    /// @param i Index value for the input coin
    /// @param j Index value for the output coin
    /// @param dx Amount of input coin being swapped in
    /// @param min_dy Minimum amount of output coin to receive
    /// @return uint256 Amount of tokens at index j received by the `receiver
    function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external returns (uint256);

    function coins(uint256 index) external view returns (address);
}

contract TriCrvStrategy is BaseStrategy {

    uint256 public constant FEE_DENOMINATOR = 10000;

    address private constant FXS = 0xFc00000000000000000000000000000000000002;
    address private constant TRIFXS_POOL = 0xa0D3911349e701A1F49C1Ba2dDA34b4ce9636569;
    address public underlying = 0xFC00000000000000000000000000000000000006; //frxETH

    constructor()
        public
        BaseStrategy(
            0xF593aE314749d0c92B450F0a13E7e1791f352bB7, //triCRV pool
            0x8b36504F2c52f59a57EE6c84d6f1b82e7698EcF0, //Fraxtal ERCFund
            FXS,
            TRIFXS_POOL,
            0x9ccC509a5b4869bAC6BE86e79Ea00c25942852F4 //triCRV gauge
        )
    {
        IERC20(want).safeApprove(rewards, uint256(-1));
        IERC20(FXS).safeApprove(currentRouter, uint256(-1));
        IERC20(underlying).safeApprove(want, uint256(-1));
    }


    // **** Views ****

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

    //Calling claimable_reward via a transaction = claim available reward tokens
    function getHarvestable() external override view returns (uint256) {
        return 0; //ICurveGauge(rewards).claimable_reward(address(this), harvestedToken);
    }

    function getFeeDistToken() public override view returns (address) {
        return FXS;
    }

    /* **** Mutative functions **** */

    function harvest() public override onlyHumanOrWhitelisted nonReentrant {
        _getReward();

        //distribute fee
        uint256 _amountHarvested = IERC20(harvestedToken).balanceOf(address(this));
        if (_amountHarvested > 0) {
            uint256 _feeAmount = _amountHarvested.mul(IERCFund(strategist).getFee()).div(FEE_DENOMINATOR);
            IERC20(harvestedToken).safeTransfer(strategist, _feeAmount);
        }

        uint256 _remainingHarvested = IERC20(harvestedToken).balanceOf(address(this));
        if (_remainingHarvested > 0) {
            //swap FXS for frxETH
            __ICurveTricryptoOptimized(currentRouter).exchange(2, 1, _remainingHarvested, 1);
        }

        uint256 _underlyingBalance = IERC20(underlying).balanceOf(address(this));
        if (_underlyingBalance > 0) {
            __ICurveTricryptoOptimized(want).add_liquidity([0, _underlyingBalance, 0], 0);

            _distributePerformanceFeesAndDeposit();
        }
    }

    function deposit() public override {
        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            ICurveGauge(rewards).deposit(_want, address(this));
        }
    }

    function _withdrawSome(uint256 _amount)
        internal
        override
        returns (uint256)
    {
        ICurveGauge(rewards).withdraw(_amount);
        return _amount;
    }

    function _getReward() internal {
        ICurveGauge(rewards).claim_rewards();
    }

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

    // Added to support recovering LP Rewards from other systems to be distributed to holders
    function salvage(address recipient, address token, uint256 amount) public onlyOwner {
        // Admin cannot withdraw the staking or harvested token from the contract
        require(token != want, "cannot salvage want");
        require(token != harvestedToken, "cannot salvage harvestedToken");
        require(token != rewards, "cannot salvage gauge token");
        IERC20(token).safeTransfer(recipient, amount);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "../../../main/strategies/base/BaseStrategy.sol";
import "../../../main/interfaces/curve/ICurveGauge.sol";
import "../../../main/interfaces/IERCFund.sol";
import "../../../main/interfaces/IGenericVault.sol";

interface ICurveTricryptoOptimized is IERC20 {
    /// @notice Deposit coins into the pool
    /// @param _amounts List of amounts of coins to deposit
    /// @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit
    function add_liquidity(uint256[3] calldata _amounts, uint256 _min_mint_amount) external;

    /// `nonReentrant`
    /// @notice Exchange using wrapped native token by default
    /// @param i Index value for the input coin
    /// @param j Index value for the output coin
    /// @param dx Amount of input coin being swapped in
    /// @param min_dy Minimum amount of output coin to receive
    /// @return uint256 Amount of tokens at index j received by the `receiver
    function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external returns (uint256);

    function coins(uint256 index) external view returns (address);
}

contract TriFxsStrategy is BaseStrategy {

    uint256 public constant FEE_DENOMINATOR = 10000;

    address private constant FXS = 0xFc00000000000000000000000000000000000002;

    constructor()
        public
        BaseStrategy(
            0xa0D3911349e701A1F49C1Ba2dDA34b4ce9636569, //triFXS pool
            0x8b36504F2c52f59a57EE6c84d6f1b82e7698EcF0, //Fraxtal ERCFund
            FXS,
            0xa0D3911349e701A1F49C1Ba2dDA34b4ce9636569, //triFXS pool
            0x8882eB84d0c4894753C0450f2D514E39883c0C82 //triFXS gauge
        )
    {
        IERC20(want).safeApprove(rewards, uint256(-1));
        IERC20(FXS).safeApprove(want, uint256(-1));
    }


    // **** Views ****

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

    //Calling claimable_reward via a transaction = claim available reward tokens
    function getHarvestable() external override view returns (uint256) {
        return 0; //ICurveGauge(rewards).claimable_reward(address(this), harvestedToken);
    }

    function getFeeDistToken() public override view returns (address) {
        return FXS;
    }

    /* **** Mutative functions **** */

    function harvest() public override onlyHumanOrWhitelisted nonReentrant {
        _getReward();

        //distribute fee
        uint256 _amountHarvested = IERC20(harvestedToken).balanceOf(address(this));
        if (_amountHarvested > 0) {
            uint256 _feeAmount = _amountHarvested.mul(IERCFund(strategist).getFee()).div(FEE_DENOMINATOR);
            IERC20(harvestedToken).safeTransfer(strategist, _feeAmount);
        }

        uint256 _underlyingBalance = IERC20(harvestedToken).balanceOf(address(this));
        if (_underlyingBalance > 0) {
            ICurveTricryptoOptimized(want).add_liquidity([0, 0, _underlyingBalance], 0);

            _distributePerformanceFeesAndDeposit();
        }
    }

    function deposit() public override {
        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            ICurveGauge(rewards).deposit(_want, address(this));
        }
    }

    function _withdrawSome(uint256 _amount)
        internal
        override
        returns (uint256)
    {
        ICurveGauge(rewards).withdraw(_amount);
        return _amount;
    }

    function _getReward() internal {
        ICurveGauge(rewards).claim_rewards();
    }

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

    // Added to support recovering LP Rewards from other systems to be distributed to holders
    function salvage(address recipient, address token, uint256 amount) public onlyOwner {
        // Admin cannot withdraw the staking or harvested token from the contract
        require(token != want, "cannot salvage want");
        require(token != harvestedToken, "cannot salvage harvestedToken");
        require(token != rewards, "cannot salvage gauge token");
        IERC20(token).safeTransfer(recipient, amount);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "../../../main/strategies/base/BaseStrategy.sol";
import "../../../main/interfaces/curve/ICurveGauge.sol";
import "../../../main/interfaces/IERCFund.sol";
import "../../../main/interfaces/IGenericVault.sol";

interface __ICurveTwocryptoOptimized is IERC20 {
    /// `nonReentrant`
    /// @dev i and j are uint256s
    /// @notice Exchange using wrapped native token by default
    /// @param i Index value for the input coin
    /// @param j Index value for the output coin
    /// @param dx Amount of input coin being swapped in
    /// @param min_dy Minimum amount of output coin to receive
    /// @return uint256 Amount of tokens at index j received by the `receiver
    function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external returns (uint256);

    function coins(uint256 index) external view returns (address);

    /// @notice Deposit coins into the pool
    /// @param _amounts List of amounts of coins to deposit
    /// @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit
    function add_liquidity(uint256[2] calldata _amounts, uint256 _min_mint_amount) external;
}

//Pools with two tokens where the reward token is one of the underlying tokens (aka a "2pool" pool)
contract TwocryptoStrategy is BaseStrategy {

    uint256 public constant FEE_DENOMINATOR = 10000;

    address public underlying;
    uint256 private index = 0;

    constructor(
        address _rewards,
        address _want,
        address _underlying
    )
        public
        BaseStrategy(
            _want,
            0x8b36504F2c52f59a57EE6c84d6f1b82e7698EcF0, //Fraxtal ERCFund
            _underlying,
            _want,
            _rewards
        )
    {
        underlying = _underlying;

        if (__ICurveTwocryptoOptimized(want).coins(1) == _underlying) {
            index = 1;
        }

        IERC20(_want).safeApprove(_rewards, uint256(-1));
        IERC20(_underlying).safeApprove(_want, uint256(-1));
    }


    // **** Views ****

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

    //Calling claimable_reward via a transaction = claim available reward tokens
    function getHarvestable() external override view returns (uint256) {
        return 0; //ICurveGauge(rewards).claimable_reward(address(this), harvestedToken);
    }

    function getFeeDistToken() public override view returns (address) {
        return harvestedToken;
    }

    /* **** Mutative functions **** */

    function harvest() public override onlyHumanOrWhitelisted nonReentrant {
        _getReward();

        //distribute fee
        uint256 _amountHarvested = IERC20(harvestedToken).balanceOf(address(this));
        if (_amountHarvested > 0) {
            uint256 _feeAmount = _amountHarvested.mul(IERCFund(strategist).getFee()).div(FEE_DENOMINATOR);
            IERC20(harvestedToken).safeTransfer(strategist, _feeAmount);
        }

        uint256 _underlyingBalance = IERC20(underlying).balanceOf(address(this));
        if (_underlyingBalance > 0) {
            if (index == 1) {
                __ICurveTwocryptoOptimized(want).add_liquidity([0, _underlyingBalance], 0);
            }
            else {
                __ICurveTwocryptoOptimized(want).add_liquidity([_underlyingBalance, 0], 0);
            }
            _distributePerformanceFeesAndDeposit();
        }
    }

    function deposit() public override {
        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            ICurveGauge(rewards).deposit(_want, address(this));
        }
    }

    function _withdrawSome(uint256 _amount)
        internal
        override
        returns (uint256)
    {
        ICurveGauge(rewards).withdraw(_amount);
        return _amount;
    }

    function _getReward() internal {
        ICurveGauge(rewards).claim_rewards();
    }

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

    // Added to support recovering LP Rewards from other systems to be distributed to holders
    function salvage(address recipient, address token, uint256 amount) public onlyOwner {
        // Admin cannot withdraw the staking or harvested token from the contract
        require(token != want, "cannot salvage want");
        require(token != harvestedToken, "cannot salvage harvestedToken");
        require(token != rewards, "cannot salvage gauge token");
        IERC20(token).safeTransfer(recipient, amount);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "../../../main/strategies/base/BaseStrategy.sol";
import "../../../main/interfaces/curve/ICurveGauge.sol";
import "../../../main/interfaces/IERCFund.sol";
import "../../../main/interfaces/IGenericVault.sol";

interface ICurveTwocryptoOptimized is IERC20 {
    /// `nonReentrant`
    /// @dev i and j are uint256s
    /// @notice Exchange using wrapped native token by default
    /// @param i Index value for the input coin
    /// @param j Index value for the output coin
    /// @param dx Amount of input coin being swapped in
    /// @param min_dy Minimum amount of output coin to receive
    /// @return uint256 Amount of tokens at index j received by the `receiver
    function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external returns (uint256);

    function coins(uint256 index) external view returns (address);

    /// @notice Deposit coins into the pool
    /// @param _amounts List of amounts of coins to deposit
    /// @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit
    function add_liquidity(uint256[2] calldata _amounts, uint256 _min_mint_amount) external;
}
interface __ICurveTricryptoOptimized is IERC20 {
    /// `nonReentrant`
    /// @notice Exchange using wrapped native token by default
    /// @param i Index value for the input coin
    /// @param j Index value for the output coin
    /// @param dx Amount of input coin being swapped in
    /// @param min_dy Minimum amount of output coin to receive
    /// @return uint256 Amount of tokens at index j received by the `receiver
    function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external returns (uint256);

    function coins(uint256 index) external view returns (address);
}

//Pools with two underlying tokens where there are two reward tokens and:
//one reward token is one of the underlying tokens (aka a "2pool" pool)
//other token is FXS, which is compounded into the second underlying token
contract TwocryptoStrategyDual is BaseStrategy {

    uint256 public constant FEE_DENOMINATOR = 10000;

    address private constant FXS = 0xFc00000000000000000000000000000000000002;
    address private constant TRIFXS_POOL = 0xa0D3911349e701A1F49C1Ba2dDA34b4ce9636569;
    address public underlying;
    address public underlying2;
    uint256 private index = 0;
    uint256 private trifxs_index = 0;

    constructor(
        address _rewards,
        address _want,
        address _underlying,
        address _underlying2
    )
        public
        BaseStrategy(
            _want,
            0x8b36504F2c52f59a57EE6c84d6f1b82e7698EcF0, //Fraxtal ERCFund
            _underlying,
            _want,
            _rewards
        )
    {
        underlying = _underlying;
        underlying2 = _underlying2;

        //frax = 0
        //frxETH = 1
        //fxs = 2
        if (underlying == 0xFC00000000000000000000000000000000000006) {
            trifxs_index = 1;
        }

        if (ICurveTwocryptoOptimized(want).coins(1) == _underlying) {
            index = 1;
        }

        IERC20(FXS).safeApprove(TRIFXS_POOL, uint256(-1));
        IERC20(_want).safeApprove(_rewards, uint256(-1));
        IERC20(_underlying).safeApprove(_want, uint256(-1));
        IERC20(_underlying2).safeApprove(_want, uint256(-1));
    }


    // **** Views ****

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

    //Calling claimable_reward via a transaction = claim available reward tokens
    function getHarvestable() external override view returns (uint256) {
        return 0; //ICurveGauge(rewards).claimable_reward(address(this), harvestedToken);
    }

    function getFeeDistToken() public override view returns (address) {
        return harvestedToken;
    }

    /* **** Mutative functions **** */

    function harvest() public override onlyHumanOrWhitelisted nonReentrant {
        _getReward();

        //distribute fee (FXS)
        uint256 _amountHarvested_fxs = IERC20(FXS).balanceOf(address(this));
        if (_amountHarvested_fxs > 0) {
            uint256 _feeAmount = _amountHarvested_fxs.mul(IERCFund(strategist).getFee()).div(FEE_DENOMINATOR);
            IERC20(FXS).safeTransfer(strategist, _feeAmount);
        }
        uint256 _remaining_fxs = IERC20(FXS).balanceOf(address(this));
        if (_remaining_fxs > 0) {
            __ICurveTricryptoOptimized(TRIFXS_POOL).exchange(2, trifxs_index, _remaining_fxs, 1);
        }

        //distribute fee (other reward)
        uint256 _amountHarvested = IERC20(harvestedToken).balanceOf(address(this));
        if (_amountHarvested > 0) {
            uint256 _feeAmount = _amountHarvested.mul(IERCFund(strategist).getFee()).div(FEE_DENOMINATOR);
            IERC20(harvestedToken).safeTransfer(strategist, _feeAmount);
        }

        uint256 _underlyingBalance = IERC20(underlying).balanceOf(address(this));
        uint256 _underlyingBalance2 = IERC20(underlying2).balanceOf(address(this));
        if (_underlyingBalance > 0) {
            if (index == 1) {
                ICurveTwocryptoOptimized(want).add_liquidity([_underlyingBalance2, _underlyingBalance], 0);
            }
            else {
                ICurveTwocryptoOptimized(want).add_liquidity([_underlyingBalance, _underlyingBalance2], 0);
            }
            _distributePerformanceFeesAndDeposit();
        }
    }

    function deposit() public override {
        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            ICurveGauge(rewards).deposit(_want, address(this));
        }
    }

    function _withdrawSome(uint256 _amount)
        internal
        override
        returns (uint256)
    {
        ICurveGauge(rewards).withdraw(_amount);
        return _amount;
    }

    function _getReward() internal {
        ICurveGauge(rewards).claim_rewards();
    }

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

    // Added to support recovering LP Rewards from other systems to be distributed to holders
    function salvage(address recipient, address token, uint256 amount) public onlyOwner {
        // Admin cannot withdraw the staking or harvested token from the contract
        require(token != want, "cannot salvage want");
        require(token != harvestedToken, "cannot salvage harvestedToken");
        require(token != rewards, "cannot salvage gauge token");
        IERC20(token).safeTransfer(recipient, amount);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "../../../main/strategies/base/BaseStrategy.sol";
import "../../../main/interfaces/IERCFund.sol";

interface ICurvePool {
    /// @notice Deposit coins into the pool
    /// @param _amounts List of amounts of coins to deposit
    /// @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit
    function add_liquidity(uint256[2] calldata _amounts, uint256 _min_mint_amount) external;
}

interface IFraxFarm {
    function lockedLiquidityOf(address account) external view returns (uint256);
    function earned(address account) external view returns (uint256[] memory _rtnEarned);
    function lockedStakesOf(address account) external view returns (LockedStake[] memory);

    function lockAdditional(bytes32 kek_id, uint256 addl_liq) external;
    function stakeLocked(uint256 liquidity, uint256 secs) external;
    //function withdrawLocked(bytes32 kek_id) external;
    function withdrawLocked(bytes32 kek_id, bool claim_rewards_deprecated) external; //`claim_rewards_deprecated` has no effect (always claims rewards regardless)
    function getReward() external returns (uint256[] memory);
}

struct LockedStake {
    bytes32 kek_id;
    uint256 start_timestamp;
    uint256 liquidity;
    uint256 ending_timestamp;
    uint256 lock_multiplier; // 6 decimals of precision. 1x = 1000000
}

//note: all stakes are technically locked, but the min lock timer is 1 second
//This strategy assumes that will always remain the case; if the min lock timer changes, I'll need to deploy new strategies
contract FraxswapStrategy is BaseStrategy {

    uint256 public constant FEE_DENOMINATOR = 10000;
    uint256 public maxLocks = 10; //max # of locks to have at a time (not relevant if the min lock timer is 1 second)

    //Tokens
    address public tokenA;
    address public tokenB;
    //address private constant WETH = 0xFC00000000000000000000000000000000000006;
    //address private constant FRAX = 0xFc00000000000000000000000000000000000001;
    address private constant FXS = 0xFc00000000000000000000000000000000000002;

    address[] public fxs_frax_path;

    address[] public reward_a_path;
    address[] public a_b_path;

    constructor(
        address _rewards,
        address _want,
        address _tokenA,
        address _tokenB,
        address _betweenToken //token between reward token and A in swap if necessary
    )
        public
        BaseStrategy(
            _want,
            0x8b36504F2c52f59a57EE6c84d6f1b82e7698EcF0, //Fraxtal ERCFund
            FXS,
            0x39cd4db6460d8B5961F73E997E86DdbB7Ca4D5F6, //FraxswapRouter
            _rewards
        )
    {
        tokenA = _tokenA;
        tokenB = _tokenB;

        if(_betweenToken == address(0)) {
            reward_a_path = [FXS, _tokenA];
            a_b_path = [_tokenA, _tokenB];
        }
        else {
            reward_a_path = [FXS, _betweenToken, _tokenA];
            a_b_path = [_tokenA, _tokenB];
        }

        IERC20(_want).safeApprove(_rewards, uint256(-1));
        if(FXS != _tokenA && FXS != _tokenB) IERC20(FXS).safeApprove(currentRouter, uint256(-1));
        IERC20(_tokenA).safeApprove(currentRouter, uint256(-1));
        IERC20(_tokenB).safeApprove(currentRouter, uint256(-1));
    }

    // **** Views ****

    function balanceOfPool() public override view returns (uint256) {
        return IFraxFarm(rewards).lockedLiquidityOf(address(this));
    }

    function getHarvestable() external override view returns (uint256) {
        return 0; //IFraxFarm(rewards).earned(address(this))[0];
    }

    function getFeeDistToken() public override view returns (address) {
        return harvestedToken;
    }

    /* **** Mutative functions **** */

    function harvest() public override onlyHumanOrWhitelisted nonReentrant {
        _getReward();

        //distribute fee
        uint256 amountHarvested = IERC20(harvestedToken).balanceOf(address(this));
        if (amountHarvested > 0) {
            uint256 feeAmount = amountHarvested.mul(IERCFund(strategist).getFee()).div(FEE_DENOMINATOR);
            IERC20(harvestedToken).safeTransfer(strategist, feeAmount);
        }

        //Swap remaining harvested tokens for tokenA if the harvested token isn't tokenA
        if(harvestedToken != tokenA) {
            uint256 remainingHarvested = IERC20(harvestedToken).balanceOf(address(this));
            if (remainingHarvested > 0) {
                _swapUniswapWithPathPreapproved(reward_a_path, remainingHarvested);
            }
        }

        //Swap 1/2 of tokenA for tokenB
        uint256 _balanceA = IERC20(tokenA).balanceOf(address(this));
        if (_balanceA > 0) {
            _swapUniswapWithPathPreapproved(a_b_path, _balanceA.div(2));
        }

        //Add liquidity
        uint256 aBalance = IERC20(tokenA).balanceOf(address(this));
        uint256 bBalance = IERC20(tokenB).balanceOf(address(this));
        if (aBalance > 0 && bBalance > 0) {
            IUniswapRouterV2(currentRouter).addLiquidity(
                tokenA, tokenB,
                aBalance, bBalance,
                0, 0,
                address(this),
                now + 60
            );
        }

        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            LockedStake[] memory lockedStakes = IFraxFarm(rewards).lockedStakesOf(address(this));

            //Add the LP tokens to the first stake we find
            //If harvest() is called when no stakes exist, no tokens will be deposited
            for (uint256 i = 0; i < lockedStakes.length; i++){
                if (lockedStakes[i].liquidity > 0) {
                    IFraxFarm(rewards).lockAdditional(lockedStakes[i].kek_id, _want);
                    break;
                }
            }
        }
        lastHarvestTime = now;
    }

    function deposit() public override {
        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            LockedStake[] memory lockedStakes = IFraxFarm(rewards).lockedStakesOf(address(this));
            if(lockedStakes.length > 500) {
                revert("too many locked stakes");
            }
            uint256 arr_idx = uint256(-1);

            //iterate through the list of locked stakes to find the first nonzero stake (should only be one for this contract)
            for (uint256 i = 0; i < lockedStakes.length; i++){
                if (lockedStakes[i].liquidity > 0) {
                    arr_idx = i;
                    break;
                }
            }

            //if balance is 0, then create a new locked stake
            if(balanceOfPool() == 0) {
                IFraxFarm(rewards).stakeLocked(_want, 1);
            }
            //else, add to the existing stake
            else {
                IFraxFarm(rewards).lockAdditional(lockedStakes[arr_idx].kek_id, _want);
            }
        }
    }

    function _withdrawSome(uint256 _amount)
        internal
        override
        returns (uint256)
    {
        LockedStake[] memory lockedStakes = IFraxFarm(rewards).lockedStakesOf(address(this));
        uint256 arr_idx = uint256(-1);
        uint256 amtWithdrew = 0;

        //iterate through unlocked stakes to find the smallest unlocked stake where stake.liquidity >= _amount
        //this is so that as many tokens remain available for withdrawal as possible
        for (uint256 i = 0; i < lockedStakes.length; i++){
            if (_amount <= lockedStakes[i].liquidity && lockedStakes[i].ending_timestamp <= now && (arr_idx == uint256(-1) || lockedStakes[arr_idx].liquidity > lockedStakes[i].liquidity)) {
                arr_idx = i;
            }
        }
        if(arr_idx != uint256(-1)) {
            amtWithdrew = lockedStakes[arr_idx].liquidity;
            IFraxFarm(rewards).withdrawLocked(lockedStakes[arr_idx].kek_id, true);
        }

        //if unable to find a stake, then withdraw each unlocked stake and add the amount withdrawn to amtWithdrew
        if(amtWithdrew == 0) {
            for (uint256 i = 0; i < lockedStakes.length; i++){
                if (lockedStakes[i].liquidity > 0 && lockedStakes[i].ending_timestamp <= now) {
                    IFraxFarm(rewards).withdrawLocked(lockedStakes[i].kek_id, true);
                    amtWithdrew = amtWithdrew.add(lockedStakes[i].liquidity);

                    if(amtWithdrew > _amount) {
                        break;
                    }
                }
            }
        }

        require(amtWithdrew >= _amount, "Not enough unlocked tokens to withdraw");

        //Relock excess tokens here, so we don't lose yield
        if(amtWithdrew > _amount) {
            IFraxFarm(rewards).stakeLocked(amtWithdrew.sub(_amount), 1);
        }
        return _amount;
    }

    function _getReward() internal {
        IFraxFarm(rewards).getReward();
    }

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

    // Added to support recovering LP Rewards from other systems to be distributed to holders
    function salvage(address recipient, address token, uint256 amount) public onlyOwner {
        require(token != want, "cannot salvage want");
        IERC20(token).safeTransfer(recipient, amount);
    }

    //Manually withdraw locked stakes if there's an issue with withdrawing
    function withdrawLocked(bytes32 kek_id) public onlyOwner {
        IFraxFarm(rewards).withdrawLocked(kek_id, true);
    }

    function setMaxLocks(uint256 amount) public onlyOwner {
        require(amount > 0);
        maxLocks = amount;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

interface IRaGauge {

    function getReward(address account, address[] calldata tokens) external;

    function claimFees() external returns (uint256 claimed0, uint256 claimed1);

    function left(address token) external view returns (uint256);

    function rewardsListLength() external view returns (uint256);

    function rewardsList() external view returns (address[] memory);

    function earned(
        address token,
        address account
    ) external view returns (uint256);

    function balanceOf(address) external view returns (uint256);

    function notifyRewardAmount(address token, uint256 amount) external;

    struct Reward {
        uint256 rewardRate;
        uint256 periodFinish;
        uint256 lastUpdateTime;
        uint256 rewardPerTokenStored;
    }

    function rewardData(address token) external view returns (Reward memory);

    function deposit(uint256 amount) external;

    function withdraw(uint256 amount) external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

interface IRaRouter {

    struct route {
        address from;
        address to;
        bool stable;
    }

    function pairFor(
        address tokenA,
        address tokenB,
        bool stable
    ) external view returns (address pair);

    function addLiquidity(
        address tokenA,
        address tokenB,
        bool stable, //every legacy pool with significant liquidity is volatile except for one
        uint256 amountADesired,
        uint256 amountBDesired,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountA, uint256 amountB, uint256 liquidity);

    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        route[] calldata routes,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "../interfaces/IMultiFeeDistribution.sol";
import "../interfaces/IVault.sol";
import "../interfaces/IMinter.sol";
import "../interfaces/governance/IBoostHandler.sol";
import "../data/interfaces/IVaultDataStorage.sol";

interface ILockedAddyVault {
    function balanceOf(address _user) external view returns (uint256);
    function getEndingTimestamp(address _user) external view returns (uint256);
    function getRatio() external view returns (uint256);
}
//Different than the existing price calculator
interface IPriceCalculatorUSD {
    //Returns price of an asset with precision = 18 decimals
    function unsafeValueOfAsset(address asset, uint amount) external view returns (uint valueInETH, uint valueInUSD);
}
interface IAprCalculator {
    function getApr(address calculator, address _vaultAddress) external view returns (uint256);
}
interface IBoosted {
    function boostHandler() external view returns (address);
}

contract BoostHandler is Ownable, IBoostHandler {
    using SafeMath for uint256;

    uint256 public constant MAX_DURATION = 1460 days; //4 years
    uint256 public constant MAX_BOOST = 1e18; //100%

    address public multiFeeDist = 0x920f22E1e5da04504b765F8110ab96A20E6408Bd;
    address public lockedStakingPlus = 0xC5bCD23f21B6288417eB6C760F8AC0fBb4bb8a56;
    address public lockedStakingBasic = 0xfFD82F81513b207fB9D7D7835e178B6193f2cA96;
    address public minter = 0xAAE758A2dB4204E1334236Acd6E6E73035704921;

    address public calculator = 0x47A4EB1a75abdB4f9FD1aE8eF11945f3B352E01F;
    //address public stablecoinCalculator = 0x85eA5d3fDDCe5009fb69A8fb445b80314c7B5fB9;

    address public aprCalcAggregator = 0x6E97FBc2718648D62180D50f525802C2d32EF50f;

    address public vaultDataWrapper = 0x87Fa4456898819373D2446E463DBB5602B3641F6;

    // **** Views **** //

    function getBoost(address _user, address _vaultAddress) external override view returns (uint256) {
        //Show user a warning if the boost would be 0, if boost is 0 when their veADDY is > 0?
        uint256 valueOfUserStakeUSD = getUserValueStaked(_user, _vaultAddress);
        if(valueOfUserStakeUSD == 0) {
            return 0;
        }

        valueOfUserStakeUSD = valueOfUserStakeUSD.add(getTotalValueStakedInSimilarVaults(_user, _vaultAddress));
        uint256 veAddy = getTotalVeAddy(_user);
        if(veAddy == 0) {
            return 0;
        }

        //boost = doubleLimit / valueStaked
        //if doubleLimit >= valueStaked, then boost is max boost, which is +100%
        uint256 doubleLimit = getDoubleLimitModified(_user, _vaultAddress);
        if(doubleLimit >= valueOfUserStakeUSD) {
            return MAX_BOOST;
        }

        uint256 valueBoosted = doubleLimit.mul(10 ** 18);
        uint256 boost = valueBoosted.div(valueOfUserStakeUSD);
        if(boost > MAX_BOOST) { //shouldn't be possible, but just in case
            return MAX_BOOST;
        }
        return boost;
    }

    //Returns ADDY earning boost based on user's veADDY if the user was to stake in vaultAddress, boost is divided by 1e18
    function getBoostForValueStaked(address _user, address _vaultAddress, uint256 valueStaked) external override view returns (uint256) {
        //Show user a warning if the boost would be 0, if boost is 0 when their veADDY is > 0?
        uint256 valueOfUserStakeUSD = getUserValueStaked(_user, _vaultAddress);
        valueOfUserStakeUSD = valueOfUserStakeUSD.add(getTotalValueStakedInSimilarVaults(_user, _vaultAddress)).add(valueStaked);

        uint256 veAddy = getTotalVeAddy(_user);
        if(veAddy == 0) {
            return 0;
        }

        uint256 doubleLimit = getDoubleLimitModified(_user, _vaultAddress);
        if(doubleLimit >= valueOfUserStakeUSD) {
            return MAX_BOOST;
        }

        uint256 valueBoosted = doubleLimit.mul(10 ** 18);
        uint256 boost = valueBoosted.div(valueOfUserStakeUSD);
        if(boost > MAX_BOOST) { //shouldn't be possible, but just in case
            return MAX_BOOST;
        }
        return boost;
    }

    function getDoubleLimitModified(address _user, address _vaultAddress) internal view returns (uint256) {
        uint256 doubleLimit = getDoubleLimit(_user);
        uint256 apr = IAprCalculator(aprCalcAggregator).getApr(calculator, _vaultAddress);

        /*
        If the APR is above:
        50%: -10% boosting power
        100%: -20% boosting power
        200%: -30% boosting power
        300%: -40% boosting power
        500%: -50% boosting power
        */

        if(apr >= 5 * 10 ** 18) {
            return doubleLimit.div(2);
        }
        if(apr >= 3 * 10 ** 18) {
            return doubleLimit.mul(60).div(100);
        }
        if(apr >= 2 * 10 ** 18) {
            return doubleLimit.mul(70).div(100);
        }
        if(apr >= 1 * 10 ** 18) {
            return doubleLimit.mul(80).div(100);
        }
        if(apr >= 5 * 10 ** 17) {
            return doubleLimit.mul(90).div(100);
        }
        return doubleLimit;
    }

    function getUserValueStaked(address _user, address _vaultAddress) public view returns (uint256) {
        uint256 userBal = IVault(_vaultAddress).balanceOf(_user);
        if(userBal == 0) {
            return 0;
        }

        userBal = userBal.mul(IJar(_vaultAddress).getRatio()).div(10 ** 18);

        (, uint256 valueOfUserStakeUSD) = IPriceCalculatorUSD(calculator).unsafeValueOfAsset(address(IVault(_vaultAddress).token()), userBal);
        if(valueOfUserStakeUSD == 0) {
            return 0;
        }
        return valueOfUserStakeUSD;
    }

    //Returns the total value staked in vaults similar to _vaultAddress
    //i.e. if _vaultAddress is a WETH/WMATIC vault then it returns the total value the user has staked in all WETH/WMATIC vaults
    function getTotalValueStakedInSimilarVaults(address _user, address _vaultAddress) public view returns (uint256 totalStaked) {
        address[] memory similarVaults = IVaultDataStorage(vaultDataWrapper).getSimilarVaults(_vaultAddress);

        for (uint i = 0; i < similarVaults.length; i++) {
            if(similarVaults[i] != address(0) && similarVaults[i] != _vaultAddress && isBoosted(similarVaults[i])) {
                totalStaked = totalStaked.add(getUserValueStaked(_user, similarVaults[i]));
            }
        }
    }

    //Returns the value in USD for which a user's ADDY earnings are doubled
    //Limit = veADDY * boost weight / 2
    function getDoubleLimit(address _user) public override view returns (uint256 doubleLimit) {
        //Boost weight is influenced by total market cap, emissions rate, total supply
        return getTotalVeAddy(_user).mul(IMinter(minter).addyPerProfitEth().div(2));
    }

    function getTotalVeAddy(address _user) public override view returns (uint256) {
        return getVeAddyFromLockedStaking(_user).add(getVeAddyFromLockedStakingBasic(_user));
    }

    //Returns veADDY from the "locked staking plus" contract (LockedAddyVault)
    function getVeAddyFromLockedStaking(address _user) public override view returns (uint256 veAddy) {
        uint256 endingTimestampPlus = ILockedAddyVault(lockedStakingPlus).getEndingTimestamp(_user);
        if(endingTimestampPlus > now) {
            //1 veAddy = 4 year lock, boost degrades over time
            uint256 diff = endingTimestampPlus.sub(now);
            uint256 userAddyLocked = ILockedAddyVault(lockedStakingPlus).balanceOf(_user);
            uint256 ratio = ILockedAddyVault(lockedStakingPlus).getRatio();
            veAddy = veAddy.add(userAddyLocked.mul(diff).div(MAX_DURATION).mul(ratio).div(10 ** 18));
        }
    }

    //Returns veADDY from the "locked staking basic" contract (StakingDualRewards)
    //The "locked staking basic" contract has 50% of the weight of the "plus" contract
    function getVeAddyFromLockedStakingBasic(address _user) public view returns (uint256 veAddy) {
        uint256 endingTimestampBasic = ILockedAddyVault(lockedStakingBasic).getEndingTimestamp(_user);
        if(endingTimestampBasic > now) {
            //1 veAddy = 4 year lock, boost degrades over time
            uint256 diff = endingTimestampBasic.sub(now);
            uint256 userAddyLocked = ILockedAddyVault(lockedStakingBasic).balanceOf(_user);
            veAddy = veAddy.add(userAddyLocked.mul(diff).div(MAX_DURATION)).div(2);
        }
    }

    function isBoosted(address _vaultAddress) public view returns (bool) {
        try IBoosted(_vaultAddress).boostHandler() {
            return IBoosted(_vaultAddress).boostHandler() != address(0);
        }
        catch {
            return false;
        }
    }

    // **** State Mutations ****

    //used if a token migration happens, which would require a new fee dist contract
    function setFeeDist(address _feeDist) public override onlyOwner {
        require(_feeDist != address(0));
        multiFeeDist = _feeDist;
    }

    function setLockedStakingVault(address _address) public onlyOwner {
        require(_address != address(0));
        lockedStakingPlus = _address;
    }

    function setLockedStakingBasic(address _address) public onlyOwner {
        require(_address != address(0));
        lockedStakingBasic = _address;
    }

    function setCalculator(address _calculator) public onlyOwner {
        require(_calculator != address(0));
        calculator = _calculator;
    }

    function setAprCalcAggregator(address _aggregator) public onlyOwner {
        require(_aggregator != address(0));
        aprCalcAggregator = _aggregator;
    }

    function setVaultDataWrapper(address _wrapper) public onlyOwner {
        require(_wrapper != address(0));
        vaultDataWrapper = _wrapper;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/access/Ownable.sol";
import "../interfaces/governance/IBoostHandler.sol";

//A proxy contract for the reward boost handler contract, so I can change the implementation over time
contract BoostHandlerProxy is Ownable, IBoostHandler {

    IBoostHandler public boostHandler;

    constructor(address _handler) public {
        boostHandler = IBoostHandler(_handler);
    }

    // **** Views **** //

    function getBoost(address _user, address _vaultAddress) external override view returns (uint256) {
        return boostHandler.getBoost(_user, _vaultAddress);
    }

    function getBoostForValueStaked(address _user, address _vaultAddress, uint256 valueStaked) external override view returns (uint256) {
        return boostHandler.getBoostForValueStaked(_user, _vaultAddress, valueStaked);
    }

    function getDoubleLimit(address _user) public override view returns (uint256 doubleLimit) {
        return boostHandler.getDoubleLimit(_user);
    }

    function getTotalVeAddy(address _user) external override view returns (uint256) {
        return boostHandler.getTotalVeAddy(_user);
    }

    function getVeAddyFromLockedStaking(address _user) external override view returns (uint256) {
        return boostHandler.getVeAddyFromLockedStaking(_user);
    }

    // **** State Mutations ****

    //used if a token migration happens, which would require a new fee dist contract
    function setFeeDist(address _feeDist) public override onlyOwner {
        boostHandler.setFeeDist(_feeDist);
        emit SetFeeDist(_feeDist);
    }

    function setBoostHandler(address _handler) public onlyOwner {
        boostHandler = IBoostHandler(_handler);
        emit SetHandler(_handler);
    }

    event SetHandler(address indexed handler);
    event SetFeeDist(address indexed feeDist);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/math/Math.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";

import "../../interfaces/uniswap/IUniswapV2Pair.sol";
import "../../interfaces/uniswap/IUniswapV2Factory.sol";
import "../../interfaces/IAggregatorInterface.sol";

interface IPriceCalculator {
    struct ReferenceData {
        uint lastData;
        uint lastUpdated;
    }

    function priceOfETH() view external returns (uint);
    function unsafeValueOfAsset(address asset, uint amount) external view returns (uint valueInETH, uint valueInUSD);
}

contract PriceCalculatorArbitrum is Ownable, IPriceCalculator {
    using SafeMath for uint;

    address private immutable ETH;

    IUniswapV2Factory private immutable defaultFactory;

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

    mapping(address => address) private pairTokens;
    mapping(address => address) private tokenFeeds;
    mapping(address => ReferenceData) public references;

    //Hashes of Uniswap LP token symbols
    mapping(bytes32 => bool) private symbolHashes;

    //Mapping of assets to Uniswap factories
    mapping(address => address) private assetToFactories;

    //Mapping of assets to calculator contracts
    mapping(address => address) private specialCalculators;

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

    //At least one oracle must be used, the ETH oracle
    //Subclasses should set up additional oracles, pair tokens, and factories
    constructor(address _defaultFactory, address _eth, address _ethOracle) public {
        tokenFeeds[_eth] = _ethOracle;
        ETH = _eth;
        defaultFactory = IUniswapV2Factory(_defaultFactory);
    }

    /* ========== Restricted Operation ========== */

    function setPairToken(address asset, address pairToken) public onlyOwner {
        pairTokens[asset] = pairToken;
    }

    function setTokenFeed(address asset, address feed) public onlyOwner {
        tokenFeeds[asset] = feed;
    }

    function setPrices(address[] memory assets, uint[] memory prices) external onlyOwner {
        for (uint i = 0; i < assets.length; i++) {
            references[assets[i]] = ReferenceData({lastData : prices[i], lastUpdated : block.timestamp});
        }
    }

    function setSpecialCalculator(address _asset, address _calc) public onlyOwner {
        specialCalculators[_asset] = _calc;
    }

    function setSymbolHash(string memory symbol, bool isValid) public onlyOwner {
        symbolHashes[keccak256(abi.encodePacked(symbol))] = isValid;
    }

    function setAssociatedFactory(address _token, address _factory) public onlyOwner {
        assetToFactories[_token] = _factory;
    }

    /* ========== Value Calculation ========== */

    function priceOfETH() view public override returns (uint) {
        int256 answer = IAggregatorInterface(tokenFeeds[ETH]).latestAnswer();
        return uint(answer).mul(1e10);
    }

    function unsafeValueOfAsset(address asset, uint amount) public override view returns (uint valueInETH, uint valueInUSD) {
        valueInUSD = 0;
        valueInETH = 0;

        if(tokenFeeds[asset] != address(0)) {
            (valueInETH, valueInUSD) = _oracleValueOf(asset, amount);
        }
        //Assumption: there will be an ETH oracle
        else if (asset == address(0)) {
            (valueInETH, valueInUSD) = _oracleValueOf(ETH, amount);
        }
        //Check if the asset is a Uniswap or Uniswap fork LP token
        else if (symbolHashes[keccak256(abi.encodePacked(IUniswapV2Pair(asset).symbol()))]) {
            if (IUniswapV2Pair(asset).totalSupply() == 0) return (0, 0);

            (uint reserve0, uint reserve1,) = IUniswapV2Pair(asset).getReserves();
            if (IUniswapV2Pair(asset).token0() == ETH) {
                valueInETH = amount.mul(reserve0).mul(2).div(IUniswapV2Pair(asset).totalSupply());
                valueInUSD = valueInETH.mul(priceOfETH()).div(1e18);
            }
            else if (IUniswapV2Pair(asset).token1() == ETH) {
                valueInETH = amount.mul(reserve1).mul(2).div(IUniswapV2Pair(asset).totalSupply());
                valueInUSD = valueInETH.mul(priceOfETH()).div(1e18);
            }
            else {
                (uint priceInETH,) = unsafeValueOfAsset(IUniswapV2Pair(asset).token0(), 1e18);
                if (priceInETH == 0) {
                    (priceInETH,) = unsafeValueOfAsset(IUniswapV2Pair(asset).token1(), 1e18);
                    reserve1 = reserve1.mul(10 ** uint(uint8(18) - ERC20(IUniswapV2Pair(asset).token1()).decimals()));
                    valueInETH = amount.mul(reserve1).mul(2).mul(priceInETH).div(1e18).div(IUniswapV2Pair(asset).totalSupply());
                }
                else {
                    reserve0 = reserve0.mul(10 ** uint(uint8(18) - ERC20(IUniswapV2Pair(asset).token0()).decimals()));
                    valueInETH = amount.mul(reserve0).mul(2).mul(priceInETH).div(1e18).div(IUniswapV2Pair(asset).totalSupply());
                }
                valueInUSD = valueInETH.mul(priceOfETH()).div(1e18);
            }
        }
        else if(specialCalculators[asset] != address(0)) {
            (valueInETH, valueInUSD) = IPriceCalculator(specialCalculators[asset]).unsafeValueOfAsset(asset, amount);
        }
        else {
            address pairToken = pairTokens[asset] == address(0) ? ETH : pairTokens[asset];

            address pair = _getFactory(asset).getPair(asset, pairToken);
            if(pair == address(0)) {
                return (0, 0);
            }

            address token0 = IUniswapV2Pair(pair).token0();
            address token1 = IUniswapV2Pair(pair).token1();

            if (IERC20(asset).balanceOf(pair) == 0) return (0, 0);

            (uint reserve0, uint reserve1,) = IUniswapV2Pair(pair).getReserves();

            if (ERC20(token0).decimals() < uint8(18)){
                reserve0 = reserve0.mul(10 ** uint(uint8(18) - ERC20(token0).decimals()));
            }

            if (ERC20(token1).decimals() < uint8(18)) {
                reserve1 = reserve1.mul(10 ** uint(uint8(18) - ERC20(token1).decimals()));
            }

            if (token0 == pairToken) {
                valueInETH = reserve0.mul(amount).div(reserve1);
            } else if (token1 == pairToken) {
                valueInETH = reserve1.mul(amount).div(reserve0);
            } else {
                return (0, 0);
            }

            if (pairToken != ETH) {
                (uint pairValueInETH,) = unsafeValueOfAsset(pairToken, 1e18);
                valueInETH = valueInETH.mul(pairValueInETH).div(1e18);
            }
            valueInUSD = valueInETH.mul(priceOfETH()).div(1e18);
        }
    }

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

    function _oracleValueOf(address asset, uint amount) private view returns (uint valueInETH, uint valueInUSD) {
        valueInUSD = 0;
        if (tokenFeeds[asset] != address(0)) {
            int256 answer = IAggregatorInterface(tokenFeeds[asset]).latestAnswer();
            valueInUSD = uint(answer).mul(1e10).mul(amount).div(1e18);
        }
        else if (references[asset].lastUpdated > block.timestamp.sub(1 days)) {
            valueInUSD = references[asset].lastData.mul(amount).div(1e18);
        }
        valueInETH = valueInUSD.mul(1e18).div(priceOfETH());
    }

    //If an oracle exists for the other token in the pair, then the price of the LP token can be returned without using this function
    //This function is needed for certain pairs where no oracle exists, and for returning the price of reward tokens in the apr calculators
    function _getFactory(address asset) private view returns (IUniswapV2Factory factory) {
        if(assetToFactories[asset] != address(0)) {
            return IUniswapV2Factory(assetToFactories[asset]);
        }
        return defaultFactory;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/math/Math.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";

import "../../interfaces/IAggregatorInterface.sol";

interface IPriceCalculator {
    function unsafeValueOfAsset(address asset, uint amount) external view returns (uint valueInETH, uint valueInUSD);
}
struct RewardData {
    address token;
    uint256 rewardRate;
    uint256 periodFinish;
}
struct UnderlyingTokenData {
    uint256 balance0;
    uint256 decimals0;
    uint256 price0;

    uint256 balance1;
    uint256 decimals1;
    uint256 price1;

    uint256 balance2;
    uint256 decimals2;
    uint256 price2;

    uint256 balance3;
    uint256 decimals3;
    uint256 price3;
}
struct CurvePoolData {
    uint256 virtualPrice;
    uint256 lastUpdateTime;

    RewardData[] rewardData;
    UnderlyingTokenData underlyingTokenData;
}
interface ICurveHelper {
    function getPoolInfo() external view returns (CurvePoolData memory);
}
contract CurveCalculatorArbitrum is Ownable, IPriceCalculator {
    using SafeMath for uint;

    address private immutable ETH;

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

    mapping(address => address) private tokenFeeds;
    mapping(address => address) public helpers; //map asset to helper address

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

    //At least one oracle must be used, the ETH oracle
    //subclasses should declare helpers
    constructor(address _eth, address _ethOracle) public {
        tokenFeeds[_eth] = _ethOracle;
        ETH = _eth;
        //helpers[0x8e0B8c8BB9db49a46697F3a5Bb8A308e744821D2] = 0xd5feA1cB361fFcd91B51243fB51e7d9361eeC0Ac;
        //helpers[0x7f90122BF0700F9E7e1F688fe926940E8839F353] = 0xfc11E0E5AeD43d009bbeb9b5Bb669f40fF9243B7;
        //helpers[0x3E01dD8a5E1fb3481F0F589056b428Fc308AF0Fb] = 0x6629017c19c0F2eB776440EAA1f75eadd6b43605;
    }

    /* ========== Restricted Operation ========== */

    function setHelper(address stakingRewards, address helper) public onlyOwner {
        helpers[stakingRewards] = helper;
    }

    function setTokenFeed(address asset, address feed) public onlyOwner {
        tokenFeeds[asset] = feed;
    }

    /* ========== Value Calculation ========== */

    function priceOfETH() view public returns (uint) {
        int256 answer = IAggregatorInterface(tokenFeeds[ETH]).latestAnswer();
        return uint(answer).mul(1e10);
    }

    //Not used on Arbitrum
    function isSupportedAsset(address _asset) view external returns (bool) {
        return helpers[_asset] != address(0);
    }

    function unsafeValueOfAsset(address asset, uint amount) public override view returns (uint valueInETH, uint valueInUSD) {
        CurvePoolData memory data = ICurveHelper(helpers[asset]).getPoolInfo();

        uint256 tvl = 0;

        if(data.underlyingTokenData.decimals0 > 0) {
            uint256 price0 = data.underlyingTokenData.price0 > 0 ? data.underlyingTokenData.price0: 1e18;
            tvl = tvl.add(data.underlyingTokenData.balance0.mul(10 ** uint(uint8(18) - data.underlyingTokenData.decimals0)).mul(price0));
        }
        if(data.underlyingTokenData.decimals1 > 0) {
            uint256 price1 = data.underlyingTokenData.price1 > 0 ? data.underlyingTokenData.price1: 1e18;
            tvl = tvl.add(data.underlyingTokenData.balance1.mul(10 ** uint(uint8(18) - data.underlyingTokenData.decimals1)).mul(price1));
        }
        if(data.underlyingTokenData.decimals2 > 0) {
            uint256 price2 = data.underlyingTokenData.price2 > 0 ? data.underlyingTokenData.price2: 1e18;
            tvl = tvl.add(data.underlyingTokenData.balance2.mul(10 ** uint(uint8(18) - data.underlyingTokenData.decimals2)).mul(price2));
        }
        if(data.underlyingTokenData.decimals3 > 0) {
            uint256 price3 = data.underlyingTokenData.price3 > 0 ? data.underlyingTokenData.price3: 1e18;
            tvl = tvl.add(data.underlyingTokenData.balance3.mul(10 ** uint(uint8(18) - data.underlyingTokenData.decimals3)).mul(price3));
        }

        valueInUSD = tvl.div(ERC20(asset).totalSupply()).mul(amount).div(10 ** uint(ERC20(asset).decimals()));
        valueInETH = valueInUSD.mul(1e18).div(priceOfETH());
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/access/Ownable.sol";
import "../../interfaces/IVault.sol";
import "../../interfaces/IStrategy.sol";

interface IAprCalculator {
    function getApr(address calculator, address _vaultAddress) external view returns (uint256);
}

contract AprCalculatorAggregator is Ownable {

    mapping(address => address) public vaultAprCalculators;
    mapping(address => address) public rewardCalcMap;

    constructor() public {
        //rewardCalcMap[DINO_CHEF] = 0x2EB5f9565bF9725833d0590B205895A713BE3F26;
        //rewardCalcMap[QI_CHEF] = 0xe59990411951E6969428002321cCFa32235B1515;
    }

    // **** Views **** //

    function getApr(address calculator, address _vaultAddress) public view returns (uint256) {
        if(vaultAprCalculators[_vaultAddress] != address(0)) {
            return IAprCalculator(vaultAprCalculators[_vaultAddress]).getApr(calculator, _vaultAddress);
        }

        address rewards = IStrategy(IVault(_vaultAddress).strategy()).rewards();
        if(rewardCalcMap[rewards] != address(0)) {
            return IAprCalculator(rewardCalcMap[rewards]).getApr(calculator, _vaultAddress);
        }
        //don't use this contract for Polygon because some older strategies don't have getHarvestedToken()
        else if(rewardCalcMap[IStrategy(IVault(_vaultAddress).strategy()).getHarvestedToken()] != address(0)) {
            return IAprCalculator(rewardCalcMap[IStrategy(IVault(_vaultAddress).strategy()).getHarvestedToken()]).getApr(calculator, _vaultAddress);
        }
        return 0;
    }

    function test(address calculator, address[] memory vaults) external view returns (uint256[] memory rates) {
        uint256 length = vaults.length;
        rates = new uint256[](length);
        for(uint256 i = 0; i < length; i++) {
            rates[i] = getApr(calculator, vaults[i]);
        }
    }

    //function removed from Arbitrum calculator, will need to do one call per address to `vaultAprCalculators`
    function getCalculators(address[] memory vaults) external view returns (address[] memory calculatorAddresses) {
        uint256 length = vaults.length;
        calculatorAddresses = new address[](length);
        for(uint256 i = 0; i < length; i++) {
            calculatorAddresses[i] = vaultAprCalculators[vaults[i]];
        }
    }

    // **** Restricted **** //

    function setAprCalculator(address _vaultAddress, address calculator) public onlyOwner {
        vaultAprCalculators[_vaultAddress] = calculator;
    }

    function setRewardCalculator(address rewardAddress, address calculator) public onlyOwner {
        rewardCalcMap[rewardAddress] = calculator;
    }
}

pragma solidity ^0.6.12;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract Addy is ERC20, Ownable {

    address public creator;

    constructor() public ERC20('Adamant Token (Arbitrum)', 'ARBY') {
        _mint(msg.sender, 1 * 10**18);
        creator = msg.sender;
    }

    function mint(address recipient_, uint256 amount_)
        public
        onlyOwner
        returns (bool)
    {
        uint256 balanceBefore = balanceOf(recipient_);
        _mint(recipient_, amount_);
        uint256 balanceAfter = balanceOf(recipient_);

        return balanceAfter > balanceBefore;
    }

    function transfer(address recipient, uint256 amount) public override returns (bool) {
        require(recipient != address(this));
        return super.transfer(recipient, amount);
    }

    //Transfer any tokens accidentally sent to the ADDY address to the owner so it can be returned
    //Note: any tokens sent from an exchange address or contract will be sent to the fee dist fund instead
    function salvage(address token) external {
        require(msg.sender == creator, "not creator");

        uint256 _token = IERC20(token).balanceOf(address(this));
        if (_token > 0) {
            IERC20(token).transfer(msg.sender, _token);
        }
    }
}

// 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/access/Ownable.sol";

import "../interfaces/uniswap/IUniswapV2Pair.sol";
import "../interfaces/uniswap/IUniswapRouterV2.sol";
import "../interfaces/IMultiFeeDistribution.sol";

//Contract where the fees are sent to before they are converted and sent to the feeDistributor contract
contract ERCFund is Ownable {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    uint256 public constant feeMAX = 3000;

    uint256 public fee = 3000;
    address public feeDistributor;
    bool public feeSharingEnabled = true;
    mapping(address => bool) public converters;

    constructor(address distributor) public {
        feeDistributor = distributor;
    }

    function notifyFeeDistribution(address token) public {
        uint256 balance = IERC20(token).balanceOf(address(this));

        IERC20(token).safeApprove(feeDistributor, 0);
        IERC20(token).safeApprove(feeDistributor, balance);
        IMultiFeeDistribution(feeDistributor).notifyRewardAmount(token, balance);
    }

    //Doesn't support Fee on Transfer tokens, convert those to something else first
    //Transfer token from sender, then transfers it to the fee distributor
    function depositToFeeDistributor(address token, uint256 amount) public {
        IERC20(token).safeTransferFrom(msg.sender, address(this), amount);

        IERC20(token).safeApprove(feeDistributor, 0);
        IERC20(token).safeApprove(feeDistributor, amount);
        IMultiFeeDistribution(feeDistributor).notifyRewardAmount(token, amount);
    }

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

    function feeShareEnabled() external view returns (bool) {
        return feeSharingEnabled;
    }

    function getFee() external view returns (uint256) {
        return fee;
    }

    /* ========== SETTER FUNCTIONS ========== */

    function setFeeDistributor(address distributor) public onlyOwner {
        feeDistributor = distributor;
    }

    function setFeeSharingEnabled(bool enabled) public onlyOwner {
        feeSharingEnabled = enabled;
    }

    function setFee(uint256 _fee) public onlyOwner {
        require(_fee <= feeMAX);
        fee = _fee;
    }

    function setConverter(address _converter, bool allowed) public onlyOwner {
        converters[_converter] = allowed;
    }

    /* ========== EMERGENCY FUNCTIONS ========== */

    //Used by the fee converter
    function recover(address token) public {
        require(converters[msg.sender], "not allowed");

        uint256 _token = IERC20(token).balanceOf(address(this));
        if (_token > 0) {
            IERC20(token).safeTransfer(msg.sender, _token);
            emit Recovered(token, _token);
        }
    }

    // **** Events ****
    event Recovered(address indexed tokenWithdrew, uint256 _amount);
    event Notified(address indexed tokenDeposited);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "../interfaces/ICalculator.sol";
import "../interfaces/IMultiFeeDistribution.sol";

contract Minter is Ownable {
    using SafeMath for uint256;

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

    mapping(address => bool) private _minters;
    address public calculator;
    address public feeDistribution;
    address public dev;

    uint256 public addyPerProfitEth = 100;

    /* ========== CONSTRUCTOR ========== */

    constructor(address _dev, address _calculator)
        public
    {
        dev = _dev;
        calculator = _calculator;
    }

    /* ========== MODIFIERS ========== */

    modifier onlyMinter {
        require(isMinter(msg.sender) == true, "AddyMinter: caller is not the minter");
        _;
    }

    function mintFor(address user, address asset, uint256 amount) external onlyMinter {
        uint256 valueInEth = ICalculator(calculator).valueOfAsset(asset, amount);

        uint256 mintAddy = amountAddyToMint(valueInEth);
        if (mintAddy == 0) return;
        IMultiFeeDistribution(feeDistribution).mint(user, mintAddy);
        //For every 100 tokens minted, 15 additional tokens will go towards development to ensure rapid innovation.
        IMultiFeeDistribution(feeDistribution).mint(dev, mintAddy.mul(15).div(100));
    }

    /* ========== VIEWS ========== */

    function isMinter(address account) public view returns (bool) {
        return _minters[account];
    }

    function amountAddyToMint(uint256 ethProfit) public view returns (uint256) {
        return ethProfit.mul(addyPerProfitEth);
    }

    function getAddyMinted(address asset, uint256 amount) public view returns (uint256) {
        return amountAddyToMint(ICalculator(calculator).valueOfAsset(asset, amount));
    }

    /* ========== RESTRICTED FUNCTIONS ========== */

    /// @dev Obviously should be timelocked
    function setAddyPerProfitEth(uint256 _ratio) external onlyOwner {
        addyPerProfitEth = _ratio;
    }

    /// @dev Should be timelocked, potential minting amount manipulation risk
    function setCalculator(address newCalculator) public onlyOwner {
        calculator = newCalculator;
    }

    function setFeeDistribution(address newFeeDistribution) public onlyOwner {
        feeDistribution = newFeeDistribution;
    }

    /// @dev Obviously should be timelocked
    function setMinter(address minter, bool canMint) external onlyOwner {
        if (canMint) {
            _minters[minter] = canMint;
        } else {
            delete _minters[minter];
        }
    }
}

// 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/Math.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";


interface IMintableToken is IERC20 {
    function mint(address _receiver, uint256 _amount) external returns (bool);
}

// EPS Staking contract for http://ellipsis.finance/
// EPS staked within this contact entitles stakers to a portion of the admin fees generated by Ellipsis' AMM contracts
// Based on SNX MultiRewards by iamdefinitelyahuman - https://github.com/iamdefinitelyahuman/multi-rewards
contract MultiFeeDistribution2 is ReentrancyGuard, Ownable {

    using SafeMath for uint256;
    using SafeERC20 for IERC20;
    using SafeERC20 for IMintableToken;

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

    struct Reward {
        uint256 periodFinish;
        uint256 rewardRate;
        uint256 lastUpdateTime;
        uint256 rewardPerTokenStored;
    }
    struct Balances {
        uint256 total;
        uint256 unlocked;
        uint256 locked;
        uint256 earned;
    }
    struct LockedBalance {
        uint256 amount;
        uint256 unlockTime;
    }
    struct RewardData {
        address token;
        uint256 amount;
    }

    IMintableToken public stakingToken;
    address[] public rewardTokens;
    mapping(address => Reward) public rewardData;

    // Duration that rewards are streamed over
    uint256 public constant rewardsDuration = 86400 * 7;

    // Duration of lock/earned penalty period
    uint256 public constant lockDuration = rewardsDuration * 13;

    // Addresses approved to call mint
    mapping(address => bool) public minters;
    // reward token -> distributor -> is approved to add rewards
    mapping(address=> mapping(address => bool)) public rewardDistributors;
    // Addresses approved to lock stakes
    mapping(address => bool) public lockedStakeWhitelist;

    // user -> reward token -> amount
    mapping(address => mapping(address => uint256)) public userRewardPerTokenPaid;
    mapping(address => mapping(address => uint256)) public rewards;

    uint256 public totalSupply;
    uint256 public lockedSupply;

    // Private mappings for balance data
    mapping(address => Balances) balances;
    mapping(address => LockedBalance[]) userLocks;
    mapping(address => LockedBalance[]) userEarnings;

    /* ========== CONSTRUCTOR ========== */

    constructor(
        address _stakingToken,
        address[] memory _minters
    ) public Ownable() {
        stakingToken = IMintableToken(_stakingToken);
        for (uint i; i < _minters.length; i++) {
            minters[_minters[i]] = true;
        }
        // First reward MUST be the staking token or things will break
        // related to the 50% penalty and distribution to locked balances
        rewardTokens.push(_stakingToken);
        rewardData[_stakingToken].lastUpdateTime = block.timestamp;
    }

    /* ========== ADMIN CONFIGURATION ========== */

    // Add a new reward token to be distributed to stakers
    function addReward(
        address _rewardsToken,
        address _distributor
    )
        public
        onlyOwner
    {
        require(rewardData[_rewardsToken].lastUpdateTime == 0);
        rewardTokens.push(_rewardsToken);
        rewardData[_rewardsToken].lastUpdateTime = block.timestamp;
        rewardDistributors[_rewardsToken][_distributor] = true;
    }

    // Modify approval for an address to call notifyRewardAmount
    function approveRewardDistributor(
        address _rewardsToken,
        address _distributor,
        bool _approved
    ) external onlyOwner {
        require(rewardData[_rewardsToken].lastUpdateTime > 0);
        rewardDistributors[_rewardsToken][_distributor] = _approved;
    }

    function setLockedStakeContract(
        address _address,
        bool _approved
    )
        public
        onlyOwner
    {
        lockedStakeWhitelist[_address] = _approved;
    }

    /* ========== VIEWS ========== */

    function _rewardPerToken(address _rewardsToken, uint256 _supply) internal view returns (uint256) {
        if (_supply == 0) {
            return rewardData[_rewardsToken].rewardPerTokenStored;
        }
        return
            rewardData[_rewardsToken].rewardPerTokenStored.add(
                lastTimeRewardApplicable(_rewardsToken).sub(
                    rewardData[_rewardsToken].lastUpdateTime).mul(
                        rewardData[_rewardsToken].rewardRate).mul(1e18).div(_supply)
            );
    }

    function _earned(
        address _user,
        address _rewardsToken,
        uint256 _balance,
        uint256 supply
    ) internal view returns (uint256) {
        return _balance.mul(
            _rewardPerToken(_rewardsToken, supply).sub(userRewardPerTokenPaid[_user][_rewardsToken])
        ).div(1e18).add(rewards[_user][_rewardsToken]);
    }

    function lastTimeRewardApplicable(address _rewardsToken) public view returns (uint256) {
        return Math.min(block.timestamp, rewardData[_rewardsToken].periodFinish);
    }

    function rewardPerToken(address _rewardsToken) external view returns (uint256) {
        uint256 supply = _rewardsToken == address(stakingToken) ? lockedSupply : totalSupply;
        return _rewardPerToken(_rewardsToken, supply);

    }

    function getRewardForDuration(address _rewardsToken) external view returns (uint256) {
        return rewardData[_rewardsToken].rewardRate.mul(rewardsDuration);
    }

    // Address and claimable amount of all reward tokens for the given account
    function claimableRewards(address account) external view returns (RewardData[] memory rewards) {
        rewards = new RewardData[](rewardTokens.length);
        for (uint256 i = 0; i < rewards.length; i++) {
            // If i == 0 this is the stakingReward, distribution is based on locked balances
            uint256 balance = i == 0 ? balances[account].locked : balances[account].total;
            uint256 supply = i == 0 ? lockedSupply : totalSupply;
            rewards[i].token = rewardTokens[i];
            rewards[i].amount = _earned(account, rewards[i].token, balance, supply);
        }
        return rewards;
    }

    // Total balance of an account, including unlocked, locked and earned tokens
    function totalBalance(address user) view external returns (uint256 amount) {
        return balances[user].total;
    }

    // Total withdrawable balance for an account to which no penalty is applied
    function unlockedBalance(address user) view external returns (uint256 amount) {
        amount = balances[user].unlocked;
        LockedBalance[] storage earnings = userEarnings[msg.sender];
        for (uint i = 0; i < earnings.length; i++) {
            if (earnings[i].unlockTime > block.timestamp) {
                break;
            }
            amount = amount.add(earnings[i].amount);
        }
        return amount;
    }

    // Information on the "earned" balances of a user
    // Earned balances may be withdrawn immediately for a 50% penalty
    function earnedBalances(
        address user
    ) view external returns (
        uint256 total,
        LockedBalance[] memory earningsData
    ) {
        LockedBalance[] storage earnings = userEarnings[user];
        uint256 idx;
        for (uint i = 0; i < earnings.length; i++) {
            if (earnings[i].unlockTime > block.timestamp) {
                if (idx == 0) {
                    earningsData = new LockedBalance[](earnings.length - i);
                }
                earningsData[idx] = earnings[i];
                idx++;
                total = total.add(earnings[i].amount);
            }
        }
        return (total, earningsData);
    }

    // Information on a user's locked balances
    function lockedBalances(
        address user
    ) view external returns (
        uint256 total,
        uint256 unlockable,
        uint256 locked,
        LockedBalance[] memory lockData
    ) {
        LockedBalance[] storage locks = userLocks[user];
        uint256 idx;
        for (uint i = 0; i < locks.length; i++) {
            if (locks[i].unlockTime > block.timestamp) {
                if (idx == 0) {
                    lockData = new LockedBalance[](locks.length - i);
                }
                lockData[idx] = locks[i];
                idx++;
                locked = locked.add(locks[i].amount);
            } else {
                unlockable = unlockable.add(locks[i].amount);
            }
        }
        return (balances[user].locked, unlockable, locked, lockData);
    }

    // Final balance received and penalty balance paid by user upon calling exit
    function withdrawableBalance(
        address user
    ) view public returns (
        uint256 amount,
        uint256 penaltyAmount
    ) {
        Balances storage bal = balances[user];
        if (bal.earned > 0) {
            uint256 amountWithoutPenalty;
            uint256 length = userEarnings[user].length;
            for (uint i = 0; i < length; i++) {
                uint256 earnedAmount = userEarnings[user][i].amount;
                if (earnedAmount == 0) continue;
                if (userEarnings[user][i].unlockTime > block.timestamp) {
                    break;
                }
                amountWithoutPenalty = amountWithoutPenalty.add(earnedAmount);
            }

            penaltyAmount = bal.earned.sub(amountWithoutPenalty).div(2);
        }
        amount = bal.unlocked.add(bal.earned).sub(penaltyAmount);
        return (amount, penaltyAmount);
    }

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

    //same function signature as original `stake` function for compatibility with original ABI
    function stake(uint256 amount, bool ignored) external {
        _stake(amount, false);
    }

    function stakeLocked(uint256 amount) external {
        // Only allow approved contracts to stake locked, since an external contract will be used to handle locked stakes
        require(lockedStakeWhitelist[msg.sender], "not whitelisted");
        _stake(amount, true);
    }

    // Stake tokens to receive rewards
    // Locked tokens cannot be withdrawn for lockDuration and are eligible to receive stakingReward rewards
    function _stake(uint256 amount, bool lock) internal nonReentrant updateReward(msg.sender) {
        require(amount > 0, "Cannot stake 0");
        totalSupply = totalSupply.add(amount);
        Balances storage bal = balances[msg.sender];
        bal.total = bal.total.add(amount);
        if (lock) {
            lockedSupply = lockedSupply.add(amount);
            bal.locked = bal.locked.add(amount);
            uint256 unlockTime = block.timestamp.div(rewardsDuration).mul(rewardsDuration).add(lockDuration);
            uint256 idx = userLocks[msg.sender].length;
            if (idx == 0 || userLocks[msg.sender][idx-1].unlockTime < unlockTime) {
                userLocks[msg.sender].push(LockedBalance({amount: amount, unlockTime: unlockTime}));
            } else {
                userLocks[msg.sender][idx-1].amount = userLocks[msg.sender][idx-1].amount.add(amount);
            }
        } else {
            bal.unlocked = bal.unlocked.add(amount);
        }
        stakingToken.safeTransferFrom(msg.sender, address(this), amount);
        emit Staked(msg.sender, amount);
    }

    // Mint new tokens
    // Minted tokens receive rewards normally but incur a 50% penalty when
    // withdrawn before lockDuration has passed.
    function mint(address user, uint256 amount) external updateReward(user) {
        require(minters[msg.sender], "not minter");
        totalSupply = totalSupply.add(amount);
        Balances storage bal = balances[user];
        bal.total = bal.total.add(amount);
        bal.earned = bal.earned.add(amount);
        uint256 unlockTime = block.timestamp.div(rewardsDuration).mul(rewardsDuration).add(lockDuration);
        LockedBalance[] storage earnings = userEarnings[user];
        uint256 idx = earnings.length;

        if (idx == 0 || earnings[idx-1].unlockTime < unlockTime) {
            earnings.push(LockedBalance({amount: amount, unlockTime: unlockTime}));
        } else {
            earnings[idx-1].amount = earnings[idx-1].amount.add(amount);
        }
        stakingToken.mint(address(this), amount);
        emit Staked(user, amount);
    }

    // Withdraw staked tokens
    // First withdraws unlocked tokens, then earned tokens. Withdrawing earned tokens
    // incurs a 50% penalty which is distributed based on locked balances.
    function withdraw(uint256 amount) public nonReentrant updateReward(msg.sender) {
        require(amount > 0, "Cannot withdraw 0");
        Balances storage bal = balances[msg.sender];
        uint256 penaltyAmount;

        if (amount <= bal.unlocked) {
            bal.unlocked = bal.unlocked.sub(amount);
        } else {
            uint256 remaining = amount.sub(bal.unlocked);
            require(bal.earned >= remaining, "Insufficient unlocked balance");
            bal.unlocked = 0;
            bal.earned = bal.earned.sub(remaining);
            for (uint i = 0; ; i++) {
                uint256 earnedAmount = userEarnings[msg.sender][i].amount;
                if (earnedAmount == 0) continue;
                if (penaltyAmount == 0 && userEarnings[msg.sender][i].unlockTime > block.timestamp) {
                    penaltyAmount = remaining;
                    require(bal.earned >= remaining, "Insufficient balance after penalty");
                    bal.earned = bal.earned.sub(remaining);
                    if (bal.earned == 0) {
                        delete userEarnings[msg.sender];
                        break;
                    }
                    remaining = remaining.mul(2);
                }
                if (remaining <= earnedAmount) {
                    userEarnings[msg.sender][i].amount = earnedAmount.sub(remaining);
                    break;
                } else {
                    delete userEarnings[msg.sender][i];
                    remaining = remaining.sub(earnedAmount);
                }
            }
        }

        uint256 adjustedAmount = amount.add(penaltyAmount);
        bal.total = bal.total.sub(adjustedAmount);
        totalSupply = totalSupply.sub(adjustedAmount);
        stakingToken.safeTransfer(msg.sender, amount);
        if (penaltyAmount > 0) {
            _notifyReward(address(stakingToken), penaltyAmount);
        }
        emit Withdrawn(msg.sender, amount);
    }

    // Claim all pending staking rewards
    function getReward() public nonReentrant updateReward(msg.sender) {
        for (uint i; i < rewardTokens.length; i++) {
            address _rewardsToken = rewardTokens[i];
            uint256 reward = rewards[msg.sender][_rewardsToken];
            if (reward > 0) {
                rewards[msg.sender][_rewardsToken] = 0;
                IERC20(_rewardsToken).safeTransfer(msg.sender, reward);
                emit RewardPaid(msg.sender, _rewardsToken, reward);
            }
        }
    }

    // Withdraw all currently locked tokens where the unlock time has passed
    function withdrawExpiredLocks() external {
        LockedBalance[] storage locks = userLocks[msg.sender];
        Balances storage bal = balances[msg.sender];
        uint256 amount;
        uint256 length = locks.length;
        if (locks[length-1].unlockTime <= block.timestamp) { //Certik: length is guaranteed to be >= 1
            amount = bal.locked;
            delete userLocks[msg.sender];
        } else {
            for (uint i = 0; i < length; i++) {
                if (locks[i].unlockTime > block.timestamp) break;
                amount = amount.add(locks[i].amount);
                delete locks[i];
            }
        }
        bal.locked = bal.locked.sub(amount);
        bal.total = bal.total.sub(amount);
        totalSupply = totalSupply.sub(amount);
        lockedSupply = lockedSupply.sub(amount);
        stakingToken.safeTransfer(msg.sender, amount);
    }

    /* ========== RESTRICTED FUNCTIONS ========== */

    function _notifyReward(address _rewardsToken, uint256 reward) internal {
        if (block.timestamp >= rewardData[_rewardsToken].periodFinish) {
            rewardData[_rewardsToken].rewardRate = reward.div(rewardsDuration);
        } else {
            uint256 remaining = rewardData[_rewardsToken].periodFinish.sub(block.timestamp);
            uint256 leftover = remaining.mul(rewardData[_rewardsToken].rewardRate);
            rewardData[_rewardsToken].rewardRate = reward.add(leftover).div(rewardsDuration);
        }

        rewardData[_rewardsToken].lastUpdateTime = block.timestamp;
        rewardData[_rewardsToken].periodFinish = block.timestamp.add(rewardsDuration);

    }

    function notifyRewardAmount(address _rewardsToken, uint256 reward) external updateReward(address(0)) {
        require(rewardDistributors[_rewardsToken][msg.sender], "Not reward distributor");
        require(reward > 0, "No reward");
        // handle the transfer of reward tokens via `transferFrom` to reduce the number
        // of transactions required and ensure correctness of the reward amount
        IERC20(_rewardsToken).safeTransferFrom(msg.sender, address(this), reward);
        _notifyReward(_rewardsToken, reward);
        emit RewardAdded(reward);
    }

    // Added to support recovering LP Rewards from other systems such as BAL to be distributed to holders
    function recoverERC20(address tokenAddress, uint256 tokenAmount) external onlyOwner {
        require(tokenAddress != address(stakingToken), "Cannot withdraw staking token");
        require(rewardData[tokenAddress].lastUpdateTime == 0, "Cannot withdraw reward token");
        IERC20(tokenAddress).safeTransfer(owner(), tokenAmount);
        emit Recovered(tokenAddress, tokenAmount);
    }

    /* ========== MODIFIERS ========== */

    modifier updateReward(address account) {
        address token = address(stakingToken);
        uint256 balance;
        uint256 supply = lockedSupply;
        rewardData[token].rewardPerTokenStored = _rewardPerToken(token, supply);
        rewardData[token].lastUpdateTime = lastTimeRewardApplicable(token);
        if (account != address(0)) {
            // Special case, use the locked balances and supply for stakingReward rewards
            rewards[account][token] = _earned(account, token, balances[account].locked, supply);
            userRewardPerTokenPaid[account][token] = rewardData[token].rewardPerTokenStored;
            balance = balances[account].total;
        }

        supply = totalSupply;
        for (uint i = 1; i < rewardTokens.length; i++) {
            token = rewardTokens[i];
            rewardData[token].rewardPerTokenStored = _rewardPerToken(token, supply);
            rewardData[token].lastUpdateTime = lastTimeRewardApplicable(token);
            if (account != address(0)) {
                rewards[account][token] = _earned(account, token, balance, supply);
                userRewardPerTokenPaid[account][token] = rewardData[token].rewardPerTokenStored;
            }
        }
        _;
    }

    /* ========== EVENTS ========== */

    event RewardAdded(uint256 reward);
    event Staked(address indexed user, uint256 amount);
    event Withdrawn(address indexed user, uint256 amount);
    event RewardPaid(address indexed user, address indexed rewardsToken, uint256 reward);
    event Recovered(address token, uint256 amount);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "@openzeppelin/contracts/access/Ownable.sol";
import "../interfaces/IMinter.sol";

contract ProxyMinter is Ownable {

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

    mapping(address => bool) private _minters;
    address public deployer;
    address public minter;

    /* ========== CONSTRUCTOR ========== */

    constructor(address _minter)
        public
    {
        minter = _minter;
    }
    
    /* ========== MODIFIERS ========== */

    modifier onlyMinter {
        require(isMinter(msg.sender) == true, "AddyMinter: caller is not the minter");
        _;
    }

    function mintFor(address user, address asset, uint256 amount) external onlyMinter {
        IMinter(minter).mintFor(user, asset, amount);
    }
    
    /* ========== VIEWS ========== */

    function isMinter(address account) public view returns (bool) {
        return _minters[account];
    }

    function amountAddyToMint(uint256 ethProfit) public view returns (uint256) {
        return IMinter(minter).amountAddyToMint(ethProfit);
    }

    /* ========== RESTRICTED FUNCTIONS ========== */

    function setDeployer(address _deployer) public onlyOwner {
        require(deployer == address(0), "already set");
        deployer = _deployer;
    }
    
    function setMinter(address _minter, bool canMint) external {
        require(deployer == msg.sender, "not deployer");

        if (canMint) {
            _minters[_minter] = canMint;
        } else {
            delete _minters[_minter];
        }
    }
    
    function disableMinter(address _minter) external onlyOwner {
        _minters[_minter] = false;
    }
}

// Copyright 2020 Compound Labs, Inc.
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Ctrl+f for XXX to see all the modifications.

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
import "../interfaces/IMinter.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

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;
    }
}

contract Timelock is ReentrancyGuard {
    using SafeMath for uint;

    event NewAdmin(address indexed newAdmin);
    event NewPendingAdmin(address indexed newPendingAdmin);
    event NewDelay(uint indexed newDelay);
    event CancelTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta);
    event ExecuteTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta);
    event QueueTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta);
    event DisabledMinter(address indexed target);

    uint public constant GRACE_PERIOD = 14 days;

    uint public constant MINIMUM_DELAY = 12 hours;
    uint public constant MAXIMUM_DELAY = 30 days;

    address public admin;
    address public pendingAdmin;
    uint public delay;

    mapping(bytes32 => bool) public queuedTransactions;


    constructor(address admin_, uint delay_) public {
        require(delay_ >= MINIMUM_DELAY, "Timelock::constructor: Delay must exceed minimum delay.");
        require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay.");
        require(admin_ != address(0));

        admin = admin_;
        delay = delay_;
    }

    // XXX: function() external payable { }
    receive() external payable {}

    function setDelay(uint delay_) public {
        require(msg.sender == address(this), "Timelock::setDelay: Call must come from Timelock.");
        require(delay_ >= MINIMUM_DELAY, "Timelock::setDelay: Delay must exceed minimum delay.");
        require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay.");
        delay = delay_;

        emit NewDelay(delay);
    }

    function acceptAdmin() public {
        require(msg.sender == pendingAdmin, "Timelock::acceptAdmin: Call must come from pendingAdmin.");
        admin = msg.sender;
        pendingAdmin = address(0);

        emit NewAdmin(admin);
    }

    function setPendingAdmin(address pendingAdmin_) public {
        require(msg.sender == address(this), "Timelock::setPendingAdmin: Call must come from Timelock.");
        require(pendingAdmin_ != address(0));
        pendingAdmin = pendingAdmin_;

        emit NewPendingAdmin(pendingAdmin);
    }

    function queueTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public returns (bytes32) {
        require(msg.sender == admin, "Timelock::queueTransaction: Call must come from admin.");
        require(eta >= getBlockTimestamp().add(delay), "Timelock::queueTransaction: Estimated execution block must satisfy delay.");

        bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
        queuedTransactions[txHash] = true;

        emit QueueTransaction(txHash, target, value, signature, data, eta);
        return txHash;
    }

    function cancelTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public {
        require(msg.sender == admin, "Timelock::cancelTransaction: Call must come from admin.");

        bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
        queuedTransactions[txHash] = false;

        emit CancelTransaction(txHash, target, value, signature, data, eta);
    }

    function executeTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public payable returns (bytes memory) {
        require(msg.sender == admin, "Timelock::executeTransaction: Call must come from admin.");

        bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
        require(queuedTransactions[txHash], "Timelock::executeTransaction: Transaction hasn't been queued.");
        require(getBlockTimestamp() >= eta, "Timelock::executeTransaction: Transaction hasn't surpassed time lock.");
        require(getBlockTimestamp() <= eta.add(GRACE_PERIOD), "Timelock::executeTransaction: Transaction is stale.");

        queuedTransactions[txHash] = false;

        bytes memory callData;

        if (bytes(signature).length == 0) {
            callData = data;
        } else {
            callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
        }

        // solium-disable-next-line security/no-call-value
        // XXX: Using ".value(...)" is deprecated. Use "{value: ...}" instead.
        (bool success, bytes memory returnData) = target.call{value : value}(callData);
        require(success, "Timelock::executeTransaction: Transaction execution reverted.");

        emit ExecuteTransaction(txHash, target, value, signature, data, eta);

        return returnData;
    }

    //made function public so I can call it to get the block timestamp
    function getBlockTimestamp() public view returns (uint) {
        // solium-disable-next-line security/no-block-members
        return block.timestamp;
    }

    //Added function to disable a minter with 0 delay in the event of an emergency
    function disableMinter(address minter, address target) public nonReentrant {
        require(msg.sender == admin, "Timelock::disableMinter: Call must come from admin.");

        IMinter(minter).setMinter(target, false);
        emit DisabledMinter(target);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

interface IVaultDataStorage {
  function setVaultHash(address vaultAddress, bytes32 _hash) external;
  function addSimilarVault(bytes32 _hash, address vaultAddress) external;
  function removeVault(bytes32 _hash, address vaultAddress) external;

  function getSimilarVaults(address vaultAddress) external view returns (address[] memory);
  function getHash(address vaultAddress) external view returns (bytes32);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/access/Ownable.sol";
import "../interfaces/IVaultDataStorage.sol";

contract VaultDataStorage is Ownable, IVaultDataStorage {

    mapping(bytes32 => address[]) private similarVaults;
    //hashes should be determined by the tokens in a LP pair
    mapping(address => bytes32) private vaultHashes;

    address internal creator = 0xB29Cd9C87a624B940335d6d5e1D4aADf7D95bEeC;

    // **** Views **** //

    //Returns the list of vaults similar to vault A
    function getSimilarVaults(address vaultAddress) public override view returns (address[] memory) {
        return similarVaults[vaultHashes[vaultAddress]];
    }

    function getHash(address vaultAddress) public override view returns (bytes32) {
        return vaultHashes[vaultAddress];
    }

    // **** State Mutations ****

    //Maps an arbitrary hash to a vault address
    function setVaultHash(address vaultAddress, bytes32 _hash) public override onlyCreator {
        vaultHashes[vaultAddress] = _hash;
    }

    //Marks a vault as being similar to all vaults with a given hash
    function addSimilarVault(bytes32 _hash, address vaultAddress) public override onlyCreator {
        similarVaults[_hash].push(vaultAddress);
    }

    //Deletes a vault from the list of similar vaults
    //The wrapper contract should also clear the hash
    function removeVault(bytes32 _hash, address vaultAddress) public override onlyCreator {
        uint theIndex;
        bool found = false;

        for (uint i = 0; i < similarVaults[_hash].length; i++){
            if (vaultAddress == similarVaults[_hash][i]) {
                theIndex = i;
                found = true;
                break;
            }
        }
        require(found, "vault address not found in array");

        delete similarVaults[_hash][theIndex];
    }

    // **** Modifiers ****

    modifier onlyCreator {
        require(_msgSender() == creator || _msgSender() == owner(), "not authorized");
        _;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/access/Ownable.sol";
import "../../interfaces/uniswap/IUniswapV2Pair.sol";
import "../../interfaces/IVault.sol";
import "../interfaces/IVaultDataStorage.sol";

contract VaultDataStorageWrapper is Ownable, IVaultDataStorage {

    mapping(bytes32 => bool) private symbolHashes;
    IVaultDataStorage vaultDataStorage;

    constructor(address _vaultDataStorage) public {
        vaultDataStorage = IVaultDataStorage(_vaultDataStorage);

        symbolHashes[keccak256("UNI-V2")] = true;
        symbolHashes[keccak256("DXS")] = true;
        symbolHashes[keccak256("SLP")] = true;
        symbolHashes[keccak256("DFYNLP")] = true;
        symbolHashes[keccak256("ELP")] = true;
        symbolHashes[keccak256("WLP")] = true;
        symbolHashes[keccak256("APE-LP")] = true;
        symbolHashes[keccak256("pWINGS-LP")] = true;
        symbolHashes[keccak256("GLP")] = true;
        symbolHashes[keccak256("CAT-LP")] = true;
        symbolHashes[keccak256("Cafe-LP")] = true;
    }

    // **** Views **** //

    //Returns the list of vaults similar to vaultAddress
    function getSimilarVaults(address vaultAddress) public override view returns (address[] memory) {
        return vaultDataStorage.getSimilarVaults(vaultAddress);
    }

    function getHash(address vaultAddress) public override view returns (bytes32) {
        return vaultDataStorage.getHash(vaultAddress);
    }

    //Calculates the hash for a vault's LP token
    function calculateHash(address vaultAddress) public view returns (bytes32) {
        address want = address(IJar(vaultAddress).token());

        //in case a non-standard sort was used when creating the LP pair
        address tokenA = IUniswapV2Pair(want).token0();
        address tokenB = IUniswapV2Pair(want).token1();
        (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
        return keccak256(abi.encodePacked(token0, token1));
    }

    // **** Vault Data Storage Mutations ****

    //Marks a vault as being similar to other vaults for the same LP pair
    //A vault is counted as "already added" if the hash mapped to it is non zero
    function addSimilarVaultDefaultWithChecks(address vaultAddress) public onlyOwner {
        if(getHash(vaultAddress) == bytes32(0)) {
            setVaultHash(vaultAddress, calculateHash(vaultAddress));
            addSimilarVault(calculateHash(vaultAddress), vaultAddress);
        }
    }

    //Deletes a vault from the mapping and sets the hash associated with that vault to 0
    function removeVaultWithChecks(address vaultAddress) public onlyOwner {
        require(getHash(vaultAddress) != bytes32(0), "no hash detected");

        vaultDataStorage.removeVault(getHash(vaultAddress), vaultAddress);
        vaultDataStorage.setVaultHash(vaultAddress, bytes32(0));
    }

    // **** Avoid direct usage of these base functions if possible ****

    //Marks vault B as being similar to vault A
    function addSimilarVault(bytes32 _hash, address vaultAddress) public override onlyOwner {
        vaultDataStorage.addSimilarVault(_hash, vaultAddress);
    }

    function setVaultHash(address vaultAddress, bytes32 _hash) public override onlyOwner {
        vaultDataStorage.setVaultHash(vaultAddress, _hash);
    }

    function removeVault(bytes32 _hash, address vaultAddress) public override onlyOwner {
        vaultDataStorage.removeVault(_hash, vaultAddress);
        vaultDataStorage.setVaultHash(vaultAddress, bytes32(0));
    }

    // **** Other functions for this contract ****

    function setSymbolHash(string memory symbol, bool isValid) public onlyOwner {
        symbolHashes[keccak256(abi.encodePacked(symbol))] = isValid;
    }

    function acquireOwnership() public onlyOwner {
        Ownable(address(vaultDataStorage)).transferOwnership(owner());
    }

    function acquireOwnership2(address _address) public onlyOwner {
        Ownable(_address).transferOwnership(owner());
    }
}

File 46 of 98 : AddyStakingRewards.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

// Modified Frax.finance staking rewards contract
// https://github.com/FraxFinance/frax-solidity/blob/master/contracts/Staking/StakingRewards.sol

import "@openzeppelin/contracts/math/Math.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

import "../lib/TransferHelper.sol";
import "../lib/StringHelpers.sol";
import "../lib/Pausable.sol";

import "../interfaces/IMinter.sol";

// Inheritance

interface IStakingRewards {
    // Views
    function lastTimeRewardApplicable() external view returns (uint256);

    function rewardPerToken() external view returns (uint256);

    function earned(address account) external view returns (uint256);

    function getRewardForDuration() external view returns (uint256);

    function totalSupply() external view returns (uint256);

    function balanceOf(address account) external view returns (uint256);

    // Mutative

    function stake(uint256 amount) external;

    function withdraw(uint256 amount) external;

    function getReward() external;

    //function exit() external;
}

contract RewardsDistributionRecipient is Ownable {
    address public rewardsDistribution;

    //function notifyRewardAmount(uint256 reward, uint256 duration) external;

    modifier onlyRewardsDistribution() {
        require(msg.sender == rewardsDistribution, "Caller is not RewardsDistribution contract");
        _;
    }

    function setRewardsDistribution(address _rewardsDistribution) external onlyOwner {
        rewardsDistribution = _rewardsDistribution;
    }
}

contract AddyStakingRewards is Ownable, IStakingRewards, RewardsDistributionRecipient, ReentrancyGuard, Pausable {
    using SafeMath for uint256;
    using SafeERC20 for ERC20;

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

    address public WETH; //Used to calculate the # of ADDY each user gets; reducing the multiplier in the minter contract will cause the # of claimable ADDY to decrease as well
    ERC20 public rewardsToken;
    ERC20 public stakingToken;
    uint256 public periodFinish = 0;

    // Constant for various precisions
    uint256 private constant PRICE_PRECISION = 1e6;
    uint256 private constant MULTIPLIER_BASE = 1e6;

    // Max reward per second
    uint256 public rewardRate;

    uint256 public rewardsDuration = 604800; // 7 * 86400  (7 days)

    uint256 public lastUpdateTime;
    uint256 public rewardPerTokenStored = 0;

    address public minter;
    address public externalStakingRewards; //The Quickswap/Sushi staking rewards we're staking in (or a wrapper contract for it)

    uint256 public locked_stake_max_multiplier = 3000000; // 6 decimals of precision. 1x = 1000000
    uint256 public locked_stake_time_for_max_multiplier = 3 * 365 * 86400; // 3 years
    uint256 public locked_stake_min_time = 604800; // 7 * 86400  (7 days)
    string private locked_stake_min_time_str = "604800"; // 7 days on genesis

    mapping(address => uint256) public userRewardPerTokenPaid;
    mapping(address => uint256) public rewards;

    uint256 private _staking_token_supply = 0;
    uint256 private _staking_token_boosted_supply = 0;
    mapping(address => uint256) private _unlocked_balances;
    mapping(address => uint256) private _locked_balances;
    mapping(address => uint256) private _boosted_balances;

    mapping(address => LockedStake[]) private lockedStakes;

    mapping(address => bool) public greylist;

    bool public unlockedStakes; // Release lock stakes in case of system migration

    struct LockedStake {
        bytes32 kek_id;
        uint256 start_timestamp;
        uint256 amount;
        uint256 ending_timestamp;
        uint256 multiplier; // 6 decimals of precision. 1x = 1000000
    }

    /* ========== CONSTRUCTOR ========== */

    constructor(
        address _rewardsDistribution,
        address _minter,
        address _rewardsToken, //0xc3FdbadC7c795EF1D6Ba111e06fF8F16A20Ea539 ADDY (or ADDY proxy token redeemable for ADDY to mitigate the damage of any possible minting exploits)
        address _stakingToken, //0xa5BF14BB945297447fE96f6cD1b31b40d31175CB ADDY/ETH LP
        address _weth_address, //0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619 WETH
        address _external_staking_rewards
    ) public {
        rewardsDistribution = _rewardsDistribution;
        minter = _minter;

        rewardsToken = ERC20(_rewardsToken);
        stakingToken = ERC20(_stakingToken);
        WETH = _weth_address;
        externalStakingRewards = _external_staking_rewards;

        lastUpdateTime = block.timestamp;
        unlockedStakes = false;
    }

    /* ========== VIEWS ========== */

    function totalSupply() external override view returns (uint256) {
        return _staking_token_supply;
    }

    function totalBoostedSupply() external view returns (uint256) {
        return _staking_token_boosted_supply;
    }

    function stakingMultiplier(uint256 secs) public view returns (uint256) {
        uint256 multiplier = uint(MULTIPLIER_BASE).add(secs.mul(locked_stake_max_multiplier.sub(MULTIPLIER_BASE)).div(locked_stake_time_for_max_multiplier));
        if (multiplier > locked_stake_max_multiplier) multiplier = locked_stake_max_multiplier;
        return multiplier;
    }

    // Total unlocked and locked liquidity tokens
    function balanceOf(address account) external override view returns (uint256) {
        return (_unlocked_balances[account]).add(_locked_balances[account]);
    }

    // Total unlocked liquidity tokens
    function unlockedBalanceOf(address account) external view returns (uint256) {
        return _unlocked_balances[account];
    }

    // Total locked liquidity tokens
    function lockedBalanceOf(address account) public view returns (uint256) {
        return _locked_balances[account];
    }

    // Total 'balance' used for calculating the percent of the pool the account owns
    // Takes into account the locked stake time multiplier
    function boostedBalanceOf(address account) external view returns (uint256) {
        return _boosted_balances[account];
    }

    function lockedStakesOf(address account) external view returns (LockedStake[] memory) {
        return lockedStakes[account];
    }

    function stakingDecimals() external view returns (uint256) {
        return stakingToken.decimals();
    }

    function rewardsFor(address account) external view returns (uint256) {
        // You may have use earned() instead, because of the order in which the contract executes
        return rewards[account];
    }

    function lastTimeRewardApplicable() public override view returns (uint256) {
        return Math.min(block.timestamp, periodFinish);
    }

    function rewardPerToken() public override view returns (uint256) {
        if (_staking_token_supply == 0) {
            return rewardPerTokenStored;
        }
        else {
            return rewardPerTokenStored.add(
                lastTimeRewardApplicable().sub(lastUpdateTime).mul(rewardRate).mul(MULTIPLIER_BASE).mul(1e18).div(PRICE_PRECISION).div(_staking_token_boosted_supply)
            );
        }
    }

    function earned(address account) public override view returns (uint256) {
        return _boosted_balances[account].mul(rewardPerToken().sub(userRewardPerTokenPaid[account])).div(1e18).add(rewards[account]);
    }

    function getRewardForDuration() external override view returns (uint256) {
        return rewardRate.mul(rewardsDuration).mul(MULTIPLIER_BASE).div(PRICE_PRECISION);
    }

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

    function stake(uint256 amount) external override nonReentrant notPaused updateReward(msg.sender) {
        require(amount > 0, "Cannot stake 0");
        require(greylist[msg.sender] == false, "address has been greylisted");
        require(msg.sender == tx.origin, "no contracts");

        // Pull the tokens from the staker
        TransferHelper.safeTransferFrom(address(stakingToken), msg.sender, address(this), amount);

        //Deposit the tokens in the external staking rewards contract
        depositToExternalStakingRewards(amount);

        // Staking token supply and boosted supply
        _staking_token_supply = _staking_token_supply.add(amount);
        _staking_token_boosted_supply = _staking_token_boosted_supply.add(amount);

        // Staking token balance and boosted balance
        _unlocked_balances[msg.sender] = _unlocked_balances[msg.sender].add(amount);
        _boosted_balances[msg.sender] = _boosted_balances[msg.sender].add(amount);

        emit Staked(msg.sender, amount);
    }

    function stakeLocked(uint256 amount, uint256 secs) external nonReentrant notPaused updateReward(msg.sender) {
        require(amount > 0, "Cannot stake 0");
        require(secs > 0, "Cannot wait for a negative number");
        require(msg.sender == tx.origin, "no contracts");
        require(greylist[msg.sender] == false, "address has been greylisted");
        require(secs >= locked_stake_min_time, StringHelpers.strConcat("Minimum stake time not met (", locked_stake_min_time_str, ")") );

        uint256 multiplier = stakingMultiplier(secs);
        uint256 boostedAmount = amount.mul(multiplier).div(PRICE_PRECISION);
        lockedStakes[msg.sender].push(LockedStake(
            keccak256(abi.encodePacked(msg.sender, block.timestamp, amount)),
            block.timestamp,
            amount,
            block.timestamp.add(secs),
            multiplier
        ));

        // Pull the tokens from the staker
        TransferHelper.safeTransferFrom(address(stakingToken), msg.sender, address(this), amount);

        //Deposit the tokens in the external staking rewards contract
        depositToExternalStakingRewards(amount);

        // Staking token supply and boosted supply
        _staking_token_supply = _staking_token_supply.add(amount);
        _staking_token_boosted_supply = _staking_token_boosted_supply.add(boostedAmount);

        // Staking token balance and boosted balance
        _locked_balances[msg.sender] = _locked_balances[msg.sender].add(amount);
        _boosted_balances[msg.sender] = _boosted_balances[msg.sender].add(boostedAmount);

        emit StakeLocked(msg.sender, amount, secs);
    }

    function withdraw(uint256 amount) public override nonReentrant updateReward(msg.sender) {
        require(amount > 0, "Cannot withdraw 0");

        // Staking token balance and boosted balance
        _unlocked_balances[msg.sender] = _unlocked_balances[msg.sender].sub(amount);
        _boosted_balances[msg.sender] = _boosted_balances[msg.sender].sub(amount);

        // Staking token supply and boosted supply
        _staking_token_supply = _staking_token_supply.sub(amount);
        _staking_token_boosted_supply = _staking_token_boosted_supply.sub(amount);

        //Withdraw the tokens from the external staking rewards contract
        withdrawFromExternalStakingRewards(amount);

        // Give the tokens to the withdrawer
        stakingToken.safeTransfer(msg.sender, amount);
        emit Withdrawn(msg.sender, amount);
    }

    function withdrawLocked(bytes32 kek_id) public nonReentrant updateReward(msg.sender) {
        LockedStake memory thisStake;
        thisStake.amount = 0;
        uint theIndex;
        for (uint i = 0; i < lockedStakes[msg.sender].length; i++){
            if (kek_id == lockedStakes[msg.sender][i].kek_id){
                thisStake = lockedStakes[msg.sender][i];
                theIndex = i;
                break;
            }
        }
        require(thisStake.kek_id == kek_id, "Stake not found");
        require(block.timestamp >= thisStake.ending_timestamp || unlockedStakes == true, "Stake is still locked!");

        uint256 theAmount = thisStake.amount;
        uint256 boostedAmount = theAmount.mul(thisStake.multiplier).div(PRICE_PRECISION);
        if (theAmount > 0){
            // Staking token balance and boosted balance
            _locked_balances[msg.sender] = _locked_balances[msg.sender].sub(theAmount);
            _boosted_balances[msg.sender] = _boosted_balances[msg.sender].sub(boostedAmount);

            // Staking token supply and boosted supply
            _staking_token_supply = _staking_token_supply.sub(theAmount);
            _staking_token_boosted_supply = _staking_token_boosted_supply.sub(boostedAmount);

            // Remove the stake from the array
            delete lockedStakes[msg.sender][theIndex];

            //Withdraw the tokens from the external staking rewards contract
            withdrawFromExternalStakingRewards(theAmount);

            // Give the tokens to the withdrawer
            stakingToken.safeTransfer(msg.sender, theAmount);

            emit WithdrawnLocked(msg.sender, theAmount, kek_id);
        }
    }

    function getReward() public override nonReentrant updateReward(msg.sender) {
        uint256 reward = rewards[msg.sender];
        if (reward > 0) {
            rewards[msg.sender] = 0;
            IMinter(minter).mintFor(msg.sender, WETH, reward); //ADDY is minted into the fee dist contract based on the WETH value of shares accumulated
            //rewardsToken.transfer(msg.sender, reward);
            emit RewardPaid(msg.sender, reward);
        }
    }

    function depositToExternalStakingRewards(uint256 amount) internal {
        ERC20(stakingToken).safeApprove(externalStakingRewards, 0);
        ERC20(stakingToken).safeApprove(externalStakingRewards, amount);
        IStakingRewards(externalStakingRewards).stake(amount);
    }

    function withdrawFromExternalStakingRewards(uint256 amount) internal {
        IStakingRewards(externalStakingRewards).withdraw(amount);
    }

    //Get reward then transfer to external fund, different for each incentivized liquidity pool for ADDY
    address public QUICK = 0x831753DD7087CaC61aB5644b308642cc1c33Dc13;
    address public FUND = 0x01fE07ce760DA7a025e4Cf3f950aE236F8e62120;
    function getRewardFromExternalStakingRewards() external onlyOwner {
        IStakingRewards(externalStakingRewards).getReward();

        uint256 _bal = ERC20(QUICK).balanceOf(address(this));
        if(_bal > 0) {
            ERC20(QUICK).safeTransfer(FUND, _bal);
        }
    }

    /* ========== RESTRICTED FUNCTIONS ========== */

    function notifyRewardAmount(uint256 reward, uint256 _rewardsDuration) external onlyRewardsDistribution updateReward(address(0)) {
        require(block.timestamp.add(_rewardsDuration) >= periodFinish, "Cannot reduce existing period");
        require(reward <= 1e19, "adding too much"); //Set limit of 10 ETH worth to add at once
        require(_rewardsDuration <= 30 days, "duration too long"); //Set limit of 30 days to guard against accidentally swapping the args

        if (block.timestamp >= periodFinish) {
            rewardRate = reward.div(_rewardsDuration);
        } else {
            uint256 remaining = periodFinish.sub(block.timestamp);
            uint256 leftover = remaining.mul(rewardRate);
            rewardRate = reward.add(leftover).div(_rewardsDuration);
        }

        // Ensure the provided reward amount is not more than the balance in the contract.
        // This keeps the reward rate in the right range, preventing overflows due to
        // very high values of rewardRate in the earned and rewardsPerToken functions;
        // Reward + leftover must be less than 2^256 / 10^18 to avoid overflow.
        //uint balance = rewardsToken.balanceOf(address(this));
        //require(rewardRate <= balance.div(_rewardsDuration), "Provided reward too high");

        //Check above not applicable, since minter contract mints ADDY rewards to the fee dist contract
        //1e77 is way more tokens than the supply will ever be

        lastUpdateTime = block.timestamp;
        periodFinish = block.timestamp.add(_rewardsDuration);
        emit RewardAdded(reward, periodFinish);
    }

    // Added to support recovering LP Rewards from other systems to be distributed to holders
    function recoverERC20(address tokenAddress, uint256 tokenAmount) external onlyOwner {
        // Admin cannot withdraw the staking token from the contract
        require(tokenAddress != address(stakingToken));
        ERC20(tokenAddress).transfer(owner(), tokenAmount);
        emit Recovered(tokenAddress, tokenAmount);
    }

    function setMultipliers(uint256 _locked_stake_max_multiplier) external onlyOwner {
        require(_locked_stake_max_multiplier >= 1, "Multiplier must be greater than or equal to 1");

        locked_stake_max_multiplier = _locked_stake_max_multiplier;

        emit LockedStakeMaxMultiplierUpdated(locked_stake_max_multiplier);
    }

    function setLockedStakeTimeForMinAndMaxMultiplier(uint256 _locked_stake_time_for_max_multiplier, uint256 _locked_stake_min_time) external onlyOwner {
        require(_locked_stake_time_for_max_multiplier >= 1, "Multiplier Max Time must be greater than or equal to 1");
        require(_locked_stake_min_time >= 1, "Multiplier Min Time must be greater than or equal to 1");

        locked_stake_time_for_max_multiplier = _locked_stake_time_for_max_multiplier;

        locked_stake_min_time = _locked_stake_min_time;
        locked_stake_min_time_str = StringHelpers.uint2str(_locked_stake_min_time);

        emit LockedStakeTimeForMaxMultiplier(locked_stake_time_for_max_multiplier);
        emit LockedStakeMinTime(_locked_stake_min_time);
    }

    function greylistAddress(address _address, bool _greylisted) external onlyOwner {
        greylist[_address] = _greylisted;
    }

    function unlockStakes() external onlyOwner {
        unlockedStakes = !unlockedStakes;
    }

    /* ========== MODIFIERS ========== */

    modifier updateReward(address account) {
        rewardPerTokenStored = rewardPerToken();
        lastUpdateTime = lastTimeRewardApplicable();
        if (account != address(0)) {
            rewards[account] = earned(account);
            userRewardPerTokenPaid[account] = rewardPerTokenStored;
        }
        _;
    }

    /* ========== EVENTS ========== */

    event RewardAdded(uint256 reward, uint256 periodFinish);
    event Staked(address indexed user, uint256 amount);
    event StakeLocked(address indexed user, uint256 amount, uint256 secs);
    event Withdrawn(address indexed user, uint256 amount);
    event WithdrawnLocked(address indexed user, uint256 amount, bytes32 kek_id);
    event RewardPaid(address indexed user, uint256 reward);
    event Recovered(address token, uint256 amount);
    event LockedStakeMaxMultiplierUpdated(uint256 multiplier);
    event LockedStakeTimeForMaxMultiplier(uint256 secs);
    event LockedStakeMinTime(uint256 secs);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

interface ICurveGauge {

    /// @notice Get the number of claimable reward tokens for a user
    /// @dev Calling it from an external account = view function
    ///      Calling it via a transaction = claim available reward tokens
    /// @param _addr Account to get reward amount for
    /// @param _token Token to get reward amount for
    /// @return uint256 Claimable reward token amount
    function claimable_reward(address _addr, address _token) external returns (uint256);
    function claimable_reward_write(address _addr, address _token) external returns (uint256);

    /// @notice Claim available reward tokens for `_addr`
    /// @param _addr Address to claim for
    /// @param _receiver Address to transfer rewards to - if set to ZERO_ADDRESS, uses the default reward receiver for the caller
    function claim_rewards(address _addr, address _receiver) external returns (uint256);
    function claim_rewards() external;

    /// @notice Deposit `_value` LP tokens
    /// @dev Depositting also claims pending reward tokens
    /// @param _value Number of tokens to deposit
    /// @param _addr Address to deposit for
    function deposit(uint256 _value, address _addr) external;
    function deposit(uint256 _value, address _addr, bool _claim_rewards) external;
    function deposit(uint256 _value) external;

    /// @notice Withdraw `_value` LP tokens
    /// @dev Withdrawing also claims pending reward tokens
    /// @param _value Number of tokens to withdraw
    function withdraw(uint256 _value) external;
    function withdraw(uint256 _value, bool _claim_rewards) external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

//atricrypto uses "outer" and "base" pool contracts
//the strategy interacts with the outer pool
interface ICurveOuterPool {

    /// @notice Deposit coins into the pool
    /// @param _amounts List of amounts of coins to deposit
    /// 0: ???
    /// 1: USDC
    /// 2-4: ???
    /// @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit
    function add_liquidity(uint256[5] calldata _amounts, uint256 _min_mint_amount, address _receiver) external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

interface ICurvePool {

    /// @notice Deposit coins into the pool
    /// @param _amounts List of amounts of coins to deposit
    /// @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit
    /// @param _use_underlying If True, deposit underlying assets instead of aTokens
    /// @return Amount of LP tokens received by depositing
    function add_liquidity(uint256[3] calldata _amounts, uint256 _min_mint_amount, bool _use_underlying) external returns (uint256);

    /// @notice Perform an exchange between two underlying coins
    /// @dev Index values can be found via the `underlying_coins` public getter method
    /// @dev This function has the `nonReentrant` modifier, which will break the multi-harvest contract if I try to harvest multiple vaults that use Curve at the same time

    /// @param i Index value for the coin to send (0 = DAI, 1 = USDC, 2 = USDT)
    /// @param j Index valie of the coin to recieve
    /// @param dx Amount of `i` being exchanged
    /// @param min_dy Minimum amount of `j` to receive
    /// @return Actual amount of `j` received
    function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 min_dy) external returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

interface IBoostHandler {

  //Returns ADDY earning boost based on user's veADDY, boost should be divided by 1e18
  function getBoost(address _user, address _vaultAddress) external view returns (uint256);

  //Returns ADDY earning boost based on user's veADDY if the user was to stake in vaultAddress, boost is divided by 1e18
  function getBoostForValueStaked(address _user, address _vaultAddress, uint256 valueStaked) external view returns (uint256);

  //Returns the value in USD for which a user's ADDY earnings are doubled
  function getDoubleLimit(address _user) external view returns (uint256 doubleLimit);

  //Returns the total VeAddy from all sources
  function getTotalVeAddy(address _user) external view returns (uint256);

  //Returns the total VeAddy from locking ADDY in the locked ADDY staking contract
  function getVeAddyFromLockedStaking(address _user) external view returns (uint256);

  function setFeeDist(address _feeDist) external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

interface IAggregatorInterface {
  function latestAnswer() external view returns (int256);
  function latestTimestamp() external view returns (uint256);
  function latestRound() external view returns (uint256);
  function getAnswer(uint256 roundId) external view returns (int256);
  function getTimestamp(uint256 roundId) external view returns (uint256);

  event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt);
  event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

interface ICalculator {
    function valueOfAsset(address asset, uint256 amount)
        external
        view
        returns (uint256 valueInETH);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

interface IERCFund {
    function feeShareEnabled() external view returns (bool);

    function depositToFeeDistributor(address token, uint256 amount) external;

    function notifyFeeDistribution(address token) external;

    function getFee() external view returns (uint256);

    function recover(address token) external;
}

pragma solidity ^0.6.12;

import "./IVault.sol";

//A normal vault is a vault where the strategy contract notifies the vault contract about the profit it generated when harvesting. 
interface IGenericVault is IVault {
    
    //Strategy calls notifyReward to let the vault know that it earned a certain amount of profit (the performance fee) for gov token stakers
    function notifyReward(address _reward, uint256 _amount) external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

//A Jar is a contract that users deposit funds into.
//Jar contracts are paired with a strategy contract that interacts with the pool being farmed.
interface IJar {
    function token() external view returns (IERC20);

    function getRatio() external view returns (uint256);

    function balance() external view returns (uint256);

    function balanceOf(address _user) external view returns (uint256);

    function depositAll() external;

    function deposit(uint256) external;

    //function depositFor(address user, uint256 amount) external;

    function withdrawAll() external;

    //function withdraw(uint256) external;

    //function earn() external;

    function strategy() external view returns (address);

    //function decimals() external view returns (uint8);

    //function getLastTimeRestaked(address _address) external view returns (uint256);

    //function notifyReward(address _reward, uint256 _amount) external;

    //function getPendingReward(address _user) external view returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "./IStakingRewards.sol";

interface ILockedStakingRewards is IStakingRewards {

    struct LockedStake {
        bytes32 kek_id;
        uint256 start_timestamp;
        uint256 amount;
        uint256 ending_timestamp;
        uint256 multiplier; // 6 decimals of precision. 1x = 1000000
    }

    function stakeLocked(uint256 amount, uint256 secs) external;
    function withdrawLocked(bytes32 kek_id) external;

    function unlockedBalanceOf(address account) external view returns (uint256);
    function totalBoostedSupply() external view returns (uint256);
    function boostedBalanceOf(address account) external view returns (uint256);
    function lockedStakesOf(address account) external view returns (LockedStake[] memory);
}

pragma solidity ^0.6.12;

interface IMasterChef {
    function deposit(uint256 _pid, uint256 _amount) external;
    function deposit(uint256 _pid, uint256 _amount, address _referrer) external;
    function withdraw(uint256 _pid, uint256 _amount) external;
    function emergencyWithdraw(uint256 _pid) external;
    function exit(uint256 _pid) external;

    function poolInfo(uint256 _pid) external view returns(uint128, uint64, uint64);
    function userInfo(uint256 _pid, address _user) external view returns(uint256, uint256);
    function balanceOf(uint256 _pid, address _user) external view returns (uint256);
    function lpToken(uint256 _pid) external view returns (address);

    //function depends on implementation of master chef
    function earned(uint256 _pid, address _user) external view returns (uint256);
    function pendingReward(uint256 _pid, address _user) external view returns (uint256);

    function totalSupply(uint256 _pid) external view returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "./IVault.sol";

//Maximizer vaults deposit `farmToken` harvested from the LP pool into a single-staking pool.
interface IMaximizerVault is IVault {

    function getPendingHarvested(address _user) external view returns (uint256);

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

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

interface IMiniChef {
    function harvest(uint256 _pid, address to) external;
    function deposit(uint256 _pid, uint256 _amount, address to) external;
    function withdraw(uint256 _pid, uint256 _amount, address to) external;
    function emergencyWithdraw(uint256 _pid, address to) external;
    function withdrawAndHarvest(uint256 pid, uint256 amount, address to) external;

    function userInfo(uint256 _pid, address _user) external view returns(uint256, int256);
    function poolInfo(uint256 _pid) external view returns(uint128, uint64, uint64);
    function pendingSushi(uint256 _pid, address _user) external view returns (uint256);

    function totalAllocPoint() external view returns (uint256);
    function sushiPerSecond() external view returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

interface IMinter {
    function isMinter(address) view external returns(bool);
    function amountAddyToMint(uint256 ethProfit) view external returns(uint256);
    function mintFor(address user, address asset, uint256 amount) external;

    function addyPerProfitEth() view external returns(uint256);

    function setMinter(address minter, bool canMint) external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

struct RewardData {
    address token;
    uint256 amount;
}
struct LockedBalance {
    uint256 amount;
    uint256 unlockTime;
}

// Interface for EPS Staking contract - http://ellipsis.finance/
interface IMultiFeeDistribution {

    /* ========== VIEWS ========== */
    function totalSupply() external view returns (uint256);
    function lockedSupply() external view returns (uint256);

    function rewardPerToken(address _rewardsToken) external view returns (uint256);

    function getRewardForDuration(address _rewardsToken) external view returns (uint256);

    // Address and claimable amount of all reward tokens for the given account
    function claimableRewards(address account) external view returns (RewardData[] memory);

    // Total balance of an account, including unlocked, locked and earned tokens
    function totalBalance(address user) view external returns (uint256 amount);

    // Total withdrawable balance for an account to which no penalty is applied
    function unlockedBalance(address user) view external returns (uint256 amount);

    // Final balance received and penalty balance paid by user upon calling exit
    function withdrawableBalance(address user) view external returns (uint256 amount, uint256 penaltyAmount);

    // Information on the "earned" balances of a user
    // Earned balances may be withdrawn immediately for a 50% penalty
    function earnedBalances(address user) view external returns (uint256 total, LockedBalance[] memory earningsData);

    // Information on a user's locked balances
    function lockedBalances(address user) view external returns (uint256 total, uint256 unlockable, uint256 locked, LockedBalance[] memory lockData);

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

    // Mint new tokens
    // Minted tokens receive rewards normally but incur a 50% penalty when
    // withdrawn before lockDuration has passed.
    function mint(address user, uint256 amount) external;

    // Withdraw full unlocked balance and claim pending rewards
    function exit() external;

    // Withdraw all currently locked tokens where the unlock time has passed
    function withdrawExpiredLocks() external;

    // Claim all pending staking rewards (both BUSD and EPS)
    function getReward() external;

    // Stake tokens to receive rewards
    // Locked tokens cannot be withdrawn for lockDuration and are eligible to receive stakingReward rewards
    function stake(uint256 amount, bool lock) external;
    
    // Withdraw staked tokens
    // First withdraws unlocked tokens, then earned tokens. Withdrawing earned tokens
    // incurs a 50% penalty which is distributed based on locked balances.
    function withdraw(uint256 amount) external;
    
    /* ========== ADMIN CONFIGURATION ========== */

    // Used to let FeeDistribution know that _rewardsToken was added to it
    function notifyRewardAmount(address _rewardsToken, uint256 rewardAmount) external;
}

pragma solidity ^0.6.12;

import "@openzeppelin/contracts/access/Ownable.sol";

abstract contract IRewardDistributionRecipient is Ownable {
    address public rewardDistribution;

    function notifyRewardAmount(uint256 reward) external virtual;

    modifier onlyRewardDistribution() {
        require(
            _msgSender() == rewardDistribution,
            'Caller is not reward distribution'
        );
        _;
    }

    function setRewardDistribution(address _rewardDistribution)
        external
        virtual
        onlyOwner
    {
        rewardDistribution = _rewardDistribution;
    }
}

pragma solidity ^0.6.12;

interface IStakingRewards {
    function balanceOf(address account) external view returns (uint256);

    function earned(address account) external view returns (uint256);

    function exit() external;

    function getReward() external;

    function claimReward() external;

    function getRewardForDuration() external view returns (uint256);

    function lastTimeRewardApplicable() external view returns (uint256);

    function lastUpdateTime() external view returns (uint256);

    function notifyRewardAmount(uint256 reward) external;

    function periodFinish() external view returns (uint256);

    function rewardPerToken() external view returns (uint256);

    function rewardPerTokenStored() external view returns (uint256);

    function rewardRate() external view returns (uint256);

    function rewards(address) external view returns (uint256);

    function rewardsDistribution() external view returns (address);

    function rewardsDuration() external view returns (uint256);

    function rewardsToken() external view returns (address);

    function stake(uint256 amount) external;

    function stake(address _beneficiary, uint256 _amount) external;

    function stakingToken() external view returns (address);

    function totalSupply() external view returns (uint256);

    function userRewardPerTokenPaid(address) external view returns (uint256);

    function withdraw(uint256 amount) external;
}

pragma solidity ^0.6.12;

interface IStrategy {
    //Returns the token sent to the fee dist contract, which is used to calculate the amount of ADDY to mint when claiming rewards
    function getFeeDistToken() external view returns (address);

    //Returns the harvested token, which is not guaranteed to be the fee dist token
    function getHarvestedToken() external view returns (address);

    function lastHarvestTime() external view returns (uint256);

    function rewards() external view returns (address);

    function want() external view returns (address);

    function deposit() external;

    function withdrawForSwap(uint256) external returns (uint256);

    function withdraw(uint256) external;

    function balanceOf() external view returns (uint256);

    function getHarvestable() external view returns (uint256);

    function harvest() external;

    function setJar(address _jar) external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "./IJar.sol";

//Vaults are jars that emit ADDY rewards.
interface IVault is IJar {

    function getBoost(address _user) external view returns (uint256);

    function getPendingReward(address _user) external view returns (uint256);

    function getLastDepositTime(address _user) external view returns (uint256);

    function getTokensStaked(address _user) external view returns (uint256);

    function totalShares() external view returns (uint256);

    function getRewardMultiplier() external view returns (uint256);   

    function rewardAllocation() external view returns (uint256);   

    function totalPendingReward() external view returns (uint256);   

    function withdrawPenaltyTime() external view returns (uint256);  

    function withdrawPenalty() external view returns (uint256);   
    
    function increaseRewardAllocation(uint256 _newReward) external;

    function setWithdrawPenaltyTime(uint256 _withdrawPenaltyTime) external;

    function setWithdrawPenalty(uint256 _withdrawPenalty) external;

    function setRewardMultiplier(uint256 _rewardMultiplier) external;
}

pragma solidity ^0.6.12;

interface IUniswapRouterV2 {
    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;

    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
        );

    function addLiquidityETH(
        address token,
        uint256 amountTokenDesired,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline
    )
        external
        payable
        returns (
            uint256 amountToken,
            uint256 amountETH,
            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 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);

    function swapETHForExactTokens(
        uint256 amountOut,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external payable returns (uint256[] memory amounts);

    function swapExactETHForTokens(
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external payable returns (uint256[] memory amounts);
}

pragma solidity >=0.5.0;

interface IUniswapV2Factory {
    event PairCreated(address indexed token0, address indexed token1, address pair, uint);

    function feeTo() external view returns (address);
    function feeToSetter() external view returns (address);

    function getPair(address tokenA, address tokenB) external view returns (address pair);
    function allPairs(uint) external view returns (address pair);
    function allPairsLength() external view returns (uint);

    function createPair(address tokenA, address tokenB) external returns (address pair);

    function setFeeTo(address) external;
    function setFeeToSetter(address) external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

interface IUniswapV2Pair {
    event Approval(address indexed owner, address indexed spender, uint value);
    event Transfer(address indexed from, address indexed to, uint 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 (uint);
    function balanceOf(address owner) external view returns (uint);
    function allowance(address owner, address spender) external view returns (uint);

    function approve(address spender, uint value) external returns (bool);
    function transfer(address to, uint value) external returns (bool);
    function transferFrom(address from, address to, uint 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 (uint);

    function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;

    event Mint(address indexed sender, uint amount0, uint amount1);
    event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
    event Swap(
        address indexed sender,
        uint amount0In,
        uint amount1In,
        uint amount0Out,
        uint amount1Out,
        address indexed to
    );
    event Sync(uint112 reserve0, uint112 reserve1);

    function MINIMUM_LIQUIDITY() external pure returns (uint);
    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 (uint);
    function price1CumulativeLast() external view returns (uint);
    function kLast() external view returns (uint);

    function mint(address to) external returns (uint liquidity);
    function burn(address to) external returns (uint amount0, uint amount1);
    function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
    function skim(address to) external;
    function sync() external;

    function initialize(address, address) external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "./ErcVaultBase.sol";

contract ErcGenericVault is ErcVaultBase {

    constructor(IStrategy _strategy, address _minter, address _ercFund)
        public
        ErcVaultBase(_strategy, _minter, _ercFund)
    {

    }

    // Handles claiming the user's pending rewards
    function _claimReward(address _user) internal override {
        UserInfo storage user = userInfo[_user];
        if (user.shares > 0) {
            uint256 pendingReward = user.shares.mul(accRewardPerShare).div(1e12).sub(user.rewardDebt);
            if (pendingReward > 0) {
                totalPendingReward = totalPendingReward.sub(pendingReward);
                //Hard cap of 3x rewards after all multipliers
                uint256 rewardCap = pendingReward.mul(3);

                //Apply reward multiplier to the pendingReward that the minter will mint for
                pendingReward = applyRewardMultiplier(pendingReward);

                //Apply boost from locked ADDY
                pendingReward = applyBoost(_user, pendingReward);

                //If pending reward > the 3x hard cap, then set the pending reward to the 3x hard cap
                if(pendingReward > rewardCap) {
                    pendingReward = rewardCap;
                }

                //Make sure the amount of the fee dist token the vault is allowed to mint for is above the amount we're trying to mint for
                //Require statement is there for end users
                require(rewardAllocation >= pendingReward, "Not enough rewards allocated");
                rewardAllocation = rewardAllocation.sub(pendingReward);

                //Minter will mint to MultiFeeDistribution and then stake the minted tokens for the user
                minter.mintFor(_user, IStrategy(strategy).getFeeDistToken(), pendingReward);
                emit Claimed(_user, pendingReward);
            }
        }
    }

    //Strategy calls notifyReward to let the vault know that it earned a certain amount of profit (the performance fee) for gov token stakers
    function notifyReward(address _reward, uint256 _amount) public {
        require(msg.sender == strategy);
        if(_amount == 0) {
            return;
        }

        totalPendingReward = totalPendingReward.add(_amount);
        accRewardPerShare = accRewardPerShare.add(_amount.mul(1e12).div(totalShares)); //shouldn't be adding reward if totalShares == 0 anyway

        emit RewardAdded(_reward, _amount);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "../interfaces/governance/IBoostHandler.sol";
import "../interfaces/IStrategy.sol";
import "../interfaces/IMinter.sol";
import "../interfaces/IVault.sol";

abstract contract ErcVaultBase is ERC20, ReentrancyGuard, Ownable {
    using SafeERC20 for IERC20;
    using Address for address;
    using SafeMath for uint256;

    // Info of each user
    struct UserInfo {
        uint256 shares;
        uint256 rewardDebt;
        uint256 lastDepositTime;
    }

    uint256 public constant keepMax = 10000;

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

    // Info of each user
    mapping (address => UserInfo) public userInfo;

    // The total amount of pending rewards available for stakers to claim
    uint256 public totalPendingReward;
    // Accumulated rewards per share, times 1e12.
    uint256 public accRewardPerShare;
    // The total # of shares issued
    uint256 public totalShares;
    // Withdrawing before this much time has passed will have a withdrawal penalty
    uint256 public withdrawPenaltyTime = 3 days;
    // Withdrawal penalty, 100 = 1%
    uint256 public withdrawPenalty = 50;
    // For vaults that are farming pools with a deposit fee
    uint256 public depositFee = 0;
    //Allowed amount of the token sent to the fee dist each vault can mint ADDY rewards for, default 5 WETH
    uint256 public rewardAllocation = 1e18 * 5;

    // ADDY can be locked to boost the ADDY rewards from vaults
    // Certain vaults also have an increased base ADDY reward multiplier (up to 3x)
    uint256 private rewardMultiplier = 1000;
    uint256 private constant MULTIPLIER_BASE = 1000;
    uint256 private constant MULTIPLIER_MAX = 3000;
    uint256 private constant BOOST_BASE = 1e18;

    IERC20 public token;
    address public strategy;
    IMinter public minter;
    address public ercFund;
    address public boostHandler;

    constructor(IStrategy _strategy, address _minter, address _ercFund)
        public
        ERC20(
            string(abi.encodePacked("Adamant Vault Receipt Token")),
            string(abi.encodePacked("ADDY-VAULT"))
        )
    {
        require(address(_strategy) != address(0));
        _setupDecimals(ERC20(_strategy.want()).decimals());
        token = IERC20(_strategy.want());
        strategy = address(_strategy);
        minter = IMinter(_minter);
        ercFund = _ercFund;
    }

    /* ========== TRANSFER FUNCTIONS ========== */

    function transfer(address recipient, uint256 amount) public override returns (bool) {
        require(recipient != address(this));
        return super.transfer(recipient, amount);
    }

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

    // 1000 = 1x multiplier
    function getRewardMultiplier() public view returns (uint256) {
        return rewardMultiplier;
    }

    function applyRewardMultiplier(uint256 _amount) internal view returns (uint256) {
        return _amount.mul(rewardMultiplier).div(MULTIPLIER_BASE);
    }

    function getBoost(address _user) public view returns (uint256) {
        if (boostHandler != address(0)) {
            return IBoostHandler(boostHandler).getBoost(_user, address(this));
        }
        return 0;
    }

    //Returns base amount + amount from boost
    function applyBoost(address _user, uint256 _amount) internal view returns (uint256) {
        if (boostHandler != address(0)) {
            return _amount.add(_amount.mul(getBoost(_user)).div(BOOST_BASE));
        }
        return _amount;
    }

    function getRatio() public view returns (uint256) {
        return balance().mul(1e18).div(totalSupply());
    }

    function balance() public view returns (uint256) {
        return
            token.balanceOf(address(this)).add(
                IStrategy(strategy).balanceOf()
            );
    }

    function getPendingReward(address _user) public view returns (uint256) {
        return userInfo[_user].shares.mul(accRewardPerShare).div(1e12).sub(userInfo[_user].rewardDebt);
    }

    function getLastDepositTime(address _user) public view returns (uint256) {
        return userInfo[_user].lastDepositTime;
    }

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

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

    function deposit(uint256 _amount) public {
        require(msg.sender == tx.origin, "no contracts");
        _claimReward(msg.sender);

        uint256 _pool = balance();
        uint256 _before = token.balanceOf(address(this));
        token.safeTransferFrom(msg.sender, address(this), _amount);
        uint256 _after = token.balanceOf(address(this));
        _amount = _after.sub(_before); // Additional check for deflationary tokens
        uint256 shares = 0;
        if (totalSupply() == 0) {
            shares = _amount;
        } else {
            shares = (_amount.mul(totalSupply())).div(_pool);
        }

        //when farming pools with a deposit fee
        if(depositFee > 0) {
            uint256 fee = shares.mul(depositFee).div(keepMax);
            shares = shares.sub(fee);
        }

        totalShares = totalShares.add(shares);

        UserInfo storage user = userInfo[msg.sender];
        user.shares = user.shares.add(shares);
        user.rewardDebt = user.shares.mul(accRewardPerShare).div(1e12);
        user.lastDepositTime = now;

        _mint(msg.sender, shares);
        earn();
        emit Deposited(msg.sender, _amount);
    }

    function withdrawAll() external {
        withdraw(balanceOf(msg.sender));
    }

    // No rebalance implementation for lower fees and faster swaps
    function withdraw(uint256 _shares) public {
        uint256 r = (balance().mul(_shares)).div(totalSupply());
        _burn(msg.sender, _shares);
        _claimReward(msg.sender);

        // Check balance
        uint256 b = token.balanceOf(address(this));
        if (b < r) {
            uint256 _withdraw = r.sub(b);
            IStrategy(strategy).withdraw(_withdraw);
            uint256 _after = token.balanceOf(address(this));
            uint256 _diff = _after.sub(b);
            if (_diff < _withdraw) {
                r = b.add(_diff);
            }
        }

        totalShares = totalShares.sub(_shares);

        //This will cause integer underflow if the caller got share tokens from another address (i.e. when liquidating someone else's leveraged position)
        //Use emergencyWithdraw() if that is the case
        UserInfo storage user = userInfo[msg.sender];
        user.shares = user.shares.sub(_shares);
        user.rewardDebt = user.shares.mul(accRewardPerShare).div(1e12);

        // Deduct early withdrawal fee if applicable
        if(user.lastDepositTime.add(withdrawPenaltyTime) >= now) {
            uint256 earlyWithdrawalFee = r.mul(withdrawPenalty).div(keepMax);
            r = r.sub(earlyWithdrawalFee);
            token.safeTransfer(ercFund, earlyWithdrawalFee);
        }

        token.safeTransfer(msg.sender, r);
        emit Withdrawn(msg.sender, r);
    }

    //Withdraw without caring about rewards. EMERGENCY ONLY.
    //If withdrawing someone else's shares, any of the caller's shares will also be withdrawn
    function emergencyWithdraw() public {
        uint256 _shares = balanceOf(msg.sender);
        uint256 r = (balance().mul(_shares)).div(totalSupply());
        _burn(msg.sender, _shares);

        // Check balance
        uint256 b = token.balanceOf(address(this));
        if (b < r) {
            uint256 _withdraw = r.sub(b);
            IStrategy(strategy).withdraw(_withdraw);
            uint256 _after = token.balanceOf(address(this));
            uint256 _diff = _after.sub(b);
            if (_diff < _withdraw) {
                r = b.add(_diff);
            }
        }

        UserInfo storage user = userInfo[msg.sender];
        if(_shares <= totalShares) {
            totalShares = totalShares.sub(_shares);
        }
        else {
            totalShares = 0;
        }
        user.shares = 0;
        user.rewardDebt = 0;

        // Deduct early withdrawal fee if applicable
        if(user.lastDepositTime.add(withdrawPenaltyTime) >= now) {
            uint256 earlyWithdrawalFee = r.mul(withdrawPenalty).div(keepMax);
            r = r.sub(earlyWithdrawalFee);
            token.safeTransfer(ercFund, earlyWithdrawalFee);
        }

        token.safeTransfer(msg.sender, r);
        emit Withdrawn(msg.sender, r);
    }

    function claim() public {
        UserInfo storage user = userInfo[msg.sender];
        require(user.shares > 0, "no stake");

        _claimReward(msg.sender);

        user.rewardDebt = user.shares.mul(accRewardPerShare).div(1e12);
    }

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

    function earn() internal {
        uint256 _bal = token.balanceOf(address(this));
        token.safeTransfer(strategy, _bal);
        IStrategy(strategy).deposit();
    }

    // Handles claiming the user's pending rewards
    function _claimReward(address _user) internal virtual;

    /* ========== RESTRICTED FUNCTIONS ========== */

    //Vault deployer also needs to register the vault with the new minter
    function setMinter(address newMinter) public onlyOwner {
        require(newMinter != address(0));
        minter = IMinter(newMinter);
    }

    //Sets a new boost handler
    //Set boost handler to the zero address in order to disable it
    function setBoostHandler(address _handler) public onlyOwner {
        boostHandler = _handler;
    }

    function setWithdrawPenaltyTime(uint256 _withdrawPenaltyTime) public onlyOwner {
        require(_withdrawPenaltyTime <= 30 days, "delay too high");
        withdrawPenaltyTime = _withdrawPenaltyTime;
    }

    function setWithdrawPenalty(uint256 _withdrawPenalty) public onlyOwner {
        require(_withdrawPenalty <= 50, "penalty too high");
        withdrawPenalty = _withdrawPenalty;
    }

    function setRewardMultiplier(uint256 _rewardMultiplier) public onlyOwner {
        require(_rewardMultiplier <= MULTIPLIER_MAX, "multiplier too high");
        rewardMultiplier = _rewardMultiplier;
    }

    //shouldn't be farming things with a high deposit fee in the first place
    function setPoolDepositFee(uint256 _depositFee) public onlyOwner {
        require(_depositFee <= 1000, "deposit fee too high");
        depositFee = _depositFee;
    }

    //Increase the amount of the token sent to the fee dist the vault is allowed to mint ADDY for
    function increaseRewardAllocation(uint256 _newReward) public onlyOwner {
        rewardAllocation = rewardAllocation.add(_newReward);
        emit RewardAllocated(_newReward, rewardAllocation);
    }

    /* ========== EVENTS ========== */

    event Deposited(address indexed user, uint256 amount);
    event Withdrawn(address indexed user, uint256 amount);
    event RewardAdded(address reward, uint256 amount);
    event Claimed(address indexed user, uint256 amount);
    event RewardAllocated(uint256 newReward, uint256 totalAllocation);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "./VaultBase.sol";

contract GenericVault is VaultBase {

    constructor(IStrategy _strategy, address _minter, address _ercFund) 
        public
        VaultBase(_strategy, _minter, _ercFund)
    {
        
    }

    // Handles claiming the user's pending rewards
    function _claimReward(address _user) internal override {
        UserInfo storage user = userInfo[_user];
        if (user.shares > 0) {
            uint256 pendingReward = user.shares.mul(accRewardPerShare).div(1e12).sub(user.rewardDebt);
            if (pendingReward > 0) {
                totalPendingReward = totalPendingReward.sub(pendingReward);
                //Hard cap of 3x rewards after all multipliers
                uint256 rewardCap = pendingReward.mul(3);

                //Apply reward multiplier to the pendingReward that the minter will mint for
                pendingReward = applyRewardMultiplier(pendingReward);

                //Apply boost from locked ADDY 
                pendingReward = applyBoost(_user, pendingReward);

                //If pending reward > the 3x hard cap, then set the pending reward to the 3x hard cap
                if(pendingReward > rewardCap) {
                    pendingReward = rewardCap;
                }
                
                //Make sure the amount of the fee dist token the vault is allowed to mint for is above the amount we're trying to mint for
                //Require statement is there for end users
                require(rewardAllocation >= pendingReward, "Not enough rewards allocated");
                rewardAllocation = rewardAllocation.sub(pendingReward);

                //Minter will mint to MultiFeeDistribution and then stake the minted tokens for the user
                minter.mintFor(_user, IStrategy(strategy).getFeeDistToken(), pendingReward);
                emit Claimed(_user, pendingReward);
            }
        }
    }

    //Strategy calls notifyReward to let the vault know that it earned a certain amount of profit (the performance fee) for gov token stakers
    function notifyReward(address _reward, uint256 _amount) public {
        require(msg.sender == strategy);
        if(_amount == 0) {
            return;
        }

        totalPendingReward = totalPendingReward.add(_amount);
        accRewardPerShare = accRewardPerShare.add(_amount.mul(1e12).div(totalShares)); //shouldn't be adding reward if totalShares == 0 anyway

        emit RewardAdded(_reward, _amount);
    }
}

// Based on https://github.com/iearn-finance/vaults/blob/master/contracts/vaults/yVault.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "../interfaces/IStrategy.sol";
import "../lib/Pausable.sol";

//Represents a share of a user's vault deposit
contract Jar is ERC20, Ownable, Pausable, ReentrancyGuard {
    using SafeERC20 for IERC20;
    using Address for address;
    using SafeMath for uint256;

    uint256 internal constant FEE_DENOMINATOR = 10000;

    IERC20 public token;
    address public strategy;
    //Because a withdrawal penalty can't be implemented, users should be made to compound the vault when depositing for certain vaults
    bool public compoundBeforeDeposit = true;
    uint256 public depositFee;

    //The vault deployer contract should determine the name and symbol for the jar
    //i.e. for Uniswap fork deployers, name = ERC20(IUniswapV2Pair(_strategy.want()).token0()).name() + ERC20(IUniswapV2Pair(_strategy.want()).token1()).name();
    constructor(IStrategy _strategy, string memory name, string memory symbol)
        public
        ERC20(name, symbol)
    {
        require(address(_strategy) != address(0));
        _setupDecimals(ERC20(_strategy.want()).decimals());
        token = IERC20(_strategy.want());
        strategy = address(_strategy);
    }

    function transfer(address recipient, uint256 amount) public override returns (bool) {
        require(recipient != address(this));
        return super.transfer(recipient, amount);
    }

    /* ========== FILLER VIEW FUNCTIONS FOR VAULT MULTICALL COMPATIBILITY ========== */

    function want() public view returns (address) {
        return address(token);
    }

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

    function totalShares() public view returns (uint256) {
        return totalSupply();
    }

    function balance() public view returns (uint256) {
        return
            token.balanceOf(address(this)).add(
                IStrategy(strategy).balanceOf()
            );
    }

    function getRatio() public view returns (uint256) {
        return balance().mul(1e18).div(totalSupply());
    }

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

    function depositAll() external {
        depositFor(token.balanceOf(msg.sender), msg.sender);
    }

    function deposit(uint256 _amount) external {
        depositFor(_amount, msg.sender);
    }

    //depositFor usually won't be called by any external contract except for the zapper
    //consider making it internal and limiting who can call the public version of the function, just to avoid any accidental/unintended usage
    //the contract ABI in the website won't include this function, and there's no reason I'd ever want to call it through Remix/Polygonscan
    function depositFor(uint256 _amount, address _beneficiary) public notPaused nonReentrant {
        if(compoundBeforeDeposit && balance() > 0) {
            IStrategy(strategy).harvest();
        }

        uint256 _pool = balance();
        uint256 _before = token.balanceOf(address(this));
        token.safeTransferFrom(msg.sender, address(this), _amount);
        uint256 _after = token.balanceOf(address(this));
        _amount = _after.sub(_before); // Additional check for deflationary tokens
        uint256 shares = 0;
        if (totalSupply() == 0) {
            shares = _amount;
        } else {
            shares = (_amount.mul(totalSupply())).div(_pool);
        }

        //when farming pools with a deposit fee i.e. Mai.finance
        if(depositFee > 0) {
            uint256 fee = shares.mul(depositFee).div(FEE_DENOMINATOR);
            shares = shares.sub(fee);
        }

        _mint(_beneficiary, shares);
        earn();
        emit Deposited(_beneficiary, _amount);
    }

    function earn() internal {
        uint256 _bal = token.balanceOf(address(this));
        token.safeTransfer(strategy, _bal);
        IStrategy(strategy).deposit();
    }

    function withdrawAll() external {
        withdraw(balanceOf(msg.sender));
    }

    // No rebalance implementation for lower fees and faster swaps
    function withdraw(uint256 _shares) public nonReentrant {
        uint256 r = (balance().mul(_shares)).div(totalSupply());
        _burn(msg.sender, _shares);

        // Check balance
        uint256 b = token.balanceOf(address(this));
        if (b < r) {
            uint256 _withdraw = r.sub(b);
            IStrategy(strategy).withdraw(_withdraw);
            uint256 _after = token.balanceOf(address(this));
            uint256 _diff = _after.sub(b);
            if (_diff < _withdraw) {
                r = b.add(_diff);
            }
        }

        token.safeTransfer(msg.sender, r);
        emit Withdrawn(msg.sender, r);
    }

    function setPoolDepositFee(uint256 _depositFee) public onlyOwner {
        require(_depositFee <= 1000); //shouldn't be farming things with a high deposit fee in the first place
        depositFee = _depositFee;
    }

    function setCompoundBeforeDeposit(bool _value) public onlyOwner {
        compoundBeforeDeposit = _value;
    }

    function salvage(address recipient, address _token, uint256 amount) public onlyOwner {
        // Admin cannot withdraw the staking token from the contract
        require(_token != address(token), "cannot salvage want");
        IERC20(token).safeTransfer(recipient, amount);
    }

    /* ========== EVENTS ========== */

    event Deposited(address indexed user, uint256 amount);
    event Withdrawn(address indexed user, uint256 amount);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "@openzeppelin/contracts/math/Math.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "../interfaces/IStrategy.sol";

contract LockedAddyVault is Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20;
    using Address for address;
    using SafeMath for uint256;

    // Info of each user
    struct UserInfo {
        uint256 shares; // User shares
        uint256 rewardDebt; // Reward debt (in terms of WMATIC)
        uint256 lastDepositTime;
        uint256 ending_timestamp; // The time the user's locked stake ends
    }

    uint256 public constant MIN_DURATION = 91 days; //13 weeks
    uint256 public constant MAX_DURATION = 1460 days; //4 years

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

    // Info of each user
    mapping (address => UserInfo) public userInfo;

    // The total amount of pending rewards available for stakers to claim
    uint256 public totalPendingReward;
    // Accumulated rewards per share, times 1e12.
    uint256 public accRewardPerShare;
    // The total # of shares issued
    uint256 public totalShares;

    IERC20 public token; //should be ADDY
    ERC20 public rewardsToken; //should be WMATIC
    bool public unlockedStakes; // Release lock stakes in case of system migration

    address public strategy;

    constructor(IStrategy _strategy, address _rewardsToken)
        public
    {
        require(address(_strategy) != address(0));
        token = IERC20(_strategy.want());
        strategy = address(_strategy);
        rewardsToken = ERC20(_rewardsToken);
    }

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

    function getRatio() public view returns (uint256) {
        return balance().mul(1e18).div(totalShares);
    }

    function balance() public view returns (uint256) {
        return
            token.balanceOf(address(this)).add(
                IStrategy(strategy).balanceOf()
            );
    }

    function balanceOf(address _user) public view returns (uint256) {
        return userInfo[_user].shares;
    }

    function getPendingReward(address _user) public view returns (uint256) {
        return userInfo[_user].shares.mul(accRewardPerShare).div(1e12).sub(userInfo[_user].rewardDebt);
    }

    function getLastDepositTime(address _user) public view returns (uint256) {
        return userInfo[_user].lastDepositTime;
    }

    function getEndingTimestamp(address _user) public view returns (uint256) {
        return userInfo[_user].ending_timestamp;
    }

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

    function depositAll(uint256 _duration) external {
        deposit(token.balanceOf(msg.sender), _duration);
    }

    //Locks ADDY for `_duration` starting from the current time
    //A new lock cannot be created when an existing lock already exists. The time on existing locks cannot be shortened.
    function deposit(uint256 _amount, uint256 _duration) public nonReentrant {
        require(msg.sender == tx.origin, "no contracts");
        //require(_amount > 0, "stake something"); //allow users to call deposit with _amount = 0 in order to extend the stake duration
        require(_duration >= MIN_DURATION, "minimum lock duration is 91 days"); //lock time of the fee distribution contract is 13 weeks = 91 days
        require(_duration <= MAX_DURATION, "maximum lock duration is 4 years");

        _claimReward(msg.sender);

        uint256 _pool = balance();
        uint256 _before = token.balanceOf(address(this));
        token.safeTransferFrom(msg.sender, address(this), _amount);
        uint256 _after = token.balanceOf(address(this));
        _amount = _after.sub(_before); // Additional check for deflationary tokens
        uint256 shares = 0;
        if (totalShares == 0) {
            shares = _amount;
        } else {
            shares = (_amount.mul(totalShares)).div(_pool);
        }

        totalShares = totalShares.add(shares);

        UserInfo storage user = userInfo[msg.sender];
        user.shares = user.shares.add(shares);
        user.rewardDebt = user.shares.mul(accRewardPerShare).div(1e12);
        user.lastDepositTime = now;
        //When adding more to an existing stake that has an ending time greater than 91 days from now, the website will call deposit() with duration = 91 days
        //So the time of the existing stake will be used
        user.ending_timestamp = Math.max(user.ending_timestamp, now.add(_duration));

        earn(); 
        emit Deposited(msg.sender, _amount, user.ending_timestamp);
    }
 
    function earn() internal {
        uint256 _bal = token.balanceOf(address(this));
        token.safeTransfer(strategy, _bal);
        IStrategy(strategy).deposit();
    }

    // Withdraw all tokens and claim rewards.
    function withdrawAll() external nonReentrant {
        UserInfo storage user = userInfo[msg.sender];
        //Even if unlockedStakes is true, locked stakes in the fee dist contract have a 91 day minimum lock time
        require(user.ending_timestamp < now || (unlockedStakes && user.lastDepositTime.add(MIN_DURATION) < now), "Can't withdraw yet");

        uint256 _shares = user.shares;
        uint256 r = (balance().mul(_shares)).div(totalShares);

        _claimReward(msg.sender);

        // Check balance
        uint256 b = token.balanceOf(address(this));
        if (b < r) {
            uint256 _withdraw = r.sub(b);
            IStrategy(strategy).withdraw(_withdraw);
            uint256 _after = token.balanceOf(address(this));
            uint256 _diff = _after.sub(b);
            if (_diff < _withdraw) {
                r = b.add(_diff);
            }
        }

        totalShares = totalShares.sub(_shares);

        user.shares = user.shares.sub(_shares);
        user.rewardDebt = user.shares.mul(accRewardPerShare).div(1e12);

        token.safeTransfer(msg.sender, r);
        emit Withdrawn(msg.sender, r);
    }

    // Withdraw all tokens without caring about rewards. EMERGENCY ONLY.
    function emergencyWithdraw() public nonReentrant {
        UserInfo storage user = userInfo[msg.sender];
        //Even if unlockedStakes is true, locked stakes in the fee dist contract have a 91 day minimum lock time
        require(user.ending_timestamp < now || (unlockedStakes && user.lastDepositTime.add(MIN_DURATION) < now), "Can't withdraw yet");

        uint256 _shares = user.shares;
        uint256 r = (balance().mul(_shares)).div(totalShares);

        // Check balance
        uint256 b = token.balanceOf(address(this));
        if (b < r) {
            uint256 _withdraw = r.sub(b);
            IStrategy(strategy).withdraw(_withdraw);
            uint256 _after = token.balanceOf(address(this));
            uint256 _diff = _after.sub(b);
            if (_diff < _withdraw) {
                r = b.add(_diff);
            }
        }

        if(_shares <= totalShares) {
            totalShares = totalShares.sub(_shares);
        }
        else {
            totalShares = 0;
        }
        user.shares = 0;
        user.rewardDebt = 0;

        token.safeTransfer(msg.sender, r);
        emit Withdrawn(msg.sender, r);
    }
    
    function claim() public nonReentrant {
        UserInfo storage user = userInfo[msg.sender];
        require(user.shares > 0, "no stake");

        _claimReward(msg.sender);

        user.rewardDebt = user.shares.mul(accRewardPerShare).div(1e12);
    }

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

    // Handles claiming the user's pending rewards
    function _claimReward(address _user) internal {
        UserInfo storage user = userInfo[_user];
        if (user.shares > 0) {
            uint256 pendingReward = user.shares.mul(accRewardPerShare).div(1e12).sub(user.rewardDebt);
            if (pendingReward > 0) {
                totalPendingReward = totalPendingReward.sub(pendingReward);
                _safeRewardTransfer(_user, pendingReward);
                emit Claimed(_user, pendingReward);
            }
        }
    }

    // Internal function to safely transfer reward in case there is a rounding error
    function _safeRewardTransfer(address _to, uint256 _amount) internal {
        uint256 _bal = rewardsToken.balanceOf(address(this));
        if (_amount > _bal) {
            rewardsToken.transfer(_to, _bal);
        } else {
            rewardsToken.transfer(_to, _amount);
        }
    }

    /* ========== RESTRICTED FUNCTIONS ========== */

    function unlockStakes() external onlyOwner {
        unlockedStakes = !unlockedStakes;
    }

    //Strategy calls notifyReward to let the vault know that it sent a certain amount of WMATIC to it
    function notifyReward(address _reward, uint256 _amount) public {
        require(msg.sender == strategy);
        if(_amount == 0) {
            return;
        }

        totalPendingReward = totalPendingReward.add(_amount);
        accRewardPerShare = accRewardPerShare.add(_amount.mul(1e12).div(totalShares)); //shouldn't be adding reward if totalShares == 0 anyway

        emit RewardAdded(_reward, _amount);
    }

    /* ========== EVENTS ========== */

    event Deposited(address indexed user, uint256 amount, uint256 endingTimeStamp);
    event Withdrawn(address indexed user, uint256 amount);
    event RewardAdded(address reward, uint256 amount);
    event Claimed(address indexed user, uint256 amount);
    event RewardAllocated(uint256 newReward, uint256 totalAllocation);
}

File 74 of 98 : StakingDualRewards2.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
//Modified SNX's StakingDualRewards contract

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/math/Math.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "../lib/Pausable.sol";
import "../interfaces/IStrategy.sol";

interface IStakingDualRewards {
    // Views
    function lastTimeRewardApplicable() external view returns (uint256);

    function rewardPerTokenA() external view returns (uint256);
    function rewardPerTokenB() external view returns (uint256);

    function earnedA(address account) external view returns (uint256);
    function earnedB(address account) external view returns (uint256);

    function getRewardAForDuration() external view returns (uint256);
    function getRewardBForDuration() external view returns (uint256);

    //Renamed from `totalSupply()` to match LockedAddyVault
    function balance() external view returns (uint256);

    function balanceOf(address account) external view returns (uint256);

    // Mutative

    //Renamed from `stake()` to match LockedAddyVault
    function deposit(uint256 amount, uint256 duration) external;

    function withdraw(uint256 amount) external;

    //Renamed from `getReward()` to match LockedAddyVault
    function claim() external;

    //Renamed from `exit()` to match LockedAddyVault
    function withdrawAll() external;
}

abstract contract DualRewardsDistributionRecipient is Ownable {
    address public dualRewardsDistribution;

    function notifyRewardAmount(uint256 rewardA, uint256 rewardB) external virtual;

    modifier onlyDualRewardsDistribution() {
        require(msg.sender == dualRewardsDistribution, "Caller is not DualRewardsDistribution contract");
        _;
    }

    function setDualRewardsDistribution(address _dualRewardsDistribution) external onlyOwner {
        dualRewardsDistribution = _dualRewardsDistribution;
    }
}

contract StakingDualRewards2 is IStakingDualRewards, DualRewardsDistributionRecipient, ReentrancyGuard, Pausable {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    uint256 public constant MIN_DURATION = 91 days; //13 weeks
    uint256 public constant MAX_DURATION = 1460 days; //4 years

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

    IERC20 public rewardsTokenA;
    IERC20 public rewardsTokenB;
    IERC20 public stakingToken;
    uint256 public periodFinish = 0;
    uint256 public rewardRateA = 0;
    uint256 public rewardRateB = 0;
    uint256 public rewardsDuration = 1; //instantly distribute rewards
    uint256 public lastUpdateTime;
    uint256 public rewardPerTokenAStored;
    uint256 public rewardPerTokenBStored;

    address public strategy;

    mapping(address => uint256) public userUnlockTime;
    mapping(address => uint256) public userDepositTime;
    mapping(address => uint256) public userRewardPerTokenAPaid;
    mapping(address => uint256) public userRewardPerTokenBPaid;
    mapping(address => uint256) public rewardsA;
    mapping(address => uint256) public rewardsB;

    mapping(address => bool) public approvedStakers;

    uint256 private _totalSupply;
    mapping(address => uint256) private _balances;

    bool public unlockedStakes; // Release lock stakes in case of system migration

    /* ========== CONSTRUCTOR ========== */

    constructor(
        address _strategy,
        address _rewardsTokenA, //WMATIC
        address _rewardsTokenB, //ADDY
        address _stakingToken
    ) public {
        require(_rewardsTokenA != _rewardsTokenB, "rewards tokens should be different");
        rewardsTokenA = IERC20(_rewardsTokenA);
        rewardsTokenB = IERC20(_rewardsTokenB);
        stakingToken = IERC20(_stakingToken);

        dualRewardsDistribution = _strategy;
        strategy = _strategy;
    }

    /* ========== VIEWS ========== */

    function balance() external override view returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(address account) external override view returns (uint256) {
        return _balances[account];
    }

    function lastTimeRewardApplicable() public override view returns (uint256) {
        return Math.min(block.timestamp, periodFinish);
    }

    function rewardPerTokenA() public override view returns (uint256) {
        if (_totalSupply == 0) {
            return rewardPerTokenAStored;
        }
        return
            rewardPerTokenAStored.add(
                lastTimeRewardApplicable().sub(lastUpdateTime).mul(rewardRateA).mul(1e18).div(_totalSupply)
            );
    }

    function rewardPerTokenB() public override view returns (uint256) {
        if (_totalSupply == 0) {
            return rewardPerTokenBStored;
        }

        return
            rewardPerTokenBStored.add(
                lastTimeRewardApplicable().sub(lastUpdateTime).mul(rewardRateB).mul(1e18).div(_totalSupply)
            );
    }

    function earnedA(address account) public override view returns (uint256) {
        return _balances[account].mul(rewardPerTokenA().sub(userRewardPerTokenAPaid[account])).div(1e18).add(rewardsA[account]);
    }

    function earnedB(address account) public override view returns (uint256) {
        return
            _balances[account].mul(rewardPerTokenB().sub(userRewardPerTokenBPaid[account])).div(1e18).add(rewardsB[account]);
    }

    function getRewardAForDuration() external override view returns (uint256) {
        return rewardRateA.mul(rewardsDuration);
    }

    function getRewardBForDuration() external override view returns (uint256) {
        return rewardRateB.mul(rewardsDuration);
    }

    //Functions added for compatibility with LockedAddyVault
    function getLastDepositTime(address _user) public view returns (uint256) {
        return userDepositTime[_user];
    }

    function getEndingTimestamp(address _user) public view returns (uint256) {
        return userUnlockTime[_user];
    }

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

    function setLockEndingDate(uint256 duration) external nonReentrant notPaused {
        require(duration >= MIN_DURATION, "minimum lock duration is 91 days");
        require(duration <= MAX_DURATION, "maximum lock duration is 4 years");
        userUnlockTime[msg.sender] = Math.max(userUnlockTime[msg.sender], now.add(duration));
    }

    //Added for compatibility with LockedAddyVault
    function depositAll(uint256 duration) external {
        deposit(stakingToken.balanceOf(msg.sender), duration);
    }

    function depositFor(address beneficiary, uint256 amount, uint256 duration) public nonReentrant notPaused updateReward(beneficiary) {
        //To prevent griefers from permanently locking users by depositing for them
        require(beneficiary == msg.sender || approvedStakers[msg.sender], "Not authorized to stake for msg.sender");

        require(amount > 0, "Cannot stake 0");
        require(duration >= MIN_DURATION, "minimum lock duration is 91 days"); //lock time of the fee distribution contract is 13 weeks = 91 days
        require(duration <= MAX_DURATION, "maximum lock duration is 4 years");

        _totalSupply = _totalSupply.add(amount);
        _balances[beneficiary] = _balances[beneficiary].add(amount);
        stakingToken.safeTransferFrom(msg.sender, address(this), amount);

        userUnlockTime[beneficiary] = Math.max(userUnlockTime[beneficiary], now.add(duration));
        userDepositTime[beneficiary] = now;

        stakingToken.safeTransfer(strategy, amount);
        IStrategy(strategy).deposit();

        emit Staked(beneficiary, amount, userUnlockTime[beneficiary]);
    }

    function deposit(uint256 amount, uint256 duration) public override {
        depositFor(msg.sender, amount, duration);
    }

    function withdraw(uint256 amount) public override nonReentrant updateReward(msg.sender) {
        require(amount > 0, "Cannot withdraw 0");
        //Even if unlockedStakes is true, locked stakes in the fee dist contract have a 91 day minimum lock time
        require(userUnlockTime[msg.sender] < now || (unlockedStakes && userDepositTime[msg.sender].add(MIN_DURATION) < now), "Can't withdraw yet");

        _totalSupply = _totalSupply.sub(amount);
        _balances[msg.sender] = _balances[msg.sender].sub(amount);

        IStrategy(strategy).withdraw(amount);

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

    function claim() public override nonReentrant updateReward(msg.sender) {
        require(userUnlockTime[msg.sender] > now || unlockedStakes, "User must withdraw and restake if their lock is expired");

        uint256 rewardAmountA = rewardsA[msg.sender];
        if (rewardAmountA > 0) {
            rewardsA[msg.sender] = 0;
            rewardsTokenA.safeTransfer(msg.sender, rewardAmountA);
            emit RewardPaid(msg.sender, address(rewardsTokenA), rewardAmountA);
        }

        uint256 rewardAmountB = rewardsB[msg.sender];
        if (rewardAmountB > 0) {
            rewardsB[msg.sender] = 0;
            rewardsTokenB.safeTransfer(msg.sender, rewardAmountB);
            emit RewardPaid(msg.sender, address(rewardsTokenB), rewardAmountB);
        }
    }

    function withdrawAll() external override {
        withdraw(_balances[msg.sender]);
        _claim();
    }

    //Copy of the normal claim() function, used by withdrawAll()
    function _claim() internal nonReentrant updateReward(msg.sender) {
        uint256 rewardAmountA = rewardsA[msg.sender];
        if (rewardAmountA > 0) {
            rewardsA[msg.sender] = 0;
            rewardsTokenA.safeTransfer(msg.sender, rewardAmountA);
            emit RewardPaid(msg.sender, address(rewardsTokenA), rewardAmountA);
        }

        uint256 rewardAmountB = rewardsB[msg.sender];
        if (rewardAmountB > 0) {
            rewardsB[msg.sender] = 0;
            rewardsTokenB.safeTransfer(msg.sender, rewardAmountB);
            emit RewardPaid(msg.sender, address(rewardsTokenB), rewardAmountB);
        }
    }

    /* ========== RESTRICTED FUNCTIONS ========== */

    function notifyRewardAmount(uint256 rewardA, uint256 rewardB) external override onlyDualRewardsDistribution updateReward(address(0)) {

        if (block.timestamp >= periodFinish) {
            rewardRateA = rewardA.div(rewardsDuration);
            rewardRateB = rewardB.div(rewardsDuration);
        } else {
            uint256 remaining = periodFinish.sub(block.timestamp);

            uint256 leftoverA = remaining.mul(rewardRateA);
            rewardRateA = rewardA.add(leftoverA).div(rewardsDuration);

            uint256 leftoverB = remaining.mul(rewardRateB);
            rewardRateB = rewardB.add(leftoverB).div(rewardsDuration);
          }

        // Ensure the provided reward amount is not more than the balance in the contract.
        // This keeps the reward rate in the right range, preventing overflows due to
        // very high values of rewardRate in the earned and rewardsPerToken functions;
        // Reward + leftover must be less than 2^256 / 10^18 to avoid overflow.
        uint balanceA = rewardsTokenA.balanceOf(address(this));
        require(rewardRateA <= balanceA.div(rewardsDuration), "Provided reward-A too high");

        uint balanceB = rewardsTokenB.balanceOf(address(this));
        require(rewardRateB <= balanceB.div(rewardsDuration), "Provided reward-B too high");

        lastUpdateTime = block.timestamp;
        periodFinish = block.timestamp.add(rewardsDuration);
        emit RewardAdded(rewardA, rewardB);
    }

    // End rewards emission earlier
    function updatePeriodFinish(uint timestamp) external onlyOwner updateReward(address(0)) {
        periodFinish = timestamp;
    }

    // Added to support recovering LP Rewards from other systems such as BAL to be distributed to holders
    function recoverERC20(address tokenAddress, uint256 tokenAmount) external onlyOwner {
        require(tokenAddress != address(stakingToken), "Cannot withdraw the staking token");
        IERC20(tokenAddress).safeTransfer(owner(), tokenAmount);
        emit Recovered(tokenAddress, tokenAmount);
    }

    function setRewardsDuration(uint256 _rewardsDuration) external onlyOwner {
        require(
            block.timestamp > periodFinish,
            "Previous rewards period must be complete before changing the duration for the new period"
        );
        rewardsDuration = _rewardsDuration;
        emit RewardsDurationUpdated(rewardsDuration);
    }

    function unlockStakes() external onlyOwner {
        unlockedStakes = !unlockedStakes;
    }

    function setApprovedStaker(address _staker, bool _approved) external onlyOwner {
        approvedStakers[_staker] = _approved;
    }

    /* ========== MODIFIERS ========== */

    modifier updateReward(address account) {

        rewardPerTokenAStored = rewardPerTokenA();
        rewardPerTokenBStored = rewardPerTokenB();
        lastUpdateTime = lastTimeRewardApplicable();
        if (account != address(0)) {
            rewardsA[account] = earnedA(account);
            userRewardPerTokenAPaid[account] = rewardPerTokenAStored;
        }

        if (account != address(0)) {
            rewardsB[account] = earnedB(account);
            userRewardPerTokenBPaid[account] = rewardPerTokenBStored;
        }
        _;
    }

    /* ========== EVENTS ========== */

    event RewardAdded(uint256 rewardA, uint256 rewardB);
    event Staked(address indexed user, uint256 amount, uint256 endingTimeStamp);
    event Withdrawn(address indexed user, uint256 amount);
    event RewardPaid(address indexed user, address rewardToken, uint256 reward);
    event RewardsDurationUpdated(uint256 newDuration);
    event Recovered(address token, uint256 amount);
}

// Based on https://github.com/iearn-finance/vaults/blob/master/contracts/vaults/yVault.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "../interfaces/governance/IBoostHandler.sol";
import "../interfaces/IStrategy.sol";
import "../interfaces/IMinter.sol";
import "../interfaces/IVault.sol";

abstract contract VaultBase is IVault, Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20;
    using Address for address;
    using SafeMath for uint256;

    // Info of each user
    struct UserInfo {
        uint256 shares; // User shares
        uint256 rewardDebt; // Reward debt (in terms of WMATIC)
        uint256 lastDepositTime;
        uint256 tokensStaked; // Number of tokens staked, only used to calculate profit on the frontend (different than shares)
    }

    uint256 public constant keepMax = 10000;

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

    // Info of each user
    mapping (address => UserInfo) public userInfo;

    // The total amount of pending rewards available for stakers to claim
    uint256 public override totalPendingReward;
    // Accumulated rewards per share, times 1e12.
    uint256 public accRewardPerShare;
    // The total # of shares issued
    uint256 public override totalShares;
    // Withdrawing before this much time has passed will have a withdrawal penalty
    uint256 public override withdrawPenaltyTime = 3 days;
    // Withdrawal penalty, 100 = 1%
    uint256 public override withdrawPenalty = 50;
    // For vaults that are farming pools with a deposit fee
    uint256 public depositFee = 0;
    //Allowed amount of the token sent to the fee dist each vault can mint ADDY rewards for, default 5 WETH
    uint256 public override rewardAllocation = 1e18 * 5;

    // Certain vaults will give up to 10x ADDY rewards
    // Additional usecase for ADDY: lock it to boost the yield of a certain vault
    uint256 private rewardMultiplier = 1000;
    uint256 private constant MULTIPLIER_BASE = 1000;
    uint256 private constant MULTIPLIER_MAX = 10000;
    uint256 private constant BOOST_BASE = 10 ** 18;

    IERC20 public override token;
    address public override strategy;
    IMinter public minter;
    address public ercFund;
    address public boostHandler;

    constructor(IStrategy _strategy, address _minter, address _ercFund)
        public
    {
        require(address(_strategy) != address(0));
        token = IERC20(_strategy.want());
        strategy = address(_strategy);
        minter = IMinter(_minter);
        ercFund = _ercFund;
    }

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

    // 1000 = 1x multiplier
    function getRewardMultiplier() public override view returns (uint256) {
        return rewardMultiplier;
    }

    function applyRewardMultiplier(uint256 _amount) internal view returns (uint256) {
        return _amount.mul(rewardMultiplier).div(MULTIPLIER_BASE);
    }

    function getBoost(address _user) public override view returns (uint256) {
        if (boostHandler != address(0)) {
            return IBoostHandler(boostHandler).getBoost(_user, address(this));
        }
        return 0;
    }

    //Returns base amount + amount from boost
    function applyBoost(address _user, uint256 _amount) internal view returns (uint256) {
        if (boostHandler != address(0)) {
            return _amount.add(_amount.mul(getBoost(_user)).div(BOOST_BASE));
        }
        return _amount;
    }

    function getRatio() public override view returns (uint256) {
        return balance().mul(1e18).div(totalShares);
    }

    function balance() public override view returns (uint256) {
        return
            token.balanceOf(address(this)).add(
                IStrategy(strategy).balanceOf()
            );
    }

    function balanceOf(address _user) public override view returns (uint256) {
        return userInfo[_user].shares;
    }

    function getPendingReward(address _user) public override view returns (uint256) {
        return userInfo[_user].shares.mul(accRewardPerShare).div(1e12).sub(userInfo[_user].rewardDebt);
    }

    function getLastDepositTime(address _user) public override view returns (uint256) {
        return userInfo[_user].lastDepositTime;
    }

    function getTokensStaked(address _user) public override view returns (uint256) {
        return userInfo[_user].tokensStaked;
    }

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

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

    function deposit(uint256 _amount) public override nonReentrant {
        require(msg.sender == tx.origin, "no contracts");
        _claimReward(msg.sender);

        uint256 _pool = balance();
        uint256 _before = token.balanceOf(address(this));
        token.safeTransferFrom(msg.sender, address(this), _amount);
        uint256 _after = token.balanceOf(address(this));
        _amount = _after.sub(_before); // Additional check for deflationary tokens
        uint256 shares = 0;
        if (totalShares == 0) {
            shares = _amount;
        } else {
            shares = (_amount.mul(totalShares)).div(_pool);
        }

        //when farming pools with a deposit fee
        if(depositFee > 0) {
            uint256 fee = shares.mul(depositFee).div(keepMax);
            shares = shares.sub(fee);
        }

        totalShares = totalShares.add(shares);

        UserInfo storage user = userInfo[msg.sender];
        user.shares = user.shares.add(shares);
        user.rewardDebt = user.shares.mul(accRewardPerShare).div(1e12);
        user.lastDepositTime = now;
        user.tokensStaked = user.tokensStaked.add(_amount);

        earn();
        emit Deposited(msg.sender, _amount);
    }

    function earn() internal {
        uint256 _bal = token.balanceOf(address(this));
        token.safeTransfer(strategy, _bal);
        IStrategy(strategy).deposit();
    }

    // Withdraw all tokens and claim rewards.
    function withdrawAll() external override nonReentrant {
        UserInfo storage user = userInfo[msg.sender];
        uint256 _shares = user.shares;
        uint256 r = (balance().mul(_shares)).div(totalShares);

        _claimReward(msg.sender);

        // Check balance
        uint256 b = token.balanceOf(address(this));
        if (b < r) {
            uint256 _withdraw = r.sub(b);
            IStrategy(strategy).withdraw(_withdraw);
            uint256 _after = token.balanceOf(address(this));
            uint256 _diff = _after.sub(b);
            if (_diff < _withdraw) {
                r = b.add(_diff);
            }
        }

        totalShares = totalShares.sub(_shares);

        user.shares = user.shares.sub(_shares);
        user.rewardDebt = user.shares.mul(accRewardPerShare).div(1e12);
        user.tokensStaked = 0;
        // Deduct early withdrawal fee if applicable
        if(user.lastDepositTime.add(withdrawPenaltyTime) >= now) {
            uint256 earlyWithdrawalFee = r.mul(withdrawPenalty).div(keepMax);
            r = r.sub(earlyWithdrawalFee);
            token.safeTransfer(ercFund, earlyWithdrawalFee);
        }

        token.safeTransfer(msg.sender, r);
        emit Withdrawn(msg.sender, r);
    }

    // Withdraw all tokens without caring about rewards in the event that the reward mechanism breaks.
    // Normal early withdrawal penalties will apply.
    function emergencyWithdraw() public nonReentrant {
        UserInfo storage user = userInfo[msg.sender];
        uint256 _shares = user.shares;
        uint256 r = (balance().mul(_shares)).div(totalShares);

        // Check balance
        uint256 b = token.balanceOf(address(this));
        if (b < r) {
            uint256 _withdraw = r.sub(b);
            IStrategy(strategy).withdraw(_withdraw);
            uint256 _after = token.balanceOf(address(this));
            uint256 _diff = _after.sub(b);
            if (_diff < _withdraw) {
                r = b.add(_diff);
            }
        }

        if(_shares <= totalShares) {
            totalShares = totalShares.sub(_shares);
        }
        else {
            totalShares = 0;
        }
        user.shares = 0;
        user.rewardDebt = 0;
        user.tokensStaked = 0;
        // Deduct early withdrawal fee if applicable
        if(user.lastDepositTime.add(withdrawPenaltyTime) >= now) {
            uint256 earlyWithdrawalFee = r.mul(withdrawPenalty).div(keepMax);
            r = r.sub(earlyWithdrawalFee);
            token.safeTransfer(ercFund, earlyWithdrawalFee);
        }

        token.safeTransfer(msg.sender, r);
        emit Withdrawn(msg.sender, r);
    }

    function claim() public nonReentrant {
        UserInfo storage user = userInfo[msg.sender];
        require(user.shares > 0, "no stake");

        _claimReward(msg.sender);

        user.rewardDebt = user.shares.mul(accRewardPerShare).div(1e12);
    }

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

    // Handles claiming the user's pending rewards
    function _claimReward(address _user) internal virtual;

    /* ========== RESTRICTED FUNCTIONS ========== */

    //Vault deployer also needs to register the vault with the new minter
    function setMinter(address newMinter) public onlyOwner {
        require(newMinter != address(0));
        minter = IMinter(newMinter);
    }

    //Sets a new boost handler
    //Set boost handler to the zero address in order to disable it
    function setBoostHandler(address _handler) public onlyOwner {
        boostHandler = _handler;
    }

    function setWithdrawPenaltyTime(uint256 _withdrawPenaltyTime) public override onlyOwner {
        require(_withdrawPenaltyTime <= 30 days, "delay too high");
        withdrawPenaltyTime = _withdrawPenaltyTime;
    }

    function setWithdrawPenalty(uint256 _withdrawPenalty) public override onlyOwner {
        require(_withdrawPenalty <= 50, "penalty too high");
        withdrawPenalty = _withdrawPenalty;
    }

    function setRewardMultiplier(uint256 _rewardMultiplier) public override onlyOwner {
        require(_rewardMultiplier <= MULTIPLIER_MAX, "multiplier too high");
        rewardMultiplier = _rewardMultiplier;
    }

    //shouldn't be farming things with a high deposit fee in the first place
    function setPoolDepositFee(uint256 _depositFee) public onlyOwner {
        require(_depositFee <= 1000, "?");
        depositFee = _depositFee;
    }

    //Increase the amount of the token sent to the fee dist the vault is allowed to mint ADDY for
    function increaseRewardAllocation(uint256 _newReward) public override onlyOwner {
        rewardAllocation = rewardAllocation.add(_newReward);
        emit RewardAllocated(_newReward, rewardAllocation);
    }

    /* ========== EVENTS ========== */

    event Deposited(address indexed user, uint256 amount);
    event Withdrawn(address indexed user, uint256 amount);
    event RewardAdded(address reward, uint256 amount);
    event Claimed(address indexed user, uint256 amount);
    event RewardAllocated(uint256 newReward, uint256 totalAllocation);
}

File 76 of 98 : Pausable.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

// Inheritance
import "@openzeppelin/contracts/access/Ownable.sol";

// https://docs.synthetix.io/contracts/Pausable
abstract contract Pausable is Ownable {
    uint public lastPauseTime;
    bool public paused;

    constructor() internal {
        // This contract is abstract, and thus cannot be instantiated directly
        //require(owner != address(0), "Owner must be set");
        // Paused will be false, and lastPauseTime will be 0 upon initialisation
    }

    /**
     * @notice Change the paused state of the contract
     * @dev Only the contract owner may call this.
     */
    function setPaused(bool _paused) external onlyOwner {
        // Ensure we're actually changing the state before we do anything
        if (_paused == paused) {
            return;
        }

        // Set our paused state.
        paused = _paused;

        // If applicable, set the last pause time.
        if (paused) {
            lastPauseTime = now;
        }

        // Let everyone know that our pause state has changed.
        emit PauseChanged(paused);
    }

    event PauseChanged(bool isPaused);

    modifier notPaused {
        require(!paused, "This action cannot be performed while the contract is paused");
        _;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;


library StringHelpers {
    function parseAddr(string memory _a) internal pure returns (address _parsedAddress) {
        bytes memory tmp = bytes(_a);
        uint160 iaddr = 0;
        uint160 b1;
        uint160 b2;
        for (uint i = 2; i < 2 + 2 * 20; i += 2) {
            iaddr *= 256;
            b1 = uint160(uint8(tmp[i]));
            b2 = uint160(uint8(tmp[i + 1]));
            if ((b1 >= 97) && (b1 <= 102)) {
                b1 -= 87;
            } else if ((b1 >= 65) && (b1 <= 70)) {
                b1 -= 55;
            } else if ((b1 >= 48) && (b1 <= 57)) {
                b1 -= 48;
            }
            if ((b2 >= 97) && (b2 <= 102)) {
                b2 -= 87;
            } else if ((b2 >= 65) && (b2 <= 70)) {
                b2 -= 55;
            } else if ((b2 >= 48) && (b2 <= 57)) {
                b2 -= 48;
            }
            iaddr += (b1 * 16 + b2);
        }
        return address(iaddr);
    }

    function strCompare(string memory _a, string memory _b) internal pure returns (int _returnCode) {
        bytes memory a = bytes(_a);
        bytes memory b = bytes(_b);
        uint minLength = a.length;
        if (b.length < minLength) {
            minLength = b.length;
        }
        for (uint i = 0; i < minLength; i ++) {
            if (a[i] < b[i]) {
                return -1;
            } else if (a[i] > b[i]) {
                return 1;
            }
        }
        if (a.length < b.length) {
            return -1;
        } else if (a.length > b.length) {
            return 1;
        } else {
            return 0;
        }
    }

    function indexOf(string memory _haystack, string memory _needle) internal pure returns (int _returnCode) {
        bytes memory h = bytes(_haystack);
        bytes memory n = bytes(_needle);
        if (h.length < 1 || n.length < 1 || (n.length > h.length)) {
            return -1;
        } else if (h.length > (2 ** 128 - 1)) {
            return -1;
        } else {
            uint subindex = 0;
            for (uint i = 0; i < h.length; i++) {
                if (h[i] == n[0]) {
                    subindex = 1;
                    while(subindex < n.length && (i + subindex) < h.length && h[i + subindex] == n[subindex]) {
                        subindex++;
                    }
                    if (subindex == n.length) {
                        return int(i);
                    }
                }
            }
            return -1;
        }
    }

    function strConcat(string memory _a, string memory _b) internal pure returns (string memory _concatenatedString) {
        return strConcat(_a, _b, "", "", "");
    }

    function strConcat(string memory _a, string memory _b, string memory _c) internal pure returns (string memory _concatenatedString) {
        return strConcat(_a, _b, _c, "", "");
    }

    function strConcat(string memory _a, string memory _b, string memory _c, string memory _d) internal pure returns (string memory _concatenatedString) {
        return strConcat(_a, _b, _c, _d, "");
    }

    function strConcat(string memory _a, string memory _b, string memory _c, string memory _d, string memory _e) internal pure returns (string memory _concatenatedString) {
        bytes memory _ba = bytes(_a);
        bytes memory _bb = bytes(_b);
        bytes memory _bc = bytes(_c);
        bytes memory _bd = bytes(_d);
        bytes memory _be = bytes(_e);
        string memory abcde = new string(_ba.length + _bb.length + _bc.length + _bd.length + _be.length);
        bytes memory babcde = bytes(abcde);
        uint k = 0;
        uint i = 0;
        for (i = 0; i < _ba.length; i++) {
            babcde[k++] = _ba[i];
        }
        for (i = 0; i < _bb.length; i++) {
            babcde[k++] = _bb[i];
        }
        for (i = 0; i < _bc.length; i++) {
            babcde[k++] = _bc[i];
        }
        for (i = 0; i < _bd.length; i++) {
            babcde[k++] = _bd[i];
        }
        for (i = 0; i < _be.length; i++) {
            babcde[k++] = _be[i];
        }
        return string(babcde);
    }

    function safeParseInt(string memory _a) internal pure returns (uint _parsedInt) {
        return safeParseInt(_a, 0);
    }

    function safeParseInt(string memory _a, uint _b) internal pure returns (uint _parsedInt) {
        bytes memory bresult = bytes(_a);
        uint mint = 0;
        bool decimals = false;
        for (uint i = 0; i < bresult.length; i++) {
            if ((uint(uint8(bresult[i])) >= 48) && (uint(uint8(bresult[i])) <= 57)) {
                if (decimals) {
                   if (_b == 0) break;
                    else _b--;
                }
                mint *= 10;
                mint += uint(uint8(bresult[i])) - 48;
            } else if (uint(uint8(bresult[i])) == 46) {
                require(!decimals, 'More than one decimal encountered in string!');
                decimals = true;
            } else {
                revert("Non-numeral character encountered in string!");
            }
        }
        if (_b > 0) {
            mint *= 10 ** _b;
        }
        return mint;
    }

    function parseInt(string memory _a) internal pure returns (uint _parsedInt) {
        return parseInt(_a, 0);
    }

    function parseInt(string memory _a, uint _b) internal pure returns (uint _parsedInt) {
        bytes memory bresult = bytes(_a);
        uint mint = 0;
        bool decimals = false;
        for (uint i = 0; i < bresult.length; i++) {
            if ((uint(uint8(bresult[i])) >= 48) && (uint(uint8(bresult[i])) <= 57)) {
                if (decimals) {
                   if (_b == 0) {
                       break;
                   } else {
                       _b--;
                   }
                }
                mint *= 10;
                mint += uint(uint8(bresult[i])) - 48;
            } else if (uint(uint8(bresult[i])) == 46) {
                decimals = true;
            }
        }
        if (_b > 0) {
            mint *= 10 ** _b;
        }
        return mint;
    }

    function uint2str(uint _i) internal pure returns (string memory _uintAsString) {
        if (_i == 0) {
            return "0";
        }
        uint j = _i;
        uint len;
        while (j != 0) {
            len++;
            j /= 10;
        }
        bytes memory bstr = new bytes(len);
        uint k = len - 1;
        while (_i != 0) {
            bstr[k--] = byte(uint8(48 + _i % 10));
            _i /= 10;
        }
        return string(bstr);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

// 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, uint 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: APPROVE_FAILED');
    }

    function safeTransfer(address token, address to, uint 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: TRANSFER_FAILED');
    }

    function safeTransferFrom(address token, address from, address to, uint 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: TRANSFER_FROM_FAILED');
    }

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

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

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 "../../interfaces/IStrategy.sol";
import "../../interfaces/IJar.sol";
import "../../interfaces/uniswap/IUniswapV2Pair.sol";
import "../../interfaces/uniswap/IUniswapRouterV2.sol";

abstract contract BaseStrategy is IStrategy, Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20;
    using Address for address;
    using SafeMath for uint256;

    uint256 public override lastHarvestTime = 0;

    // Tokens
    address public override want; //The token being staked.
    address internal harvestedToken; //The token we harvest. If the reward pool emits multiple tokens, they should be converted to a single token.

    // Contracts
    address public override rewards; //The staking rewards/MasterChef contract
    address public strategist; //The address the performance fee is sent to
    address public multiHarvest; //0x3355743Db830Ed30FF4089DB8b18DEeb683F8546; //The multi harvest contract
    address public jar; //The vault/jar contract

    // Dex
    address public currentRouter; //0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff; //Quickswap router

    constructor(
        address _want,
        address _strategist,
        address _harvestedToken,
        address _currentRouter,
        address _rewards
    ) public {
        require(_want != address(0));
        require(_strategist != address(0));
        require(_harvestedToken != address(0));
        require(_currentRouter != address(0));
        require(_rewards != address(0));

        want = _want;
        strategist = _strategist;
        harvestedToken = _harvestedToken;
        currentRouter = _currentRouter;
        rewards = _rewards;
    }

    // **** Modifiers **** //

    //prevent unauthorized smart contracts from calling harvest()
    modifier onlyHumanOrWhitelisted {
        require(msg.sender == tx.origin || msg.sender == owner() || msg.sender == multiHarvest || msg.sender == jar, "not authorized");
        _;
    }

    // **** Views **** //

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

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

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

    function getHarvestedToken() public override view returns (address) {
        return harvestedToken;
    }

    // **** Setters **** //

    function setJar(address _jar) external override onlyOwner {
        require(jar == address(0), "jar already set");
        require(IJar(_jar).strategy() == address(this), "incorrect jar");
        jar = _jar;
        emit SetJar(_jar);
    }

    function setMultiHarvest(address _address) external onlyOwner {
        require(_address != address(0));
        multiHarvest = _address;
    }

    // **** State mutations **** //
    function deposit() public override virtual;

    // Withdraw partial funds, normally used with a jar withdrawal
    function withdraw(uint256 _amount) external override {
        require(msg.sender == jar, "!jar");
        uint256 _balance = IERC20(want).balanceOf(address(this));
        if (_balance < _amount) {
            _amount = _withdrawSome(_amount.sub(_balance));
            _amount = _amount.add(_balance);
        }

        IERC20(want).safeTransfer(jar, _amount);
    }

    // Withdraw funds, used to swap between strategies
    // Not utilized right now, but could be used for i.e. multi stablecoin strategies
    function withdrawForSwap(uint256 _amount)
        external override
        returns (uint256 balance)
    {
        require(msg.sender == jar, "!jar");
        _withdrawSome(_amount);

        balance = IERC20(want).balanceOf(address(this));

        IERC20(want).safeTransfer(jar, balance);
    }

    function _withdrawSome(uint256 _amount) internal virtual returns (uint256);

    function harvest() public override virtual;

    // **** Internal functions ****

    //Performs a swap through the current router, assuming infinite approval for the token was already given
    function _swapUniswapWithPathPreapproved(
        address[] memory path,
        uint256 _amount,
        address _router
    ) internal {
        require(path[1] != address(0));

        IUniswapRouterV2(_router).swapExactTokensForTokens(
            _amount,
            0,
            path,
            address(this),
            now.add(60)
        );
    }

    function _swapUniswapWithPathPreapproved(
        address[] memory path,
        uint256 _amount
    ) internal {
        _swapUniswapWithPathPreapproved(path, _amount, currentRouter);
    }

    //Legacy swap functions left in to not break compatibility with older strategy contracts
    function _swapUniswapWithPath(
        address[] memory path,
        uint256 _amount,
        address _router
    ) internal {
        require(path[1] != address(0));

        // Swap with uniswap
        IERC20(path[0]).safeApprove(_router, 0);
        IERC20(path[0]).safeApprove(_router, _amount);

        IUniswapRouterV2(_router).swapExactTokensForTokens(
            _amount,
            0,
            path,
            address(this),
            now.add(60)
        );
    }

    function _swapUniswapWithPath(
        address[] memory path,
        uint256 _amount
    ) internal {
        _swapUniswapWithPath(path, _amount, currentRouter);
    }

    function _swapUniswapWithPathForFeeOnTransferTokens(
        address[] memory path,
        uint256 _amount,
        address _router
    ) internal {
        require(path[1] != address(0));

        // Swap with uniswap
        IERC20(path[0]).safeApprove(_router, 0);
        IERC20(path[0]).safeApprove(_router, _amount);

        IUniswapRouterV2(_router).swapExactTokensForTokensSupportingFeeOnTransferTokens(
            _amount,
            0,
            path,
            address(this),
            now.add(60)
        );
    }

    function _swapUniswapWithPathForFeeOnTransferTokens(
        address[] memory path,
        uint256 _amount
    ) internal {
        _swapUniswapWithPathForFeeOnTransferTokens(path, _amount, currentRouter);
    }

    function _distributePerformanceFeesAndDeposit() internal {
        uint256 _want = IERC20(want).balanceOf(address(this));

        if (_want > 0) {
            deposit();
        }
        lastHarvestTime = now;
    }

    // **** Events **** //
    event SetJar(address indexed jar);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "../base/BaseStrategy.sol";
import "../../interfaces/curve/ICurveGauge.sol";
import "../../interfaces/curve/ICurveOuterPool.sol";
import "../../interfaces/curve/ICurvePool.sol";
import "../../interfaces/IERCFund.sol";
import "../../interfaces/IGenericVault.sol";

contract CurveStrategyAtricrypto is BaseStrategy {

    uint256 public constant keepMax = 10000;

    address public WMATIC = 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270;
    address public USDC = 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174;
    address public CRV = 0x172370d5Cd63279eFa6d502DAB29171933a610AF;
    address public WETH = 0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619;
    address public am3CRV = 0xE7a24EF0C5e95Ffb0f6684b813A78F2a3AD7D171;

    address public am3crv_pool = 0x445FE580eF8d70FF569aB36e80c647af338db351;

    address public pool = 0x1d8b86e3D88cDb2d34688e87E72F388Cb541B7C8; //outer pool contract
    address public inner_pool = 0x92215849c439E1f8612b6646060B4E3E5ef822cC; //inner pool contract

    address public crvUSDBTCETH = 0xdAD97F7713Ae9437fa9249920eC8507e5FbB23d3;
    address public crvUSDBTCETH_deposit = 0x3B6B158A76fd8ccc297538F454ce7B4787778c7C;

    address public sushiRouter = 0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506;
    address public FUND = 0x01fE07ce760DA7a025e4Cf3f950aE236F8e62120;

    address[] public wmatic_usdc_path;
    address[] public crv_usdc_path;
    address[] public crv_wmatic_path;

    constructor() public BaseStrategy(crvUSDBTCETH, FUND, WMATIC, sushiRouter, crvUSDBTCETH_deposit) {
        wmatic_usdc_path = new address[](2);
        wmatic_usdc_path[0] = WMATIC;
        wmatic_usdc_path[1] = USDC;

        crv_usdc_path = new address[](3);
        crv_usdc_path[0] = CRV;
        crv_usdc_path[1] = WETH;
        crv_usdc_path[2] = USDC;

        crv_wmatic_path = new address[](3);
        crv_wmatic_path[0] = CRV;
        crv_wmatic_path[1] = WETH;
        crv_wmatic_path[2] = WMATIC;

        IERC20(crvUSDBTCETH).safeApprove(crvUSDBTCETH_deposit, uint256(-1));
        IERC20(USDC).safeApprove(pool, uint256(-1));
        IERC20(CRV).safeApprove(sushiRouter, uint256(-1));
        IERC20(WMATIC).safeApprove(sushiRouter, uint256(-1));
    }

    // **** Views ****

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

    //Calling claimable_reward via a transaction = claim available reward tokens
    function getHarvestable() external override view returns (uint256) {
        return 0; //ICurveGauge(rewards).claimable_reward(address(this), harvestedToken);
    }

    function getFeeDistToken() public override view returns (address) {
        return harvestedToken;
    }

    /* **** Mutative functions **** */

    function harvest() public override onlyHumanOrWhitelisted nonReentrant {
        //Calculate the amount of tokens harvested
        uint256 balance_before = IERC20(harvestedToken).balanceOf(address(this));
        uint256 crv_balance_before = IERC20(CRV).balanceOf(address(this));
        _getReward();

        //Convert all harvested CRV to WMATIC
        uint256 crv_harvested = IERC20(CRV).balanceOf(address(this)).sub(crv_balance_before);
        if(crv_harvested > 0) {
            _swapUniswapWithPathPreapproved(crv_wmatic_path, crv_harvested);
        }

        //Deduct fee from the converted CRV + harvested MATIC
        uint256 amountHarvested = IERC20(harvestedToken).balanceOf(address(this)).sub(balance_before);
        if (amountHarvested > 0) {
            uint256 feeAmount = amountHarvested.mul(IERCFund(strategist).getFee()).div(keepMax);
            _notifyJar(feeAmount);
            IERC20(harvestedToken).safeTransfer(strategist, feeAmount);
        }

        //Swap remaining WMATIC for USDC
        uint256 remainingHarvested = IERC20(harvestedToken).balanceOf(address(this));
        if (remainingHarvested > 0) {
            _swapUniswapWithPathPreapproved(wmatic_usdc_path, remainingHarvested);
        }

        //Deposit USDC in pool to get atricrypto LP tokens
        uint256 _usdcBalance = IERC20(USDC).balanceOf(address(this));
        if (_usdcBalance > 0) {
            ICurveOuterPool(pool).add_liquidity([0, _usdcBalance, 0, 0, 0], 0, address(this));
        }
    }

    //Manually compound any leftover CRV that happens to be in this contract to USDC
    function crvToWant() public onlyHumanOrWhitelisted {
        //Swap CRV for USDC
        uint256 _crv = IERC20(CRV).balanceOf(address(this));
        if (_crv > 0) {
            _swapUniswapWithPathPreapproved(crv_usdc_path, _crv);
        }

        //Deposit USDC in pool
        uint256 _usdcBalance = IERC20(USDC).balanceOf(address(this));
        if (_usdcBalance > 0) {
            //IERC20(USDC).safeApprove(pool, 0);
            //IERC20(USDC).safeApprove(pool, _usdcBalance);
            ICurveOuterPool(pool).add_liquidity([0, _usdcBalance, 0, 0, 0], 0, address(this));
        }
    }

    function deposit() public override {
        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            //IERC20(want).safeApprove(rewards, 0);
            //IERC20(want).safeApprove(rewards, _want);
            ICurveGauge(rewards).deposit(_want, address(this));
        }
    }

    function _withdrawSome(uint256 _amount)
        internal
        override
        returns (uint256)
    {
        //not sure if the safeApprove calls are needed, the gauge modifies its own balance with "self.balanceOf[msg.sender] = new_balance"
        IERC20(rewards).safeApprove(rewards, 0);
        IERC20(rewards).safeApprove(rewards, _amount);
        ICurveGauge(rewards).withdraw(_amount);
        return _amount;
    }

    function _notifyJar(uint256 _amount) internal {
        IGenericVault(jar).notifyReward(getFeeDistToken(), _amount);
    }

    function _getReward() internal {
        ICurveGauge(rewards).claim_rewards();
    }

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

    // Added to support recovering LP Rewards from other systems to be distributed to holders
    function salvage(address recipient, address token, uint256 amount) public onlyOwner {
        // Admin cannot withdraw the staking or harvested token from the contract
        require(token != want, "cannot salvage want");
        require(token != harvestedToken, "cannot salvage harvestedToken");
        require(token != rewards, "cannot salvage gauge token");
        IERC20(token).safeTransfer(recipient, amount);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "../base/BaseStrategy.sol";
import "../../interfaces/curve/ICurveGauge.sol";
import "../../interfaces/IERCFund.sol";
import "../../interfaces/IGenericVault.sol";

interface ICurvePool {
    /// @notice Deposit coins into the pool
    /// @param _amounts List of amounts of coins to deposit
    /// @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit
    /// @param _use_underlying If True, deposit underlying assets instead of aTokens
    /// @return Amount of LP tokens received by depositing
    function add_liquidity(uint256[2] calldata _amounts, uint256 _min_mint_amount, bool _use_underlying) external returns (uint256);
}

//Strategy for Curve REN/WBTC pools that distribute chain token rewards (i.e. MATIC, AVAX, FTM)
//If there are only CRV rewards, use another strategy
contract CurveStrategyBtc is BaseStrategy {

    uint256 public constant keepMax = 10000;

    address public pool; //outer pool contract

    address public wrapped; //token of the wrapped chain this strategy is for, since it's usually the reward token
    address public wbtc;
    address public crv;

    address[] public wrapped_btc_path;
    address[] public crv_btc_path;
    address[] public crv_wrapped_path;

    constructor(
        address _want,
        address _strategist,
        address _harvestedToken, //the wrapped chain token rewarded
        address _currentRouter,
        address _rewards, //the reward gauge
        address _pool, //contract the underlying BTC/WBTC are in
        address _wbtc,
        address _crv,
        address _betweenChainBtc,
        address _betweenCrvBtc,
        address _betweenCrvChain
    )
        public
        BaseStrategy(_want, _strategist, _harvestedToken, _currentRouter, _rewards)
    {
        wbtc = _wbtc;
        crv = _crv;
        pool = _pool;
        wrapped = _harvestedToken;

        wrapped_btc_path = [wrapped, wbtc];
        crv_btc_path = [_crv, wbtc];
        crv_wrapped_path = [_crv, wrapped];

        if(_betweenChainBtc != address(0)) {
            wrapped_btc_path = [wrapped, _betweenChainBtc, wbtc];
        }
        if(_betweenCrvBtc != address(0)) {
            crv_btc_path = [_crv, _betweenCrvBtc, wbtc];
        }
        if(_betweenCrvChain != address(0)) {
            crv_wrapped_path = [_crv, _betweenCrvChain, wrapped];
        }

        IERC20(_want).safeApprove(_rewards, uint256(-1));
        IERC20(_wbtc).safeApprove(_pool, uint256(-1));
        IERC20(_crv).safeApprove(_currentRouter, uint256(-1));
        IERC20(wrapped).safeApprove(_currentRouter, uint256(-1));
    }

    // **** Views ****

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

    //Calling claimable_reward via a transaction = claim available reward tokens
    function getHarvestable() external override view returns (uint256) {
        return 0; //ICurveGauge(rewards).claimable_reward(address(this), harvestedToken);
    }

    function getFeeDistToken() public override view returns (address) {
        return wrapped;
    }

    /* **** Mutative functions **** */

    function harvest() public override onlyHumanOrWhitelisted nonReentrant {
        uint256 wrappedBalance = IERC20(wrapped).balanceOf(address(this));
        if(wrappedBalance > 0) {
            IERC20(wrapped).safeTransfer(strategist, wrappedBalance);
        }
        uint256 crvBalance = IERC20(crv).balanceOf(address(this));
        if(crvBalance > 0) {
            IERC20(crv).safeTransfer(strategist, crvBalance);
        }

        _getReward();

        //Convert all harvested CRV to wrapped
        uint256 crv_harvested = IERC20(crv).balanceOf(address(this));
        if(crv_harvested > 0) {
            _swapUniswapWithPathPreapproved(crv_wrapped_path, crv_harvested);
        }

        //Deduct fee from the converted CRV + harvested wrapped
        uint256 amountHarvested = IERC20(wrapped).balanceOf(address(this));
        if (amountHarvested > 0) {
            uint256 feeAmount = amountHarvested.mul(IERCFund(strategist).getFee()).div(keepMax);
            _notifyJar(feeAmount);
            IERC20(wrapped).safeTransfer(strategist, feeAmount);
        }

        //Swap remaining wrapped for BTC
        uint256 remainingHarvested = IERC20(harvestedToken).balanceOf(address(this));
        if (remainingHarvested > 0) {
            _swapUniswapWithPathPreapproved(wrapped_btc_path, remainingHarvested);
        }

        //Deposit WBTC in pool to get atricrypto LP tokens
        uint256 _btcBalance = IERC20(wbtc).balanceOf(address(this));
        if (_btcBalance > 0) {
            ICurvePool(pool).add_liquidity([_btcBalance, 0], 0, true);
        }
    }

    function deposit() public override {
        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            ICurveGauge(rewards).deposit(_want, address(this), false);
        }
    }

    function _withdrawSome(uint256 _amount)
        internal
        override
        returns (uint256)
    {
        ICurveGauge(rewards).withdraw(_amount, false);
        return _amount;
    }

    function _notifyJar(uint256 _amount) internal {
        IGenericVault(jar).notifyReward(getFeeDistToken(), _amount);
    }

    function _getReward() internal {
        ICurveGauge(rewards).claim_rewards();
    }

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

    // Added to support recovering LP Rewards from other systems to be distributed to holders
    function salvage(address recipient, address token, uint256 amount) public onlyOwner {
        require(token != want, "cannot salvage want");
        require(token != rewards, "cannot salvage gauge token");
        IERC20(token).safeTransfer(recipient, amount);
    }
}

File 82 of 98 : CurveStrategyBtcPolygon.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "./CurveStrategyBtc.sol";

contract CurveStrategyBtcPolygon is CurveStrategyBtc {

    address public btcCrvPool = 0xC2d95EEF97Ec6C17551d45e77B590dc1F9117C67;
    address public btcCRV = 0xf8a57c1d3b9629b77b6726a042ca48990A84Fb49;
    address public btcCRV_deposit = 0xffbACcE0CC7C19d46132f1258FC16CF6871D153c;

    address public sushiRouter = 0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506;

    address public WMATIC = 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270;
    address public CRV = 0x172370d5Cd63279eFa6d502DAB29171933a610AF;
    address public WETH = 0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619;
    address public WBTC = 0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6;

    address public FUND = 0x01fE07ce760DA7a025e4Cf3f950aE236F8e62120;

    constructor()
        public
        CurveStrategyBtc(btcCRV,
        FUND,
        WMATIC,
        sushiRouter,
        btcCRV_deposit,
        btcCrvPool,
        WBTC,
        CRV,
        WETH,
        WETH,
        WETH)
    {
        multiHarvest = 0x3355743Db830Ed30FF4089DB8b18DEeb683F8546;
    }
}

pragma solidity ^0.6.12;

import "../../base/BaseStrategy.sol";
import "../../../interfaces/IMasterChef.sol";

// Base contract for MasterChef/SakeMaster/etc rewards contract interfaces

abstract contract BaseStrategyMasterChef is BaseStrategy {

    uint256 public poolId;

    constructor(
        address _rewards,
        address _want,
        address _strategist,
        uint256 _poolId,
        address _harvestedToken,
        address _currentRouter
    )
        public
        BaseStrategy(_want, _strategist, _harvestedToken, _currentRouter, _rewards)
    {
        poolId = _poolId;
    }

    // **** Getters ****
    function balanceOfPool() public override view returns (uint256) {
        (uint256 amount, ) = IMasterChef(rewards).userInfo(poolId, address(this));
        return amount;
        //return IMasterChef(rewards).balanceOf(poolId, address(this));
    }

    // **** Setters ****

    function _withdrawSome(uint256 _amount)
        internal
        override
        returns (uint256)
    {
        IMasterChef(rewards).withdraw(poolId, _amount);
        return _amount;
    }

    /* **** Other Mutative functions **** */

    function _getReward() internal {
        IMasterChef(rewards).withdraw(poolId, 0);
        //Usually different for other pools based on IMasterChef
        //IMasterChef(rewards).claim(poolId);
    }

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

    function salvage(address token) public onlyOwner {
        require(token != want && token != harvestedToken, "cannot salvage");

        uint256 _token = IERC20(token).balanceOf(address(this));
        if (_token > 0) {
            IERC20(token).safeTransfer(msg.sender, _token);
        }
    }

    function emergencyWithdraw() public onlyOwner {
        IMasterChef(rewards).emergencyWithdraw(poolId);

        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            IERC20(want).safeTransfer(jar, _want);
        }
    }
}

pragma solidity ^0.6.12;

import "../../base/BaseStrategy.sol";
import "../../../interfaces/IMiniChef.sol";

// Base contract for the new version of MasterChef that Sushi uses on Matic
abstract contract BaseStrategyMiniChef is BaseStrategy {

    uint256 public poolId;

    constructor(
        address _rewards,
        address _want,
        address _strategist,
        uint256 _poolId,
        address _harvestedToken,
        address _currentRouter
    )
        public
        BaseStrategy(_want, _strategist, _harvestedToken, _currentRouter, _rewards)
    {
        poolId = _poolId;
        IERC20(_want).safeApprove(_rewards, uint256(-1));
    }

    // **** Getters ****
    function balanceOfPool() public override view returns (uint256) {
        (uint256 amount, ) = IMiniChef(rewards).userInfo(poolId, address(this));
        return amount;
    }

    function getHarvestable() external override view returns (uint256) {
        return IMiniChef(rewards).pendingSushi(poolId, address(this));
    }

    // **** Setters ****

    function deposit() public override {
        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            IMiniChef(rewards).deposit(poolId, _want, address(this));
        }
    }

    function _withdrawSome(uint256 _amount)
        internal
        override
        returns (uint256)
    {
        IMiniChef(rewards).withdraw(poolId, _amount, address(this));
        return _amount;
    }

    /* **** Other Mutative functions **** */

    function _getReward() internal {
        IMiniChef(rewards).harvest(poolId, address(this));
    }

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

    function salvage(address token) public onlyOwner {
        require(token != want && token != harvestedToken, "cannot salvage");

        uint256 _token = IERC20(token).balanceOf(address(this));
        if (_token > 0) {
            IERC20(token).safeTransfer(msg.sender, _token);
        }
    }

    function emergencyWithdraw() public onlyOwner {
        IMiniChef(rewards).emergencyWithdraw(poolId, address(this));

        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            IERC20(want).safeTransfer(jar, _want);
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "./base/BaseStrategyMasterChef.sol";
import "../../interfaces/IERCFund.sol";
import "../../interfaces/IGenericVault.sol";

//For reward/other LP pairs in Master Chef contracts with a referral system
contract MasterChefReferralStrategy is BaseStrategyMasterChef {

    uint256 public constant keepMax = 10000;

    //Tokens
    address public wrapped;
    address public otherToken;

    // Uniswap swap paths
    address[] public reward_other_path;
    address[] public reward_wmatic_path;

    constructor(
        address _rewards,
        address _want,
        address _otherToken,
        uint256 _poolId,
        address _strategist,
        address _harvestedToken,
        address _currentRouter,
        address _wrapped
    )
        public
        BaseStrategyMasterChef(
            _rewards,
            _want,
            _strategist,
            _poolId,
            _harvestedToken,
            _currentRouter
        )
    {
        otherToken = _otherToken;
        wrapped = _wrapped;

        reward_other_path = new address[](2);
        reward_other_path[0] = _harvestedToken;
        reward_other_path[1] = _otherToken;

        reward_wmatic_path = new address[](2);
        reward_wmatic_path[0] = _harvestedToken;
        reward_wmatic_path[1] = wrapped;
    }

    function getHarvestable() external override view returns (uint256) {
        return 0; //the multicall contract doesn't call it anyway
    }

    function getFeeDistToken() public override view returns (address) {
        return wrapped;
    }

    // **** State Mutations ****

    //Make sure that a pair for reward/wrapped
    function swapRewardToWmaticAndDistributeFee(uint256 feeAmount) internal {
        if(feeAmount > 0) {
            _swapUniswapWithPath(reward_wmatic_path, feeAmount);
            uint256 _maticFee = IERC20(wrapped).balanceOf(address(this));
            _notifyJar(_maticFee);
            IERC20(wrapped).safeTransfer(strategist, _maticFee);
        }
    }

    function harvest() public override onlyHumanOrWhitelisted nonReentrant {
        //Transfer wrapped that may already be in the contract to the fee dist fund
        uint256 matic_bal = IERC20(wrapped).balanceOf(address(this));
        if(matic_bal > 0) {
            IERC20(wrapped).safeTransfer(strategist, matic_bal);
        }

        //Calculate the amount of tokens harvested and distribute fee
        uint256 balance_before = IERC20(harvestedToken).balanceOf(address(this));
        _getReward();
        uint256 amountHarvested = IERC20(harvestedToken).balanceOf(address(this)).sub(balance_before);
        if (amountHarvested > 0) {
            uint256 feeAmount = amountHarvested.mul(IERCFund(strategist).getFee()).div(keepMax);
            swapRewardToWmaticAndDistributeFee(feeAmount);
        }

        //Swap 1/2 of the remaining harvestedToken for otherToken
        uint256 remainingHarvested = IERC20(harvestedToken).balanceOf(address(this));
        if (remainingHarvested > 0) {
            _swapUniswapWithPath(reward_other_path, remainingHarvested.div(2));
        }

        uint256 harvestedTokenBalance = IERC20(harvestedToken).balanceOf(address(this));
        uint256 otherBalance = IERC20(otherToken).balanceOf(address(this));
        if (harvestedTokenBalance > 0 && otherBalance > 0) {
            IERC20(harvestedToken).safeApprove(currentRouter, 0);
            IERC20(harvestedToken).safeApprove(currentRouter, harvestedTokenBalance);
            IERC20(otherToken).safeApprove(currentRouter, 0);
            IERC20(otherToken).safeApprove(currentRouter, otherBalance);

            IUniswapRouterV2(currentRouter).addLiquidity(
                harvestedToken, otherToken,
                harvestedTokenBalance, otherBalance,
                0, 0,
                address(this),
                now + 60
            );
        }

        // Stake the LP tokens
        //_distributePerformanceFeesAndDeposit();
    }

    function deposit() public override {
        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            IERC20(want).safeApprove(rewards, 0);
            IERC20(want).safeApprove(rewards, _want);
            IMasterChef(rewards).deposit(poolId, _want, 0xB29Cd9C87a624B940335d6d5e1D4aADf7D95bEeC);
        }
    }

    function _notifyJar(uint256 _amount) internal {
        IGenericVault(jar).notifyReward(getFeeDistToken(), _amount);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "./base/BaseStrategyMasterChef.sol";
import "../../interfaces/IERCFund.sol";
import "../../interfaces/IGenericVault.sol";

//For Master Chef contracts without a referral system
contract MasterChefStrategy is BaseStrategyMasterChef {

    uint256 public constant keepMax = 10000;

    //Tokens
    address public tokenA;
    address public tokenB;
    address public wrapped;

    // Uniswap swap paths
    address[] public reward_a_path;
    address[] public a_b_path;
    address[] public reward_matic_path;

    constructor(
        address _rewards,
        address _want,
        address _tokenA,
        address _tokenB,
        address _betweenToken, //token between reward token and A in swap if necessary
        address _betweenReward, //token between reward token and matic in swap if necessary
        uint256 _poolId,
        address _strategist,
        address _harvestedToken,
        address _currentRouter,
        address _wrapped
    )
        public
        BaseStrategyMasterChef(
            _rewards,
            _want,
            _strategist,
            _poolId,
            _harvestedToken,
            _currentRouter
        )
    {
        tokenA = _tokenA;
        tokenB = _tokenB;
        wrapped = _wrapped;

        if(_betweenToken == address(0)) {
            reward_a_path = [_harvestedToken, _tokenA];
            a_b_path = [_tokenA, _tokenB];
        }
        else {
            reward_a_path = [_harvestedToken, _betweenToken, _tokenA];
            a_b_path = [_tokenA, _tokenB];
        }

        if(_betweenReward == address(0)) {
            reward_matic_path = [_harvestedToken, wrapped];
        }
        else {
            reward_matic_path = [_harvestedToken, _betweenReward, wrapped];
        }

        //Give infinite approval for:
        //-the LP token for the rewards contract
        //-harvested token + token A + token B
        //Make sure that the rewards contract and the router can't be upgraded
        IERC20(_want).safeApprove(_rewards, uint256(-1));
        IERC20(_harvestedToken).safeApprove(_currentRouter, uint256(-1));
        if(_harvestedToken != _tokenA) IERC20(_tokenA).safeApprove(_currentRouter, uint256(-1));
        IERC20(_tokenB).safeApprove(_currentRouter, uint256(-1));
    }

    function getHarvestable() external override view returns (uint256) {
        return 0; //the multicall contract doesn't call it anyway
    }

    function getFeeDistToken() public override view returns (address) {
        return wrapped;
    }

    // **** State Mutations ****

    //Make sure that a pair for reward/wrapped
    function swapRewardToWmaticAndDistributeFee(uint256 feeAmount) internal {
        if(feeAmount > 0) {
            _swapUniswapWithPathPreapproved(reward_matic_path, feeAmount);
            uint256 _maticFee = IERC20(wrapped).balanceOf(address(this));
            _notifyJar(_maticFee);
            IERC20(wrapped).safeTransfer(strategist, _maticFee);
        }
    }

    function harvest() public override onlyHumanOrWhitelisted nonReentrant {
        //Transfer wrapped that may already be in the contract to the fee dist fund
        uint256 matic_bal = IERC20(wrapped).balanceOf(address(this));
        if(matic_bal > 0) {
            IERC20(wrapped).safeTransfer(strategist, matic_bal);
        }

        //Calculate the amount of tokens harvested and distribute fee
        uint256 balance_before = IERC20(harvestedToken).balanceOf(address(this));
        _getReward();
        uint256 amountHarvested = IERC20(harvestedToken).balanceOf(address(this)).sub(balance_before);
        if (amountHarvested > 0) {
            uint256 feeAmount = amountHarvested.mul(IERCFund(strategist).getFee()).div(keepMax);
            swapRewardToWmaticAndDistributeFee(feeAmount);
        }

        //Swap remaining harvested tokens for tokenA if the harvested token isn't tokenA
        if(harvestedToken != tokenA) {
            uint256 remainingHarvested = IERC20(harvestedToken).balanceOf(address(this));
            if (remainingHarvested > 0) {
                _swapUniswapWithPathPreapproved(reward_a_path, remainingHarvested);
            }
        }

        //Swap 1/2 of tokenA for tokenB
        uint256 _balanceA = IERC20(tokenA).balanceOf(address(this));
        if (_balanceA > 0) {
            _swapUniswapWithPathPreapproved(a_b_path, _balanceA.div(2));
        }

        //Add liquidity
        uint256 aBalance = IERC20(tokenA).balanceOf(address(this));
        uint256 bBalance = IERC20(tokenB).balanceOf(address(this));
        if (aBalance > 0 && bBalance > 0) {
            //IERC20(tokenA).safeApprove(currentRouter, 0);
            //IERC20(tokenA).safeApprove(currentRouter, aBalance);
            //IERC20(tokenB).safeApprove(currentRouter, 0);
            //IERC20(tokenB).safeApprove(currentRouter, bBalance);

            IUniswapRouterV2(currentRouter).addLiquidity(
                tokenA, tokenB,
                aBalance, bBalance,
                0, 0,
                address(this),
                now + 60
            );
        }

        // Stake the LP tokens
        _distributePerformanceFeesAndDeposit();
    }

    function deposit() public override {
        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            //IERC20(want).safeApprove(rewards, 0);
            //IERC20(want).safeApprove(rewards, _want);
            IMasterChef(rewards).deposit(poolId, _want);
        }
    }

    function _notifyJar(uint256 _amount) internal {
        IGenericVault(jar).notifyReward(getFeeDistToken(), _amount);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "./base/BaseStrategyMasterChef.sol";
import "../../interfaces/IERCFund.sol";
import "../../interfaces/IGenericVault.sol";

//For reward/other LP pairs in Master Chef contracts without a referral system
contract MasterChefStrategyNonReentrant is BaseStrategyMasterChef {

    //Tokens
    address public tokenA;
    address public tokenB;
    address public wrapped;

    uint256 public constant keepMax = 10000;

    // Uniswap swap paths
    address[] public reward_a_path;
    address[] public a_b_path;
    address[] public reward_matic_path;

    constructor(
        address _rewards,
        address _want,
        address _tokenA,
        address _tokenB,
        uint256 _poolId,
        address _strategist,
        address _harvestedToken,
        address _currentRouter,
        address _wrapped
    )
        public
        BaseStrategyMasterChef(
            _rewards,
            _want,
            _strategist,
            _poolId,
            _harvestedToken,
            _currentRouter
        )
    {
        tokenA = _tokenA;
        tokenB = _tokenB;
        wrapped = _wrapped;

        reward_a_path = new address[](2);
        reward_a_path[0] = _harvestedToken;
        reward_a_path[1] = _tokenA;

        a_b_path = new address[](2);
        a_b_path[0] = _tokenA;
        a_b_path[1] = _tokenB;

        reward_matic_path = new address[](2);
        reward_matic_path[0] = _harvestedToken;
        reward_matic_path[1] = wrapped;

        //Give infinite approval for:
        //-the LP token for the rewards contract
        //-harvested token + token A + token B
        //Make sure that the rewards contract and the router can't be upgraded
        IERC20(_want).safeApprove(_rewards, uint256(-1));
        IERC20(_harvestedToken).safeApprove(_currentRouter, uint256(-1));
        if(_harvestedToken != _tokenA) IERC20(_tokenA).safeApprove(_currentRouter, uint256(-1));
        IERC20(_tokenB).safeApprove(_currentRouter, uint256(-1));
    }

    function getHarvestable() external override view returns (uint256) {
        return 0; //the multicall contract doesn't call it anyway
    }

    function getFeeDistToken() public override view returns (address) {
        return wrapped;
    }

    // **** State Mutations ****

    //Make sure that a pair for reward/wrapped
    function swapRewardToWmaticAndDistributeFee(uint256 feeAmount) internal {
        if(feeAmount > 0) {
            _swapUniswapWithPathPreapproved(reward_matic_path, feeAmount);
            uint256 _maticFee = IERC20(wrapped).balanceOf(address(this));
            _notifyJar(_maticFee);
            IERC20(wrapped).safeTransfer(strategist, _maticFee);
        }
    }

    //disable on UI unless there's tokens in the contract, otherwise people clicking the button will refresh the timer
    function harvest() public override onlyHumanOrWhitelisted nonReentrant {
        //Transfer wrapped that may already be in the contract to the fee dist fund
        uint256 matic_bal = IERC20(wrapped).balanceOf(address(this));
        if(matic_bal > 0) {
            IERC20(wrapped).safeTransfer(strategist, matic_bal);
        }

        //Calculate the amount of tokens harvested and distribute fee
        uint256 balance_before = IERC20(harvestedToken).balanceOf(address(this));
        _getReward();
        uint256 amountHarvested = IERC20(harvestedToken).balanceOf(address(this)).sub(balance_before);
        if (amountHarvested > 0) {
            uint256 feeAmount = amountHarvested.mul(IERCFund(strategist).getFee()).div(keepMax);
            swapRewardToWmaticAndDistributeFee(feeAmount);
        }

        //Swap remaining harvested tokens for tokenA if the harvested token isn't tokenA
        if(harvestedToken != tokenA) {
            uint256 remainingHarvested = IERC20(harvestedToken).balanceOf(address(this));
            if (remainingHarvested > 0) {
                _swapUniswapWithPathPreapproved(reward_a_path, remainingHarvested);
            }
        }

        //Swap 1/2 of tokenA for tokenB
        uint256 _balanceA = IERC20(tokenA).balanceOf(address(this));
        if (_balanceA > 0) {
            _swapUniswapWithPathPreapproved(a_b_path, _balanceA.div(2));
        }

        //Add liquidity
        uint256 aBalance = IERC20(tokenA).balanceOf(address(this));
        uint256 bBalance = IERC20(tokenB).balanceOf(address(this));
        if (aBalance > 0 && bBalance > 0) {
            //IERC20(tokenA).safeApprove(currentRouter, 0);
            //IERC20(tokenA).safeApprove(currentRouter, aBalance);
            //IERC20(tokenB).safeApprove(currentRouter, 0);
            //IERC20(tokenB).safeApprove(currentRouter, bBalance);

            IUniswapRouterV2(currentRouter).addLiquidity(
                tokenA, tokenB,
                aBalance, bBalance,
                0, 0,
                address(this),
                now + 60
            );
        }

        // Stake the LP tokens
        //_distributePerformanceFeesAndDeposit();
    }

    function deposit() public override {
        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            //IERC20(want).safeApprove(rewards, 0);
            //IERC20(want).safeApprove(rewards, _want);
            IMasterChef(rewards).deposit(poolId, _want);
        }
    }

    function _notifyJar(uint256 _amount) internal {
        IGenericVault(jar).notifyReward(getFeeDistToken(), _amount);
    }
}

pragma solidity ^0.6.12;

import "../../base/BaseStrategy.sol";
import "../../../interfaces/IStakingRewards.sol";

// Base contract for SNX Staking rewards contract interfaces

abstract contract BaseStrategyStakingRewards is BaseStrategy {

    constructor(
        address _rewards,
        address _want,
        address _strategist,
        address _harvestedToken,
        address _currentRouter
    )
        public
        BaseStrategy(_want, _strategist, _harvestedToken, _currentRouter, _rewards)
    {
        IERC20(_want).safeApprove(_rewards, uint256(-1));
    }

    // **** Views ****

    function balanceOfPool() public override view returns (uint256) {
        return IStakingRewards(rewards).balanceOf(address(this));
    }

    function getHarvestable() external override view returns (uint256) {
        return IStakingRewards(rewards).earned(address(this));
    }

    // **** Setters ****

    function deposit() public override {
        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            IStakingRewards(rewards).stake(_want);
        }
    }

    function _withdrawSome(uint256 _amount)
        internal
        override
        returns (uint256)
    {
        IStakingRewards(rewards).withdraw(_amount);
        return _amount;
    }

    function _notifyJar(uint256 _amount) internal virtual;

    /* **** Mutative functions **** */

    function _getReward() internal {
        IStakingRewards(rewards).getReward();
    }

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

    // Added to support recovering LP Rewards from other systems to be distributed to holders
    function salvage(address recipient, address token, uint256 amount) public onlyOwner {
        // Admin cannot withdraw the staking or harvested token from the contract
        require(token != want, "cannot salvage want");
        require(token != harvestedToken, "cannot salvage harvestedToken");
        IERC20(token).safeTransfer(recipient, amount);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "./base/BaseStrategyStakingRewards.sol";
import "../../interfaces/IERCFund.sol";
import "../../interfaces/IGenericVault.sol";

//For A/B token pairs, where I have to convert the harvested token to A (ETH/MATIC/USDC/etc) and then sell 1/2 of A for B
abstract contract BaseStrategyOtherPair is BaseStrategyStakingRewards {

    // Addresses for MATIC
    address public tokenA;
    address public tokenB;
    address public wrapped;

    uint256 public constant keepMax = 10000;

    // Uniswap swap paths
    address[] public reward_a_path;
    address[] public a_b_path;
    address[] public reward_matic_path;

    constructor(
        address _rewards,
        address _want,
        address _tokenA,
        address _tokenB,
        address _harvestedToken,
        address _strategist,
        address _router,
        address _wrapped
    )
        public
        BaseStrategyStakingRewards(
            _rewards,
            _want,
            _strategist,
            _harvestedToken,
            _router
        )
    {
        tokenA = _tokenA;
        tokenB = _tokenB;
        wrapped = _wrapped;

        reward_a_path = new address[](2);
        reward_a_path[0] = _harvestedToken;
        reward_a_path[1] = _tokenA;

        a_b_path = new address[](2);
        a_b_path[0] = _tokenA;
        a_b_path[1] = _tokenB;

        reward_matic_path = new address[](2);
        reward_matic_path[0] = _harvestedToken;
        reward_matic_path[1] = wrapped;
    }

    function getFeeDistToken() public override view returns (address) {
        return wrapped;
    }

    // **** State Mutations ****

    //Swap feeAmount to wrapped before sending it to the fee dist, since profit is calculated in terms of wrapped
    function swapRewardToWmaticAndDistributeFee(uint256 feeAmount) internal {
        if(feeAmount > 0) {
            _swapUniswapWithPath(reward_matic_path, feeAmount);
            uint256 _maticFee = IERC20(wrapped).balanceOf(address(this));
            _notifyJar(_maticFee);
            IERC20(wrapped).safeTransfer(strategist, _maticFee);
        }
    }

    function harvest() public override onlyHumanOrWhitelisted nonReentrant {
        //Transfer wrapped that may already be in the contract to the fee dist fund
        IERC20(wrapped).safeTransfer(strategist, IERC20(wrapped).balanceOf(address(this)));

        //Calculate the amount of tokens harvested and distribute fee
        uint256 balance_before = IERC20(harvestedToken).balanceOf(address(this));
        _getReward();
        uint256 amountHarvested = IERC20(harvestedToken).balanceOf(address(this)).sub(balance_before);
        if (amountHarvested > 0) {
            uint256 feeAmount = amountHarvested.mul(IERCFund(strategist).getFee()).div(keepMax);
            swapRewardToWmaticAndDistributeFee(feeAmount);
        }

        //Swap remaining harvested tokens for tokenA
        uint256 remainingHarvested = IERC20(harvestedToken).balanceOf(address(this));
        if (remainingHarvested > 0) {
            _swapUniswapWithPath(reward_a_path, remainingHarvested);
        }

        //Swap 1/2 of tokenA for tokenB
        uint256 _balanceA = IERC20(tokenA).balanceOf(address(this));
        if (_balanceA > 0) {
            _swapUniswapWithPath(a_b_path, _balanceA.div(2));
        }

        //Add liquidity
        uint256 aBalance = IERC20(tokenA).balanceOf(address(this));
        uint256 bBalance = IERC20(tokenB).balanceOf(address(this));
        if (aBalance > 0 && bBalance > 0) {
            IERC20(tokenA).safeApprove(currentRouter, 0);
            IERC20(tokenA).safeApprove(currentRouter, aBalance);
            IERC20(tokenB).safeApprove(currentRouter, 0);
            IERC20(tokenB).safeApprove(currentRouter, bBalance);

            IUniswapRouterV2(currentRouter).addLiquidity(
                tokenA, tokenB,
                aBalance, bBalance,
                0, 0,
                address(this),
                now + 60
            );
        }

        // Stake the LP tokens
        _distributePerformanceFeesAndDeposit();
    }

    function _notifyJar(uint256 _amount) internal override {
        IGenericVault(jar).notifyReward(getFeeDistToken(), _amount);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "./base/BaseStrategyStakingRewards.sol";
import "../../interfaces/IERCFund.sol";
import "../../interfaces/IGenericVault.sol";

//For Reward/Other token pairs i.e. Quick/Frax
contract BaseStrategyRewardPair is BaseStrategyStakingRewards {

    uint256 public constant keepMax = 10000;

    address public otherToken;
    address public wrapped;
    // Uniswap swap paths
    address[] public reward_other_path;
    address[] public reward_matic_path;

    constructor(
        address _rewards,
        address _want,
        address _otherToken,
        address _harvestedToken,
        address _strategist,
        address _router,
        address _wrapped
    )
        public
        BaseStrategyStakingRewards(
            _rewards,
            _want,
            _strategist,
            _harvestedToken,
            _router
        )
    {
        require(_otherToken != address(0));
        otherToken = _otherToken;
        wrapped = _wrapped;

        reward_other_path = new address[](2);
        reward_other_path[0] = _harvestedToken;
        reward_other_path[1] = otherToken;

        reward_matic_path = new address[](2);
        reward_matic_path[0] = _harvestedToken;
        reward_matic_path[1] = wrapped;

        //Given: want isn't _harvestedToken or _otherToken, because want is a LP token
        IERC20(_harvestedToken).safeApprove(_router, uint256(-1));
        IERC20(_otherToken).safeApprove(_router, uint256(-1));
    }

    function getFeeDistToken() public override view returns (address) {
        return wrapped;
    }

    // **** State Mutations ****

    //Swap feeAmount to wrapped before sending it to the fee dist, since profit is calculated in terms of wrapped
    function swapRewardToWmaticAndDistributeFee(uint256 feeAmount) internal {
        if(feeAmount > 0) {
            _swapUniswapWithPathPreapproved(reward_matic_path, feeAmount);
            uint256 _maticFee = IERC20(wrapped).balanceOf(address(this));
            _notifyJar(_maticFee);
            IERC20(wrapped).safeTransfer(strategist, _maticFee);
        }
    }

    function harvest() public override onlyHumanOrWhitelisted nonReentrant {
        //Transfer wrapped that may already be in the contract to the fee dist fund
        uint256 matic_bal = IERC20(wrapped).balanceOf(address(this));
        if(matic_bal > 0) {
            IERC20(wrapped).safeTransfer(strategist, matic_bal);
        }

        //Calculate the amount of tokens harvested and distribute fee
        uint256 balance_before = IERC20(harvestedToken).balanceOf(address(this));
        _getReward();
        uint256 amountHarvested = IERC20(harvestedToken).balanceOf(address(this)).sub(balance_before);
        if (amountHarvested > 0) {
            uint256 feeAmount = amountHarvested.mul(IERCFund(strategist).getFee()).div(keepMax);
            swapRewardToWmaticAndDistributeFee(feeAmount);
        }

        //Swap 1/2 of the remaining harvestedToken for otherToken
        uint256 remainingHarvested = IERC20(harvestedToken).balanceOf(address(this));
        if (remainingHarvested > 0) {
            _swapUniswapWithPathPreapproved(reward_other_path, remainingHarvested.div(2));
        }

        uint256 harvestedTokenBalance = IERC20(harvestedToken).balanceOf(address(this));
        uint256 otherBalance = IERC20(otherToken).balanceOf(address(this));
        if (harvestedTokenBalance > 0 && otherBalance > 0) {
            IUniswapRouterV2(currentRouter).addLiquidity(
                harvestedToken, otherToken,
                harvestedTokenBalance, otherBalance,
                0, 0,
                address(this),
                now + 60
            );
        }

        // Stake the LP tokens
        _distributePerformanceFeesAndDeposit();
    }

    function _notifyJar(uint256 _amount) internal override {
        IGenericVault(jar).notifyReward(getFeeDistToken(), _amount);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "../base/BaseStrategy.sol";
import "../../interfaces/IERCFund.sol";
import "../../interfaces/IGenericVault.sol";
import "../../interfaces/IStakingRewards.sol";

interface IDualStakingRewards {
    function earnedA(address account) external view returns (uint256);
    function earnedB(address account) external view returns (uint256);

    function getRewardAForDuration() external view returns (uint256);
    function getRewardBForDuration() external view returns (uint256);
}

//For staking reward contracts where two tokens are rewarded i.e. SNX StakingDualRewards
contract StrategyDualRewardsNonReentrant is BaseStrategy {

    uint256 public constant keepMax = 10000;

    // Tokens
    address public otherToken;
    address public harvestedToken2;
    address public wrapped;

    // Uniswap swap paths
    address[] public reward_other_path; //i.e. TEL -> wrapped
    address[] public reward2_reward1_path; //Assumes there's a reward1/reward2 pair i.e. QUICK -> TEL
    address[] public reward_matic_path;

    constructor(
        address _rewards,
        address _want,
        address _otherToken,
        address _harvestedToken, //this should be the primary reward token i.e. TEL
        address _harvestedToken2, //this should be the secondary reward token i.e. QUICK
        address _strategist,
        address _router,
        address _wrapped
    )
        public
        BaseStrategy(
            _want,
            _strategist,
            _harvestedToken,
            _router,
            _rewards
        )
    {
        otherToken = _otherToken;
        harvestedToken2 = _harvestedToken2;
        wrapped = _wrapped;

        reward2_reward1_path = [_harvestedToken2, _harvestedToken];
        reward_other_path = [_harvestedToken, _otherToken];
        reward_matic_path = [_harvestedToken, wrapped];
    }

    function getFeeDistToken() public override view returns (address) {
        return wrapped;
    }

    function balanceOfPool() public override view returns (uint256) {
        return IStakingRewards(rewards).balanceOf(address(this));
    }

    function getHarvestable() external override view returns (uint256) {
        return IDualStakingRewards(rewards).earnedA(address(this));
    }

    // **** State Mutations ****

    //Swap feeAmount to wrapped before sending it to the fee dist, since profit is calculated in terms of wrapped
    function swapRewardToWmaticAndDistributeFee(uint256 feeAmount) internal {
        if(feeAmount > 0) {
            _swapUniswapWithPath(reward_matic_path, feeAmount);
            uint256 _maticFee = IERC20(wrapped).balanceOf(address(this));
            _notifyJar(_maticFee);
            IERC20(wrapped).safeTransfer(strategist, _maticFee);
        }
    }

    function harvest() public override onlyHumanOrWhitelisted nonReentrant {
        uint256 matic_bal = IERC20(wrapped).balanceOf(address(this));
        if(matic_bal > 0) {
            IERC20(wrapped).safeTransfer(strategist, matic_bal);
        }
        uint256 h2_bal = IERC20(harvestedToken2).balanceOf(address(this));
        if(h2_bal > 0) {
            IERC20(harvestedToken2).safeTransfer(strategist, h2_bal);
        }

        uint256 balance_before = IERC20(harvestedToken).balanceOf(address(this));
        _getReward();
        //Convert the 2nd harvested token to the 1st harvested token
        uint256 amountHarvested2 = IERC20(harvestedToken2).balanceOf(address(this));
        if (amountHarvested2 > 0) {
            _swapUniswapWithPath(reward2_reward1_path, amountHarvested2);
        }
        //Calculate the amount of tokens harvested
        uint256 amountHarvested = IERC20(harvestedToken).balanceOf(address(this)).sub(balance_before);
        if (amountHarvested > 0) {
            uint256 feeAmount = amountHarvested.mul(IERCFund(strategist).getFee()).div(keepMax);
            swapRewardToWmaticAndDistributeFee(feeAmount);
        }

        //Swap 1/2 of the remaining harvestedToken for otherToken
        uint256 remainingHarvested = IERC20(harvestedToken).balanceOf(address(this));
        if (remainingHarvested > 0) {
            _swapUniswapWithPath(reward_other_path, remainingHarvested.div(2));
        }

        uint256 harvestedTokenBalance = IERC20(harvestedToken).balanceOf(address(this));
        uint256 otherBalance = IERC20(otherToken).balanceOf(address(this));
        if (harvestedTokenBalance > 0 && otherBalance > 0) {
            IERC20(harvestedToken).safeApprove(currentRouter, 0);
            IERC20(harvestedToken).safeApprove(currentRouter, harvestedTokenBalance);
            IERC20(otherToken).safeApprove(currentRouter, 0);
            IERC20(otherToken).safeApprove(currentRouter, otherBalance);

            IUniswapRouterV2(currentRouter).addLiquidity(
                harvestedToken, otherToken,
                harvestedTokenBalance, otherBalance,
                0, 0,
                address(this),
                now + 60
            );
        }

        // Stake the LP tokens
        //_distributePerformanceFeesAndDeposit();
    }

    function deposit() public override {
        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            IERC20(want).safeApprove(rewards, 0);
            IERC20(want).safeApprove(rewards, _want);
            IStakingRewards(rewards).stake(_want);
        }
    }

    function _withdrawSome(uint256 _amount)
        internal
        override
        returns (uint256)
    {
        IStakingRewards(rewards).withdraw(_amount);
        return _amount;
    }

    function _getReward() internal {
        IStakingRewards(rewards).getReward();
    }

    function _notifyJar(uint256 _amount) internal {
        IGenericVault(jar).notifyReward(getFeeDistToken(), _amount);
    }

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

    // Added to support recovering LP Rewards from other systems to be distributed to holders
    function salvage(address recipient, address token, uint256 amount) public onlyOwner {
        // Admin cannot withdraw the staking or harvested token from the contract
        require(token != want, "cannot salvage want");
        require(token != harvestedToken, "cannot salvage harvestedToken");
        IERC20(token).safeTransfer(recipient, amount);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "./base/BaseStrategyStakingRewards.sol";
import "../../interfaces/IERCFund.sol";
import "../../interfaces/IGenericVault.sol";

//For A/B token pairs, where I have to convert the harvested token to A (ETH/MATIC/USDC/etc) and then sell 1/2 of A for B
//For pools where the reward contract functions have the "nonReentrant" modifier
//In addition, the reward is swapped to wrapped in order to minimize the number of tokens that the calculator needs to support
contract StrategyOtherPairNonReentrant is BaseStrategyStakingRewards {

    uint256 public constant keepMax = 10000;

    address public tokenA;
    address public tokenB;
    address public wrapped;
    // Uniswap swap paths
    address[] public reward_a_path;
    address[] public reward_matic_path;
    address[] public a_b_path;

    constructor(
        address _rewards,
        address _want,
        address _tokenA,
        address _tokenB,
        address _harvestedToken,
        address _strategist,
        address _router,
        address _wrapped
    )
        public
        BaseStrategyStakingRewards(
            _rewards,
            _want,
            _strategist,
            _harvestedToken,
            _router
        )
    {
        tokenA = _tokenA;
        tokenB = _tokenB;
        wrapped = _wrapped;

        reward_a_path = new address[](2);
        reward_a_path[0] = _harvestedToken;
        reward_a_path[1] = _tokenA;

        reward_matic_path = new address[](2);
        reward_matic_path[0] = _harvestedToken;
        reward_matic_path[1] = wrapped;

        a_b_path = new address[](2);
        a_b_path[0] = _tokenA;
        a_b_path[1] = _tokenB;
    }

    function getFeeDistToken() public override view returns (address) {
        return wrapped;
    }

    // **** State Mutations ****

    //Swap feeAmount to wrapped before sending it to the fee dist, since profit is calculated in terms of wrapped
    function swapRewardToWmaticAndDistributeFee(uint256 feeAmount) internal {
        if(feeAmount > 0) {
            _swapUniswapWithPath(reward_matic_path, feeAmount);
            uint256 _maticFee = IERC20(wrapped).balanceOf(address(this));
            _notifyJar(_maticFee);
            IERC20(wrapped).safeTransfer(strategist, _maticFee);
        }
    }

    //deposit() needs to be called manually afterward because deposit() and getReward() have the nonReentrant modifier, so I cannot claim and deposit in the same tx
    function harvest() public override onlyHumanOrWhitelisted nonReentrant {
        //Transfer any harvestedToken and wrapped that may already be in the contract to the fee dist fund
        IERC20(harvestedToken).safeTransfer(strategist, IERC20(harvestedToken).balanceOf(address(this)));
        IERC20(wrapped).safeTransfer(strategist, IERC20(wrapped).balanceOf(address(this)));

        _getReward();

        uint256 _harvested_balance = IERC20(harvestedToken).balanceOf(address(this));

        //Distribute fee and swap harvestedToken for tokenA
        if (_harvested_balance > 0) {
            uint256 feeAmount = _harvested_balance.mul(IERCFund(strategist).getFee()).div(keepMax);
            uint256 afterFeeAmount = _harvested_balance.sub(feeAmount);

            swapRewardToWmaticAndDistributeFee(feeAmount);

            _swapUniswapWithPath(reward_a_path, afterFeeAmount);
        }

        //Swap 1/2 of tokenA for tokenB
        uint256 _balanceA = IERC20(tokenA).balanceOf(address(this));
        if (_balanceA > 0) {
            _swapUniswapWithPath(a_b_path, _balanceA.div(2));
        }

        //Add liquidity
        uint256 aBalance = IERC20(tokenA).balanceOf(address(this));
        uint256 bBalance = IERC20(tokenB).balanceOf(address(this));
        if (aBalance > 0 && bBalance > 0) {
            IERC20(tokenA).safeApprove(currentRouter, 0);
            IERC20(tokenA).safeApprove(currentRouter, aBalance);
            IERC20(tokenB).safeApprove(currentRouter, 0);
            IERC20(tokenB).safeApprove(currentRouter, bBalance);

            IUniswapRouterV2(currentRouter).addLiquidity(
                tokenA, tokenB,
                aBalance, bBalance,
                0, 0,
                address(this),
                now + 60
            );
        }

        // Call deposit() separately for non-reentrant vaults
        //_distributePerformanceFeesAndDeposit();
    }

    function _notifyJar(uint256 _amount) internal override {
        IGenericVault(jar).notifyReward(getFeeDistToken(), _amount);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "./base/BaseStrategyStakingRewards.sol";
import "../../interfaces/IERCFund.sol";
import "../../interfaces/IGenericVault.sol";

//For Reward/Other token pairs i.e. Quick/Frax
//For pools where the reward contract functions have the "nonReentrant" modifier
//In addition, the reward is swapped to wrapped in order to minimize the number of tokens that the calculator needs to support
contract StrategyRewardPairNonReentrant is BaseStrategyStakingRewards {

    uint256 public constant keepMax = 10000;

    address public otherToken;
    address public wrapped;
    // Uniswap swap paths
    address[] public reward_other_path;
    address[] public reward_matic_path;

    constructor(
        address _rewards,
        address _want,
        address _otherToken,
        address _harvestedToken,
        address _strategist,
        address _router,
        address _wrapped
    )
        public
        BaseStrategyStakingRewards(
            _rewards,
            _want,
            _strategist,
            _harvestedToken,
            _router
        )
    {
        otherToken = _otherToken;
        wrapped = _wrapped;

        reward_other_path = new address[](2);
        reward_other_path[0] = _harvestedToken;
        reward_other_path[1] = otherToken;

        reward_matic_path = new address[](2);
        reward_matic_path[0] = _harvestedToken;
        reward_matic_path[1] = wrapped;

        //Given: want isn't _harvestedToken or _otherToken, because want is a LP token
        IERC20(_harvestedToken).safeApprove(_router, uint256(-1));
        IERC20(_otherToken).safeApprove(_router, uint256(-1));
    }

    function getFeeDistToken() public override view returns (address) {
        return wrapped;
    }

    // **** State Mutations ****

    //Swap feeAmount to wrapped before sending it to the fee dist, since profit is calculated in terms of wrapped
    function swapRewardToWmaticAndDistributeFee(uint256 feeAmount) internal {
        if(feeAmount > 0) {
            _swapUniswapWithPathPreapproved(reward_matic_path, feeAmount);
            uint256 _maticFee = IERC20(wrapped).balanceOf(address(this));
            _notifyJar(_maticFee);
            IERC20(wrapped).safeTransfer(strategist, _maticFee);
        }
    }

    function harvest() public override onlyHumanOrWhitelisted nonReentrant {
        //Transfer wrapped that may already be in the contract to the fee dist fund
        uint256 matic_bal = IERC20(wrapped).balanceOf(address(this));
        if(matic_bal > 0) {
            IERC20(wrapped).safeTransfer(strategist, matic_bal);
        }

        //Calculate the amount of tokens harvested and distribute fee
        uint256 balance_before = IERC20(harvestedToken).balanceOf(address(this));
        _getReward();
        uint256 amountHarvested = IERC20(harvestedToken).balanceOf(address(this)).sub(balance_before);
        if (amountHarvested > 0) {
            uint256 feeAmount = amountHarvested.mul(IERCFund(strategist).getFee()).div(keepMax);
            swapRewardToWmaticAndDistributeFee(feeAmount);
        }

        //Swap 1/2 of the remaining harvestedToken for otherToken
        uint256 remainingHarvested = IERC20(harvestedToken).balanceOf(address(this));
        if (remainingHarvested > 0) {
            _swapUniswapWithPathPreapproved(reward_other_path, remainingHarvested.div(2));
        }

        uint256 harvestedTokenBalance = IERC20(harvestedToken).balanceOf(address(this));
        uint256 otherBalance = IERC20(otherToken).balanceOf(address(this));
        if (harvestedTokenBalance > 0 && otherBalance > 0) {
            IUniswapRouterV2(currentRouter).addLiquidity(
                harvestedToken, otherToken,
                harvestedTokenBalance, otherBalance,
                0, 0,
                address(this),
                now + 60
            );
        }

        // Call deposit() separately for non-reentrant vaults
        //_distributePerformanceFeesAndDeposit();
    }

    function _notifyJar(uint256 _amount) internal override {
        IGenericVault(jar).notifyReward(getFeeDistToken(), _amount);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/math/Math.sol";

contract TestCalculator {
    using SafeMath for uint256;

    function valueOfAsset(address asset, uint256 amount) public view returns (uint256) {
        return amount;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "../strategies/base/BaseStrategy.sol";
import "../lib/Pausable.sol";
import "../interfaces/IMultiFeeDistribution.sol";
import "../interfaces/IGenericVault.sol";

//Stakes ADDY in the fee dist contract and sells WMATIC received from dividends for more ADDY
contract TestFeeDistStrategyLocked is BaseStrategy, Pausable {

    //Matic addresses
    //address public WAULT_ROUTER = 0x3a1D87f206D12415f5b0A33E786967680AAb4f6d;
    //address public MULTI_FEE_DIST = 0x920f22E1e5da04504b765F8110ab96A20E6408Bd;

    //address public WMATIC = 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270;
    //address public ADDY = 0xc3FdbadC7c795EF1D6Ba111e06fF8F16A20Ea539;

    // Uniswap swap paths
    address[] public wmaticAddyPath;

    constructor(address _addy, address _wmatic, address _router, address _rewards)
        public
        BaseStrategy(
            _addy,
            msg.sender, //strategist arg isn't used, since this strategy has no fee
            _wmatic, //Fee dist currently pays dividends in WMATIC
            _router, //uniswap router
            _rewards //the multi fee distribution contract
        )
    {
        IERC20(want).safeApprove(_rewards, uint256(-1));
    }

    // **** State Mutations ****

    // **** State Mutations ****

    function harvest() public override onlyHumanOrWhitelisted nonReentrant notPaused {
        _getReward();

        //Send WMATIC fee-sharing dividends to the vault
        uint256 _harvested_balance = IERC20(harvestedToken).balanceOf(address(this));
        if (_harvested_balance > 0) {
            IERC20(harvestedToken).safeTransfer(jar, _harvested_balance);
            IGenericVault(jar).notifyReward(getFeeDistToken(), _harvested_balance);
        }

        // Lock the ADDY tokens
        _distributePerformanceFeesAndDeposit();
    }

    function deposit() public override notPaused {
        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            IMultiFeeDistribution(rewards).stake(_want, true);
        }
    }

    function _withdrawSome(uint256 _amount) internal override returns (uint256) {
        uint256 unlocked = IMultiFeeDistribution(rewards).unlockedBalance(address(this));

        //After the first locked withdrawal, there will be a buffer of unlocked tokens remaining in the contract
        if(unlocked >= _amount) {
            IMultiFeeDistribution(rewards).withdraw(_amount);
            return _amount;
        }
        else {
            //Not enough unlocked tokens available, need to withdraw locked stakes as well
            if(unlocked > 0) {
                IMultiFeeDistribution(rewards).withdraw(unlocked);
            }
            //Need to withdraw ALL locked stakes
            IMultiFeeDistribution(rewards).withdrawExpiredLocks();

            uint256 _balance = IERC20(want).balanceOf(address(this));
            require(_balance >= _amount, "Not enough unlocked tokens to process withdrawal");

            //Deposit remaining tokens into buffer of unlocked tokens
            uint256 extra = _balance.sub(_amount);
            if(extra > 0) {
                IMultiFeeDistribution(rewards).stake(extra, false);
            }
            return _amount;
        }
    }
    
    function _getReward() internal {
        IMultiFeeDistribution(rewards).getReward();
    }

    // **** Views ****

    function balanceOfPool() public override view returns (uint256) {
        return IMultiFeeDistribution(rewards).totalBalance(address(this));
    }

    function getHarvestable() external override view returns (uint256) {
        return 0; //use AddyData on the frontend to get the amount of pending WMATIC and ADDY, since this function was only designed to handle one token
    }

    //Not used
    function getFeeDistToken() public override view returns (address) {
        return harvestedToken;
    }
    
    // **** Admin functions ****

    //Need this function to relock stakes because it's possible for all locked stakes to have been withdrawn
    //Transfer ownership to an external contract that calculates the number of users with stakes ending in > 91 days & their stake amounts
    function relock(uint256 amount) public onlyOwner nonReentrant {
        require(IMultiFeeDistribution(rewards).unlockedBalance(address(this)) >= amount, "Not enough unlocked tokens to withdraw");
        
        IMultiFeeDistribution(rewards).withdraw(amount);
        deposit();
    }

    // Added to support recovering LP Rewards from other systems to be distributed to holders
    function salvage(address recipient, address token, uint256 amount) public onlyOwner {
        // Admin cannot withdraw the staking or harvested token from the contract
        require(token != want, "cannot salvage want");
        require(token != harvestedToken, "cannot salvage harvestedToken");
        IERC20(token).safeTransfer(recipient, amount);
    }
    
    //Withdraw as much ADDY as possible from the fee dist contract and return it to the vault
    function emergencyExit() public onlyOwner {
        IMultiFeeDistribution(rewards).exit();
        IMultiFeeDistribution(rewards).withdrawExpiredLocks();
        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            IERC20(want).safeTransfer(jar, _want);
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "../strategies/base/BaseStrategy.sol";
import "../lib/Pausable.sol";
import "../interfaces/IMultiFeeDistribution.sol";

interface IStakingDualRewards {
    function notifyRewardAmount(uint256 rewardA, uint256 rewardB) external;
}
interface IMultiFeeDistribution2 {
    function stakeLocked(uint256 amount) external;
}

//Locks ADDY in the fee dist contract and sends ADDY + WMATIC it receives to a external vault contract
contract TestFeeDistStrategyLockedDual is BaseStrategy, Pausable {

    address public TIMELOCK = 0x52D3Dcf0E59237B032802b40E69f65877091171F;

    constructor(address _addy, address _wmatic, address _router, address _rewards)
        public
        BaseStrategy(
            _addy,
            msg.sender, //strategist arg isn't used, since this strategy has no fee
            _wmatic, //Fee dist currently pays dividends in WMATIC, though this would be WETH on Arbitrum
            _router, //uniswap router
            _rewards //the multi fee distribution contract
        )
    {
        IERC20(_addy).safeApprove(_rewards, uint256(-1));
    }

    // **** State Mutations ****

    function harvest() public override onlyHumanOrWhitelisted nonReentrant notPaused {
        _getReward();

        //Send WMATIC and ADDY to the vault
        uint256 _wmatic_balance = IERC20(harvestedToken).balanceOf(address(this));
        uint256 _addy_balance = IERC20(want).balanceOf(address(this));
        if (_wmatic_balance > 0) {
            IERC20(harvestedToken).safeTransfer(jar, _wmatic_balance);
            IStakingDualRewards(jar).notifyRewardAmount(_wmatic_balance, 0);
        }
        if (_addy_balance > 0) {
            IERC20(want).safeTransfer(jar, _addy_balance);
            IStakingDualRewards(jar).notifyRewardAmount(0, _addy_balance);
        }
    }

    function deposit() public override notPaused {
        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            IMultiFeeDistribution2(rewards).stakeLocked(_want);
        }
    }

    function _withdrawSome(uint256 _amount) internal override returns (uint256) {
        uint256 unlocked = IMultiFeeDistribution(rewards).unlockedBalance(address(this));

        //After the first locked withdrawal, there will be a buffer of unlocked tokens remaining in the contract
        if(unlocked >= _amount) {
            IMultiFeeDistribution(rewards).withdraw(_amount);
            return _amount;
        }
        else {
            //Not enough unlocked tokens available, need to withdraw locked stakes as well
            if(unlocked > 0) {
                IMultiFeeDistribution(rewards).withdraw(unlocked);
            }
            //Need to withdraw ALL locked stakes
            IMultiFeeDistribution(rewards).withdrawExpiredLocks();

            uint256 _balance = IERC20(want).balanceOf(address(this));
            require(_balance >= _amount, "Not enough unlocked tokens to process withdrawal");

            //Deposit remaining tokens into buffer of unlocked tokens
            uint256 extra = _balance.sub(_amount);
            if(extra > 0) {
                IMultiFeeDistribution(rewards).stake(extra, false);
            }
            return _amount;
        }
    }

    function _getReward() internal {
        IMultiFeeDistribution(rewards).getReward();
    }

    // **** Views ****

    function balanceOfPool() public override view returns (uint256) {
        return IMultiFeeDistribution(rewards).totalBalance(address(this));
    }

    function getHarvestable() external override view returns (uint256) {
        return 0; //use AddyData on the frontend to get the amount of pending WMATIC and ADDY, since this function was only designed to handle one token
    }

    //Not used
    function getFeeDistToken() public override view returns (address) {
        return harvestedToken;
    }

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

    //Need this function to relock stakes because it's possible for all locked stakes to have been withdrawn
    //Transfer ownership to an external contract that calculates the number of users with stakes ending in > 91 days & their stake amounts
    function relock(uint256 amount) public onlyOwner nonReentrant {
        require(IMultiFeeDistribution(rewards).unlockedBalance(address(this)) >= amount, "Not enough unlocked tokens to withdraw");

        IMultiFeeDistribution(rewards).withdraw(amount);
        deposit();
    }

    // Added to support recovering LP Rewards from other systems to be distributed to holders
    function salvage(address recipient, address token, uint256 amount) public onlyOwner {
        // Admin cannot withdraw the staking or harvested token from the contract
        require(token != want, "cannot salvage want");
        require(token != harvestedToken, "cannot salvage harvestedToken");
        IERC20(token).safeTransfer(recipient, amount);
    }

    //Withdraw as much ADDY as possible from the fee dist contract and return it to the vault
    function emergencyExit() public onlyOwner {
        IMultiFeeDistribution(rewards).exit();
        IMultiFeeDistribution(rewards).withdrawExpiredLocks();
        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            IERC20(want).safeTransfer(jar, _want);
        }
    }

    function executeTransaction(address target, uint value, string memory signature, bytes memory data) public payable onlyOwner returns (bytes memory) {
        require(msg.sender == TIMELOCK, "not timelock");
        require(target != address(0), "!target");

        bytes memory callData;

        if (bytes(signature).length == 0) {
            callData = data;
        } else {
            callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
        }

        // solium-disable-next-line security/no-call-value
        // XXX: Using ".value(...)" is deprecated. Use "{value: ...}" instead.
        (bool success, bytes memory returnData) = target.call{value : value}(callData);
        require(success, "Timelock::executeTransaction: Transaction execution reverted.");

        return returnData;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

import "../interfaces/ICalculator.sol";
import "../interfaces/IMultiFeeDistribution.sol";

contract TestMinter is Ownable {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

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

    mapping(address => bool) private _minters;
    address public calculator = 0xAc2F66971BC37eB443c4E11AAb277e19d1C4864C;
    //address public feeDistribution;
    address public dev = 0xB29Cd9C87a624B940335d6d5e1D4aADf7D95bEeC;

    //balance is stored in here instead of fee dist
    mapping(address => uint256) public totalBalance;

    uint256 public addyPerProfitEth = 500; //500 ADDY per ETH = $4-5 per ADDY, similar to price of BUNNY during presale

    /* ========== CONSTRUCTOR ========== */

    /*constructor(address _dev, address _calculator)
        public
    {
        dev = _dev;
        calculator = _calculator;
    }*/

    /* ========== MODIFIERS ========== */

    modifier onlyMinter {
        require(isMinter(msg.sender) == true, "AddyMinter: caller is not the minter");
        _;
    }

    function mintFor(address user, address asset, uint256 amount) external onlyMinter {
        uint256 valueInEth = amount; //ICalculator(calculator).valueOfAsset(asset, amount);

        uint256 mintAddy = amountAddyToMint(valueInEth);
        if (mintAddy == 0) return;

        totalBalance[user] = totalBalance[user].add(mintAddy);
        //For every 100 tokens minted, 15 additional tokens will go towards development to ensure rapid innovation.
        totalBalance[dev] = totalBalance[dev].add(mintAddy.mul(15).div(100));
    }

    /* ========== VIEWS ========== */

    function isMinter(address account) public view returns (bool) {
        return _minters[account];
    }

    function amountAddyToMint(uint256 ethProfit) public view returns (uint256) {
        return ethProfit.mul(addyPerProfitEth);
    }

    /* ========== RESTRICTED FUNCTIONS ========== */

    /// @dev Obviously should be timelocked
    function setAddyPerProfitEth(uint256 _ratio) external onlyOwner {
        addyPerProfitEth = _ratio;
    }

    /// @dev Should be timelocked, potential minting amount manipulation risk
    function setCalculator(address newCalculator) public onlyOwner {
        calculator = newCalculator;
    }

    /*function setFeeDistribution(address newFeeDistribution) public onlyOwner {
        feeDistribution = newFeeDistribution;
    }*/

    /// @dev Obviously should be timelocked
    function setMinter(address minter, bool canMint) external onlyOwner {
        if (canMint) {
            _minters[minter] = canMint;
        } else {
            delete _minters[minter];
        }
    }
}

pragma solidity ^0.6.12;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract TestWETH is ERC20, Ownable {
    
    constructor() public ERC20('TestWETH', 'TestWETH') {
        _mint(msg.sender, 1 * 10**18);
    }

    function mint(address recipient_, uint256 amount_)
        public
        onlyOwner
        returns (bool)
    {
        uint256 balanceBefore = balanceOf(recipient_);
        _mint(recipient_, amount_);
        uint256 balanceAfter = balanceOf(recipient_);

        return balanceAfter > balanceBefore;
    }

    function transfer(address recipient, uint256 amount) public override returns (bool)
    {
        require(recipient != address(this));
        return super.transfer(recipient, amount);
    }
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {}
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"_rewards","type":"address"},{"internalType":"address","name":"_want","type":"address"},{"internalType":"address","name":"_tokenA","type":"address"},{"internalType":"address","name":"_tokenB","type":"address"},{"internalType":"address","name":"_betweenToken","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":true,"internalType":"address","name":"jar","type":"address"}],"name":"SetJar","type":"event"},{"inputs":[],"name":"FEE_DENOMINATOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"a_b_path","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"balanceOfPool","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"balanceOfWant","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentRouter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getFeeDistToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getHarvestable","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getHarvestedToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"harvest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"jar","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastHarvestTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"multiHarvest","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"reward_a_path","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewards","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"salvage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_jar","type":"address"}],"name":"setJar","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"setMultiHarvest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"strategist","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenA","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenB","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"want","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdrawForSwap","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}]

608060405260006002553480156200001657600080fd5b5060405162002b6038038062002b6083398101604081905262000039916200072a565b83738b36504f2c52f59a57ee6c84d6f1b82e7698ecf073fc0000000000000000000000000000000000000273aaa45c8f5ef92a000a121d102f4e89278a711faa88600062000086620003b9565b600080546001600160a01b0319166001600160a01b0383169081178255604051929350917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908290a350600180556001600160a01b038516620000e857600080fd5b6001600160a01b038416620000fc57600080fd5b6001600160a01b0383166200011057600080fd5b6001600160a01b0382166200012457600080fd5b6001600160a01b0381166200013857600080fd5b600380546001600160a01b03199081166001600160a01b03978816179091556006805482169587169590951790945560048054851693861693909317909255600980548416918516919091179055600580548316918416919091179055600a80548216868416179055600b805490911684831617905581166200022b576040805180820190915273fc0000000000000000000000000000000000000281526001600160a01b0384166020820152620001f590600c9060026200069f565b50604080518082019091526001600160a01b038085168252831660208201526200022490600d9060026200069f565b50620002a6565b6040805160608101825273fc0000000000000000000000000000000000000281526001600160a01b0383811660208301528516918101919091526200027590600c9060036200069f565b50604080518082019091526001600160a01b03808516825283166020820152620002a490600d9060026200069f565b505b620002cd85600019866001600160a01b0316620003bd60201b620013f0179092919060201c565b73fc000000000000000000000000000000000000026001600160a01b0384161480159062000318575073fc000000000000000000000000000000000000026001600160a01b03831614155b156200035a576009546200035a9073fc00000000000000000000000000000000000002906001600160a01b0316600019620003bd602090811b620013f017901c565b60095462000384906001600160a01b038581169116600019620003bd602090811b620013f017901c565b600954620003ae906001600160a01b038481169116600019620003bd602090811b620013f017901c565b5050505050620009da565b3390565b8015806200044c5750604051636eb1769f60e11b81526001600160a01b0384169063dd62ed3e90620003f6903090869060040162000802565b60206040518083038186803b1580156200040f57600080fd5b505afa15801562000424573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200044a9190620007cb565b155b620004745760405162461bcd60e51b81526004016200046b9062000931565b60405180910390fd5b620004cf8363095ea7b360e01b8484604051602401620004969291906200081c565b60408051808303601f190181529190526020810180516001600160e01b0319939093166001600160e01b0393841617905290620004d416565b505050565b606062000530826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166200057060201b620014ea179092919060201c565b805190915015620004cf5780806020019051810190620005519190620007a9565b620004cf5760405162461bcd60e51b81526004016200046b90620008e7565b60606200058184846000856200058b565b90505b9392505050565b606082471015620005b05760405162461bcd60e51b81526004016200046b906200086a565b620005bb856200065b565b620005da5760405162461bcd60e51b81526004016200046b90620008b0565b60006060866001600160a01b03168587604051620005f99190620007e4565b60006040518083038185875af1925050503d806000811462000638576040519150601f19603f3d011682016040523d82523d6000602084013e6200063d565b606091505b5090925090506200065082828662000661565b979650505050505050565b3b151590565b606083156200067257508162000584565b825115620006835782518084602001fd5b8160405162461bcd60e51b81526004016200046b919062000835565b828054828255906000526020600020908101928215620006f7579160200282015b82811115620006f757825182546001600160a01b0319166001600160a01b03909116178255602090920191600190910190620006c0565b506200070592915062000709565b5090565b5b80821115620007055780546001600160a01b03191681556001016200070a565b600080600080600060a0868803121562000742578081fd5b85516200074f81620009c1565b60208701519095506200076281620009c1565b60408701519094506200077581620009c1565b60608701519093506200078881620009c1565b60808701519092506200079b81620009c1565b809150509295509295909350565b600060208284031215620007bb578081fd5b8151801515811462000584578182fd5b600060208284031215620007dd578081fd5b5051919050565b60008251620007f88184602087016200098e565b9190910192915050565b6001600160a01b0392831681529116602082015260400190565b6001600160a01b03929092168252602082015260400190565b6000602082528251806020840152620008568160408501602087016200098e565b601f01601f19169190910160400192915050565b60208082526026908201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6040820152651c8818d85b1b60d21b606082015260800190565b6020808252601d908201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604082015260600190565b6020808252602a908201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6040820152691bdd081cdd58d8d9595960b21b606082015260800190565b60208082526036908201527f5361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f60408201527f20746f206e6f6e2d7a65726f20616c6c6f77616e636500000000000000000000606082015260800190565b60005b83811015620009ab57818101518382015260200162000991565b83811115620009bb576000848401525b50505050565b6001600160a01b0381168114620009d757600080fd5b50565b61217680620009ea6000396000f3fe608060405234801561001057600080fd5b50600436106101c45760003560e01c8063715018a6116100f9578063c6223e2611610097578063d73792a911610071578063d73792a9146102dd578063f2fde38b146102e5578063f6f482aa146102f8578063feffbe831461030b576101c4565b8063c6223e26146102ba578063ceb0f626146102cd578063d0e30db0146102d5576101c4565b80639d27ab70116100d35780639d27ab701461028f5780639ec5a894146102a2578063be38a4fe146102aa578063c1a3d44c146102b2576101c4565b8063715018a614610277578063722713f71461027f5780638da5cb5b14610287576101c4565b80631f1fcd51116101665780634641257d116101405780634641257d1461025f578063506508f1146102675780635f64b55b1461026f57806367367d2314610234576101c4565b80631f1fcd511461023c5780631fe4a686146102445780632e1a7d4d1461024c576101c4565b806311588086116101a25780631158808614610211578063122fea3b1461021957806315f1a102146102215780631836edca14610234576101c4565b80630547104d146101c95780630fc63d10146101e75780631113ef52146101fc575b600080fd5b6101d161031e565b6040516101de9190612021565b60405180910390f35b6101ef610323565b6040516101de9190611b84565b61020f61020a3660046119f3565b610332565b005b6101d16103c1565b6101ef610447565b6101ef61022f366004611ae8565b610456565b6101ef61047d565b6101ef61048c565b6101ef61049b565b61020f61025a366004611ae8565b6104aa565b61020f6105a1565b6101ef610e65565b6101ef610e74565b61020f610e83565b6101d1610f0c565b6101ef610f27565b61020f61029d3660046119bb565b610f36565b6101ef610faa565b6101ef610fb9565b6101d1610fc8565b6101d16102c8366004611ae8565b610ff9565b6101d16110d5565b61020f6110db565b6101d16111ca565b61020f6102f33660046119bb565b6111d0565b61020f6103063660046119bb565b611290565b6101ef610319366004611ae8565b6113e3565b600090565b600a546001600160a01b031681565b61033a611503565b6001600160a01b031661034b610f27565b6001600160a01b03161461037a5760405162461bcd60e51b815260040161037190611e99565b60405180910390fd5b6003546001600160a01b03838116911614156103a85760405162461bcd60e51b815260040161037190611cef565b6103bc6001600160a01b0383168483611507565b505050565b6005546040516370a0823160e01b81526000916001600160a01b0316906370a08231906103f2903090600401611b84565b60206040518083038186803b15801561040a57600080fd5b505afa15801561041e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104429190611b00565b905090565b6009546001600160a01b031681565b600c818154811061046357fe5b6000918252602090912001546001600160a01b0316905081565b6004546001600160a01b031690565b6003546001600160a01b031681565b6006546001600160a01b031681565b6008546001600160a01b031633146104d45760405162461bcd60e51b815260040161037190611ece565b6003546040516370a0823160e01b81526000916001600160a01b0316906370a0823190610505903090600401611b84565b60206040518083038186803b15801561051d57600080fd5b505afa158015610531573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105559190611b00565b9050818110156105805761057161056c8383611526565b611553565b915061057d82826115bc565b91505b60085460035461059d916001600160a01b03918216911684611507565b5050565b333214806105c757506105b2610f27565b6001600160a01b0316336001600160a01b0316145b806105dc57506007546001600160a01b031633145b806105f157506008546001600160a01b031633145b61060d5760405162461bcd60e51b815260040161037190611e30565b600260015414156106305760405162461bcd60e51b815260040161037190611f94565b600260015561063d6115e1565b600480546040516370a0823160e01b81526000926001600160a01b03909216916370a082319161066f91309101611b84565b60206040518083038186803b15801561068757600080fd5b505afa15801561069b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106bf9190611b00565b9050801561078757600061076561271061075f600660009054906101000a90046001600160a01b03166001600160a01b031663ced72f876040518163ffffffff1660e01b815260040160206040518083038186803b15801561072057600080fd5b505afa158015610734573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107589190611b00565b8590611708565b90611742565b600654600454919250610785916001600160a01b03908116911683611507565b505b600a546004546001600160a01b03908116911614610ab657600480546040516370a0823160e01b81526000926001600160a01b03909216916370a08231916107d191309101611b84565b60206040518083038186803b1580156107e957600080fd5b505afa1580156107fd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108219190611b00565b600c54909150606090600214156108e45760408051600180825281830190925290816020015b61084f61199b565b8152602001906001900390816108475790505090506040518060600160405280600c60008154811061087d57fe5b600091825260209182902001546001600160a01b03168252600c8054929091019160019081106108a957fe5b60009182526020808320909101546001600160a01b031683529190910181905282518391906108d457fe5b6020026020010181905250610a1b565b600c5460031415610a1b576040805160028082526060820190925290816020015b61090d61199b565b8152602001906001900390816109055790505090506040518060600160405280600c60008154811061093b57fe5b600091825260209182902001546001600160a01b03168252600c80549290910191600190811061096757fe5b60009182526020808320909101546001600160a01b0316835291909101819052825183919061099257fe5b60200260200101819052506040518060600160405280600c6001815481106109b657fe5b600091825260209182902001546001600160a01b03168252600c8054929091019160029081106109e257fe5b60009182526020808320909101546001600160a01b031683529190910152815182906001908110610a0f57fe5b60200260200101819052505b6009546001600160a01b031663f41766d88360008430610a3c42603c6115bc565b6040518663ffffffff1660e01b8152600401610a5c95949392919061202a565b600060405180830381600087803b158015610a7657600080fd5b505af1158015610a8a573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610ab29190810190611a33565b5050505b600a546040516370a0823160e01b81526000916001600160a01b0316906370a0823190610ae7903090600401611b84565b60206040518083038186803b158015610aff57600080fd5b505afa158015610b13573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b379190611b00565b90508015610c9257604080516001808252818301909252606091816020015b610b5e61199b565b815260200190600190039081610b565790505090506040518060600160405280600d600081548110610b8c57fe5b600091825260209182902001546001600160a01b03168252600d805492909101916001908110610bb857fe5b60009182526020808320909101546001600160a01b03168352919091018190528251839190610be357fe5b60209081029190910101526009546001600160a01b031663f41766d8610c0a846002611742565b60008430610c1942603c6115bc565b6040518663ffffffff1660e01b8152600401610c3995949392919061202a565b600060405180830381600087803b158015610c5357600080fd5b505af1158015610c67573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610c8f9190810190611a33565b50505b600a546040516370a0823160e01b81526000916001600160a01b0316906370a0823190610cc3903090600401611b84565b60206040518083038186803b158015610cdb57600080fd5b505afa158015610cef573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d139190611b00565b600b546040516370a0823160e01b81529192506000916001600160a01b03909116906370a0823190610d49903090600401611b84565b60206040518083038186803b158015610d6157600080fd5b505afa158015610d75573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d999190611b00565b9050600082118015610dab5750600081115b15610e5357600954600a54600b54604051635a47ddc360e01b81526001600160a01b0393841693635a47ddc393610dfd939082169291169060009088908890839081903090603c420190600401611bb2565b606060405180830381600087803b158015610e1757600080fd5b505af1158015610e2b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e4f9190611b3b565b5050505b610e5b611774565b5050600180555050565b6007546001600160a01b031681565b600b546001600160a01b031681565b610e8b611503565b6001600160a01b0316610e9c610f27565b6001600160a01b031614610ec25760405162461bcd60e51b815260040161037190611e99565b600080546040516001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600080546001600160a01b0319169055565b6000610442610f196103c1565b610f21610fc8565b906115bc565b6000546001600160a01b031690565b610f3e611503565b6001600160a01b0316610f4f610f27565b6001600160a01b031614610f755760405162461bcd60e51b815260040161037190611e99565b6001600160a01b038116610f8857600080fd5b600780546001600160a01b0319166001600160a01b0392909216919091179055565b6005546001600160a01b031681565b6008546001600160a01b031681565b6003546040516370a0823160e01b81526000916001600160a01b0316906370a08231906103f2903090600401611b84565b6008546000906001600160a01b031633146110265760405162461bcd60e51b815260040161037190611ece565b61102f82611553565b506003546040516370a0823160e01b81526001600160a01b03909116906370a0823190611060903090600401611b84565b60206040518083038186803b15801561107857600080fd5b505afa15801561108c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110b09190611b00565b6008546003549192506110d0916001600160a01b03908116911683611507565b919050565b60025481565b6003546040516370a0823160e01b81526000916001600160a01b0316906370a082319061110c903090600401611b84565b60206040518083038186803b15801561112457600080fd5b505afa158015611138573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061115c9190611b00565b905080156111c75760055460405163b6b55f2560e01b81526001600160a01b039091169063b6b55f2590611194908490600401612021565b600060405180830381600087803b1580156111ae57600080fd5b505af11580156111c2573d6000803e3d6000fd5b505050505b50565b61271081565b6111d8611503565b6001600160a01b03166111e9610f27565b6001600160a01b03161461120f5760405162461bcd60e51b815260040161037190611e99565b6001600160a01b0381166112355760405162461bcd60e51b815260040161037190611ca9565b600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b611298611503565b6001600160a01b03166112a9610f27565b6001600160a01b0316146112cf5760405162461bcd60e51b815260040161037190611e99565b6008546001600160a01b0316156112f85760405162461bcd60e51b815260040161037190611d8a565b306001600160a01b0316816001600160a01b031663a8c62e766040518163ffffffff1660e01b815260040160206040518083038186803b15801561133b57600080fd5b505afa15801561134f573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061137391906119d7565b6001600160a01b0316146113995760405162461bcd60e51b815260040161037190611eec565b600880546001600160a01b0319166001600160a01b0383169081179091556040517f55c9c1522358ac20ccd3c4887fe1886c26bd0f045ab7fd11acd78680bb77956990600090a250565b600d818154811061046357fe5b8015806114785750604051636eb1769f60e11b81526001600160a01b0384169063dd62ed3e906114269030908690600401611b98565b60206040518083038186803b15801561143e57600080fd5b505afa158015611452573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114769190611b00565b155b6114945760405162461bcd60e51b815260040161037190611fcb565b6103bc8363095ea7b360e01b84846040516024016114b3929190611c5d565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b03199093169290921790915261180c565b60606114f9848460008561189b565b90505b9392505050565b3390565b6103bc8363a9059cbb60e01b84846040516024016114b3929190611c5d565b6000828211156115485760405162461bcd60e51b815260040161037190611d53565b508082035b92915050565b600554604051632e1a7d4d60e01b81526000916001600160a01b031690632e1a7d4d90611584908590600401612021565b600060405180830381600087803b15801561159e57600080fd5b505af11580156115b2573d6000803e3d6000fd5b5093949350505050565b6000828201838110156114fc5760405162461bcd60e51b815260040161037190611d1c565b60408051600180825281830190925260609160208083019080368337505060045482519293506001600160a01b03169183915060009061161d57fe5b6001600160a01b0392831660209182029290920101526005546040516331279d3d60e01b81529116906331279d3d9061165c9030908590600401611c01565b600060405180830381600087803b15801561167657600080fd5b505af115801561168a573d6000803e3d6000fd5b50506005546040805163d294f09360e01b815281516001600160a01b03909316945063d294f0939350600480820193918290030181600087803b1580156116d057600080fd5b505af11580156116e4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103bc9190611b18565b6000826117175750600061154d565b8282028284828161172457fe5b04146114fc5760405162461bcd60e51b815260040161037190611e58565b60008082116117635760405162461bcd60e51b815260040161037190611df9565b81838161176c57fe5b049392505050565b6003546040516370a0823160e01b81526000916001600160a01b0316906370a08231906117a5903090600401611b84565b60206040518083038186803b1580156117bd57600080fd5b505afa1580156117d1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117f59190611b00565b90508015611805576118056110db565b5042600255565b6060611861826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166114ea9092919063ffffffff16565b8051909150156103bc578080602001905181019061187f9190611ac8565b6103bc5760405162461bcd60e51b815260040161037190611f4a565b6060824710156118bd5760405162461bcd60e51b815260040161037190611db3565b6118c68561195c565b6118e25760405162461bcd60e51b815260040161037190611f13565b60006060866001600160a01b031685876040516118ff9190611b68565b60006040518083038185875af1925050503d806000811461193c576040519150601f19603f3d011682016040523d82523d6000602084013e611941565b606091505b5091509150611951828286611962565b979650505050505050565b3b151590565b606083156119715750816114fc565b8251156119815782518084602001fd5b8160405162461bcd60e51b81526004016103719190611c76565b604080516060810182526000808252602082018190529181019190915290565b6000602082840312156119cc578081fd5b81356114fc8161212b565b6000602082840312156119e8578081fd5b81516114fc8161212b565b600080600060608486031215611a07578182fd5b8335611a128161212b565b92506020840135611a228161212b565b929592945050506040919091013590565b60006020808385031215611a45578182fd5b825167ffffffffffffffff811115611a5b578283fd5b8301601f81018513611a6b578283fd5b8051611a7e611a79826120db565b6120b4565b8181528381019083850185840285018601891015611a9a578687fd5b8694505b83851015611abc578051835260019490940193918501918501611a9e565b50979650505050505050565b600060208284031215611ad9578081fd5b815180151581146114fc578182fd5b600060208284031215611af9578081fd5b5035919050565b600060208284031215611b11578081fd5b5051919050565b60008060408385031215611b2a578182fd5b505080516020909101519092909150565b600080600060608486031215611b4f578283fd5b8351925060208401519150604084015190509250925092565b60008251611b7a8184602087016120fb565b9190910192915050565b6001600160a01b0391909116815260200190565b6001600160a01b0392831681529116602082015260400190565b6001600160a01b03998a168152978916602089015295151560408801526060870194909452608086019290925260a085015260c084015290921660e08201526101008101919091526101200190565b6001600160a01b038381168252604060208084018290528451918401829052600092858201929091906060860190855b81811015611c4f578551851683529483019491830191600101611c31565b509098975050505050505050565b6001600160a01b03929092168252602082015260400190565b6000602082528251806020840152611c958160408501602087016120fb565b601f01601f19169190910160400192915050565b60208082526026908201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160408201526564647265737360d01b606082015260800190565b60208082526013908201527218d85b9b9bdd081cd85b1d9859d9481dd85b9d606a1b604082015260600190565b6020808252601b908201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604082015260600190565b6020808252601e908201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604082015260600190565b6020808252600f908201526e1a985c88185b1c9958591e481cd95d608a1b604082015260600190565b60208082526026908201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6040820152651c8818d85b1b60d21b606082015260800190565b6020808252601a908201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604082015260600190565b6020808252600e908201526d1b9bdd08185d5d1a1bdc9a5e995960921b604082015260600190565b60208082526021908201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6040820152607760f81b606082015260800190565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b60208082526004908201526310b530b960e11b604082015260600190565b6020808252600d908201526c34b731b7b93932b1ba103530b960991b604082015260600190565b6020808252601d908201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604082015260600190565b6020808252602a908201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6040820152691bdd081cdd58d8d9595960b21b606082015260800190565b6020808252601f908201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604082015260600190565b60208082526036908201527f5361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f60408201527520746f206e6f6e2d7a65726f20616c6c6f77616e636560501b606082015260800190565b90815260200190565b600060a0820187835260208781850152604060a08186015282885180855260c087019150838a019450855b8181101561209257855180516001600160a01b03908116855286820151168685015284015115158484015294840194606090920191600101612055565b50506001600160a01b0397909716606086015250505050608001529392505050565b60405181810167ffffffffffffffff811182821017156120d357600080fd5b604052919050565b600067ffffffffffffffff8211156120f1578081fd5b5060209081020190565b60005b838110156121165781810151838201526020016120fe565b83811115612125576000848401525b50505050565b6001600160a01b03811681146111c757600080fdfea26469706673582212204091d628033fc0147e6a15a97ca3de5ff2e5c58f290eaa1a0483a631e229b75d64736f6c634300060c003300000000000000000000000052edc4da8423266d81f5cda947f2d1b438d479a10000000000000000000000004f66cbd698f1fe6e2692d1de5d758027a5272787000000000000000000000000fc00000000000000000000000000000000000002000000000000000000000000fc000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106101c45760003560e01c8063715018a6116100f9578063c6223e2611610097578063d73792a911610071578063d73792a9146102dd578063f2fde38b146102e5578063f6f482aa146102f8578063feffbe831461030b576101c4565b8063c6223e26146102ba578063ceb0f626146102cd578063d0e30db0146102d5576101c4565b80639d27ab70116100d35780639d27ab701461028f5780639ec5a894146102a2578063be38a4fe146102aa578063c1a3d44c146102b2576101c4565b8063715018a614610277578063722713f71461027f5780638da5cb5b14610287576101c4565b80631f1fcd51116101665780634641257d116101405780634641257d1461025f578063506508f1146102675780635f64b55b1461026f57806367367d2314610234576101c4565b80631f1fcd511461023c5780631fe4a686146102445780632e1a7d4d1461024c576101c4565b806311588086116101a25780631158808614610211578063122fea3b1461021957806315f1a102146102215780631836edca14610234576101c4565b80630547104d146101c95780630fc63d10146101e75780631113ef52146101fc575b600080fd5b6101d161031e565b6040516101de9190612021565b60405180910390f35b6101ef610323565b6040516101de9190611b84565b61020f61020a3660046119f3565b610332565b005b6101d16103c1565b6101ef610447565b6101ef61022f366004611ae8565b610456565b6101ef61047d565b6101ef61048c565b6101ef61049b565b61020f61025a366004611ae8565b6104aa565b61020f6105a1565b6101ef610e65565b6101ef610e74565b61020f610e83565b6101d1610f0c565b6101ef610f27565b61020f61029d3660046119bb565b610f36565b6101ef610faa565b6101ef610fb9565b6101d1610fc8565b6101d16102c8366004611ae8565b610ff9565b6101d16110d5565b61020f6110db565b6101d16111ca565b61020f6102f33660046119bb565b6111d0565b61020f6103063660046119bb565b611290565b6101ef610319366004611ae8565b6113e3565b600090565b600a546001600160a01b031681565b61033a611503565b6001600160a01b031661034b610f27565b6001600160a01b03161461037a5760405162461bcd60e51b815260040161037190611e99565b60405180910390fd5b6003546001600160a01b03838116911614156103a85760405162461bcd60e51b815260040161037190611cef565b6103bc6001600160a01b0383168483611507565b505050565b6005546040516370a0823160e01b81526000916001600160a01b0316906370a08231906103f2903090600401611b84565b60206040518083038186803b15801561040a57600080fd5b505afa15801561041e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104429190611b00565b905090565b6009546001600160a01b031681565b600c818154811061046357fe5b6000918252602090912001546001600160a01b0316905081565b6004546001600160a01b031690565b6003546001600160a01b031681565b6006546001600160a01b031681565b6008546001600160a01b031633146104d45760405162461bcd60e51b815260040161037190611ece565b6003546040516370a0823160e01b81526000916001600160a01b0316906370a0823190610505903090600401611b84565b60206040518083038186803b15801561051d57600080fd5b505afa158015610531573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105559190611b00565b9050818110156105805761057161056c8383611526565b611553565b915061057d82826115bc565b91505b60085460035461059d916001600160a01b03918216911684611507565b5050565b333214806105c757506105b2610f27565b6001600160a01b0316336001600160a01b0316145b806105dc57506007546001600160a01b031633145b806105f157506008546001600160a01b031633145b61060d5760405162461bcd60e51b815260040161037190611e30565b600260015414156106305760405162461bcd60e51b815260040161037190611f94565b600260015561063d6115e1565b600480546040516370a0823160e01b81526000926001600160a01b03909216916370a082319161066f91309101611b84565b60206040518083038186803b15801561068757600080fd5b505afa15801561069b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106bf9190611b00565b9050801561078757600061076561271061075f600660009054906101000a90046001600160a01b03166001600160a01b031663ced72f876040518163ffffffff1660e01b815260040160206040518083038186803b15801561072057600080fd5b505afa158015610734573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107589190611b00565b8590611708565b90611742565b600654600454919250610785916001600160a01b03908116911683611507565b505b600a546004546001600160a01b03908116911614610ab657600480546040516370a0823160e01b81526000926001600160a01b03909216916370a08231916107d191309101611b84565b60206040518083038186803b1580156107e957600080fd5b505afa1580156107fd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108219190611b00565b600c54909150606090600214156108e45760408051600180825281830190925290816020015b61084f61199b565b8152602001906001900390816108475790505090506040518060600160405280600c60008154811061087d57fe5b600091825260209182902001546001600160a01b03168252600c8054929091019160019081106108a957fe5b60009182526020808320909101546001600160a01b031683529190910181905282518391906108d457fe5b6020026020010181905250610a1b565b600c5460031415610a1b576040805160028082526060820190925290816020015b61090d61199b565b8152602001906001900390816109055790505090506040518060600160405280600c60008154811061093b57fe5b600091825260209182902001546001600160a01b03168252600c80549290910191600190811061096757fe5b60009182526020808320909101546001600160a01b0316835291909101819052825183919061099257fe5b60200260200101819052506040518060600160405280600c6001815481106109b657fe5b600091825260209182902001546001600160a01b03168252600c8054929091019160029081106109e257fe5b60009182526020808320909101546001600160a01b031683529190910152815182906001908110610a0f57fe5b60200260200101819052505b6009546001600160a01b031663f41766d88360008430610a3c42603c6115bc565b6040518663ffffffff1660e01b8152600401610a5c95949392919061202a565b600060405180830381600087803b158015610a7657600080fd5b505af1158015610a8a573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610ab29190810190611a33565b5050505b600a546040516370a0823160e01b81526000916001600160a01b0316906370a0823190610ae7903090600401611b84565b60206040518083038186803b158015610aff57600080fd5b505afa158015610b13573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b379190611b00565b90508015610c9257604080516001808252818301909252606091816020015b610b5e61199b565b815260200190600190039081610b565790505090506040518060600160405280600d600081548110610b8c57fe5b600091825260209182902001546001600160a01b03168252600d805492909101916001908110610bb857fe5b60009182526020808320909101546001600160a01b03168352919091018190528251839190610be357fe5b60209081029190910101526009546001600160a01b031663f41766d8610c0a846002611742565b60008430610c1942603c6115bc565b6040518663ffffffff1660e01b8152600401610c3995949392919061202a565b600060405180830381600087803b158015610c5357600080fd5b505af1158015610c67573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610c8f9190810190611a33565b50505b600a546040516370a0823160e01b81526000916001600160a01b0316906370a0823190610cc3903090600401611b84565b60206040518083038186803b158015610cdb57600080fd5b505afa158015610cef573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d139190611b00565b600b546040516370a0823160e01b81529192506000916001600160a01b03909116906370a0823190610d49903090600401611b84565b60206040518083038186803b158015610d6157600080fd5b505afa158015610d75573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d999190611b00565b9050600082118015610dab5750600081115b15610e5357600954600a54600b54604051635a47ddc360e01b81526001600160a01b0393841693635a47ddc393610dfd939082169291169060009088908890839081903090603c420190600401611bb2565b606060405180830381600087803b158015610e1757600080fd5b505af1158015610e2b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e4f9190611b3b565b5050505b610e5b611774565b5050600180555050565b6007546001600160a01b031681565b600b546001600160a01b031681565b610e8b611503565b6001600160a01b0316610e9c610f27565b6001600160a01b031614610ec25760405162461bcd60e51b815260040161037190611e99565b600080546040516001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600080546001600160a01b0319169055565b6000610442610f196103c1565b610f21610fc8565b906115bc565b6000546001600160a01b031690565b610f3e611503565b6001600160a01b0316610f4f610f27565b6001600160a01b031614610f755760405162461bcd60e51b815260040161037190611e99565b6001600160a01b038116610f8857600080fd5b600780546001600160a01b0319166001600160a01b0392909216919091179055565b6005546001600160a01b031681565b6008546001600160a01b031681565b6003546040516370a0823160e01b81526000916001600160a01b0316906370a08231906103f2903090600401611b84565b6008546000906001600160a01b031633146110265760405162461bcd60e51b815260040161037190611ece565b61102f82611553565b506003546040516370a0823160e01b81526001600160a01b03909116906370a0823190611060903090600401611b84565b60206040518083038186803b15801561107857600080fd5b505afa15801561108c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110b09190611b00565b6008546003549192506110d0916001600160a01b03908116911683611507565b919050565b60025481565b6003546040516370a0823160e01b81526000916001600160a01b0316906370a082319061110c903090600401611b84565b60206040518083038186803b15801561112457600080fd5b505afa158015611138573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061115c9190611b00565b905080156111c75760055460405163b6b55f2560e01b81526001600160a01b039091169063b6b55f2590611194908490600401612021565b600060405180830381600087803b1580156111ae57600080fd5b505af11580156111c2573d6000803e3d6000fd5b505050505b50565b61271081565b6111d8611503565b6001600160a01b03166111e9610f27565b6001600160a01b03161461120f5760405162461bcd60e51b815260040161037190611e99565b6001600160a01b0381166112355760405162461bcd60e51b815260040161037190611ca9565b600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b611298611503565b6001600160a01b03166112a9610f27565b6001600160a01b0316146112cf5760405162461bcd60e51b815260040161037190611e99565b6008546001600160a01b0316156112f85760405162461bcd60e51b815260040161037190611d8a565b306001600160a01b0316816001600160a01b031663a8c62e766040518163ffffffff1660e01b815260040160206040518083038186803b15801561133b57600080fd5b505afa15801561134f573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061137391906119d7565b6001600160a01b0316146113995760405162461bcd60e51b815260040161037190611eec565b600880546001600160a01b0319166001600160a01b0383169081179091556040517f55c9c1522358ac20ccd3c4887fe1886c26bd0f045ab7fd11acd78680bb77956990600090a250565b600d818154811061046357fe5b8015806114785750604051636eb1769f60e11b81526001600160a01b0384169063dd62ed3e906114269030908690600401611b98565b60206040518083038186803b15801561143e57600080fd5b505afa158015611452573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114769190611b00565b155b6114945760405162461bcd60e51b815260040161037190611fcb565b6103bc8363095ea7b360e01b84846040516024016114b3929190611c5d565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b03199093169290921790915261180c565b60606114f9848460008561189b565b90505b9392505050565b3390565b6103bc8363a9059cbb60e01b84846040516024016114b3929190611c5d565b6000828211156115485760405162461bcd60e51b815260040161037190611d53565b508082035b92915050565b600554604051632e1a7d4d60e01b81526000916001600160a01b031690632e1a7d4d90611584908590600401612021565b600060405180830381600087803b15801561159e57600080fd5b505af11580156115b2573d6000803e3d6000fd5b5093949350505050565b6000828201838110156114fc5760405162461bcd60e51b815260040161037190611d1c565b60408051600180825281830190925260609160208083019080368337505060045482519293506001600160a01b03169183915060009061161d57fe5b6001600160a01b0392831660209182029290920101526005546040516331279d3d60e01b81529116906331279d3d9061165c9030908590600401611c01565b600060405180830381600087803b15801561167657600080fd5b505af115801561168a573d6000803e3d6000fd5b50506005546040805163d294f09360e01b815281516001600160a01b03909316945063d294f0939350600480820193918290030181600087803b1580156116d057600080fd5b505af11580156116e4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103bc9190611b18565b6000826117175750600061154d565b8282028284828161172457fe5b04146114fc5760405162461bcd60e51b815260040161037190611e58565b60008082116117635760405162461bcd60e51b815260040161037190611df9565b81838161176c57fe5b049392505050565b6003546040516370a0823160e01b81526000916001600160a01b0316906370a08231906117a5903090600401611b84565b60206040518083038186803b1580156117bd57600080fd5b505afa1580156117d1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117f59190611b00565b90508015611805576118056110db565b5042600255565b6060611861826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166114ea9092919063ffffffff16565b8051909150156103bc578080602001905181019061187f9190611ac8565b6103bc5760405162461bcd60e51b815260040161037190611f4a565b6060824710156118bd5760405162461bcd60e51b815260040161037190611db3565b6118c68561195c565b6118e25760405162461bcd60e51b815260040161037190611f13565b60006060866001600160a01b031685876040516118ff9190611b68565b60006040518083038185875af1925050503d806000811461193c576040519150601f19603f3d011682016040523d82523d6000602084013e611941565b606091505b5091509150611951828286611962565b979650505050505050565b3b151590565b606083156119715750816114fc565b8251156119815782518084602001fd5b8160405162461bcd60e51b81526004016103719190611c76565b604080516060810182526000808252602082018190529181019190915290565b6000602082840312156119cc578081fd5b81356114fc8161212b565b6000602082840312156119e8578081fd5b81516114fc8161212b565b600080600060608486031215611a07578182fd5b8335611a128161212b565b92506020840135611a228161212b565b929592945050506040919091013590565b60006020808385031215611a45578182fd5b825167ffffffffffffffff811115611a5b578283fd5b8301601f81018513611a6b578283fd5b8051611a7e611a79826120db565b6120b4565b8181528381019083850185840285018601891015611a9a578687fd5b8694505b83851015611abc578051835260019490940193918501918501611a9e565b50979650505050505050565b600060208284031215611ad9578081fd5b815180151581146114fc578182fd5b600060208284031215611af9578081fd5b5035919050565b600060208284031215611b11578081fd5b5051919050565b60008060408385031215611b2a578182fd5b505080516020909101519092909150565b600080600060608486031215611b4f578283fd5b8351925060208401519150604084015190509250925092565b60008251611b7a8184602087016120fb565b9190910192915050565b6001600160a01b0391909116815260200190565b6001600160a01b0392831681529116602082015260400190565b6001600160a01b03998a168152978916602089015295151560408801526060870194909452608086019290925260a085015260c084015290921660e08201526101008101919091526101200190565b6001600160a01b038381168252604060208084018290528451918401829052600092858201929091906060860190855b81811015611c4f578551851683529483019491830191600101611c31565b509098975050505050505050565b6001600160a01b03929092168252602082015260400190565b6000602082528251806020840152611c958160408501602087016120fb565b601f01601f19169190910160400192915050565b60208082526026908201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160408201526564647265737360d01b606082015260800190565b60208082526013908201527218d85b9b9bdd081cd85b1d9859d9481dd85b9d606a1b604082015260600190565b6020808252601b908201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604082015260600190565b6020808252601e908201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604082015260600190565b6020808252600f908201526e1a985c88185b1c9958591e481cd95d608a1b604082015260600190565b60208082526026908201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6040820152651c8818d85b1b60d21b606082015260800190565b6020808252601a908201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604082015260600190565b6020808252600e908201526d1b9bdd08185d5d1a1bdc9a5e995960921b604082015260600190565b60208082526021908201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6040820152607760f81b606082015260800190565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b60208082526004908201526310b530b960e11b604082015260600190565b6020808252600d908201526c34b731b7b93932b1ba103530b960991b604082015260600190565b6020808252601d908201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604082015260600190565b6020808252602a908201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6040820152691bdd081cdd58d8d9595960b21b606082015260800190565b6020808252601f908201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604082015260600190565b60208082526036908201527f5361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f60408201527520746f206e6f6e2d7a65726f20616c6c6f77616e636560501b606082015260800190565b90815260200190565b600060a0820187835260208781850152604060a08186015282885180855260c087019150838a019450855b8181101561209257855180516001600160a01b03908116855286820151168685015284015115158484015294840194606090920191600101612055565b50506001600160a01b0397909716606086015250505050608001529392505050565b60405181810167ffffffffffffffff811182821017156120d357600080fd5b604052919050565b600067ffffffffffffffff8211156120f1578081fd5b5060209081020190565b60005b838110156121165781810151838201526020016120fe565b83811115612125576000848401525b50505050565b6001600160a01b03811681146111c757600080fdfea26469706673582212204091d628033fc0147e6a15a97ca3de5ff2e5c58f290eaa1a0483a631e229b75d64736f6c634300060c0033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

00000000000000000000000052edc4da8423266d81f5cda947f2d1b438d479a10000000000000000000000004f66cbd698f1fe6e2692d1de5d758027a5272787000000000000000000000000fc00000000000000000000000000000000000002000000000000000000000000fc000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000

-----Decoded View---------------
Arg [0] : _rewards (address): 0x52edC4da8423266D81F5CdA947F2d1B438d479a1
Arg [1] : _want (address): 0x4f66cBd698F1fe6E2692D1De5d758027a5272787
Arg [2] : _tokenA (address): 0xFc00000000000000000000000000000000000002
Arg [3] : _tokenB (address): 0xFC00000000000000000000000000000000000006
Arg [4] : _betweenToken (address): 0x0000000000000000000000000000000000000000

-----Encoded View---------------
5 Constructor Arguments found :
Arg [0] : 00000000000000000000000052edc4da8423266d81f5cda947f2d1b438d479a1
Arg [1] : 0000000000000000000000004f66cbd698f1fe6e2692d1de5d758027a5272787
Arg [2] : 000000000000000000000000fc00000000000000000000000000000000000002
Arg [3] : 000000000000000000000000fc00000000000000000000000000000000000006
Arg [4] : 0000000000000000000000000000000000000000000000000000000000000000


Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.