// File: contracts/PowerFarms/wstETHPowerFarm/wstETHManager.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.23;

/**
 * @author Christoph Krpoun
 * @author René Hochmuth
 * @author Vitally Marinchenko
 */

import "./wstETHFarm.sol";
import "../../OwnableMaster.sol";
import "../PowerFarmNFTs/MinterReserver.sol";

/**
 * @dev The wstETH power farm is an automated leverage contract working as a
 * second layer for Wise lending. It needs to be registed inside the latter one
 * to have access to the pools. It uses BALANCER FLASHLOANS as well as CURVE POOLS and
 * the LIDO contracts for staked ETH and wrapped staked ETH.
 * The corresponding contract addresses can be found in {wstETHFarmDeclarations.sol}.
 *
 * It allows to open leverage positions with wrapped ETH in form of aave wrapped ETH.
 * For opening a position the user needs to have {_initalAmount} of ETH or WETH in the wallet.
 * A maximum of 15x leverage is possible. Once the user registers with its position NFT that
 * NFT is locked for ALL other interactions with wise lending as long as the positon is open!
 *
 * For more infos see {https://wisesoft.gitbook.io/wise/}
 */

contract wstETHManager is OwnableMaster, wstETHFarm, MinterReserver {

    /**
     * @dev Standard receive functions forwarding
     * directly send ETH to the master address.
     */
    receive()
        external
        payable
    {
        emit ETHReceived(
            msg.value,
            msg.sender
        );
    }

    constructor(
        address _wiseLendingAddress,
        uint256 _collateralFactor,
        address _powerFarmNFTs
    )
        OwnableMaster(
            msg.sender
        )
        MinterReserver(
            _powerFarmNFTs
        )
        wstETHFarmDeclarations(
            _wiseLendingAddress,
            _collateralFactor
        )
    {
    }

    function changeMinDeposit(
        uint256 _newMinDeposit
    )
        external
        onlyMaster
    {
        minDepositEthAmount = _newMinDeposit;

        emit MinDepositChange(
            _newMinDeposit,
            block.timestamp
        );
    }

    function getMinAmountOut(
        uint256 _keyId,
        uint256 _slippage
    )
        external
        view
        returns (uint256)
    {
        uint256 collateral = _getPostionCollateralTokenAmount(
            farmingKeys[_keyId]
        );

        uint256 amountStETH = WST_ETH.getStETHByWstETH(
            collateral
        );

        uint256 amountOut = CURVE.get_dy(
            1,
            0,
            amountStETH
        );

        return amountOut
            * _slippage
            / PRECISION_FACTOR_E18;
    }

    /**
     * @dev External function deactivating the power farm by
     * disableing the openPosition function. Allowing user
     * to manualy payback and withdraw.
     */
    function shutDownFarm(
        bool _state
    )
        external
        onlyMaster
    {
        isShutdown = _state;

        emit FarmStatus(
            _state,
            block.timestamp
        );
    }

    /**
     * @dev External set function to change referral address
     * for lido staking. Can only be called by master.
     */
    function changeRefAddress(
        address _newAddress
    )
        external
        onlyMaster
    {
        referralAddress = _newAddress;

        emit ReferralUpdate(
            _newAddress,
            block.timestamp
        );
    }

    function enterFarm(
        bool _isAave,
        uint256 _amount,
        uint256 _leverage
    )
        external
        isActive
        updatePools
        returns (uint256)
    {
        uint256 wiseLendingNFT = _getWiseLendingNFT();

        _safeTransferFrom(
            WETH_ADDRESS,
            msg.sender,
            address(this),
            _amount
        );

        _openPosition(
            _isAave,
            wiseLendingNFT,
            _amount,
            _leverage
        );

        uint256 keyId = _reserveKey(
            msg.sender,
            wiseLendingNFT
        );

        isAave[keyId] = _isAave;

        emit FarmEntry(
            keyId,
            wiseLendingNFT,
            _leverage,
            _amount,
            block.timestamp
        );

        return keyId;
    }

    function enterFarmETH(
        bool _isAave,
        uint256 _leverage
    )
        external
        payable
        isActive
        updatePools
        returns (uint256)
    {
        uint256 wiseLendingNFT = _getWiseLendingNFT();

        _wrapETH(
            msg.value
        );

        _openPosition(
            _isAave,
            wiseLendingNFT,
            msg.value,
            _leverage
        );

        uint256 keyId = _reserveKey(
            msg.sender,
            wiseLendingNFT
        );

        isAave[keyId] = _isAave;

        emit FarmEntry(
            keyId,
            wiseLendingNFT,
            _leverage,
            msg.value,
            block.timestamp
        );

        return keyId;
    }

    function _getWiseLendingNFT()
        internal
        returns (uint256)
    {
        if (availableNFTCount == 0) {

            uint256 nftId = POSITION_NFT.mintPosition();

            _registrationFarm(
                nftId
            );

            POSITION_NFT.approve(
                AAVE_HUB_ADDRESS,
                nftId
            );

            return nftId;
        }

        return availableNFTs[
            availableNFTCount--
        ];
    }

    function exitFarm(
        uint256 _keyId,
        uint256 _minOutAmount,
        bool _ethBack
    )
        external
        updatePools
        onlyKeyOwner(_keyId)
    {
        uint256 wiseLendingNFT = farmingKeys[
            _keyId
        ];

        delete farmingKeys[
            _keyId
        ];

        if (reservedKeys[msg.sender] == _keyId) {
            reservedKeys[msg.sender] = 0;
        } else {
            FARMS_NFTS.burnKey(
                _keyId
            );
        }

        emit FarmExit(
            _keyId,
            wiseLendingNFT,
            _minOutAmount,
            block.timestamp
        );

        availableNFTs[
            ++availableNFTCount
        ] = wiseLendingNFT;

        _closingPosition(
            isAave[_keyId],
            wiseLendingNFT,
            _minOutAmount,
            _ethBack
        );
    }

    function manuallyPaybackShares(
        uint256 _keyId,
        uint256 _paybackShares
    )
        external
        updatePools
    {
        _manuallyPaybackShares(
            farmingKeys[_keyId],
            _paybackShares
        );

        emit ManualPaybackShares(
            _keyId,
            farmingKeys[_keyId],
            _paybackShares,
            block.timestamp
        );
    }

    function manuallyWithdrawShares(
        uint256 _keyId,
        uint256 _withdrawShares
    )
        external
        updatePools
        onlyKeyOwner(_keyId)
    {
        _manuallyWithdrawShares(
            farmingKeys[_keyId],
            _withdrawShares
        );

        emit ManualWithdrawShares(
            _keyId,
            farmingKeys[_keyId],
            _withdrawShares,
            block.timestamp
        );
    }
}

// File: contracts/PowerFarms/PowerFarmNFTs/MinterReserver.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.23;

import "../../InterfaceHub/IPowerFarmsNFTs.sol";

error InvalidKey();
error AlreadyReserved();

