// File: RecoverCollateral.sol



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

import "IERC20ElasticSupply.sol";
import "IGenesisLiquidityPool.sol";


/**
* @title Recover collateral
* @author Geminon Protocol
* @notice This contract burns the GEX supply locked on deprecated smart contracts of the Geminon Protocol and recovers
* the collateral from the pools to be deposited into the protocol vault.
*/
contract RecoverCollateral {
    
    IERC20ElasticSupply private immutable _GEX;
    IGenesisLiquidityPool private immutable _natGLP;

    address private immutable _owner;

    mapping(address => bool) private _validBurnAddress;


    modifier onlyOwner() {
        require(msg.sender == _owner);
        _;
    }
    
    
    constructor() {
        _GEX = IERC20ElasticSupply(0x2743Bb6962fb1D7d13C056476F3Bc331D7C3E112);
        _natGLP = IGenesisLiquidityPool(0xA4df7a003303552AcDdF550A0A65818c4A218315);
        _owner = msg.sender;
        
        // Native Coin Pool (ETH/BNB/AVAX)
        _validBurnAddress[0xA4df7a003303552AcDdF550A0A65818c4A218315] = true;
        // BTC Pool
        _validBurnAddress[0x5ae76CbAedf4E0F710C2b429890B4cCC0737104D] = true;
        // PAXG Pool
        _validBurnAddress[0x48A814C44beeFE3A1C7c165367c1Ea12eA599b48] = true;
        // XAUT Pool
        _validBurnAddress[0xE7e708277A03dA75186C231b5B43FcFB34BEd29B] = true;
        // SCMinter
        _validBurnAddress[0xeF0dfe8cF872B4dF3681Ad37A17Ef5e2D473B877] = true;
        // Broken BTC Pool
        _validBurnAddress[0x9aFEf3344369943509b8E2103F9eC312f014d424] = true;
        // Broken XAUT Pool
        _validBurnAddress[0xA782893006050ba4599558bF842Da3DB7225A00c] = true;        
    }
    
    
    receive() external payable {}
    

    function approvePool(address pool) external onlyOwner {
        _GEX.approve(pool, type(uint256).max);
    }

    function recoverCollateral(address pool, uint256 amountGEX) external onlyOwner {
        _mint(amountGEX);
        IGenesisLiquidityPool(pool).redeemSwap(amountGEX, 0);
    }

    function withdrawToken(address token) external onlyOwner {
        uint256 amount = IERC20(token).balanceOf(address(this));
        IERC20(token).transfer(_owner, amount);
    }

    function burnSupply(address target) external onlyOwner {
        require(_validBurnAddress[target]);
        _GEX.burn(target, _GEX.balanceOf(target));
    }


    function redeemAll() external {
        _redeem(_GEX.balanceOf(msg.sender));
    }
    
    function redeem(uint256 amount) external {
        require(amount <= _GEX.balanceOf(msg.sender));
        _redeem(amount);
    }


    function getHolderShare(address holder) external view returns(
        uint256 share, 
        uint256 collatAmount, 
        uint256 collatPrice,
        uint256 usdValue
    ) {
        return getRedeemValues(_GEX.balanceOf(holder));
    }

    function getRedeemValues(uint256 amount) public view returns(
        uint256 share, 
        uint256 collatAmount, 
        uint256 collatPrice,
        uint256 usdValue
    ) {
        share = (1e18 * amount) / _GEX.totalSupply();
        collatAmount = (share * address(this).balance) / 1e18;
        collatPrice = IGenesisLiquidityPool(_natGLP).collateralPrice();
        usdValue = (collatAmount * collatPrice) / 1e18;
    }


    function _redeem(uint256 amountGEX) private {
        uint256 collatAmount = (amountGEX * address(this).balance) / _GEX.totalSupply();
        
        _GEX.burn(msg.sender, amountGEX);
        payable(msg.sender).transfer(collatAmount);
    }


    function _mint(uint256 amount) private {
        require(amount <= 1e24);
        _GEX.mint(address(this), amount);
    }
}

// File: IERC20ElasticSupply.sol



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

