ETH Price: $3,197.84 (+4.82%)

Contract Diff Checker

Contract Name:
StabilizeStrategyFEIArbV2

Contract Source Code:

File 1 of 1 : StabilizeStrategyFEIArbV2

// SPDX-License-Identifier: MIT
// File: @openzeppelin/contracts/token/ERC20/IERC20.sol

pragma solidity ^0.6.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);
    
    function decimals() external view returns (uint8);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

// File: @openzeppelin/contracts/math/SafeMath.sol

pragma solidity ^0.6.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, 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) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * 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);
        uint256 c = a - b;

        return c;
    }

    /**
     * @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) {
        // 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 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts 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) {
        return div(a, b, "SafeMath: division by zero");
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts with custom message 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, string memory errorMessage) internal pure returns (uint256) {
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

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

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts with custom message 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, string memory errorMessage) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }
}

// File: @openzeppelin/contracts/utils/Address.sol

pragma solidity ^0.6.2;

/**
 * @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) {
        // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
        // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
        // for accounts without code, i.e. `keccak256('')`
        bytes32 codehash;
        bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
        // solhint-disable-next-line no-inline-assembly
        assembly { codehash := extcodehash(account) }
        return (codehash != accountHash && codehash != 0x0);
    }

    /**
     * @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");
        return _functionCallWithValue(target, data, value, errorMessage);
    }

    function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) {
        require(isContract(target), "Address: call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.call{ value: weiValue }(data);
        if (success) {
            return returndata;
        } else {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly

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

// File: @openzeppelin/contracts/token/ERC20/SafeERC20.sol

pragma solidity ^0.6.0;

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using SafeMath for uint256;
    using Address for address;

    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(IERC20 token, address spender, uint256 value) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        // solhint-disable-next-line max-line-length
        require((value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 newAllowance = token.allowance(address(this), spender).add(value);
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        if (returndata.length > 0) { // Return data is optional
            // solhint-disable-next-line max-line-length
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}

// File: @openzeppelin/contracts/GSN/Context.sol

pragma solidity ^0.6.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: @openzeppelin/contracts/access/Ownable.sol

pragma solidity ^0.6.0;

/**
 * @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.
 */