contract MinterReserver {

    IPowerFarmsNFTs immutable FARMS_NFTS;

    // Tracks increment of keys
    uint256 public totalMinted;

    // Tracks reserved counter
    uint256 public totalReserved;

    // Tracks amount of reusable NFTs
    uint256 public availableNFTCount;

    // Maps access to wiseLendingNFT through farmNFT
    mapping(uint256 => uint256) public farmingKeys;

    // Tracks reserved NFTs mapped to address
    mapping(address => uint256) public reservedKeys;

    // Tracks reusable wiseLendingNFTs after burn
    mapping(uint256 => uint256) public availableNFTs;

    modifier onlyKeyOwner(
        uint256 _keyId
    ) {
        _onlyKeyOwner(
            _keyId
        );
        _;
    }

    function _onlyKeyOwner(
        uint256 _keyId
    )
        private
        view
    {
        require(
            isOwner(
                _keyId,
                msg.sender
            ) == true
        );
    }

    constructor(
        address _powerFarmNFTs
    ) {
        FARMS_NFTS = IPowerFarmsNFTs(
            _powerFarmNFTs
        );
    }

    function _incrementReserved()
        internal
        returns (uint256)
    {
        return ++totalReserved;
    }

    function _getNextReserveKey()
        internal
        returns (uint256)
    {
        return totalMinted + _incrementReserved();
    }

    function _reserveKey(
        address _userAddress,
        uint256 _wiseLendingNFT
    )
        internal
        returns (uint256)
    {
        if (reservedKeys[_userAddress] > 0) {
            revert AlreadyReserved();
        }

        uint256 keyId = _getNextReserveKey();

        reservedKeys[_userAddress] = keyId;
        farmingKeys[keyId] = _wiseLendingNFT;

        return keyId;
    }

    function isOwner(
        uint256 _keyId,
        address _owner
    )
        public
        view
        returns (bool)
    {
        if (reservedKeys[_owner] == _keyId) {
            return true;
        }

        if (FARMS_NFTS.ownerOf(_keyId) == _owner) {
            return true;
        }

        return false;
    }

    function _mintKeyForUser(
        uint256 _keyId,
        address _userAddress
    )
        internal
        returns (uint256)
    {
        if (_keyId == 0) {
            revert InvalidKey();
        }

        delete reservedKeys[
            _userAddress
        ];

        FARMS_NFTS.mintKey(
            _userAddress,
            _keyId
        );

        totalMinted++;
        totalReserved--;

        return _keyId;
    }

    function mintReserved()
        external
        returns (uint256)
    {
        return _mintKeyForUser(
            reservedKeys[
                msg.sender
            ],
            msg.sender
        );
    }

    event ERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes _data
    );

    function onERC721Received(
        address _operator,
        address _from,
        uint256 _tokenId,
        bytes calldata _data
    )
        external
        returns (bytes4)
    {
        emit ERC721Received(
            _operator,
            _from,
            _tokenId,
            _data
        );

        return this.onERC721Received.selector;
    }
}

// File: contracts/OwnableMaster.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.23;

error NoValue();
error NotMaster();
error NotProposed();

contract OwnableMaster {

    address public master;
    address private proposedMaster;

    address internal constant ZERO_ADDRESS = address(0x0);

    modifier onlyProposed() {
        _onlyProposed();
        _;
    }

    function _onlyMaster()
        private
        view
    {
        if (msg.sender == master) {
            return;
        }

        revert NotMaster();
    }

    modifier onlyMaster() {
        _onlyMaster();
        _;
    }

    function _onlyProposed()
        private
        view
    {
        if (msg.sender == proposedMaster) {
            return;
        }

        revert NotProposed();
    }

    event MasterProposed(
        address indexed proposer,
        address indexed proposedMaster
    );

    event ClaimedOwnership(
        address indexed newMaster,
        address indexed previousMaster
    );

    event RenouncedOwnership(
        address indexed previousMaster
    );

    constructor(
        address _master
    ) {
        if (_master == ZERO_ADDRESS) {
            revert NoValue();
        }
        master = _master;
    }

    /**
     * @dev Allows to propose next master.
     * Must be claimed by proposer.
     */
    function proposeOwner(
        address _proposedOwner
    )
        external
        onlyMaster
    {
        if (_proposedOwner == ZERO_ADDRESS) {
            revert NoValue();
        }

        proposedMaster = _proposedOwner;

        emit MasterProposed(
            msg.sender,
            _proposedOwner
        );
    }

    /**
     * @dev Allows to claim master role.
     * Must be called by proposer.
     */
    function claimOwnership()
        external
        onlyProposed
    {
        master = proposedMaster;

        emit ClaimedOwnership(
            master,
            msg.sender
        );
    }

    /**
     * @dev Removes master role.
     * No ability to be in control.
     */
    function renounceOwnership()
        external
        onlyMaster
    {
        master = ZERO_ADDRESS;
        proposedMaster = ZERO_ADDRESS;

        emit RenouncedOwnership(
            msg.sender
        );
    }
}

// File: contracts/PowerFarms/wstETHPowerFarm/wstETHFarm.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.23;

import "./wstETHFarmLeverageLogic.sol";

abstract contract wstETHFarm is wstETHFarmLeverageLogic {

    /**
     * @dev External view function approximating the
     * new resulting net APY for a position setup.
     *
     * Note: Not 100% accurate because no syncPool is performed.
     */
    function getApproxNetAPY(
        uint256 _initialAmount,
        uint256 _leverage,
        uint256 _wstETHAPY
    )
        external
        view
        returns (
            uint256,
            bool
        )
    {
        return _getApproxNetAPY(
            _initialAmount,
            _leverage,
            _wstETHAPY
        );
    }

    /**
     * @dev External view function approximating the
     * new borrow amount for the pool when {_borrowAmount}
     * is borrowed.
     *
     * Note: Not 100% accurate because no syncPool is performed.
     */
    function getNewBorrowRate(
        uint256 _borrowAmount
    )
        external
        view
        returns (uint256)
    {
        return _getNewBorrowRate(
            _borrowAmount
        );
    }

    /**
     * @dev View functions returning the current
     * debt ratio of the postion with {_nftId}
     */
    function getLiveDebtRatio(
        uint256 _nftId
    )
        external
        view
        returns (uint256)
    {
        uint256 totalCollateral = getTotalWeightedCollateralETH(
            _nftId
        );

        if (totalCollateral == 0) {
            return 0;
        }

        return getPositionBorrowETH(_nftId)
            * PRECISION_FACTOR_E18
            / totalCollateral;
    }

    /**
     * @dev Liquidation function for open power farm
     * postions which have a debtratio greater 100 %.
     *
     * NOTE: The borrow token is defined by the power farm
     * and is wrapped ETH.
     * The receiving token is always wrapped staked ETH.
     */
    function liquidatePartiallyFromToken(
        uint256 _nftId,
        uint256 _nftIdLiquidator,
        uint256 _shareAmountToPay
    )
        external
        updatePools
        returns (
            uint256 paybackAmount,
            uint256 receivingAmount
        )
    {
        return _coreLiquidation(
            _nftId,
            _nftIdLiquidator,
            _shareAmountToPay
        );
    }

    /**
     * @dev Manually payback function for users. Takes
     * {_paybackShares} which can be converted
     * into token with {paybackAmount()} or vice verse
     * with {calculateBorrowShares()} from wise lending
     * contract.
     */
    function _manuallyPaybackShares(
        uint256 _nftId,
        uint256 _paybackShares
    )
        internal
    {
        uint256 paybackAmount = WISE_LENDING.paybackAmount(
            WETH_ADDRESS,
            _paybackShares
        );

        _safeTransferFrom(
            WETH_ADDRESS,
            msg.sender,
            address(this),
            paybackAmount
        );

        WISE_LENDING.paybackExactShares(
            _nftId,
            WETH_ADDRESS,
            _paybackShares
        );
    }

    /**
     * @dev Manually withdraw function for users. Takes
     * {_withdrawShares} which can be converted
     * into token with {cashoutAmount()} or vice verse
     * with {calculateLendingShares()} from wise lending
     * contract.
     */
    function _manuallyWithdrawShares(
        uint256 _nftId,
        uint256 _withdrawShares
    )
        internal
    {
        uint256 withdrawAmount = WISE_LENDING.cashoutAmount(
            WETH_ADDRESS,
            _withdrawShares
        );

        if (_checkBorrowLimit(_nftId, withdrawAmount) == false) {
            revert ResultsInBadDebt();
        }

        withdrawAmount = WISE_LENDING.withdrawExactShares(
            _nftId,
            WST_ETH_ADDRESS,
            _withdrawShares
        );

        _safeTransfer(
            WST_ETH_ADDRESS,
            msg.sender,
            withdrawAmount
        );
    }

    /**
     * @dev Internal function combining the core
     * logic for {openPosition()}.
     */
    function _openPosition(
        bool _isAave,
        uint256 _nftId,
        uint256 _initialAmount,
        uint256 _leverage
    )
        internal
    {
        if (_leverage > MAX_LEVERAGE) {
            revert LeverageTooHigh();
        }

        uint256 leveragedAmount = getLeverageAmount(
            _initialAmount,
            _leverage
        );

        uint256 flashloanAmount = leveragedAmount
            - _initialAmount;

        if (_aboveMinDepositAmount(leveragedAmount) == false) {
            revert AmountTooSmall();
        }

        _executeBalancerFlashLoan(
            {
                _nftId: _nftId,
                _amount: flashloanAmount,
                _initialAmount: _initialAmount,
                _lendingShares: 0,
                _borrowShares: 0,
                _minAmountOut: 0,
                _ethBack: false,
                _isAave: _isAave
            }
        );
    }

    /**
     * @dev Internal function combining the core
     * logic for {closingPosition()}.
     *
     * Note: {_minOutAmount} passed through UI by querring
     * {get_dy()} from curve pool contract.
     */
    function _closingPosition(
        bool _isAave,
        uint256 _nftId,
        uint256 _minOutAmount,
        bool _ethBack
    )
        internal
    {
        uint256 borrowShares = _getPositionBorrowShares(
            _nftId
        );

        uint256 lendingShares = _getPositionLendingShares(
            _nftId
        );

        uint256 borrowAmount = _getPositionBorrowToken(
            _nftId
        );

        _executeBalancerFlashLoan(
            {
                _nftId: _nftId,
                _amount: borrowAmount,
                _initialAmount: 0,
                _lendingShares: lendingShares,
                _borrowShares: borrowShares,
                _minAmountOut: _minOutAmount,
                _ethBack: _ethBack,
                _isAave: _isAave
            }
        );
    }

    /**
     * @dev Makes a call to WISE_LENDING to
     * register {_nftId} for specific farm use.
     */
    function _registrationFarm(
        uint256 _nftId
    )
        internal
    {
        WISE_LENDING.setRegistrationIsolationPool(
            _nftId,
            true
        );

        emit RegistrationFarm(
            _nftId,
            block.timestamp
        );
    }
}