import "IERC20.sol";


/**
* @title IERC20ElasticSupply
* @author Geminon Protocol
* @dev Interface for the ERC20ElasticSupply contract
*/
interface IERC20ElasticSupply is IERC20 {

    function addMinter(address newMinter) external;
    function removeMinter(address minter) external;
    function mint(address to, uint256 amount) external;
    function burn(address from, uint256 amount) external;
    function maxAmountMintable() external view returns(uint256);
}

// File: IERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

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

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

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

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

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

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

// File: IGenesisLiquidityPool.sol



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

import "ICollectible.sol";


interface IGenesisLiquidityPool is ICollectible {

    // +++++++++++++++++++  PUBLIC STATE VARIABLES  +++++++++++++++++++++++++

    function initMintedAmount() external view returns(uint256);

    function poolWeight() external view returns(uint16);
    
    function mintedGEX() external view returns(int256);

    function balanceCollateral() external view returns(uint256);

    function balanceGEX() external view returns(uint256);
    
    function blockTimestampLast() external view returns(uint64);
    
    function lastCollatPrice() external view returns(uint256);
    
    function meanPrice() external view returns(uint256);
    
    function lastPrice() external view returns(uint256);
    
    function meanVolume() external view returns(uint256);
    
    function lastVolume() external view returns(uint256);

    function isMigrationRequested() external view returns(bool);
    
    function isRemoveRequested() external view returns(bool);    
    

    // ++++++++++++++++++++++++++  MIGRATION  +++++++++++++++++++++++++++++++

    function receiveMigration(uint256 amountGEX, uint256 amountCollateral, uint256 initMintedAmount) external;

    function bailoutMinter() external returns(uint256);

    function lendCollateral(uint256 amount) external returns(uint256);

    function repayCollateral(uint256 amount) external returns(uint256);

    
    // ++++++++++++++++++++++++  USER FUNCTIONS  ++++++++++++++++++++++++++++
    
    function mintSwap(uint256 inCollatAmount, uint256 minOutGEXAmount) external;

    function redeemSwap(uint256 inGEXAmount, uint256 minOutCollatAmount) external;
    
    
    // ++++++++++++++++++++  INFORMATIVE FUNCTIONS  +++++++++++++++++++++++++

    function collateralPrice() external view returns(uint256);

    function collateralQuote() external view returns(uint256);

    function getCollateralValue() external view returns(uint256);

    function GEXPrice() external view returns(uint256);

    function GEXQuote() external view returns(uint256);

    function amountFeeMint(uint256 amountGEX) external view returns(uint256);

    function amountFeeRedeem(uint256 amountGEX) external view returns(uint256);

    function getMintInfo(uint256 inCollatAmount) external view returns(
        uint256 collateralPriceUSD, 
        uint256 gexPriceUSD,
        uint256 collatQuote,
        uint256 gexQuote,
        uint256 fee,
        uint256 feeAmount,
        uint256 outGEXAmount,
        uint256 finalGEXPriceUSD,
        uint256 priceImpact
    );

    function getRedeemInfo(uint256 inGEXAmount) external view returns(
        uint256 collateralPriceUSD, 
        uint256 gexPriceUSD,
        uint256 collatQuote,
        uint256 gexQuote,
        uint256 fee,
        uint256 feeAmount,
        uint256 outCollatAmount,
        uint256 finalGEXPriceUSD,
        uint256 priceImpact
    );

    function amountOutGEX(uint256 inCollatAmount) external view returns(uint256);

    function amountOutCollateral(uint256 inGEXAmount) external view returns(uint256);

    function amountMint(uint256 outGEXAmount) external view returns(uint256);

    function amountBurn(uint256 inGEXAmount) external view returns(uint256);

    function variableFee(uint256 amountGEX, uint256 baseFee) external view returns(uint256);
}

// File: ICollectible.sol



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


/**
* @title ICollectible
* @author Geminon Protocol
* @notice Interface for smart contracts whose fees have
* to be collected by the FeeCollector contract.
*/
interface ICollectible {
    function collectFees() external returns(uint256);
}