contract Ownable is Context {
    address private _governance;

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

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

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

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

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

// File: contracts/strategies/StabilizeStrategyFEIArbV2.sol

pragma solidity =0.6.6;

// This is a strategy that takes advantage of arb opportunities for fei
// Users deposit fei into the strategy and the strategy will sell into usdc when above usdc and usdc into fei when below
// Selling will occur via Uniswap and buying WETH via Uniswap
// Strat will also arbitrage with Aave flash loans between FEI bonding curve and  Uniswap to multiply gains

interface StabilizeStakingPool {
    function notifyRewardAmount(uint256) external;
}

interface UniswapRouter {
    function swapExactETHForTokens(uint, address[] calldata, address, uint) external payable returns (uint[] memory);
    function swapExactTokensForTokens(uint, uint, address[] calldata, address, uint) external returns (uint[] memory);
    function swapExactTokensForTokensSupportingFeeOnTransferTokens(uint, uint, address[] calldata, address, uint) external;
    function getAmountsOut(uint, address[] calldata) external view returns (uint[] memory); // For a value in, it calculates value out
}

interface LendingPoolAddressesProvider {
    function getLendingPool() external view returns (address);
}

interface LendingPool {
  function flashLoan(address, address[] calldata, uint256[] calldata, uint256[] calldata, address, bytes calldata params, uint16) external;
}

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

interface WrappedEther {
    function withdraw(uint256) external;
    function deposit() external payable;
}

interface FeiToken {
    function incentiveContract(address) external view returns (address); // Returns the Incentive contract for an LP token contract
}

interface FeiIncentiveContract {
    function getBuyIncentive(uint256 amount) external view returns (uint256, uint32);
    function getSellPenalty(uint256 amount) external view returns (uint256, uint32);
}

interface FeiBondingCurve {
    function getAmountOut(uint256 amountIn) external view returns (uint256 amountOut);
    function purchase(address to, uint256 amountIn) external payable returns (uint256 amountOut);
    function paused() external view returns (bool);
}

interface FeiOracle{
    function update() external returns (bool);
}

contract StabilizeStrategyFEIArbV2 is Ownable {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;
    using Address for address;
    
    address public treasuryAddress; // Address of the treasury
    address public stakingAddress; // Address to the STBZ staking pool
    address public zsTokenAddress; // The address of the controlling zs-Token
    
    uint256 constant DIVISION_FACTOR = 100000;
    uint256 public lastTradeTime = 0;
    uint256 public lastActionBalance = 0; // Balance before last deposit or withdraw
    uint256 public maxPoolSize = 10000000e18; // The maximum amount of ust tokens this strategy can hold, 10 mil by default
    uint256 public percentTradeTrigger = 10000; // 10% change in value will trigger a trade
    uint256 public maxPercentSell = 80000; // Up to 80% of the tokens are sold to the cheapest token
    uint256 public maxAmountSell = 500000; // The maximum amount of tokens that can be sold at once
    uint256 public percentDepositor = 50000; // 1000 = 1%, depositors earn 50% of all gains
    uint256 public percentFlashDepositor = 10000; // Depositors by default get 10% of flash loan profit, since funds aren't used for trade
    uint256 public percentExecutor = 10000; // 10000 = 10% of WETH goes to executor, 5% of total profit. This is on top of gas stipend
    uint256 public percentStakers = 50000; // 50% of non-depositors WETH goes to stakers, can be changed
    uint256 public minTradeSplit = 20000; // If the balance is less than or equal to this, it trades the entire balance
    uint256 public maxPercentStipend = 30000; // The maximum amount of WETH profit that can be allocated to the executor for gas in percent
    uint256 public gasStipend = 2000000; // This is the gas units that are covered by executing a trade taken from the WETH profit
    bool public incentiveActive = false; // FEI has disabled the incentive calculator
    uint256[3] private flashParams; // Global parameters guiding the flash loan setup
    uint256 constant minGain = 1e16; // Minimum amount of gain (0.01 coin) before buying WETH and splitting it
    
    // Token information
    // This strategy accepts fei and usdc
    struct TokenInfo {
        IERC20 token; // Reference of token
        uint256 decimals; // Decimals of token
    }
    
    TokenInfo[] private tokenList; // An array of tokens accepted as deposits

    // Strategy specific variables
    address constant UNISWAP_ROUTER_ADDRESS = address(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); //Address of Uniswap
    address constant WETH_ADDRESS = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
    address constant GAS_ORACLE_ADDRESS = address(0x169E633A2D1E6c10dD91238Ba11c4A708dfEF37C); // Chainlink address for fast gas oracle
    address constant LENDING_POOL_ADDRESS_PROVIDER = address(0xB53C1a33016B2DC2fF3653530bfF1848a515c8c5); // Provider for Aave addresses
    address constant FEI_LP_CONTRACT = address(0x94B0A3d511b6EcDb17eBF877278Ab030acb0A878);
    address constant FEI_BONDING_CURVE = address(0xe1578B4a32Eaefcd563a9E6d0dc02a4213f673B7); // ETH Bonding curve
    address constant FEI_UNISWAP_ORACLE = address(0x087F35bd241e41Fc28E43f0E8C58d283DD55bD65);
    address constant FEI_BONDING_ORACLE = address(0x89714d3AC9149426219a3568543200D1964101C4);
    uint256 constant MAX_ETH_BUY = 1000 ether;
    uint256 constant WETH_ID = 2; // This strat will buy USDC with WETH and sell other tokens for WETH
    
    constructor(
        address _treasury,
        address _staking,
        address _zsToken
    ) public {
        treasuryAddress = _treasury;
        stakingAddress = _staking;
        zsTokenAddress = _zsToken;
        setupWithdrawTokens();
    }

    // Initialization functions
    
    function setupWithdrawTokens() internal {
        // Start with FEI
        IERC20 _token = IERC20(address(0x956F47F50A910163D8BF957Cf5846D573E7f87CA));
        tokenList.push(
            TokenInfo({
                token: _token,
                decimals: _token.decimals()
            })
        );
        
        // USDC
        _token = IERC20(address(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48));
        tokenList.push(
            TokenInfo({
                token: _token,
                decimals: _token.decimals()
            })
        );
    }
    
    // Modifier
    modifier onlyZSToken() {
        require(zsTokenAddress == _msgSender(), "Call not sent from the zs-Token");
        _;
    }
    
    // Read functions
    
    function rewardTokensCount() external view returns (uint256) {
        return tokenList.length;
    }
    
    function rewardTokenAddress(uint256 _pos) external view returns (address) {
        require(_pos < tokenList.length,"No token at that position");
        return address(tokenList[_pos].token);
    }
    
    function balance() public view returns (uint256) {
        return getNormalizedTotalBalance(address(this));
    }
    
    function getNormalizedTotalBalance(address _address) public view returns (uint256) {
        uint256 _balance = 0;
        for(uint256 i = 0; i < tokenList.length; i++){
            uint256 _bal = tokenList[i].token.balanceOf(_address);
            _bal = _bal.mul(1e18).div(10**tokenList[i].decimals);
            _balance = _balance.add(_bal); // This has been normalized to 1e18 decimals
        }
        return _balance;
    }
    
    function withdrawTokenReserves() public view returns (address, uint256) {
        // This function will return the address and amount of fei, then usdc
        if(tokenList[0].token.balanceOf(address(this)) > 0){
            return (address(tokenList[0].token), tokenList[0].token.balanceOf(address(this)));
        }else if(tokenList[1].token.balanceOf(address(this)) > 0){
            return (address(tokenList[1].token), tokenList[1].token.balanceOf(address(this)));
        }else{
            return (address(0), 0); // No balance
        }
    }
    
    // Write functions
    
    receive() external payable {
        // We need an anonymous fallback function to accept ether into this contract
    }
    
    function enter() external onlyZSToken {
        deposit(false);
    }
    
    function exit() external onlyZSToken {
        // The ZS token vault is removing all tokens from this strategy
        withdraw(_msgSender(),1,1, false);
    }
    
    function deposit(bool nonContract) public onlyZSToken {
        // Only the ZS token can call the function
        
        // No trading is performed on deposit
        if(nonContract == true){ }
        lastActionBalance = balance();
        require(lastActionBalance <= maxPoolSize,"This strategy has reached its maximum balance");
    }
    
    function simulateExchange(uint256 _inID, uint256 _outID, uint256 _amount, bool doFlashLoan) internal view returns (uint256) {
        if(doFlashLoan == false){
            UniswapRouter router = UniswapRouter(UNISWAP_ROUTER_ADDRESS);
            if(_inID == 0){
                // Selling Fei, apply the fee on the amount in
                _amount = applyFEIFee(_amount);
                if(_amount == 0){return 0;}
            }
            
            if(_inID == _outID) { return 0;}
            if(_inID == WETH_ID || _outID == WETH_ID){
                // We will use Uniswap to buy or sell ETH
                address _inputToken;
                address _outputToken;
                if(_inID == WETH_ID){
                    _inputToken = WETH_ADDRESS;
                    _outputToken = address(tokenList[_outID].token);
                }else{
                    _inputToken = address(tokenList[_inID].token); 
                    _outputToken = WETH_ADDRESS;          
                }
                address[] memory path = new address[](2);
                path[0] = _inputToken;
                path[1] = _outputToken;
                uint256[] memory estimates = router.getAmountsOut(_amount, path);
                _amount = estimates[estimates.length - 1]; // Get output amount
            }else{
                // USDC for FEI or FEI for USDC
                address _inputToken = address(tokenList[_inID].token);
                address _outputToken = address(tokenList[_outID].token);
                address[] memory path = new address[](3);
                path[0] = _inputToken;
                path[1] = WETH_ADDRESS;
                path[2] = _outputToken;
                uint256[] memory estimates = router.getAmountsOut(_amount, path);
                _amount = estimates[estimates.length - 1]; // Get output amount                
            }
            
            if(_outID == 0){
                // Buying Fei, apply the bonus on amount out
                _amount = applyFEIBonus(_amount);
            }
            return _amount;
        }else{
            if(_inID != WETH_ID || _outID != WETH_ID) { return 0; } // Only can use ETH
            // We want to buy from bonding curve and sell to Uniswap for more ETH
            FeiBondingCurve bond = FeiBondingCurve(FEI_BONDING_CURVE);
            if(bond.paused() == true) { return 0; } // Not active
            _amount = bond.getAmountOut(_amount); // Will return FEI back
            _amount = simulateExchange(0, WETH_ID, _amount, false); // Will return ETH back
            return _amount;
        }
    }

    function applyFEIFee(uint256 _amount) internal view returns (uint256) {
        if(incentiveActive == false) { return _amount; }
        FeiIncentiveContract incCon = FeiIncentiveContract(FeiToken(address(tokenList[0].token)).incentiveContract(FEI_LP_CONTRACT));
        (uint256 feiFee,) = incCon.getSellPenalty(_amount);
        if(feiFee >= _amount) { return 0;}
        return _amount.sub(feiFee);
    }
    
    function applyFEIBonus(uint256 _amount) internal view returns (uint256) {
        if(incentiveActive == false) { return _amount; }
        FeiIncentiveContract incCon = FeiIncentiveContract(FeiToken(address(tokenList[0].token)).incentiveContract(FEI_LP_CONTRACT));
        (uint256 feiBonus,) = incCon.getBuyIncentive(_amount);
        return _amount.add(feiBonus);
    }
    
    function exchange(uint256 _inID, uint256 _outID, uint256 _amount, bool doFlashLoan) internal {
        if(doFlashLoan == false){
            UniswapRouter router = UniswapRouter(UNISWAP_ROUTER_ADDRESS);
            if(_inID == _outID) { return;}
            if(_inID == WETH_ID || _outID == WETH_ID){
                // We will use Uniswap to buy or sell ETH
                address _inputToken;
                address _outputToken;
                if(_inID == WETH_ID){
                    _inputToken = WETH_ADDRESS;
                    _outputToken = address(tokenList[_outID].token);
                }else{
                    _inputToken = address(tokenList[_inID].token); 
                    _outputToken = WETH_ADDRESS;          
                }
                address[] memory path = new address[](2);
                path[0] = _inputToken;
                path[1] = _outputToken;
                // Fees / bonuses are applied automatically
                IERC20(_inputToken).safeApprove(UNISWAP_ROUTER_ADDRESS, 0);
                IERC20(_inputToken).safeApprove(UNISWAP_ROUTER_ADDRESS, _amount);
                router.swapExactTokensForTokensSupportingFeeOnTransferTokens(_amount, 1, path, address(this), now.add(60));
                return;
            }else{
                // USDC for FEI or FEI for USDC
                address _inputToken = address(tokenList[_inID].token);
                address _outputToken = address(tokenList[_outID].token);
                address[] memory path = new address[](3);
                path[0] = _inputToken;
                path[1] = WETH_ADDRESS;
                path[2] = _outputToken;
                // Fees / bonuses are applied automatically
                IERC20(_inputToken).safeApprove(UNISWAP_ROUTER_ADDRESS, 0);
                IERC20(_inputToken).safeApprove(UNISWAP_ROUTER_ADDRESS, _amount);
                router.swapExactTokensForTokensSupportingFeeOnTransferTokens(_amount, 1, path, address(this), now.add(60));
                return;              
            }            
        }else{
            if(_inID != WETH_ID || _outID != WETH_ID) { return; } // Only can use ETH
            // We want to buy from bonding curve and sell to Uniswap for more ETH
            FeiBondingCurve bond = FeiBondingCurve(FEI_BONDING_CURVE);
            // We need to convert WETH to ETH as Aave supplies in WETH
            WrappedEther(WETH_ADDRESS).withdraw(_amount);
            uint256 _bal = tokenList[0].token.balanceOf(address(this));
            bond.purchase{value: _amount}(address(this), _amount); // Buy FEI from bonding curve with ETH
            _amount = tokenList[0].token.balanceOf(address(this)).sub(_bal); // Balance gain in FEI
            exchange(0, WETH_ID, _amount, false); // Buy WETH with FEI
            if(address(this).balance > 0){
                // Convert to WETH if any leftover ETH
                WrappedEther(WETH_ADDRESS).deposit{value: address(this).balance}();
            }
            return;      
        }
    }

    function getCheaperToken() internal view returns (uint256) {
        // This will give us the ID of the cheapest token on Uniswap
        // We will estimate the return for trading 1 FEI
        // The higher the return, the lower the price of the other token
        
        uint256 targetID = 0;
        uint256 mainAmount = uint256(1).mul(10**tokenList[0].decimals);
        
        // Estimate sell FEI for USDC, inclusive of FEI sell penalty
        uint256 estimate = 0;
        estimate = simulateExchange(0,1,mainAmount,false);
        estimate = estimate.mul(10**tokenList[0].decimals).div(10**tokenList[1].decimals); // Convert to main decimals
        if(estimate > mainAmount){
            // This token is worth less than the UST on Uniswap
            targetID = 1;
        }
        return targetID;
    }
    
    function estimateSellAtMaximumProfit(uint256 originID, uint256 targetID, uint256 _tokenBalance, bool doFlashLoan) internal view returns (uint256) {
        // This will estimate the amount that can be sold for the maximum profit possible
        // We discover the price then compare it to the actual return
        // The return must be positive to return a positive amount
        
        uint256 originDecimals = 0;
        uint256 targetDecimals = 0;
        if(doFlashLoan == false){
            originDecimals = tokenList[originID].decimals;
            targetDecimals = tokenList[targetID].decimals;
        }else{
            originDecimals = 18;
            targetDecimals = 18;
        }
        
        // Discover the price with near 0 slip
        uint256 _minAmount = _tokenBalance.mul(maxPercentSell.div(1000)).div(DIVISION_FACTOR);
        if(_minAmount == 0){ return 0; } // Nothing to sell, can't calculate
        uint256 _minReturn = _minAmount.mul(10**targetDecimals).div(10**originDecimals); // Convert decimals
        uint256 _return = simulateExchange(originID, targetID, _minAmount, doFlashLoan);
        if(_return <= _minReturn){
            return 0; // We are not going to gain from this trade
        }
        _return = _return.mul(10**originDecimals).div(10**targetDecimals); // Convert to origin decimals
        uint256 _startPrice = _return.mul(1e18).div(_minAmount);
        
        // Now get the price at a higher amount, expected to be lower due to slippage
        uint256 _bigAmount = _tokenBalance.mul(maxPercentSell).div(DIVISION_FACTOR);
        _return = simulateExchange(originID, targetID, _bigAmount, doFlashLoan);
        _return = _return.mul(10**originDecimals).div(10**targetDecimals); // Convert to origin decimals
        uint256 _endPrice = _return.mul(1e18).div(_bigAmount);
        if(_endPrice >= _startPrice){
            // Really good liquidity
            return _bigAmount;
        }
        
        // Else calculate amount at max profit
        uint256 scaleFactor = uint256(1).mul(10**originDecimals);
        uint256 _targetAmount = _startPrice.sub(1e18).mul(scaleFactor).div(_startPrice.sub(_endPrice).mul(scaleFactor).div(_bigAmount.sub(_minAmount))).div(2);
        if(_targetAmount > _bigAmount){
            // Cannot create an estimate larger than what we want to sell
            return _bigAmount;
        }
        return _targetAmount;
    }
    
    // Flash loan functions
    function estimateFlashLoanResult(uint256 _tradeSize, bool _asWETH) internal view returns (uint256) {
        // This will estimate the return of our flash loan minus the fee
        uint256 fee = _tradeSize.mul(90).div(DIVISION_FACTOR); // This is the Aave fee (0.09%)
        uint256 _return = 0;
        _return = simulateExchange(WETH_ID, WETH_ID, _tradeSize, true);
        if(_return > _tradeSize.add(fee)){
            // Positive return on the flash
            _return = _return.sub(fee).sub(_tradeSize); // This is return in WETH gain
            if(_asWETH == false){
                _return = simulateExchange(WETH_ID, 1, _return, false); // Convert return to USDC amount
                _return = _return.mul(1e18).div(10**tokenList[1].decimals); // Normalize return
            }
            return _return;
        }else{
            return 0; // Do not take out a flash loan as not enough gain
        }
    }
    
    function performFlashLoan(uint256 _amount) internal returns (uint256) {
        // Will call Aave
        IERC20 weth = IERC20(WETH_ADDRESS);
        uint256 flashGain = weth.balanceOf(address(this)); // Get base weth amount
        
        flashParams[0] = 1; // Authorize flash loan receiving
        
        // Call the flash loan
        LendingPool lender = LendingPool(LendingPoolAddressesProvider(LENDING_POOL_ADDRESS_PROVIDER).getLendingPool()); // Load the lending pool
        
        address[] memory assets = new address[](1);
        assets[0] = address(WETH_ADDRESS);       
        
        uint256[] memory amounts = new uint256[](1);
        amounts[0] = _amount; // The amount we want to borrow
        
        uint256[] memory modes = new uint256[](1);
        modes[0] = 0; // Revert if we fail to return funds
        
        bytes memory params = "";
        lender.flashLoan(address(this), assets, amounts, modes, address(this), params, 0);
        
        flashParams[0] = 0; // Deactivate flash loan receiving
        uint256 newBal = weth.balanceOf(address(this));
        require(newBal > flashGain, "Flash loan failed to increase balance");
        flashGain = newBal.sub(flashGain);
        uint256 depositorPayout = 0;
        if(balance() > 0){
            // Only pay depositors if there are depositors here
            depositorPayout = flashGain.mul(percentFlashDepositor).div(DIVISION_FACTOR);
            if(depositorPayout > 0){
                // Convert part to USDC
                exchange(WETH_ID, 1, depositorPayout, false);
                flashGain = flashGain.sub(depositorPayout);
            }
        }
        return flashGain;
    }
    
    function executeOperation(
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata premiums,
        address initiator,
        bytes calldata params
    )
        external
        returns (bool)
    {
        require(flashParams[0] == 1, "No flash loan authorized on this contract");
        address lendingPool = LendingPoolAddressesProvider(LENDING_POOL_ADDRESS_PROVIDER).getLendingPool();
        require(_msgSender() == lendingPool, "Not called from Aave");
        require(initiator == address(this), "Not Authorized"); // Prevent other contracts from calling this function
        if(params.length == 0){} // Removes the warning
        flashParams[0] = 0; // Prevent a replay;
        
        exchange(WETH_ID, WETH_ID, amounts[0], true); // Buy WETH from bonding curve and sell on Uniswap
        
        // Authorize Aave to pull funds from this contract
        // Approve the LendingPool contract allowance to *pull* the owed amount
        {
            for(uint256 i = 0; i < assets.length; i++) {
                uint256 amountOwing = amounts[i].add(premiums[i]);
                IERC20(assets[i]).safeApprove(lendingPool, 0);
                IERC20(assets[i]).safeApprove(lendingPool, amountOwing);
            }
        }
        
        return true;
    }
    // ---------------------
    
    function getFastGasPrice() internal view returns (uint256) {
        AggregatorV3Interface gasOracle = AggregatorV3Interface(GAS_ORACLE_ADDRESS);
        ( , int intGasPrice, , , ) = gasOracle.latestRoundData(); // We only want the answer 
        return uint256(intGasPrice);
    }
    
    function checkAndSwapTokens(address _executor, uint256 targetID, bool doFlashLoan) internal {
        lastTradeTime = now;
        
        // Update the Fei Uniswap Oracle
        FeiOracle(FEI_UNISWAP_ORACLE).update();

        uint256 gain = 0;
        if(doFlashLoan == false){
            uint256 length = tokenList.length;
            // Now sell all the other tokens into this token
            uint256 _totalBalance = balance(); // Get the token balance at this contract, should increase
            bool _expectIncrease = false;
            for(uint256 i = 0; i < length; i++){
                if(i != targetID){
                    uint256 sellBalance = 0;
                    uint256 _bal = tokenList[i].token.balanceOf(address(this));
                    uint256 _minTradeTarget = minTradeSplit.mul(10**tokenList[i].decimals);
                    uint256 _maxTradeTarget = maxAmountSell.mul(10**tokenList[i].decimals); // Determine the maximum amount of tokens to sell at once
                    if(_bal <= _minTradeTarget){
                        // If balance is too small,sell all tokens at once
                        sellBalance = _bal;
                    }else{
                        sellBalance = estimateSellAtMaximumProfit(i, targetID, _bal, false);
                    }
                    if(sellBalance > _maxTradeTarget){
                        // If greater than the maximum trade allowed, match it
                        sellBalance = _maxTradeTarget;
                    }
                    if(sellBalance > 0){
                        uint256 minReceiveBalance = sellBalance.mul(10**tokenList[targetID].decimals).div(10**tokenList[i].decimals); // Change to match decimals of destination
                        uint256 estimate = simulateExchange(i, targetID, sellBalance, false);
                        if(estimate > minReceiveBalance){
                            _expectIncrease = true;
                            exchange(i, targetID, sellBalance, false);
                        }                        
                    }
                }
            }
            uint256 _newBalance = balance();
            if(_expectIncrease == true){
                // There may be rare scenarios where we don't gain any by calling this function
                require(_newBalance > _totalBalance, "Failed to gain in balance from selling tokens");
            }
            gain = _newBalance.sub(_totalBalance);
        }else{
            // We want to perform a flash loan
            FeiOracle(FEI_BONDING_ORACLE).update();
            uint256 tradeSize = estimateSellAtMaximumProfit(WETH_ID, WETH_ID, MAX_ETH_BUY, true); // This will return our ideal trade size
            if(tradeSize > 0){
                performFlashLoan(tradeSize); // Our weth balance should increase, otherwise this will revert
            }            
        }

        IERC20 weth = IERC20(WETH_ADDRESS);
        uint256 _wethBalance = weth.balanceOf(address(this));
        if(gain >= minGain){
            // Buy WETH from Uniswap with tokens
            uint256 sellBalance = gain.mul(10**tokenList[targetID].decimals).div(1e18); // Convert to target decimals
            sellBalance = sellBalance.mul(uint256(100000).sub(percentDepositor)).div(DIVISION_FACTOR);
            if(sellBalance <= tokenList[targetID].token.balanceOf(address(this))){
                // Sell some of our gained token for WETH
                exchange(targetID, WETH_ID, sellBalance, false);
                _wethBalance = weth.balanceOf(address(this));
            }
        }
        if(_wethBalance > 0){
            // Split the rest between the stakers and such
            // This is pure profit, figure out allocation
            // Split the amount sent to the treasury, stakers and executor if one exists
            if(_executor != address(0)){
                // Executors will get a gas reimbursement in WETH and a percent of the remaining
                uint256 maxGasFee = getFastGasPrice().mul(gasStipend); // This is gas stipend in wei
                uint256 gasFee = tx.gasprice.mul(gasStipend); // This is gas fee requested
                if(gasFee > maxGasFee){
                    gasFee = maxGasFee; // Gas fee cannot be greater than the maximum
                }
                uint256 executorAmount = gasFee;
                if(gasFee >= _wethBalance.mul(maxPercentStipend).div(DIVISION_FACTOR)){
                    executorAmount = _wethBalance.mul(maxPercentStipend).div(DIVISION_FACTOR); // The executor will get the entire amount up to point
                }else{
                    // Add the executor percent on top of gas fee
                    executorAmount = _wethBalance.sub(gasFee).mul(percentExecutor).div(DIVISION_FACTOR).add(gasFee);
                }
                if(executorAmount > 0){
                    weth.safeTransfer(_executor, executorAmount);
                    _wethBalance = weth.balanceOf(address(this)); // Recalculate WETH in contract           
                }
            }
            if(_wethBalance > 0){
                uint256 stakersAmount = _wethBalance.mul(percentStakers).div(DIVISION_FACTOR);
                uint256 treasuryAmount = _wethBalance.sub(stakersAmount);
                if(treasuryAmount > 0){
                    weth.safeTransfer(treasuryAddress, treasuryAmount);
                }
                if(stakersAmount > 0){
                    if(stakingAddress != address(0)){
                        weth.safeTransfer(stakingAddress, stakersAmount);
                        StabilizeStakingPool(stakingAddress).notifyRewardAmount(stakersAmount);                                
                    }else{
                        // No staking pool selected, just send to the treasury
                        weth.safeTransfer(treasuryAddress, stakersAmount);
                    }
                }
            }                
        }
    }
    
    function expectedProfit(bool inWETHForExecutor, bool checkFlashLoan) external view returns (uint256, uint256) {
        // This view will return the amount of gain a forced swap or loan will make on next call
        
        // Now find our target token to sell into
        uint256 targetID = 0;
        uint256 _normalizedGain = 0;
        if(checkFlashLoan == false){
            targetID = getCheaperToken();
            uint256 length = tokenList.length;
            for(uint256 i = 0; i < length; i++){
                if(i != targetID){
                    uint256 sellBalance = 0;
                    uint256 _bal = tokenList[i].token.balanceOf(address(this));
                    uint256 _minTradeTarget = minTradeSplit.mul(10**tokenList[i].decimals);
                    uint256 _maxTradeTarget = maxAmountSell.mul(10**tokenList[i].decimals); // Determine the maximum amount of tokens to sell at once
                    if(_bal <= _minTradeTarget){
                        // If balance is too small,sell all tokens at once
                        sellBalance = _bal;
                    }else{
                        sellBalance = estimateSellAtMaximumProfit(i, targetID, _bal, false);
                    }
                    if(sellBalance > _maxTradeTarget){
                        // If greater than the maximum trade allowed, match it
                        sellBalance = _maxTradeTarget;
                    }
                    if(sellBalance > 0){
                        uint256 minReceiveBalance = sellBalance.mul(10**tokenList[targetID].decimals).div(10**tokenList[i].decimals); // Change to match decimals of destination
                        uint256 estimate = simulateExchange(i, targetID, sellBalance, false);
                        if(estimate > minReceiveBalance){
                            uint256 _gain = estimate.sub(minReceiveBalance).mul(1e18).div(10**tokenList[targetID].decimals); // Normalized gain
                            _normalizedGain = _normalizedGain.add(_gain);
                        }                        
                    }
                }
            }
        }else{
            // We want to see if flash loans are profitable
            uint256 tradeSize = estimateSellAtMaximumProfit(WETH_ID, WETH_ID, MAX_ETH_BUY, true); // This will return our ideal trade size
            if(tradeSize > 0){
                _normalizedGain = estimateFlashLoanResult(tradeSize, inWETHForExecutor);
            }
        }

        if(inWETHForExecutor == false){
            return (_normalizedGain, 0); // This will be in stablecoins regardless of whether flash loan or not
        }else{
            if(_normalizedGain == 0){
                return (0, 0);
            }
            // Calculate how much WETH the executor would make as profit
            uint256 estimate = 0;
            if(checkFlashLoan == false){
                if(_normalizedGain > 0){
                    uint256 sellBalance = _normalizedGain.mul(10**tokenList[targetID].decimals).div(1e18); // Convert to target decimals
                    sellBalance = sellBalance.mul(uint256(100000).sub(percentDepositor)).div(DIVISION_FACTOR);
                    // Estimate output
                    estimate = simulateExchange(targetID, WETH_ID, sellBalance, false);           
                }
            }else{
                // If we are checking for flash loan, normalized gain is WETH profit from loan, allocate percent for depositors
                if(balance() > 0){
                    estimate = _normalizedGain.mul(uint256(100000).sub(percentFlashDepositor)).div(DIVISION_FACTOR);
                }else{
                    // If there is no balance here, allocate all to stakers, treasury and executors only
                    estimate = _normalizedGain;
                }
            }
            // Now calculate the amount going to the executor
            uint256 gasFee = getFastGasPrice().mul(gasStipend); // This is gas stipend in wei
            if(gasFee >= estimate.mul(maxPercentStipend).div(DIVISION_FACTOR)){ // Max percent of total
                return (estimate.mul(maxPercentStipend).div(DIVISION_FACTOR), targetID); // The executor will get max percent of total
            }else{
                estimate = estimate.sub(gasFee); // Subtract fee from remaining balance
                return (estimate.mul(percentExecutor).div(DIVISION_FACTOR).add(gasFee), targetID); // Executor amount with fee added
            }
        }  
    }
    
    function withdraw(address _depositor, uint256 _share, uint256 _total, bool nonContract) public onlyZSToken returns (uint256) {
        require(balance() > 0, "There are no tokens in this strategy");
        if(nonContract == true){
            if( _share > _total.mul(percentTradeTrigger).div(DIVISION_FACTOR)){
                uint256 buyID = getCheaperToken();
                checkAndSwapTokens(address(0), buyID, false);
            }
        }
        
        uint256 withdrawAmount = 0;
        uint256 _balance = balance();
        if(_share < _total){
            uint256 _myBalance = _balance.mul(_share).div(_total);
            withdrawPerOrder(_depositor, _myBalance, false); // This will withdraw based on token price
            withdrawAmount = _myBalance;
        }else{
            // We are all shares, transfer all
            withdrawPerOrder(_depositor, _balance, true);
            withdrawAmount = _balance;
        }       
        lastActionBalance = balance();
        
        return withdrawAmount;
    }
    
    // This will withdraw the tokens from the contract based on their order
    function withdrawPerOrder(address _receiver, uint256 _withdrawAmount, bool _takeAll) internal {
        uint256 length = tokenList.length;
        if(_takeAll == true){
            // Send the entire balance
            for(uint256 i = 0; i < length; i++){
                uint256 _bal = tokenList[i].token.balanceOf(address(this));
                if(_bal > 0){
                    tokenList[i].token.safeTransfer(_receiver, _bal);
                }
            }
            return;
        }
        
        for(uint256 i = 0; i < length; i++){
            // Determine the balance left
            uint256 _normalizedBalance = tokenList[i].token.balanceOf(address(this)).mul(1e18).div(10**tokenList[i].decimals);
            if(_normalizedBalance <= _withdrawAmount){
                // Withdraw the entire balance of this token
                if(_normalizedBalance > 0){
                    _withdrawAmount = _withdrawAmount.sub(_normalizedBalance);
                    tokenList[i].token.safeTransfer(_receiver, tokenList[i].token.balanceOf(address(this)));                    
                }
            }else{
                // Withdraw a partial amount of this token
                if(_withdrawAmount > 0){
                    // Convert the withdraw amount to the token's decimal amount
                    uint256 _balance = _withdrawAmount.mul(10**tokenList[i].decimals).div(1e18);
                    _withdrawAmount = 0;
                    tokenList[i].token.safeTransfer(_receiver, _balance);
                }
                break; // Nothing more to withdraw
            }
        }
    }
    
    function executorSwapTokens(address _executor, uint256 _minSecSinceLastTrade, uint256 _buyID, bool doFlash) external {
        // Function designed to promote trading with incentive. Users get 20% of WETH from profitable trades
        require(now.sub(lastTradeTime) > _minSecSinceLastTrade, "The last trade was too recent");
        require(_msgSender() == tx.origin, "Contracts cannot interact with this function");
        checkAndSwapTokens(_executor, _buyID, doFlash);
    }
    
    // Governance functions
    function governanceSwapTokens(uint256 _buyID, bool doFlash) external onlyGovernance {
        // This is function that force trade tokens at anytime. It can only be called by governance
        checkAndSwapTokens(governance(), _buyID, doFlash);
    }

    // Change the trading conditions used by the strategy without timelock
    // --------------------
    function changeTradingConditions(uint256 _pTradeTrigger,
                                    uint256 _minSplit,
                                    uint256 _pSellPercent, 
                                    uint256 _maxSell,
                                    uint256 _pStipend,
                                    uint256 _maxStipend,
                                    bool _changeIncentive) external onlyGovernance {
        // Changes a lot of trading parameters in one call
        require(_pTradeTrigger <= 100000 && _pSellPercent <= 100000 && _pStipend <= 100000,"Percent cannot be greater than 100%");
        percentTradeTrigger = _pTradeTrigger;
        minTradeSplit = _minSplit;
        maxPercentSell = _pSellPercent;
        maxAmountSell = _maxSell;
        maxPercentStipend = _pStipend;
        gasStipend = _maxStipend;
        incentiveActive = _changeIncentive;
    }
    // --------------------
    
    // Timelock variables
    
    uint256 private _timelockStart; // The start of the timelock to change governance variables
    uint256 private _timelockType; // The function that needs to be changed
    uint256 constant TIMELOCK_DURATION = 86400; // Timelock is 24 hours
    
    // Reusable timelock variables
    address private _timelock_address;
    uint256[5] private _timelock_data;
    
    modifier timelockConditionsMet(uint256 _type) {
        require(_timelockType == _type, "Timelock not acquired for this function");
        _timelockType = 0; // Reset the type once the timelock is used
        if(balance() > 0){ // Timelock only applies when balance exists
            require(now >= _timelockStart + TIMELOCK_DURATION, "Timelock time not met");
        }
        _;
    }
    
    // Change the owner of the token contract
    // --------------------
    function startGovernanceChange(address _address) external onlyGovernance {
        _timelockStart = now;
        _timelockType = 1;
        _timelock_address = _address;       
    }
    
    function finishGovernanceChange() external onlyGovernance timelockConditionsMet(1) {
        transferGovernance(_timelock_address);
    }
    // --------------------
    
    // Change the treasury address
    // --------------------
    function startChangeTreasury(address _address) external onlyGovernance {
        _timelockStart = now;
        _timelockType = 2;
        _timelock_address = _address;
    }
    
    function finishChangeTreasury() external onlyGovernance timelockConditionsMet(2) {
        treasuryAddress = _timelock_address;
    }
    // --------------------
    
    // Change the staking address
    // --------------------
    function startChangeStakingPool(address _address) external onlyGovernance {
        _timelockStart = now;
        _timelockType = 3;
        _timelock_address = _address;
    }
    
    function finishChangeStakingPool() external onlyGovernance timelockConditionsMet(3) {
        stakingAddress = _timelock_address;
    }
    // --------------------
    
    // Change the zsToken address
    // --------------------
    function startChangeZSToken(address _address) external onlyGovernance {
        _timelockStart = now;
        _timelockType = 4;
        _timelock_address = _address;
    }
    
    function finishChangeZSToken() external onlyGovernance timelockConditionsMet(4) {
        zsTokenAddress = _timelock_address;
    }
    // --------------------
    
    // Change the strategy allocations between the parties
    // --------------------
    
    function startChangeStrategyAllocations(uint256 _pDepositors, uint256 _pFDepositors, 
                                            uint256 _pExecutor, uint256 _pStakers, uint256 _maxPool) external onlyGovernance {
        // Changes strategy allocations in one call
        require(_pDepositors <= 100000 && _pFDepositors <= 100000 && _pExecutor <= 100000 && _pStakers <= 100000,"Percent cannot be greater than 100%");
        _timelockStart = now;
        _timelockType = 5;
        _timelock_data[0] = _pDepositors;
        _timelock_data[1] = _pExecutor;
        _timelock_data[2] = _pStakers;
        _timelock_data[3] = _maxPool;
        _timelock_data[4] = _pFDepositors;
    }
    
    function finishChangeStrategyAllocations() external onlyGovernance timelockConditionsMet(5) {
        percentDepositor = _timelock_data[0];
        percentExecutor = _timelock_data[1];
        percentStakers = _timelock_data[2];
        maxPoolSize = _timelock_data[3];
        percentFlashDepositor = _timelock_data[4];
    }
    // --------------------
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):