// File: contracts/PowerFarms/wstETHPowerFarm/wstETHFarmLeverageLogic.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.23;

import "./wstETHFarmMathLogic.sol";

abstract contract wstETHFarmLeverageLogic is
    wstETHFarmMathLogic,
    IFlashLoanRecipient
{
    /**
     * @dev Wrapper function preparing balancer flashloan and
     * loading data to pass into receiver.
     */
    function _executeBalancerFlashLoan(
        uint256 _nftId,
        uint256 _amount,
        uint256 _initialAmount,
        uint256 _lendingShares,
        uint256 _borrowShares,
        uint256 _minAmountOut,
        bool _ethBack,
        bool _isAave
    )
        internal
    {
        bytes memory data = abi.encode(
            _nftId,
            _initialAmount,
            _lendingShares,
            _borrowShares,
            _minAmountOut,
            msg.sender,
            _ethBack,
            _isAave
        );

        IERC20[] memory tokens = new IERC20[](1);
        uint256[] memory amount = new uint256[](1);

        tokens[0] = WETH;
        amount[0] = _amount;

        allowEnter = true;

        BALANCER_VAULT.flashLoan(
            this,
            tokens,
            amount,
            data
        );
    }

    /**
     * @dev Receive function from balancer flashloan. Body
     * is called from balancer at the end of their {flashLoan()}
     * logic. Overwritten with opening flows.
     */
    function receiveFlashLoan(
        IERC20[] memory _flashloanToken,
        uint256[] memory _amounts,
        uint256[] memory _feeAmounts,
        bytes memory _userData
    )
        external
    {
        if (allowEnter == false) {
            revert AccessDenied();
        }

        allowEnter = false;

        if (_flashloanToken.length == 0) {
            revert InvalidParam();
        }

        if (msg.sender != BALANCER_ADDRESS) {
            revert NotBalancerVault();
        }

        uint256 flashloanAmount = _amounts[0];

        uint256 totalDebtBalancer = flashloanAmount
            + _feeAmounts[0];

        (
            uint256 nftId,
            uint256 initialAmount,
            uint256 lendingShares,
            uint256 borrowShares,
            uint256 minOutAmount,
            address caller,
            bool ethBack,
            bool isAave
        ) = abi.decode(
            _userData,
            (
                uint256,
                uint256,
                uint256,
                uint256,
                uint256,
                address,
                bool,
                bool
            )
        );

        if (initialAmount == 0) {
            _logicClosePosition(
                nftId,
                borrowShares,
                lendingShares,
                totalDebtBalancer,
                minOutAmount,
                caller,
                ethBack,
                isAave
            );

            return;
        }

        _logicOpenPosition(
            isAave,
            nftId,
            flashloanAmount + initialAmount,
            totalDebtBalancer
        );
    }

    /**
     * @dev Core logic for closing a position using balancer
     * flashloans.
     */
    function _logicClosePosition(
        uint256 _nftId,
        uint256 _borrowShares,
        uint256 _lendingShares,
        uint256 _totalDebtBalancer,
        uint256 _minOutAmount,
        address _caller,
        bool _ethBack,
        bool _isAave
    )
        internal
    {
        _paybackExactShares(
            _isAave,
            _nftId,
            _borrowShares
        );

        uint256 withdrawAmount = WISE_LENDING.withdrawExactShares(
            _nftId,
            WST_ETH_ADDRESS,
            _lendingShares
        );

        uint256 stETHAmount = WST_ETH.unwrap(
            withdrawAmount
        );

        uint256 ethAmount = _swapStETHintoETH(
            stETHAmount,
            _minOutAmount
        );

        if (_ethBack == true) {

            _closingRouteETH(
                ethAmount,
                _totalDebtBalancer,
                _caller
            );

            return;
        }

        _closingRouteWETH(
            ethAmount,
            _totalDebtBalancer,
            _caller
        );
    }

    function _paybackExactShares(
        bool _isAave,
        uint256 _nftId,
        uint256 _borrowShares
    )
        internal
    {
        if (_isAave == true) {
            AAVE_HUB.paybackExactShares(
                _nftId,
                WETH_ADDRESS,
                _borrowShares
            );

            return;
        }

        WISE_LENDING.paybackExactShares(
            _nftId,
            WETH_ADDRESS,
            _borrowShares
        );
    }

    /**
     * @dev Internal wrapper function for a closing route
     * which returns WETH to the owner in the end.
     */
    function _closingRouteWETH(
        uint256 _ethAmount,
        uint256 _totalDebtBalancer,
        address _caller
    )
        internal
    {
        _wrapETH(
            _ethAmount
        );

        _safeTransfer(
            WETH_ADDRESS,
            msg.sender,
            _totalDebtBalancer
        );

        _safeTransfer(
            WETH_ADDRESS,
            _caller,
            _ethAmount - _totalDebtBalancer
        );
    }

    /**
     * @dev Internal wrapper function for a closing route
     * which returns ETH to the owner in the end.
     */
    function _closingRouteETH(
        uint256 _ethAmount,
        uint256 _totalDebtBalancer,
        address _caller
    )
        internal
    {
        _wrapETH(
            _totalDebtBalancer
        );

        _safeTransfer(
            WETH_ADDRESS,
            msg.sender,
            _totalDebtBalancer
        );

        _sendValue(
            _caller,
            _ethAmount - _totalDebtBalancer
        );
    }

    function _sendValue(
        address _recipient,
        uint256 _amount
    )
        private
    {
        if (address(this).balance < _amount) {
            revert AmountTooSmall();
        }

        sendingProgress = true;

        (bool success, ) = payable(_recipient).call{
            value: _amount
        }("");

        sendingProgress = false;

        if (success == false) {
            revert InvalidParam();
        }
    }

    /**
     * @dev Internal wrapper function for curve swap
     * of stETH into ETH.
     */
    function _swapStETHintoETH(
        uint256 _swapAmount,
        uint256 _minOutAmount
    )
        internal
        returns (uint256)
    {
        return CURVE.exchange(
            {
                fromIndex: 1,
                toIndex: 0,
                exactAmountFrom: _swapAmount,
                minReceiveAmount: _minOutAmount
            }
        );
    }

    /**
     * @dev Internal function executing the
     * collateral deposit by converting ETH
     * into wstETH, adding it as collateral and
     * borrowing the flashloan token (WETH) to pay
     * back {_totalDebtBalancer}.
     */
    function _logicOpenPosition(
        bool _isAave,
        uint256 _nftId,
        uint256 _depositAmount,
        uint256 _totalDebtBalancer
    )
        internal
    {
        _unwrapETH(
            _depositAmount
        );

        uint256 stETHShares = ST_ETH.submit{
            value: _depositAmount
        }(
            referralAddress
        );

        uint256 stETHAmount = ST_ETH.getPooledEthByShares(
            stETHShares
        );

        uint256 wstETHAmount = WST_ETH.wrap(
            stETHAmount
        );

        WISE_LENDING.depositExactAmount(
            _nftId,
            WST_ETH_ADDRESS,
            wstETHAmount
        );

        _borrowExactAmount(
            _isAave,
            _nftId,
            _totalDebtBalancer
        );

        if (_checkDebtRatio(_nftId) == false) {
            revert DebtRatioTooHigh();
        }

        _safeTransfer(
            WETH_ADDRESS,
            BALANCER_ADDRESS,
            _totalDebtBalancer
        );
    }

    function _borrowExactAmount(
        bool _isAave,
        uint256 _nftId,
        uint256 _totalDebtBalancer
    )
        internal
    {
        if (_isAave == true) {
            AAVE_HUB.borrowExactAmount(
                _nftId,
                WETH_ADDRESS,
                _totalDebtBalancer
            );

            return;
        }

        WISE_LENDING.borrowExactAmount(
            _nftId,
            WETH_ADDRESS,
            _totalDebtBalancer
        );
    }

    /**
     * @dev Internal function summarizing liquidation
     * checks and interface call for core liquidation
     * from wise lending.
     */
    function _coreLiquidation(
        uint256 _nftId,
        uint256 _nftIdLiquidator,
        uint256 _shareAmountToPay
    )
        internal
        returns (
            uint256 paybackAmount,
            uint256 receivingAmount
        )
    {
        if (_checkDebtRatio(_nftId) == true) {
            revert DebtRatioTooLow();
        }

        paybackAmount = WISE_LENDING.paybackAmount(
            WETH_ADDRESS,
            _shareAmountToPay
        );

        receivingAmount = WISE_LENDING.coreLiquidationIsolationPools(
            _nftId,
            _nftIdLiquidator,
            msg.sender,
            msg.sender,
            WETH_ADDRESS,
            WST_ETH_ADDRESS,
            paybackAmount,
            _shareAmountToPay
        );
    }
}

