// NOTE: I recommend anyone wishing to use this generic strategy to first test amend 'token_1'
//       (and all other token_1 fixtures) in the tests for their desired 'want,'
//       and making sure that all tests pass.
contract Strategy is BaseStrategy {
    using SafeERC20 for IERC20;
    using Address for address;
    using SafeMath for uint256;

    event Cloned(address indexed clone);

    bool public isOriginal = true;

    ILiquidityPool public tokemakLiquidityPool;
    IERC20 public tAsset;
    string internal strategyName;

    IManager internal constant tokemakManager =
        IManager(0xA86e412109f77c45a3BC1c5870b880492Fb86A14);

    IERC20 internal constant tokeToken =
        IERC20(0x2e9d63788249371f1DFC918a52f8d799F4a38C94);

    IRewards internal constant tokemakRewards =
        IRewards(0x79dD22579112d8a5F7347c5ED7E609e60da713C5);

    address public tradeFactory = address(0);

    // ********** SETUP & CLONING **********

    constructor(
        address _vault,
        address _tokemakLiquidityPool,
        string memory _strategyName
    ) public BaseStrategy(_vault) {
        _initializeStrategy(_tokemakLiquidityPool, _strategyName);
    }

    function _initializeStrategy(
        address _tokemakLiquidityPool,
        string memory _strategyName
    ) internal {
        ILiquidityPool _liquidityPool = ILiquidityPool(_tokemakLiquidityPool);
        require(_liquidityPool.underlyer() == address(want), "!pool_matches");

        tokemakLiquidityPool = _liquidityPool;
        tAsset = IERC20(_tokemakLiquidityPool);
        strategyName = _strategyName;
    }

    // Cloning & initialization code adapted from https://github.com/yearn/yearn-vaults/blob/43a0673ab89742388369bc0c9d1f321aa7ea73f6/contracts/BaseStrategy.sol#L866

    function initialize(
        address _vault,
        address _strategist,
        address _rewards,
        address _keeper,
        address _tokemakLiquidityPool,
        string memory _strategyName
    ) external virtual {
        _initialize(_vault, _strategist, _rewards, _keeper);
        _initializeStrategy(_tokemakLiquidityPool, _strategyName);
    }

    function clone(
        address _vault,
        address _tokemakLiquidityPool,
        string memory _strategyName
    ) external returns (address) {
        return
            this.clone(
                _vault,
                msg.sender,
                msg.sender,
                msg.sender,
                _tokemakLiquidityPool,
                _strategyName
            );
    }

    function clone(
        address _vault,
        address _strategist,
        address _rewards,
        address _keeper,
        address _tokemakLiquidityPool,
        string memory _strategyName
    ) external returns (address newStrategy) {
        require(isOriginal, "!clone");
        bytes20 addressBytes = bytes20(address(this));

        assembly {
            // EIP-1167 bytecode
            let clone_code := mload(0x40)
            mstore(
                clone_code,
                0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000
            )
            mstore(add(clone_code, 0x14), addressBytes)
            mstore(
                add(clone_code, 0x28),
                0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000
            )
            newStrategy := create(0, clone_code, 0x37)
        }

        Strategy(newStrategy).initialize(
            _vault,
            _strategist,
            _rewards,
            _keeper,
            _tokemakLiquidityPool,
            _strategyName
        );

        emit Cloned(newStrategy);
    }

    // ********** CORE **********

    function name() external view override returns (string memory) {
        return strategyName;
    }

    function estimatedTotalAssets() public view override returns (uint256) {
        // 1 tWant = 1 want *guaranteed*
        // Tokemak team confirming that a tAsset will have the same decimals as the underlying asset
        return tAssetBalance().add(wantBalance());
    }

    function prepareReturn(uint256 _debtOutstanding)
        internal
        override
        returns (
            uint256 _profit,
            uint256 _loss,
            uint256 _debtPayment
        )
    {
        require(tradeFactory != address(0), "Trade factory must be set.");
        // How much do we owe to the vault?
        uint256 totalDebt = vault.strategies(address(this)).totalDebt;

        uint256 totalAssets = estimatedTotalAssets();

        if (totalAssets >= totalDebt) {
            _profit = totalAssets.sub(totalDebt);
        } else {
            _loss = totalDebt.sub(totalAssets);
        }

        (uint256 _liquidatedAmount, ) =
            liquidatePosition(_debtOutstanding.add(_profit));

        _debtPayment = Math.min(
            _debtOutstanding,
            _liquidatedAmount.sub(_profit)
        ); // _liquidatedAmount will always be greater or equal to profit, since profit needs to be in ETH, so this will never cause sub overflow
    }

    function adjustPosition(uint256 _debtOutstanding) internal override {
        uint256 wantBalance = wantBalance();

        if (wantBalance > _debtOutstanding) {
            uint256 _amountToInvest = wantBalance.sub(_debtOutstanding);

            _checkAllowance(
                address(tokemakLiquidityPool),
                address(want),
                _amountToInvest
            );

            try tokemakLiquidityPool.deposit(_amountToInvest) {} catch {}
        }
    }

    function liquidatePosition(uint256 _amountNeeded)
        internal
        override
        returns (uint256 _liquidatedAmount, uint256 _loss)
    {
        // NOTE: Maintain invariant `_liquidatedAmount + _loss <= _amountNeeded`

        uint256 _existingLiquidAssets = wantBalance();

        if (_existingLiquidAssets >= _amountNeeded) {
            return (_amountNeeded, 0);
        }

        uint256 _amountToWithdraw = _amountNeeded.sub(_existingLiquidAssets);

        (
            uint256 _cycleIndexWhenWithdrawable,
            uint256 _requestedWithdrawAmount
        ) = tokemakLiquidityPool.requestedWithdrawals(address(this));

        if (
            _requestedWithdrawAmount == 0 ||
            _cycleIndexWhenWithdrawable > tokemakManager.getCurrentCycleIndex()
        ) {
            tokemakLiquidityPool.requestWithdrawal(_amountToWithdraw);

            return (_existingLiquidAssets, 0);
        }

        // Cannot withdraw more than withdrawable
        _amountToWithdraw = Math.min(
            _amountToWithdraw,
            _requestedWithdrawAmount
        );

        try tokemakLiquidityPool.withdraw(_amountToWithdraw) {
            uint256 _newLiquidAssets = wantBalance();

            _liquidatedAmount = Math.min(_newLiquidAssets, _amountNeeded);

            if (_liquidatedAmount < _amountNeeded) {
                // If we couldn't liquidate the full amount needed, start the withdrawal process for the remaining
                tokemakLiquidityPool.requestWithdrawal(
                    _amountNeeded.sub(_liquidatedAmount)
                );
            }
        } catch {
            return (_existingLiquidAssets, 0);
        }
    }

    function liquidateAllPositions()
        internal
        override
        returns (uint256 _amountFreed)
    {
        (_amountFreed, ) = liquidatePosition(estimatedTotalAssets());
    }

    // NOTE: Can override `tendTrigger` and `harvestTrigger` if necessary

    function prepareMigration(address _newStrategy) internal override {
        uint256 _tAssetToTransfer = tAssetBalance();
        uint256 _tokeTokenToTransfer = tokeTokenBalance();

        tAsset.safeTransfer(_newStrategy, _tAssetToTransfer);
        tokeToken.safeTransfer(_newStrategy, _tokeTokenToTransfer);
    }

    // Override this to add all tokens/tokenized positions this contract manages
    // on a *persistent* basis (e.g. not just for swapping back to want ephemerally)
    // NOTE: Do *not* include `want`, already included in `sweep` below
    //
    // Example:
    //
    //    function protectedTokens() internal override view returns (address[] memory) {
    //      address[] memory protected = new address[](3);
    //      protected[0] = tokenA;
    //      protected[1] = tokenB;
    //      protected[2] = tokenC;
    //      return protected;
    //    }
    function protectedTokens()
        internal
        view
        override
        returns (address[] memory)
    {}

    /**
     * @notice
     *  Provide an accurate conversion from `_amtInWei` (denominated in wei)
     *  to `want` (using the native decimal characteristics of `want`).
     * @dev
     *  Care must be taken when working with decimals to assure that the conversion
     *  is compatible. As an example:
     *
     *      given 1e17 wei (0.1 ETH) as input, and want is USDC (6 decimals),
     *      with USDC/ETH = 1800, this should give back 1800000000 (180 USDC)
     *
     * @param _amtInWei The amount (in wei/1e-18 ETH) to convert to `want`
     * @return The amount in `want` of `_amtInEth` converted to `want`
     **/
    function ethToWant(uint256 _amtInWei)
        public
        view
        virtual
        override
        returns (uint256)
    {
        // TODO create an accurate price oracle
        return _amtInWei;
    }

    // ----------------- TOKEMAK OPERATIONS ---------

    function requestWithdrawal(uint256 amount) external onlyVaultManagers {
        tokemakLiquidityPool.requestWithdrawal(amount);
    }

    function claimRewards(
        IRewards.Recipient calldata _recipient,
        uint8 _v,
        bytes32 _r,
        bytes32 _s // bytes calldata signature
    ) external onlyKeepers {
        require(
            _recipient.wallet == address(this),
            "Recipient wallet must be strategy"
        );
        tokemakRewards.claim(_recipient, _v, _r, _s);
    }

    // ----------------- YSWAPS FUNCTIONS ---------------------

    function setTradeFactory(address _tradeFactory) external onlyGovernance {
        if (tradeFactory != address(0)) {
            _removeTradeFactoryPermissions();
        }

        // approve and set up trade factory
        tokeToken.safeApprove(_tradeFactory, type(uint256).max);
        ITradeFactory tf = ITradeFactory(_tradeFactory);
        tf.enable(address(tokeToken), address(want));
        tradeFactory = _tradeFactory;
    }

    function removeTradeFactoryPermissions() external onlyEmergencyAuthorized {
        _removeTradeFactoryPermissions();
    }

    function _removeTradeFactoryPermissions() internal {
        tokeToken.safeApprove(tradeFactory, 0);
        tradeFactory = address(0);
    }

    // ----------------- SUPPORT & UTILITY FUNCTIONS ----------

    function tokeTokenBalance() public view returns (uint256) {
        return tokeToken.balanceOf(address(this));
    }

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

    function tAssetBalance() public view returns (uint256) {
        return tAsset.balanceOf(address(this));
    }

    // _checkAllowance adapted from https://github.com/therealmonoloco/liquity-stability-pool-strategy/blob/1fb0b00d24e0f5621f1e57def98c26900d551089/contracts/Strategy.sol#L316

    function _checkAllowance(
        address _contract,
        address _token,
        uint256 _amount
    ) internal {
        if (IERC20(_token).allowance(address(this), _contract) < _amount) {
            IERC20(_token).safeApprove(_contract, 0);
            IERC20(_token).safeApprove(_contract, _amount);
        }
    }
}
[{"inputs":[{"internalType":"address","name":"_vault","type":"address"},{"internalType":"address","name":"_tokemakLiquidityPool","type":"address"},{"internalType":"string","name":"_strategyName","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"clone","type":"address"}],"name":"Cloned","type":"event"},{"anonymous":false,"inputs":[],"name":"EmergencyExitEnabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"profit","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"loss","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"debtPayment","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"debtOutstanding","type":"uint256"}],"name":"Harvested","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"debtThreshold","type":"uint256"}],"name":"UpdatedDebtThreshold","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newKeeper","type":"address"}],"name":"UpdatedKeeper","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"delay","type":"uint256"}],"name":"UpdatedMaxReportDelay","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"metadataURI","type":"string"}],"name":"UpdatedMetadataURI","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"delay","type":"uint256"}],"name":"UpdatedMinReportDelay","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"profitFactor","type":"uint256"}],"name":"UpdatedProfitFactor","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"rewards","type":"address"}],"name":"UpdatedRewards","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newStrategist","type":"address"}],"name":"UpdatedStrategist","type":"event"},{"inputs":[],"name":"apiVersion","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"uint256","name":"cycle","type":"uint256"},{"internalType":"address","name":"wallet","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct IRewards.Recipient","name":"_recipient","type":"tuple"},{"internalType":"uint8","name":"_v","type":"uint8"},{"internalType":"bytes32","name":"_r","type":"bytes32"},{"internalType":"bytes32","name":"_s","type":"bytes32"}],"name":"claimRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_vault","type":"address"},{"internalType":"address","name":"_strategist","type":"address"},{"internalType":"address","name":"_rewards","type":"address"},{"internalType":"address","name":"_keeper","type":"address"},{"internalType":"address","name":"_tokemakLiquidityPool","type":"address"},{"internalType":"string","name":"_strategyName","type":"string"}],"name":"clone","outputs":[{"internalType":"address","name":"newStrategy","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_vault","type":"address"},{"internalType":"address","name":"_tokemakLiquidityPool","type":"address"},{"internalType":"string","name":"_strategyName","type":"string"}],"name":"clone","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"debtThreshold","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"delegatedAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"doHealthCheck","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"emergencyExit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"estimatedTotalAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amtInWei","type":"uint256"}],"name":"ethToWant","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"harvest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"callCostInWei","type":"uint256"}],"name":"harvestTrigger","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"healthCheck","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_vault","type":"address"},{"internalType":"address","name":"_strategist","type":"address"},{"internalType":"address","name":"_rewards","type":"address"},{"internalType":"address","name":"_keeper","type":"address"},{"internalType":"address","name":"_tokemakLiquidityPool","type":"address"},{"internalType":"string","name":"_strategyName","type":"string"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isOriginal","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"keeper","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxReportDelay","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"metadataURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_newStrategy","type":"address"}],"name":"migrate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"minReportDelay","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"profitFactor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"removeTradeFactoryPermissions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"requestWithdrawal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rewards","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_debtThreshold","type":"uint256"}],"name":"setDebtThreshold","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"_doHealthCheck","type":"bool"}],"name":"setDoHealthCheck","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"setEmergencyExit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_healthCheck","type":"address"}],"name":"setHealthCheck","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_keeper","type":"address"}],"name":"setKeeper","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_delay","type":"uint256"}],"name":"setMaxReportDelay","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_metadataURI","type":"string"}],"name":"setMetadataURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_delay","type":"uint256"}],"name":"setMinReportDelay","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_profitFactor","type":"uint256"}],"name":"setProfitFactor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_rewards","type":"address"}],"name":"setRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_strategist","type":"address"}],"name":"setStrategist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_tradeFactory","type":"address"}],"name":"setTradeFactory","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"strategist","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"sweep","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"tAsset","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tAssetBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tend","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"callCostInWei","type":"uint256"}],"name":"tendTrigger","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokeTokenBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokemakLiquidityPool","outputs":[{"internalType":"contract ILiquidityPool","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tradeFactory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"vault","outputs":[{"internalType":"contract VaultAPI","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"want","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"wantBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amountNeeded","type":"uint256"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"_loss","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}]