abstract contract VoxStrategyBase {
    using SafeERC20 for IERC20;
    using Address for address;
    using SafeMath for uint256;

    // Perfomance fees
    uint256 public treasuryFee = 1000;
    uint256 public constant treasuryFeeMax = 2000;
    uint256 public constant treasuryFeeBase = 10000;

    uint256 public strategyFee = 500;
    uint256 public constant strategyFeeMax = 5000;
    uint256 public constant strategyFeeBase = 10000;

    uint256 public developerFee = 250;
    uint256 public constant developerFeeMax = 500;
    uint256 public constant developerFeeBase = 10000;

    // Withdrawal fee
    uint256 public withdrawalFee = 15;
    uint256 public constant withdrawalFeeMax = 15;
    uint256 public constant withdrawalFeeBase = 10000;

    // Tokens
    address public underlying;
    address public constant weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address public constant wbtc = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599;

    // User accounts
    address public governance;
    address public strategist;
    address public treasury;
    address public devfund;
    address public timelock;
    address public vault;

    // Dex
    address public univ2Router2 = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;

    constructor(
        address _underlying,
        address _governance,
        address _strategist,
        address _treasury,
        address _devfund,
        address _timelock,
        address _vault
    ) public {
        require(_underlying != address(0));
        require(_governance != address(0));
        require(_strategist != address(0));
        require(_treasury != address(0));
        require(_devfund != address(0));
        require(_timelock != address(0));
        require(_vault != address(0));

        underlying = _underlying;
        governance = _governance;
        strategist = _strategist;
        treasury = _treasury;
        devfund = _devfund;
        timelock = _timelock;
        vault = _vault;
    }

    // **** Modifiers **** //

    modifier restricted {
        require(
            msg.sender == tx.origin ||
                msg.sender == governance ||
                msg.sender == strategist
        );
        _;
    }

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

    function balanceOfUnderlying() public view returns (uint256) {
        return IERC20(underlying).balanceOf(address(this));
    }

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

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

    function getName() external virtual pure returns (string memory);

    // **** Setters **** //

    function setTreasuryFee(uint256 _treasuryFee) external {
        require(msg.sender == timelock, "!timelock");
        treasuryFee = _treasuryFee;
    }

    function setStrategyFee(uint256 _strategyFee) external {
        require(msg.sender == timelock, "!timelock");
        strategyFee = _strategyFee;
    }

    function setDeveloperFee(uint256 _developerFee) external {
        require(msg.sender == timelock, "!timelock");
        developerFee = _developerFee;
    }

    function setWithdrawalFee(uint256 _withdrawalFee) external {
        require(msg.sender == timelock, "!timelock");
        withdrawalFee = _withdrawalFee;
    }

    function setStrategist(address _strategist) external {
        require(msg.sender == governance, "!governance");
        strategist = _strategist;
    }

    function setGovernance(address _governance) external {
        require(msg.sender == governance, "!governance");
        governance = _governance;
    }

    function setTreasury(address _treasury) external {
        require(msg.sender == governance, "!governance");
        treasury = _treasury;
    }

    function setDevFund(address _devfund) external {
        require(msg.sender == devfund, "!devfund");
        devfund = _devfund;
    }

    function setTimelock(address _timelock) external {
        require(msg.sender == timelock, "!timelock");
        timelock = _timelock;
    }

    function setVault(address _vault) external {
        require(msg.sender == timelock, "!timelock");
        vault = _vault;
    }

    // **** State mutations **** //
    function deposit() public virtual;

    // Governance only function to salvage non-underlying assets
    function salvage(IERC20 _asset) external returns (uint256 balance) {
        require(msg.sender == governance, "!governance");
        require(underlying != address(_asset), "underlying");
        balance = _asset.balanceOf(address(this));
        _asset.safeTransfer(treasury, balance);
    }

    // Withdraw partial funds, normally used with a vault withdrawal
    function withdraw(uint256 _amount) external {
        require(msg.sender == vault, "!vault");
        uint256 _balance = IERC20(underlying).balanceOf(address(this));
        if (_balance < _amount) {
            _amount = _withdrawSome(_amount.sub(_balance));
            _amount = _amount.add(_balance);
        }

        uint256 _withdrawalFee = _amount.mul(withdrawalFee).div(withdrawalFeeBase);
        IERC20(underlying).safeTransfer(treasury, _withdrawalFee);

        require(vault != address(0), "!vault"); // additional protection so we don't burn the funds
        IERC20(underlying).safeTransfer(vault, _amount.sub(_withdrawalFee));
    }

    // Withdraw all funds, normally used when migrating strategies
    function withdrawAll() external restricted returns (uint256 balance) {
        _withdrawAll();
        balance = IERC20(underlying).balanceOf(address(this));

        require(vault != address(0), "!vault"); // additional protection so we don't burn the funds
        IERC20(underlying).safeTransfer(vault, balance);
    }

    function _withdrawAll() internal {
        _withdrawSome(balanceOfPool());
    }

    function _withdrawSome(uint256 _amount) internal virtual returns (uint256);

    function harvest() public virtual;

    function _distributeAndDeposit() internal {
        uint256 _underlying = IERC20(underlying).balanceOf(address(this));

        if (_underlying > 0) {
            // Treasury fees
            IERC20(underlying).safeTransfer(
                treasury,
                _underlying.mul(treasuryFee).div(treasuryFeeBase)
            );

            // Strategy fee
            IERC20(underlying).safeTransfer(
                strategist,
                _underlying.mul(strategyFee).div(strategyFeeBase)
            );

            // Developer fee
            IERC20(underlying).safeTransfer(
                devfund,
                _underlying.mul(developerFee).div(developerFeeBase)
            );

            deposit();
        }
    }

    // **** Emergency functions ****

    function execute(address _target, bytes memory _data)
        public
        payable
        returns (bytes memory response)
    {
        require(msg.sender == timelock, "!timelock");
        require(_target != address(0), "!target");

        // call contract in current context
        assembly {
            let succeeded := delegatecall(
                sub(gas(), 5000),
                _target,
                add(_data, 0x20),
                mload(_data),
                0,
                0
            )
            let size := returndatasize()

            response := mload(0x40)
            mstore(
                0x40,
                add(response, and(add(add(size, 0x20), 0x1f), not(0x1f)))
            )
            mstore(response, size)
            returndatacopy(add(response, 0x20), 0, size)

            switch iszero(succeeded)
                case 1 {
                    // throw if delegatecall failed
                    revert(add(response, 0x20), size)
                }
        }
    }

    // **** Internal functions ****
    function _swapUniswap(
        address _from,
        address _to,
        uint256 _amount
    ) internal {
        require(_to != address(0));

        // Swap with uniswap
        IERC20(_from).safeApprove(univ2Router2, 0);
        IERC20(_from).safeApprove(univ2Router2, _amount);

        address[] memory path;

        if (_from == weth || _to == weth) {
            path = new address[](2);
            path[0] = _from;
            path[1] = _to;
        } else {
            path = new address[](3);
            path[0] = _from;
            path[1] = weth;
            path[2] = _to;
        }

        UniswapRouterV2(univ2Router2).swapExactTokensForTokens(
            _amount,
            0,
            path,
            address(this),
            now.add(60)
        );
    }
}
// Base of all strategy contracts
function _swapUniswap(
    address _from,
    address _to,
    uint256 _amount
) internal {
    require(_to != address(0));

    // Swap with uniswap
    IERC20(_from).safeApprove(univ2Router2, 0);
    IERC20(_from).safeApprove(univ2Router2, _amount);

    address[] memory path;

    if (_from == weth || _to == weth) {
        path = new address[](2);
        path[0] = _from;
        path[1] = _to;
    } else {
        path = new address[](3);
        path[0] = _from;
        path[1] = weth;
        path[2] = _to;
    }

    UniswapRouterV2(univ2Router2).swapExactTokensForTokens(
        _amount,
        0,
        path,
        address(this),
        now.add(60)
    );
}

// **** Internal functions ****