// File: contracts/InterfaceHub/IPowerFarmsNFTs.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.23;

interface IPowerFarmsNFTs {

    function ownerOf(
        uint256 _tokenId
    )
        external
        view
        returns (address);

    function mintKey(
        address _keyOwner,
        uint256 _keyId
    )
        external;

    /**
     * @dev Burns farming NFT
     */
    function burnKey(
        uint256 _keyId
    )
        external;
}

// File: contracts/PowerFarms/wstETHPowerFarm/wstETHFarmMathLogic.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.23;

import "./wstETHFarmDeclarations.sol";

abstract contract wstETHFarmMathLogic is wstETHFarmDeclarations {

    /**
     * @dev Wrapper for wrapping
     * ETH call.
     */
    function _wrapETH(
        uint256 _value
    )
        internal
    {
        WETH.deposit{
            value: _value
        }();
    }

    /**
     * @dev Wrapper for unwrapping
     * ETH call.
     */
    function _unwrapETH(
        uint256 _value
    )
        internal
    {
        WETH.withdraw(
            _value
        );
    }

    modifier updatePools() {
        _updatePools();
        _;
    }

    /**
     * @dev Update logic for pools via wise lending
     * interfaces
     */
    function _updatePools()
        private
    {
        _checkReentrancy();

        WISE_LENDING.preparePool(
            WST_ETH_ADDRESS
        );

        WISE_LENDING.preparePool(
            WETH_ADDRESS
        );
    }

    function _checkReentrancy()
        private
        view
    {
        if (sendingProgress == true) {
            revert AccessDenied();
        }

        if (WISE_LENDING.sendingProgress() == true) {
            revert AccessDenied();
        }

        if (AAVE_HUB.sendingProgressAaveHub() == true) {
            revert AccessDenied();
        }
    }

    /**
     * @dev Internal function getting the
     * borrow shares from position {_nftId}
     * with token {_borrowToken}
     */
    function _getPositionBorrowShares(
        uint256 _nftId
    )
        internal
        view
        returns (uint256)
    {
        return WISE_LENDING.getPositionBorrowShares(
            _nftId,
            WETH_ADDRESS
        );
    }

    /**
     * @dev Internal function converting
     * borrow shares into tokens.
     */
    function _getPositionBorrowToken(
        uint256 _nftId
    )
        internal
        view
        returns(uint256)
    {
        return WISE_LENDING.paybackAmount(
            WETH_ADDRESS,
            _getPositionBorrowShares(
                _nftId
            )
        );
    }

    /**
     * @dev Internal function getting the
     * lending shares from position {_nftId}
     * with token {_borrowToken}
     */
    function _getPositionLendingShares(
        uint256 _nftId
    )
        internal
        view
        returns (uint256)
    {
        return WISE_LENDING.getPositionLendingShares(
            _nftId,
            WST_ETH_ADDRESS
        );
    }

    /**
     * @dev Internal function converting
     * lending shares into tokens.
     */
    function _getPostionCollateralTokenAmount(
        uint256 _nftId
    )
        internal
        view
        returns(uint256)
    {
        return WISE_LENDING.cashoutAmount(
            {
                _poolToken: WST_ETH_ADDRESS,
                _shares: _getPositionLendingShares(
                    _nftId
                )
                // _maxAmount: false
            }
        );
    }

    /**
     * @dev Read function returning the total
     * borrow amount in ETH from postion {_nftId}
     */
    function getPositionBorrowETH(
        uint256 _nftId
    )
        public
        view
        returns (uint256)
    {
        return ORACLE_HUB.getTokensInETH(
            WETH_ADDRESS,
            _getPositionBorrowToken(
                _nftId
            )
        );
    }

    /**
     * @dev Read function returning the total
     * lending amount in ETH from postion {_nftId}
     */
    function getTotalWeightedCollateralETH(
        uint256 _nftId
    )
        public
        view
        returns (uint256)
    {
        return ORACLE_HUB.getTokensInETH(
            WST_ETH_ADDRESS,
            _getPostionCollateralTokenAmount(_nftId)
        )
            * collateralFactor
            / PRECISION_FACTOR_E18;
    }

    /**
     * @dev Internal function checking if a position
     * with {_nftId} is still used to lock it for
     * unregister function.
     */
    function getLeverageAmount(
        uint256 _initialAmount,
        uint256 _leverage
    )
        public
        pure
        returns (uint256)
    {
        return _initialAmount
            * _leverage
            / PRECISION_FACTOR_E18;
    }

    /**
     * @dev Internal function with math logic for approximating
     * the net APY for the postion aftrer creation.
     */
    function _getApproxNetAPY(
        uint256 _initialAmount,
        uint256 _leverage,
        uint256 _wstETHAPY
    )
        internal
        view
        returns (
            uint256,
            bool
        )
    {
        if (_leverage < PRECISION_FACTOR_E18) {
            return (0, false);
        }

        uint256 leveragedAmount = getLeverageAmount(
            _initialAmount,
            _leverage
        );

        uint256 flashloanAmount = leveragedAmount
            - _initialAmount;

        uint256 newBorrowRate = _getNewBorrowRate(
            flashloanAmount
        );

        uint256 leveragedPositivAPY = _wstETHAPY
            * _leverage
            / PRECISION_FACTOR_E18;

        uint256 leveragedNegativeAPY = newBorrowRate
            * (_leverage - PRECISION_FACTOR_E18)
            / PRECISION_FACTOR_E18;

        bool isPositive = leveragedPositivAPY >= leveragedNegativeAPY;

        uint256 netAPY = isPositive == true
            ? leveragedPositivAPY - leveragedNegativeAPY
            : leveragedNegativeAPY - leveragedPositivAPY;

        return (
            netAPY,
            isPositive
        );
    }

    /**
     * @dev Internal function with math logic for approximating
     * the new borrow APY.
     */
    function _getNewBorrowRate(
        uint256 _borrowAmount
    )
        internal
        view
        returns (uint256)
    {
        uint256 totalPool = WISE_LENDING.getTotalPool(
            WETH_ADDRESS
        );

        uint256 pseudoPool = WISE_LENDING.getPseudoTotalPool(
            WETH_ADDRESS
        );

        if (totalPool > pseudoPool) {
            return 0;
        }

        uint256 newUtilization = PRECISION_FACTOR_E18 - (PRECISION_FACTOR_E18
            * (totalPool - _borrowAmount)
            / pseudoPool
        );

        uint256 pole = WISE_LENDING.borrowRatesData(
            WETH_ADDRESS
        ).pole;

        uint256 mulFactor = WISE_LENDING.borrowRatesData(
            WETH_ADDRESS
        ).multiplicativeFactor;

        uint256 baseDivider = pole
            * (pole - newUtilization);

        return mulFactor
            * PRECISION_FACTOR_E18
            * newUtilization
            / baseDivider;
    }

    /**
     * @dev Internal function checking if a position
     * with {_nftId} has a debt ratio under 100%.
     */
    function _checkDebtRatio(
        uint256 _nftId
    )
        internal
        view
        returns (bool res)
    {
        res = getTotalWeightedCollateralETH(_nftId)
            > getPositionBorrowETH(_nftId);
    }

    /**
     * @dev Internal function checking if the debt
     * ratio threshold fof 100 % is reached when a
     * manually withdraw is performed.
     */
    function _checkBorrowLimit(
        uint256 _nftId,
        uint256 _amount
    )
        internal
        view
        returns (bool)
    {
        uint256 borrowAmount = getPositionBorrowETH(
            _nftId
        );

        if (borrowAmount == 0) {
            return true;
        }

        uint256 withdrawValue = ORACLE_HUB.getTokensInETH(
            WST_ETH_ADDRESS,
            _amount
        )
            * collateralFactor
            / PRECISION_FACTOR_E18;

        return getTotalWeightedCollateralETH(_nftId) - withdrawValue
            > borrowAmount;
    }

    /**
     * @dev Internal function checking if the leveraged
     * amount is above 3 ETH in value.
     */
    function _aboveMinDepositAmount(
        uint256 _amount
    )
        internal
        view
        returns (bool)
    {
        uint256 equivETH = ORACLE_HUB.getTokensInETH(
            WETH_ADDRESS,
            _amount
        );

        return equivETH >= minDepositEthAmount;
    }
}

// File: contracts/PowerFarms/wstETHPowerFarm/wstETHFarmDeclarations.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.23;

import "../../InterfaceHub/IWETH.sol";
import "../../InterfaceHub/IAave.sol";
import "../../InterfaceHub/IStETH.sol";
import "../../InterfaceHub/ICurve.sol";
import "../../InterfaceHub/IWstETH.sol";
import "../../InterfaceHub/IAaveHub.sol";
import "../../InterfaceHub/IWiseLending.sol";
import "../../InterfaceHub/IWiseSecurity.sol";
import "../../InterfaceHub/IPositionNFTs.sol";
import "../../InterfaceHub/IWiseOracleHub.sol";
import "../../InterfaceHub/IBalancerFlashloan.sol";

import "../../TransferHub/TransferHelper.sol";
import "../../TransferHub/ApprovalHelper.sol";

error Deactivated();
error InvalidParam();
error InvalidOwner();
error AccessDenied();
error AmountTooSmall();
error LeverageTooHigh();
error DebtRatioTooLow();
error NotBalancerVault();
error DebtRatioTooHigh();
error ResultsInBadDebt();

contract wstETHFarmDeclarations is
    TransferHelper,
    ApprovalHelper
{
    // Mapping of {KeyNFT} to type of pool
    mapping(uint256 => bool) public isAave;

    // Bool indicating that a power farm is deactivated
    bool public isShutdown;

    // Bool gating for callback (flashloan balancer)
    bool public allowEnter;

    // Bool indicating ETH transfer in progress
    bool public sendingProgress;

    // Corresponding Aave borrow token used by farm
    address public immutable aaveTokenAddresses;

    // Borrow token used by farm
    address public immutable borrowTokenAddresses;

    // Referral address used for lido
    address public referralAddress;

    // Collateral factor used for sDAI collateral
    uint256 public collateralFactor;

    IWETH public immutable WETH;
    IAave public immutable AAVE;
    ICurve public immutable CURVE;
    IStETH public immutable ST_ETH;
    IWstETH public immutable WST_ETH;
    IAaveHub public immutable AAVE_HUB;
    IWiseLending public immutable WISE_LENDING;
    IWiseOracleHub public immutable ORACLE_HUB;
    IWiseSecurity public immutable WISE_SECURITY;
    IBalancerVault public immutable BALANCER_VAULT;
    IPositionNFTs public immutable POSITION_NFT;

    // @TODO: need to fetch from AAVE_HUB
    address internal constant AAVE_ADDRESS = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2;

    // @TODO: need to fetch from WISE_LENDING
    address internal constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

    // @TODO: can we fetch this from somewhere dynamically? if not pass in constructor
    address internal constant ST_ETH_ADDRESS = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;

    // @TODO: can we fetch this from somewhere dynamically? if not pass in constructor
    address internal constant WST_ETH_ADDRESS = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;

    // @TODO: can we fetch this from somewhere dynamically? if not pass in constructor
    address internal constant BALANCER_ADDRESS = 0xBA12222222228d8Ba445958a75a0704d566BF2C8;

    // @TODO: need to fetch from WISE_LENDING
    address internal constant AAVE_HUB_ADDRESS = 0xCd87EDBb1eCF759162f4F0462CfdD2A913A96C7d;

    // @TODO: need to fetch from AAVE_HUB
    address internal constant AAVE_WETH_ADDRESS = 0x4d5F47FA6A74757f35C14fD3a6Ef8E3C9BC514E8;

    // @TODO: can we fetch this from somewhere dynamically? if not pass in constructor
    address internal constant CURVE_POOL_ADDRESS = 0xDC24316b9AE028F1497c275EB9192a3Ea0f67022;

    // Math constant for computations
    uint256 internal constant PRECISION_FACTOR_E18 = 1E18;

    // Maximal allowed leverage factor
    uint256 internal constant MAX_LEVERAGE = 15 * PRECISION_FACTOR_E18;

    // Minimal required deposit amount for leveraged postion in ETH
    uint256 public minDepositEthAmount = 3 * PRECISION_FACTOR_E18;

    // Max possible amount for uint256
    uint256 internal constant MAX_AMOUNT = type(uint256).max;

    event FarmEntry(
        uint256 indexed keyId,
        uint256 indexed wiseLendingNFT,
        uint256 indexed leverage,
        uint256 amount,
        uint256 timestamp
    );

    event FarmExit(
        uint256 indexed keyId,
        uint256 indexed wiseLendingNFT,
        uint256 amount,
        uint256 timestamp
    );

    event FarmStatus(
        bool indexed state,
        uint256 timestamp
    );

    event ReferralUpdate(
        address indexed referralAddress,
        uint256 timestamp
    );

    event ManualPaybackShares(
        uint256 indexed keyId,
        uint256 indexed wiseLendingNFT,
        uint256 amount,
        uint256 timestamp
    );

    event ManualWithdrawShares(
        uint256 indexed keyId,
        uint256 indexed wiseLendingNFT,
        uint256 amount,
        uint256 timestamp
    );

    event BaseUrlChange(
        string baseUrl,
        uint256 timestamp
    );

    event BaseExtensionChange(
        string baseExtension,
        uint256 timestamp
    );

    event MinDepositChange(
        uint256 minDepositEthAmount,
        uint256 timestamp
    );

    event ETHReceived(
        uint256 amount,
        address from
    );

    event RegistrationFarm(
        uint256 nftId,
        uint256 timestamp
    );

    constructor(
        address _wiseLendingAddress,
        uint256 _collateralFactor
    ) {
        WISE_LENDING = IWiseLending(
            _wiseLendingAddress
        );

        ORACLE_HUB = IWiseOracleHub(
            WISE_LENDING.WISE_ORACLE()
        );

        BALANCER_VAULT = IBalancerVault(
            BALANCER_ADDRESS
        );

        WISE_SECURITY = IWiseSecurity(
            WISE_LENDING.WISE_SECURITY()
        );

        AAVE = IAave(
            AAVE_ADDRESS
        );

        WETH = IWETH(
            WETH_ADDRESS
        );

        ST_ETH = IStETH(
            ST_ETH_ADDRESS
        );

        WST_ETH = IWstETH(
            WST_ETH_ADDRESS
        );

        AAVE_HUB = IAaveHub(
            AAVE_HUB_ADDRESS
        );

        CURVE = ICurve(
            CURVE_POOL_ADDRESS
        );

        POSITION_NFT = IPositionNFTs(
            WISE_LENDING.POSITION_NFT()
        );

        collateralFactor = _collateralFactor;

        borrowTokenAddresses = WETH_ADDRESS;
        aaveTokenAddresses = AAVE_WETH_ADDRESS;

        _doApprovals(
            _wiseLendingAddress
        );
    }

    function doApprovals()
        external
    {
        _doApprovals(
            address(WISE_LENDING)
        );
    }

    function _doApprovals(
        address _wiseLendingAddress
    )
        internal
    {
        _safeApprove(
            WETH_ADDRESS,
            _wiseLendingAddress,
            MAX_AMOUNT
        );

        _safeApprove(
            ST_ETH_ADDRESS,
            WST_ETH_ADDRESS,
            MAX_AMOUNT
        );

        _safeApprove(
            WST_ETH_ADDRESS,
            address(WISE_LENDING),
            MAX_AMOUNT
        );

        _safeApprove(
            ST_ETH_ADDRESS,
            CURVE_POOL_ADDRESS,
            MAX_AMOUNT
        );
    }

    modifier isActive() {
        if (isShutdown == true) {
            revert Deactivated();
        }
        _;
    }
}

// File: contracts/TransferHub/ApprovalHelper.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.23;

import "./CallOptionalReturn.sol";

contract ApprovalHelper is CallOptionalReturn {

    /**
     * @dev
     * Allows to execute safe approve for a token
     */
    function _safeApprove(
        address _token,
        address _spender,
        uint256 _value
    )
        internal
    {
        _callOptionalReturn(
            _token,
            abi.encodeWithSelector(
                IERC20.approve.selector,
                _spender,
                _value
            )
        );
    }
}
// File: contracts/TransferHub/TransferHelper.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.23;

import "./CallOptionalReturn.sol";

contract TransferHelper is CallOptionalReturn {

    /**
     * @dev
     * Allows to execute safe transfer for a token
     */
    function _safeTransfer(
        address _token,
        address _to,
        uint256 _value
    )
        internal
    {
        _callOptionalReturn(
            _token,
            abi.encodeWithSelector(
                IERC20.transfer.selector,
                _to,
                _value
            )
        );
    }

    /**
     * @dev
     * Allows to execute safe transferFrom for a token
     */
    function _safeTransferFrom(
        address _token,
        address _from,
        address _to,
        uint256 _value
    )
        internal
    {
        _callOptionalReturn(
            _token,
            abi.encodeWithSelector(
                IERC20.transferFrom.selector,
                _from,
                _to,
                _value
            )
        );
    }
}

// File: contracts/InterfaceHub/IBalancerFlashloan.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.23;

import "../InterfaceHub/IERC20.sol";

interface IBalancerVault {

    /**
     * @dev Performs a 'flash loan', sending tokens to `recipient`, executing the `receiveFlashLoan` hook on it,
     * and then reverting unless the tokens plus a proportional protocol fee have been returned.
     *
     * The `tokens` and `amounts` arrays must have the same length, and each entry in these indicates the loan amount
     * for each token contract. `tokens` must be sorted in ascending order.
     *
     * The 'userData' field is ignored by the Vault, and forwarded as-is to `recipient` as part of the
     * `receiveFlashLoan` call.
     *
     * Emits `FlashLoan` events.
     */
    function flashLoan(
        IFlashLoanRecipient _recipient,
        IERC20[] memory _tokens,
        uint256[] memory _amounts,
        bytes memory _userData
    )
        external;
}

interface IFlashLoanRecipient {

    /**
     * @dev When `flashLoan` is called on the Vault, it invokes the `receiveFlashLoan` hook on the recipient.
     *
     * At the time of the call, the Vault will have transferred `amounts` for `tokens` to the recipient. Before this
     * call returns, the recipient must have transferred `amounts` plus `feeAmounts` for each token back to the
     * Vault, or else the entire flash loan will revert.
     *
     * `userData` is the same value passed in the `IVault.flashLoan` call.
     */
    function receiveFlashLoan(
        IERC20[] memory _tokens,
        uint256[] memory _amounts,
        uint256[] memory _feeAmounts,
        bytes memory _userData
    )
        external;
}
// File: contracts/InterfaceHub/IWiseOracleHub.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.23;

interface IWiseOracleHub {

    function getTokensPriceFromUSD(
        address _tokenAddress,
        uint256 _usdValue
    )
        external
        view
        returns (uint256);

    function getTokensPriceInUSD(
        address _tokenAddress,
        uint256 _tokenAmount
    )
        external
        view
        returns (uint256);

    function latestResolver(
        address _tokenAddress
    )
        external
        view
        returns (uint256);

    function getTokensFromUSD(
        address _tokenAddress,
        uint256 _usdValue
    )
        external
        view
        returns (uint256);

    function getTokensFromETH(
        address _tokenAddress,
        uint256 _ethValue
    )
        external
        view
        returns (uint256);

    function getTokensInUSD(
        address _tokenAddress,
        uint256 _amount
    )
        external
        view
        returns (uint256);

    function getTokensInETH(
        address _tokenAddress,
        uint256 _tokenAmount
    )
        external
        view
        returns (uint256);

    function chainLinkIsDead(
        address _tokenAddress
    )
        external
        view
        returns (bool);

    function decimalsUSD()
        external
        pure
        returns (uint8);

    function addOracle(
        address _tokenAddress,
        address _priceFeedAddress,
        address[] calldata _underlyingFeedTokens
    )
        external;

    function recalibrate(
        address _tokenAddress
    )
        external;

    function WETH_ADDRESS()
        external
        view
        returns (address);
}

// File: contracts/InterfaceHub/IPositionNFTs.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.23;

interface IPositionNFTs {

    function ownerOf(
        uint256 _nftId
    )
        external
        view
        returns (address);

    function getOwner(
        uint256 _nftId
    )
        external
        view
        returns (address);

    function totalSupply()
        external
        view
        returns (uint256);

    function reserved(
        address _owner
    )
        external
        view
        returns (uint256);

    function reservePosition()
        external;

    function mintPosition()
        external
        returns (uint256);

    function tokenOfOwnerByIndex(
        address _owner,
        uint256 _index
    )
        external
        view
        returns (uint256);

    function walletOfOwner(
        address _owner
    )
        external
        view
        returns (uint256[] memory);

    function mintPositionForUser(
        address _user
    )
        external
        returns (uint256);

    function reservePositionForUser(
        address _user
    )
        external
        returns (uint256);

    function getApproved(
        uint256 _nftId
    )
        external
        view
        returns (address);

    function approve(
        address _to,
        uint256 _nftId
    )
        external;

    function isOwner(
        uint256 _nftId,
        address _caller
    )
        external
        view
        returns (bool);

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

// File: contracts/InterfaceHub/IWiseSecurity.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.23;

struct CurveSwapStructToken {
    uint256 curvePoolTokenIndexFrom;
    uint256 curvePoolTokenIndexTo;
    uint256 curveMetaPoolTokenIndexFrom;
    uint256 curveMetaPoolTokenIndexTo;
}

struct CurveSwapStructData {
    address curvePool;
    address curveMetaPool;
    bytes swapBytesPool;
    bytes swapBytesMeta;
}

interface IWiseSecurity {

    function overallETHBorrowHeartbeat(
        uint256 _nftId
    )
        external
        view
        returns (uint256 buffer);

    function checkBadDebt(
        uint256 _nftId
    )
        external;

    function checksLiquidation(
        uint256 _nftIdLiquidate,
        address _tokenToPayback,
        uint256 _shareAmountToPay
    )
        external
        view;

    function getPositionBorrowAmount(
        uint256 _nftId,
        address _poolToken
    )
        external
        view
        returns (uint256);

    function getPositionLendingAmount(
        uint256 _nftId,
        address _poolToken
    )
        external
        view
        returns (uint256);

    function getLiveDebtRatio(
        uint256 _nftId
    )
        external
        view
        returns (uint256);

    function overallUSDCollateralsBare(
        uint256 _nftId
    )
        external
        view
        returns (uint256 amount);

    function overallETHCollateralsBare(
        uint256 _nftId
    )
        external
        view
        returns (uint256 amount);

    function FEE_MANAGER()
        external
        view
        returns (address);

    function AAVE_HUB()
        external
        view
        returns (address);

    function curveSecurityCheck(
        address _poolAddress
    )
        external;

    function prepareCurvePools(
        address _poolToken,
        CurveSwapStructData calldata _curveSwapStructData,
        CurveSwapStructToken calldata _curveSwapStructToken
    )
        external;

    function checksWithdraw(
        uint256 _nftId,
        address _caller,
        address _poolToken,
        uint256 _amount
    )
        external
        view;

    function checksBorrow(
        uint256 _nftId,
        address _caller,
        address _poolToken,
        uint256 _amount
    )
        external
        view;

    function checksSolelyWithdraw(
        uint256 _nftId,
        address _caller,
        address _poolToken,
        uint256 _amount
    )
        external
        view;

    function checkOwnerPosition(
        uint256 _nftId,
        address _caller
    )
        external
        view;

    function checksCollateralizeDeposit(
        uint256 _nftIdCaller,
        address _caller,
        address _poolAddress
    )
        external
        view;

    function calculateWishPercentage(
        uint256 _nftId,
        address _receiveToken,
        uint256 _paybackETH,
        uint256 _maxFeeETH,
        uint256 _baseRewardLiquidation
    )
        external
        view
        returns (uint256);

    function checkUncollateralizedDeposit(
        uint256 _nftIdCaller,
        address _poolToken
    )
        external
        view;

    function checkPositionLocked(
        uint256 _nftId,
        address _caller
    )
        external
        view;

    function maxFeeETH()
        external
        view
        returns (uint256);

    function maxFeeFarmETH()
        external
        view
        returns (uint256);

    function baseRewardLiquidation()
        external
        view
        returns (uint256);

    function baseRewardLiquidationFarm()
        external
        view
        returns (uint256);

    function checksRegister(
        uint256 _nftId,
        address _caller
    )
        external
        view;

    function getLendingRate(
        address _poolToken
    )
        external
        view
        returns (uint256);
}

// File: contracts/InterfaceHub/IWiseLending.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.23;

struct GlobalPoolEntry {
    uint256 totalPool;
    uint256 utilization;
    uint256 totalBareToken;
    uint256 poolFee;
}

struct BorrowPoolEntry {
    bool allowBorrow;
    uint256 pseudoTotalBorrowAmount;
    uint256 totalBorrowShares;
    uint256 borrowRate;
}

struct LendingPoolEntry {
    uint256 pseudoTotalPool;
    uint256 totalDepositShares;
    uint256 collateralFactor;
}

struct PoolEntry {
    uint256 totalPool;
    uint256 utilization;
    uint256 totalBareToken;
    uint256 poolFee;
}

struct BorrowRatesEntry {
    uint256 pole;
    uint256 deltaPole;
    uint256 minPole;
    uint256 maxPole;
    uint256 multiplicativeFactor;
}

interface IWiseLending {

    function borrowRatesData(
        address _pooToken
    )
        external
        view
        returns (BorrowRatesEntry memory);

    function newBorrowRate(
        address _poolToken
    )
        external;

    function calculateBorrowShares(
        address _poolToken,
        uint256 _amount,
        bool _maxSharePrice
    )
        external
        view
        returns (uint256);

    function borrowPoolData(
        address _poolToken
    )
        external
        view
        returns (BorrowPoolEntry memory);

    function lendingPoolData(
        address _poolToken
    )
        external
        view
        returns (LendingPoolEntry memory);

    function getPositionBorrowShares(
        uint256 _nftId,
        address _poolToken
    )
        external
        view
        returns (uint256);

    function getTimeStamp(
        address _poolToken
    )
        external
        view
        returns (uint256);

    function getPureCollateralAmount(
        uint256 _nftId,
        address _poolToken
    )
        external
        view
        returns (uint256);

    function isUncollateralized(
        uint256 _nftId,
        address _poolToken
    )
        external
        view
        returns (bool);

    function verifiedIsolationPool(
        address _poolAddress
    )
        external
        view
        returns (bool);

    function positionLocked(
        uint256 _nftId
    )
        external
        view
        returns (bool);

    function getTotalBareToken(
        address _poolToken
    )
        external
        view
        returns (uint256);

    function maxDepositValueToken(
        address _poolToken
    )
        external
        view
        returns (uint256);

    function master()
        external
        view
        returns (address);

    function WETH_ADDRESS()
        external
        view
        returns (address);

    function WISE_ORACLE()
        external
        view
        returns (address);

    function POSITION_NFT()
        external
        view
        returns (address);

    function FEE_MANAGER()
        external
        view
        returns (address);

    function WISE_SECURITY()
        external
        view
        returns (address);

    function lastUpdated(
        address _poolAddress
    )
        external
        view
        returns (uint256);

    function isolationPoolRegistered(
        uint256 _nftId,
        address _isolationPool
    )
        external
        view
        returns (bool);

    function calculateLendingShares(
        address _poolToken,
        uint256 _amount,
        bool _maxSharePrice
    )
        external
        view
        returns (uint256);

    function pureCollateralAmount(
        uint256 _nftId,
        address _poolToken
    )
        external
        returns (uint256);

    function getTotalPool(
        address _poolToken
    )
        external
        view
        returns (uint256);

    function depositExactAmount(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        external
        returns (uint256);

    function withdrawOnBehalfExactAmount(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        external
        returns (uint256);

    function syncManually(
        address _poolToken
    )
        external;

    function withdrawOnBehalfExactShares(
        uint256 _nftId,
        address _poolToken,
        uint256 _shares
    )
        external
        returns (uint256);

    function borrowOnBehalfExactAmount(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        external
        returns (uint256);

    function borrowExactAmount(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        external
        returns (uint256);

    function paybackExactAmountETH(
        uint256 _nftId
    )
        external
        payable
        returns (uint256);

    function solelyDeposit(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        external;

    function solelyWithdrawOnBehalf(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        external;

    function paybackExactAmount(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        external
        returns (uint256);

    function paybackExactShares(
        uint256 _nftId,
        address _poolToken,
        uint256 _shares
    )
        external
        returns (uint256);

    function setPoolFee(
        address _poolToken,
        uint256 _newFee
    )
        external;

    function getPositionLendingShares(
        uint256 _nftId,
        address _poolToken
    )
        external
        view
        returns (uint256);

    function collateralizeDeposit(
        uint256 _nftId,
        address _poolToken
    )
        external;

    function approve(
        address _spender,
        address _poolToken,
        uint256 _amount
    )
        external;

    function withdrawExactShares(
        uint256 _nftId,
        address _poolToken,
        uint256 _shares
    )
        external
        returns (uint256);

    function withdrawExactAmount(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        external
        returns (uint256);

    function poolTokenAddresses()
        external
        returns (address[] memory);

    function corePaybackFeeManager(
        address _poolToken,
        uint256 _nftId,
        uint256 _amount,
        uint256 _shares
    )
        external;

    function sendingProgress()
        external
        view
        returns (bool);

    function depositExactAmountETH(
        uint256 _nftId
    )
        external
        payable
        returns (uint256);

    function coreLiquidationIsolationPools(
        uint256 _nftId,
        uint256 _nftIdLiquidator,
        address _caller,
        address _receiver,
        address _tokenToPayback,
        address _tokenToRecieve,
        uint256 _paybackAmount,
        uint256 _shareAmountToPay
    )
        external
        returns (uint256 reveiveAmount);

    function preparePool(
        address _poolToken
    )
        external;

    function getPositionBorrowTokenLength(
        uint256 _nftId
    )
        external
        view
        returns (uint256);

    function getPositionBorrowTokenByIndex(
        uint256 _nftId,
        uint256 _index
    )
        external
        view
        returns (address);

    function getPositionLendingTokenByIndex(
        uint256 _nftId,
        uint256 _index
    )
        external
        view
        returns (address);

    function getPositionLendingTokenLength(
        uint256 _nftId
    )
        external
        view
        returns (uint256);

    function globalPoolData(
        address _poolToken
    )
        external
        view
        returns (GlobalPoolEntry memory);


    function getGlobalBorrowAmount(
        address _token
    )
        external
        view
        returns (uint256);

    function getPseudoTotalBorrowAmount(
        address _token
    )
        external
        view
        returns (uint256);

    function getInitialBorrowAmountUser(
        address _user,
        address _token
    )
        external
        view
        returns (uint256);

    function getPseudoTotalPool(
        address _token
    )
        external
        view
        returns (uint256);

    function getInitialDepositAmountUser(
        address _user,
        address _token
    )
        external
        view
        returns (uint256);

    function getGlobalDepositAmount(
        address _token
    )
        external
        view
        returns (uint256);

    function paybackAmount(
        address _token,
        uint256 _shares
    )
        external
        view
        returns (uint256);

    function getPositionLendingShares(
        address _user,
        address _token
    )
        external
        view
        returns (uint256);

    function cashoutAmount(
        address _poolToken,
        uint256 _shares
    )
        external
        view
        returns (uint256);

    function getTotalDepositShares(
        address _token
    )
        external
        view
        returns (uint256);

    function getTotalBorrowShares(
        address _token
    )
        external
        view
        returns (uint256);

    function checkPositionLocked(
        uint256 _nftId,
        address _caller
    )
        external
        view;

    function checkDeposit(
        uint256 _nftId,
        address _caller,
        address _poolToken,
        uint256 _amount
    )
        external
        view;

    function setRegistrationIsolationPool(
        uint256 _nftId,
        bool _state
    )
        external;
}

// File: contracts/InterfaceHub/IAaveHub.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.23;

interface IAaveHub {

    function AAVE_ADDRESS()
        external
        view
        returns (address);

    function aaveTokenAddress(
        address _underlyingToken
    )
        external
        view
        returns (address);

    function borrowExactAmount(
        uint256 _nftId,
        address _underlyingAsset,
        uint256 _borrowAmount
    )
        external
        returns (uint256);

    function paybackExactShares(
        uint256 _nftId,
        address _underlyingAsset,
        uint256 _shares
    )
        external
        returns (uint256);

    function paybackExactAmountETH(
        uint256 _nftId
    )
        external
        payable
        returns (uint256);

    function paybackExactAmount(
        uint256 _nftId,
        address _underlyingAsset,
        uint256 _shares
    )
        external
        returns (uint256);

    function depositExactAmount(
        uint256 _nftId,
        address _underlyingAsset,
        uint256 _amount
    )
        external
        returns (uint256);

    function depositExactAmountETH(
        uint256 _nftId
    )
        external
        payable
        returns (uint256);

    function withdrawExactShares(
        uint256 _nftId,
        address _underlyingAsset,
        uint256 _shares
    )
        external
        returns (uint256);

    function withdrawExactAmount(
        uint256 _nftId,
        address _token,
        uint256 _amount
    )
        external
        returns (uint256);

    function sendingProgressAaveHub()
        external
        view
        returns (bool);
}

// File: contracts/InterfaceHub/IWstETH.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.23;

interface IWstETH {

    function wrap(
        uint256 _stETHAmount
    )
        external
        returns (uint256);

    function unwrap(
        uint256 _wstETHAmount
    )
        external
        returns (uint256);

    function getStETHByWstETH(
        uint256 _wstETHAmount
    )
        external
        view
        returns (uint256);

    function decimals()
        external
        view
        returns (uint8);

    function balanceOf(
        address _account
    )
        external
        view
        returns (uint256);
}
// File: contracts/InterfaceHub/ICurve.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.23;

interface ICurve {

    function add_liquidity(
        address _pool,
        uint256[4] memory _depositAmounts,
        uint256 _minOutAmount
    )
        external
        returns (uint256);

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

    function get_dy(
        int128 i,
        int128 j,
        uint256 dx
    )
        external
        view
        returns (uint256);

    function get_dy_underlying(
        int128 i,
        int128 j,
        uint256 dx
    )
        external
        view
        returns (uint256);

    function exchange(
        int128 fromIndex,
        int128 toIndex,
        uint256 exactAmountFrom,
        uint256 minReceiveAmount
    )
        external
        returns (uint256);

    function exchange_underlying(
        int128 i,
        int128 j,
        uint256 dx,
        uint256 min_dy
    )
        external;

    function remove_liquidity(
        address _pool,
        uint256 _burnAmount,
        uint256[4] memory _coins
    )
        external;

    function remove_liquidity_one_coin(
        address _addy,
        uint256 _burnAmount,
        int128 _i,
        uint256 _minReceived
    )
        external;

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

    function decimals()
        external
        view
        returns (uint8);

    function totalSupply()
        external
        view
        returns (uint256);

    function balances(
        uint256 arg0
    )
        external
        view
        returns (uint256);

    function approve(
        address _spender,
        uint256 _amount
    )
        external
        returns (bool);
}

// File: contracts/InterfaceHub/IStETH.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.23;

interface IStETH {

    function submit(
        address _referral
    )
        external
        payable
        returns (uint256);

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

    function getPooledEthByShares(
        uint256 _sharesAmount
    )
        external
        view
        returns (uint256);
}

// File: contracts/InterfaceHub/IAave.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.23;

interface IAave {

    struct ReserveData {

        // Stores the reserve configuration
        ReserveConfigurationMap configuration;

        // Liquidity index. Expressed in ray
        uint128 liquidityIndex;

        // Current supply rate. Expressed in ray
        uint128 currentLiquidityRate;

        // Variable borrow index. Expressed in ray
        uint128 variableBorrowIndex;

        // Current variable borrow rate. Expressed in ray
        uint128 currentVariableBorrowRate;

        // Current stable borrow rate. Expressed in ray
        uint128 currentStableBorrowRate;

        // Timestamp of last update
        uint40 lastUpdateTimestamp;

        // Id of the reserve.
        uint16 id;

        // aToken address
        address aTokenAddress;

        // stableDebtToken address
        address stableDebtTokenAddress;

        // VariableDebtToken address
        address variableDebtTokenAddress;

        // Address of the interest rate strategy
        address interestRateStrategyAddress;

        // Current treasury balance, scaled
        uint128 accruedToTreasury;

        // Outstanding unbacked aTokens minted through the bridging feature
        uint128 unbacked;

        // Outstanding debt borrowed against this asset in isolation mode
        uint128 isolationModeTotalDebt;
    }

    struct ReserveConfigurationMap {
        uint256 data;
    }

    function deposit(
        address _token,
        uint256 _amount,
        address _owner,
        uint16 _referralCode
    )
        external;

    function withdraw(
        address _token,
        uint256 _amount,
        address _recipient
    )
        external
        returns (uint256);

    function getReserveData(
        address asset
    )
        external
        view
        returns (ReserveData memory);

}
// File: contracts/InterfaceHub/IWETH.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.23;

import "./IERC20.sol";

interface IWETH is IERC20 {

    function deposit()
        external
        payable;

    function withdraw(
        uint256
    )
        external;
}

// File: contracts/TransferHub/CallOptionalReturn.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.23;

import "../InterfaceHub/IERC20.sol";

contract CallOptionalReturn {

    /**
     * @dev Helper function to do low-level call
     */
    function _callOptionalReturn(
        address token,
        bytes memory data
    )
        internal
        returns (bool call)
    {
        (
            bool success,
            bytes memory returndata
        ) = token.call(
            data
        );

        bool results = returndata.length == 0 || abi.decode(
            returndata,
            (bool)
        );

        if (success == false) {
            revert();
        }

        call = success
            && results
            && token.code.length > 0;
    }
}
// File: contracts/InterfaceHub/IERC20.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.23;

interface IERC20 {

    function totalSupply()
        external
        view
        returns (uint256);

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

    function transferFrom(
        address _sender,
        address _recipient,
        uint256 _amount
    )
        external
        returns (bool);

    function transfer(
        address _recipient,
        uint256 _amount
    )
        external
        returns (bool);

    function allowance(
        address owner,
        address spender
    )
        external
        view
        returns (uint256);

    function approve(
        address _spender,
        uint256 _amount
    )
        external
        returns (bool);

    function decimals()
        external
        view
        returns (uint8);

    event Transfer(
        address indexed from,
        address indexed to,
        uint256 value
    );

    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
    );

    event  Deposit(
        address indexed dst,
        uint wad
    );

    event  Withdrawal(
        address indexed src,
        uint wad
    );
}