// File: contracts/pool/PoolRegistry.sol



// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.4;

// Libraries
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

// Interfaces
import { IGemGlobalConfig } from "../interfaces/IGemGlobalConfig.sol";
import { IPoolFactory } from "../interfaces/IPoolFactory.sol";
import { IDefaultTokenRegistry } from "../interfaces/IDefaultTokenRegistry.sol";
import { ITokenRegistry } from "../savings/interfaces/ITokenRegistry.sol";
import { ISavingAccount } from "../savings/interfaces/ISavingAccount.sol";
import { IGlobalConfig } from "../savings/interfaces/IGlobalConfig.sol";
import { IBank } from "../savings/interfaces/IBank.sol";

// Imports
import { FixedPriceOracleNative } from "../oracle/FixedPriceOracleNative.sol";

enum PoolStatus {
    None,
    Cloned,
    Initialized,
    Configured
}

contract PoolRegistry {
    using Address for address;

    // constants
    address public constant NATIVE_TOKEN = 0x000000000000000000000000000000000000000E;
    uint256 public constant ONE_YEAR = 365 days;
    uint256 public constant MAX_MATURITY_DATE = 10 * ONE_YEAR; // 10 years
    uint256 public constant MIN_APR = 3; // 3%
    uint256 public constant MAX_APR = 300; // 300%

    // immutable
    IPoolFactory public immutable poolFactory;
    IGemGlobalConfig public gemGlobalConfig;

    // storage variables
    uint256 public nextPoolId;
    IDefaultTokenRegistry public defaultTokenRegistry;

    // <poolID> => <Pool>
    mapping(uint256 => Pool) public pools;

    // <creator> => <PoolIdsArray>
    // to maintain the pool-ids a creator has created
    mapping(address => uint256[]) public creatorToPools;

    struct Pool {
        address poolCreator; // Pool Creator
        address baseToken; // Base Token provided by pool-creator
        address globalConfig; // Savings - GlobalConfig.sol
        address savingAccount; // Savings - SavingAccount.sol
        address bank; // Savings - Bank.sol
        address accounts; // Savings - Accounts.sol
        address tokenRegistry; // Savings - TokenRegistry.sol
        address claim; // Savings - Claim.sol
        address fixedPriceOracle; // Fixed Price Oracle for Base Token
        PoolStatus poolStatus; // Pool Status, Clone=1 / Initialized=2 / Configured=3
        uint256 maturesOn; // Pool maturity date after that withdrawals allowed
    }

    // EVENTS
    event NewPoolCloned(uint256 indexed _poolId);
    event NewPoolInitialized(uint256 indexed _poolId);
    event NewPoolConfigured(uint256 indexed _poolId);

    constructor(
        IGemGlobalConfig _gemGlobalConfig,
        IPoolFactory _poolFactory,
        IDefaultTokenRegistry _defaultTokenRegistry
    ) {
        gemGlobalConfig = _gemGlobalConfig;
        poolFactory = _poolFactory;
        defaultTokenRegistry = _defaultTokenRegistry;
    }

    function createNewPoolStep1()
        external
        returns (
            uint256 newPoolId,
            address globalConfig,
            address savingAccount,
            address bank,
            address accounts,
            address tokenRegistry,
            address claim
        )
    {
        newPoolId = nextPoolId;
        (globalConfig, savingAccount, bank, accounts, tokenRegistry, claim) = poolFactory.createNewPool(newPoolId);
        pools[newPoolId] = Pool(
            msg.sender, // pool creator
            address(0), // zero baseToken
            globalConfig,
            savingAccount,
            bank,
            accounts,
            tokenRegistry,
            claim,
            address(0), // FixedPriceOracle, initiaized in step-2
            PoolStatus.Cloned,
            0 // maturityDate
        );
        nextPoolId++;

        // add poolId for creator
        creatorToPools[msg.sender].push(newPoolId);

        emit NewPoolCloned(newPoolId);
    }

    function createNewPoolStep2(
        uint256 _poolId,
        address _baseToken,
        uint256 _initTokenPriceInUSD8,
        uint256 _borrowLTV
    ) external payable {
        Pool storage pool = pools[_poolId];
        require(msg.sender == pool.poolCreator, "not a pool creator");
        require(_baseToken != address(0), "base token address is zero");
        require(pool.poolStatus == PoolStatus.Cloned, "pool should be Cloned");
        require(_baseToken.isContract(), "baseToken is not a contract");
        // avoid checking for FIN tokens while deploying DeFiner default pool
        if (_poolId != 0) {
            // ensure that the pool creator has some tokens of the given `_token`
            require(IERC20(_baseToken).balanceOf(pool.poolCreator) > 0, "pool creator not have tokens");
        }

        // 1. add all default token
        uint256 tokensLength = defaultTokenRegistry.getTokensLength();
        for (uint256 i = 0; i < tokensLength; i++) {
            address token = defaultTokenRegistry.tokens(i);
            (
                ,
                ,
                bool isSupportedOnCompound,
                address cToken,
                address chainLinkOracle,
                uint256 borrowLTV
            ) = defaultTokenRegistry.tokenInfo(token);
            // add token
            ITokenRegistry(pool.tokenRegistry).addTokenByPoolRegistry(
                token,
                isSupportedOnCompound,
                cToken,
                chainLinkOracle,
                borrowLTV
            );
            // approve token
            if (cToken != address(0) && token != NATIVE_TOKEN) {
                ISavingAccount(pool.savingAccount).approveAll(token);
            }
        }

        // 2. Create Fixed price oracle for token
        FixedPriceOracleNative fixedPriceOracleNative = new FixedPriceOracleNative(
            address(gemGlobalConfig),
            _baseToken,
            _initTokenPriceInUSD8
        );
        pool.fixedPriceOracle = address(fixedPriceOracleNative);

        // 3. add user's collateral token
        ITokenRegistry(pool.tokenRegistry).addTokenByPoolRegistry(
            _baseToken,
            false, // token is not supported on Compound
            address(0), // cToken address is zero as its not supported on Compound
            address(fixedPriceOracleNative),
            _borrowLTV
        );

        pool.baseToken = _baseToken;
        pool.poolStatus = PoolStatus.Initialized;

        // avoid taking fee when deploying DeFiner default pool with poolId=0
        if (_poolId != 0) {
            uint256 poolCreationFeeInNative = gemGlobalConfig.getPoolCreationFeeInNative();
            require(msg.value >= poolCreationFeeInNative, "insuffecient fee");
            uint256 extraAmt = msg.value - poolCreationFeeInNative;

            // send pool creation fee to deFinerCommunityFund
            gemGlobalConfig.deFinerCommunityFund().transfer(poolCreationFeeInNative);
            // send extra fee to the sender
            payable(msg.sender).transfer(extraAmt);
        }

        emit NewPoolInitialized(_poolId);
    }

    /**
     * @param _poolId pool id of the pool
     * @param _maturesOn maturity date when the pool matures (date time in epoch format)
     * @param _minBorrowAPRInPercent minimum borrow APR in percentage. ex: 3 for 3%
     * @param _maxBorrowAPRInPercent maximum borrow APR in percentage. ex: 150 for 150%
     * @param _tokens array of tokens for which mining speeds to be configured.
     * @param _depositMiningSpeeds deposit mining speeds of the tokens.
     * @param _borrowMiningSpeeds borrow mining speeds of the tokens.
     */
    function createNewPoolStep3(
        uint256 _poolId,
        uint256 _maturesOn,
        uint256 _minBorrowAPRInPercent,
        uint256 _maxBorrowAPRInPercent,
        address[] calldata _tokens,
        uint256[] calldata _depositMiningSpeeds,
        uint256[] calldata _borrowMiningSpeeds
    ) external {
        Pool storage pool = pools[_poolId];
        require(msg.sender == pool.poolCreator, "not a pool creator");
        require(pool.poolStatus == PoolStatus.Initialized, "pool should be Initialized");

        // _maturesOn == 0 (NONE) || _maturesOn > currentTime
        require(_maturesOn == 0 || _maturesOn > block.timestamp, "past maturity date");
        // _maturesOn must be less than 10 years from today
        require(_maturesOn <= block.timestamp + MAX_MATURITY_DATE, "invalid maturity");

        // arrays
        require(_tokens.length == _depositMiningSpeeds.length, "array length not match");
        require(_depositMiningSpeeds.length == _borrowMiningSpeeds.length, "array length not match");

        // borrowAPR
        require(_minBorrowAPRInPercent <= _maxBorrowAPRInPercent, "_minBorrowAPRInPercent > _maxBorrowAPRInPercent");
        require(_minBorrowAPRInPercent >= MIN_APR, "_minBorrowAPRInPercent is out-of-bound");
        require(_maxBorrowAPRInPercent <= MAX_APR, "_maxBorrowAPRInPercent is out-of-bound");

        pool.maturesOn = _maturesOn;

        // configure SavingAccount
        // -----------------------
        // baseToken is treated as miningToken
        ISavingAccount(pool.savingAccount).configure(pool.baseToken, pool.baseToken, _maturesOn);

        // Configure loan APR
        // -------------------
        uint256 rateCurveConstant = _minBorrowAPRInPercent * 10**16; // because 10^18 = 100%
        IGlobalConfig(pool.globalConfig).updateRateCurveConstant(rateCurveConstant);
        uint256 _maxBorrowAPR = _maxBorrowAPRInPercent * 10**16;
        IBank(pool.bank).configureMaxUtilToCalcBorrowAPR(_maxBorrowAPR);

        // update deposit/borrow mining speeds
        // ------------------------------------
        for (uint256 i = 0; i < _tokens.length; i++) {
            address token = _tokens[i];
            uint256 depositMiningSpeed = _depositMiningSpeeds[i];
            uint256 borrowMiningSpeed = _borrowMiningSpeeds[i];
            ITokenRegistry(pool.tokenRegistry).updateMiningSpeed(token, depositMiningSpeed, borrowMiningSpeed);
        }

        pool.poolStatus = PoolStatus.Configured;
        emit NewPoolConfigured(_poolId);
    }

    function isAnyPoolUninitialized(address _creator) public view returns (uint256[] memory unInitPools) {
        uint256[] memory poolIds = creatorToPools[_creator];
        uint256 len = poolIds.length;
        uint256 unInitPoolsCount = 0;
        for (uint256 i = 0; i < len; i++) {
            if (pools[poolIds[i]].poolStatus == PoolStatus.Configured) continue;
            unInitPoolsCount++;
        }

        unInitPools = new uint256[](unInitPoolsCount);

        uint256 j = 0;
        for (uint256 i = 0; i < len; i++) {
            if (pools[poolIds[i]].poolStatus == PoolStatus.Configured) continue;
            unInitPools[j++] = poolIds[i];
        }
    }

    function isPoolConfigured(uint256 _poolId) public view returns (bool) {
        Pool memory pool = pools[_poolId];
        return (pool.poolStatus == PoolStatus.Configured);
    }

    function getPoolStatus(uint256 _poolId) external view returns (uint8) {
        return uint8(pools[_poolId].poolStatus);
    }

    function getPoolsByCreator(address _creator) external view returns (uint256[] memory) {
        return creatorToPools[_creator];
    }
}

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



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.

        uint256 size;
        assembly {
            size := extcodesize(account)
        }
        return size > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCall(target, data, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        require(isContract(target), "Address: call to non-contract");

        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        require(isContract(target), "Address: static call to non-contract");

        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(isContract(target), "Address: delegate call to non-contract");

        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly

                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

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



// SPDX-License-Identifier: MIT

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 `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

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

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

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

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

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

// File: contracts/interfaces/IGemGlobalConfig.sol



// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.4;

interface IGemGlobalConfig {
    function finToken() external view returns (address);

    function governor() external view returns (address);

    function definerAdmin() external view returns (address);

    function nativeTokenOracleForPriceInUSD8() external view returns (address);

    function deFinerCommunityFund() external view returns (address payable);

    function getPoolCreationFeeInNative() external view returns (uint256);

    function getNativeTokenPriceInUSD8() external view returns (int256);

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

// File: contracts/interfaces/IPoolFactory.sol



// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.4;

interface IPoolFactory {
    function poolRegistry() external view returns (address);

    function createNewPool(uint256 _poolId)
        external
        returns (
            address globalConfig,
            address savingAccount,
            address bank,
            address accounts,
            address tokenRegistry,
            address claim
        );
}

// File: contracts/interfaces/IDefaultTokenRegistry.sol



// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.4;

interface IDefaultTokenRegistry {
    function tokenInfo(address _token)
        external
        view
        returns (
            uint8 index,
            uint8 decimals,
            bool isSupportedOnCompound,
            address cToken,
            address chainLinkOracle,
            uint256 borrowLTV
        );

    function tokens(uint256 _index) external view returns (address);

    function getTokensLength() external view returns (uint256 length);
}

// File: contracts/savings/interfaces/ITokenRegistry.sol



// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.4;

interface ITokenRegistry {
    function initialize(
        address _gemGlobalConfig,
        address _poolRegistry,
        address _globalConfig
    ) external;

    function tokenInfo(address _token)
        external
        view
        returns (
            uint8 index,
            uint8 decimals,
            bool enabled,
            bool _isSupportedOnCompound, // compiler warning
            address cToken,
            address chainLinkOracle,
            uint256 borrowLTV
        );

    function addTokenByPoolRegistry(
        address _token,
        bool _isSupportedOnCompound,
        address _cToken,
        address _chainLinkOracle,
        uint256 _borrowLTV
    ) external;

    function getTokenDecimals(address) external view returns (uint8);

    function getCToken(address) external view returns (address);

    function getCTokens() external view returns (address[] calldata);

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

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

    function isSupportedOnCompound(address) external view returns (bool);

    function getTokens() external view returns (address[] calldata);

    function getTokenInfoFromAddress(address _token)
        external
        view
        returns (
            uint8,
            uint256,
            uint256,
            uint256
        );

    function getTokenInfoFromIndex(uint256 index)
        external
        view
        returns (
            address,
            uint256,
            uint256,
            uint256
        );

    function getTokenIndex(address _token) external view returns (uint8);

    function addressFromIndex(uint256 index) external view returns (address);

    function isTokenExist(address _token) external view returns (bool isExist);

    function isTokenEnabled(address _token) external view returns (bool);

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

    function updateMiningSpeed(
        address _token,
        uint256 _depositeMiningSpeed,
        uint256 _borrowMiningSpeed
    ) external;
}

// File: contracts/savings/interfaces/ISavingAccount.sol



// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.4;

interface ISavingAccount {
    function initialize(
        address[] memory _tokenAddresses,
        address[] memory _cTokenAddresses,
        address _globalConfig,
        address _poolRegistry,
        uint256 _poolId
    ) external;

    function configure(
        address _baseToken,
        address _miningToken,
        uint256 _maturesOn
    ) external;

    function toCompound(address, uint256) external;

    function fromCompound(address, uint256) external;

    function approveAll(address _token) external;
}

// File: contracts/savings/interfaces/IGlobalConfig.sol



// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.4;

import "./ITokenRegistry.sol";
import "./IBank.sol";
import "./ISavingAccount.sol";
import "./IAccounts.sol";
import "./IConstant.sol";

interface IGlobalConfig {
    function initialize(
        address _gemGlobalConfig,
        address _bank,
        address _savingAccount,
        address _tokenRegistry,
        address _accounts,
        address _poolRegistry
    ) external;

    function tokenRegistry() external view returns (ITokenRegistry);

    function chainLink() external view returns (address);

    function bank() external view returns (IBank);

    function savingAccount() external view returns (ISavingAccount);

    function accounts() external view returns (IAccounts);

    function maxReserveRatio() external view returns (uint256);

    function midReserveRatio() external view returns (uint256);

    function minReserveRatio() external view returns (uint256);

    function rateCurveConstant() external view returns (uint256);

    function compoundSupplyRateWeights() external view returns (uint256);

    function compoundBorrowRateWeights() external view returns (uint256);

    function deFinerRate() external view returns (uint256);

    function liquidationThreshold() external view returns (uint256);

    function liquidationDiscountRatio() external view returns (uint256);

    function governor() external view returns (address);

    function updateRateCurveConstant(uint256 _rateCurveConstant) external;
}

// File: contracts/savings/interfaces/IBank.sol



// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.4;

interface IBank {
    /* solhint-disable func-name-mixedcase */
    function BLOCKS_PER_YEAR() external view returns (uint256);

    function initialize(address _globalConfig, address _poolRegistry) external;

    function newRateIndexCheckpoint(address) external;

    function deposit(
        address _to,
        address _token,
        uint256 _amount
    ) external;

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

    function borrow(
        address _from,
        address _token,
        uint256 _amount
    ) external;

    function repay(
        address _to,
        address _token,
        uint256 _amount
    ) external returns (uint256);

    function getDepositAccruedRate(address _token, uint256 _depositRateRecordStart) external view returns (uint256);

    function getBorrowAccruedRate(address _token, uint256 _borrowRateRecordStart) external view returns (uint256);

    function depositeRateIndex(address _token, uint256 _blockNum) external view returns (uint256);

    function borrowRateIndex(address _token, uint256 _blockNum) external view returns (uint256);

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

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

    function updateMining(address _token) external;

    function updateDepositFINIndex(address _token) external;

    function updateBorrowFINIndex(address _token) external;

    function depositFINRateIndex(address, uint256) external view returns (uint256);

    function borrowFINRateIndex(address, uint256) external view returns (uint256);

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

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

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

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

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

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

    function getTokenState(address _token)
        external
        view
        returns (
            uint256 deposits,
            uint256 loans,
            uint256 reserveBalance,
            uint256 remainingAssets
        );

    function configureMaxUtilToCalcBorrowAPR(uint256 _maxBorrowAPR) external;
}

// File: contracts/oracle/FixedPriceOracleNative.sol



// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.4;

import { Governable } from "../governance/Governable.sol";
import { AggregatorInterface } from "@chainlink/contracts/src/v0.8/interfaces/AggregatorInterface.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";

/**
 * @title FixedPriceOracle to store fix price for the token in USD rate and return in native token.
 * The USD price is in 8 decimals and converted in native token
 */
contract FixedPriceOracleNative is AggregatorInterface, Governable {
    using SafeCast for uint256;

    // Token for which the price is stored
    address public token;
    // price of the token in USD in 8 decimals meaning `1 USD = 10^8`
    uint256 public price;

    uint256 public lastUpdated;

    event PriceUpdated(address indexed _token, uint256 _newPrice);

    constructor(
        address _gemGlobalConfig,
        address _token,
        uint256 _initPriceInUSD8
    ) {
        _init(_gemGlobalConfig);
        token = _token;
        price = _initPriceInUSD8;
        emit PriceUpdated(token, price);
    }

    /**
     * @dev Governance can update the price of the token
     * @param _token token address
     * @param _newPriceInUSD8 new price of the token in USD with 8 decimal places
     */
    function updatePrice(address _token, uint256 _newPriceInUSD8) external onlyGov {
        // for safety checking that the token address is correct
        require(_token == token, "FixedPriceOracle: token mismatch");
        price = _newPriceInUSD8;
        lastUpdated = block.timestamp;
        emit PriceUpdated(_token, _newPriceInUSD8);
    }

    /**
     * @dev Convert the USD price with respect to native token price
     * @return priceInNative price of the token in native currency
     */
    function latestAnswer() external view override returns (int256 priceInNative) {
        int256 nativeTokenPriceInUSD8 = gemGlobalConfig.getNativeTokenPriceInUSD8();
        int256 tokenPriceInUSD8 = price.toInt256();
        priceInNative = (1e18 * tokenPriceInUSD8) / nativeTokenPriceInUSD8;
    }

    function latestTimestamp() external view override returns (uint256) {
        // just returning current time as this oracle should not expire
        return block.timestamp;
    }

    /* solhint-disable no-empty-blocks */

    function latestRound() external view override returns (uint256) {
        // NO BODY
    }

    function getAnswer(uint256 roundId) external view override returns (int256) {
        // NO BODY
    }

    function getTimestamp(uint256 roundId) external view override returns (uint256) {
        // NO BODY
    }

    /* solhint-enable no-empty-blocks */
}

// File: contracts/savings/interfaces/IAccounts.sol



// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.4;

interface IAccounts {
    function initialize(address _globalConfig, address _gemGlobalConfig) external;

    function deposit(
        address,
        address,
        uint256
    ) external;

    function borrow(
        address,
        address,
        uint256
    ) external;

    function getBorrowPrincipal(address, address) external view returns (uint256);

    function withdraw(
        address,
        address,
        uint256
    ) external returns (uint256);

    function repay(
        address,
        address,
        uint256
    ) external returns (uint256);

    function getDepositPrincipal(address _accountAddr, address _token) external view returns (uint256);

    function getDepositBalanceCurrent(address _token, address _accountAddr) external view returns (uint256);

    function getDepositInterest(address _account, address _token) external view returns (uint256);

    function getBorrowInterest(address _accountAddr, address _token) external view returns (uint256);

    function getBorrowBalanceCurrent(address _token, address _accountAddr)
        external
        view
        returns (uint256 borrowBalance);

    function getBorrowETH(address _accountAddr) external view returns (uint256 borrowETH);

    function getDepositETH(address _accountAddr) external view returns (uint256 depositETH);

    function getBorrowPower(address _borrower) external view returns (uint256 power);

    function liquidate(
        address _liquidator,
        address _borrower,
        address _borrowedToken,
        address _collateralToken
    ) external returns (uint256, uint256);

    function claim(address _account) external returns (uint256);

    function claimForToken(address _account, address _token) external returns (uint256);
}

// File: contracts/savings/interfaces/IConstant.sol



// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.4;

/* solhint-disable */
interface IConstant {
    function ETH_ADDR() external view returns (address);

    function INT_UNIT() external view returns (uint256);

    function ACCURACY() external view returns (uint256);

    function BLOCKS_PER_YEAR() external view returns (uint256);
}
/* solhint-enable */

// File: contracts/governance/Governable.sol



// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.4;

import { IGemGlobalConfig } from "../interfaces/IGemGlobalConfig.sol";

abstract contract Governable {
    IGemGlobalConfig public gemGlobalConfig;

    modifier onlyGov() {
        require(msg.sender == governor(), "Governable: not authorized");
        _;
    }

    function governor() public view returns (address) {
        return gemGlobalConfig.governor();
    }

    function _init(address _gemGlobalConfig) internal {
        gemGlobalConfig = IGemGlobalConfig(_gemGlobalConfig);
    }
}

// File: @chainlink/contracts/src/v0.8/interfaces/AggregatorInterface.sol



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

interface AggregatorInterface {
  function latestAnswer() external view returns (int256);

  function latestTimestamp() external view returns (uint256);

  function latestRound() external view returns (uint256);

  function getAnswer(uint256 roundId) external view returns (int256);

  function getTimestamp(uint256 roundId) external view returns (uint256);

  event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt);

  event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt);
}

// File: @openzeppelin/contracts/utils/math/SafeCast.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
 * checks.
 *
 * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
 * easily result in undesired exploitation or bugs, since developers usually
 * assume that overflows raise errors. `SafeCast` restores this intuition by
 * reverting the transaction when such an operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 *
 * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
 * all math on `uint256` and `int256` and then downcasting.
 */
library SafeCast {
    /**
     * @dev Returns the downcasted uint224 from uint256, reverting on
     * overflow (when the input is greater than largest uint224).
     *
     * Counterpart to Solidity's `uint224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     */
    function toUint224(uint256 value) internal pure returns (uint224) {
        require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits");
        return uint224(value);
    }

    /**
     * @dev Returns the downcasted uint128 from uint256, reverting on
     * overflow (when the input is greater than largest uint128).
     *
     * Counterpart to Solidity's `uint128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     */
    function toUint128(uint256 value) internal pure returns (uint128) {
        require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits");
        return uint128(value);
    }

    /**
     * @dev Returns the downcasted uint96 from uint256, reverting on
     * overflow (when the input is greater than largest uint96).
     *
     * Counterpart to Solidity's `uint96` operator.
     *
     * Requirements:
     *
     * - input must fit into 96 bits
     */
    function toUint96(uint256 value) internal pure returns (uint96) {
        require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits");
        return uint96(value);
    }

    /**
     * @dev Returns the downcasted uint64 from uint256, reverting on
     * overflow (when the input is greater than largest uint64).
     *
     * Counterpart to Solidity's `uint64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     */
    function toUint64(uint256 value) internal pure returns (uint64) {
        require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits");
        return uint64(value);
    }

    /**
     * @dev Returns the downcasted uint32 from uint256, reverting on
     * overflow (when the input is greater than largest uint32).
     *
     * Counterpart to Solidity's `uint32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     */
    function toUint32(uint256 value) internal pure returns (uint32) {
        require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits");
        return uint32(value);
    }

    /**
     * @dev Returns the downcasted uint16 from uint256, reverting on
     * overflow (when the input is greater than largest uint16).
     *
     * Counterpart to Solidity's `uint16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     */
    function toUint16(uint256 value) internal pure returns (uint16) {
        require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits");
        return uint16(value);
    }

    /**
     * @dev Returns the downcasted uint8 from uint256, reverting on
     * overflow (when the input is greater than largest uint8).
     *
     * Counterpart to Solidity's `uint8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits.
     */
    function toUint8(uint256 value) internal pure returns (uint8) {
        require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits");
        return uint8(value);
    }

    /**
     * @dev Converts a signed int256 into an unsigned uint256.
     *
     * Requirements:
     *
     * - input must be greater than or equal to 0.
     */
    function toUint256(int256 value) internal pure returns (uint256) {
        require(value >= 0, "SafeCast: value must be positive");
        return uint256(value);
    }

    /**
     * @dev Returns the downcasted int128 from int256, reverting on
     * overflow (when the input is less than smallest int128 or
     * greater than largest int128).
     *
     * Counterpart to Solidity's `int128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     *
     * _Available since v3.1._
     */
    function toInt128(int256 value) internal pure returns (int128) {
        require(value >= type(int128).min && value <= type(int128).max, "SafeCast: value doesn't fit in 128 bits");
        return int128(value);
    }

    /**
     * @dev Returns the downcasted int64 from int256, reverting on
     * overflow (when the input is less than smallest int64 or
     * greater than largest int64).
     *
     * Counterpart to Solidity's `int64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     *
     * _Available since v3.1._
     */
    function toInt64(int256 value) internal pure returns (int64) {
        require(value >= type(int64).min && value <= type(int64).max, "SafeCast: value doesn't fit in 64 bits");
        return int64(value);
    }

    /**
     * @dev Returns the downcasted int32 from int256, reverting on
     * overflow (when the input is less than smallest int32 or
     * greater than largest int32).
     *
     * Counterpart to Solidity's `int32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     *
     * _Available since v3.1._
     */
    function toInt32(int256 value) internal pure returns (int32) {
        require(value >= type(int32).min && value <= type(int32).max, "SafeCast: value doesn't fit in 32 bits");
        return int32(value);
    }

    /**
     * @dev Returns the downcasted int16 from int256, reverting on
     * overflow (when the input is less than smallest int16 or
     * greater than largest int16).
     *
     * Counterpart to Solidity's `int16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     *
     * _Available since v3.1._
     */
    function toInt16(int256 value) internal pure returns (int16) {
        require(value >= type(int16).min && value <= type(int16).max, "SafeCast: value doesn't fit in 16 bits");
        return int16(value);
    }

    /**
     * @dev Returns the downcasted int8 from int256, reverting on
     * overflow (when the input is less than smallest int8 or
     * greater than largest int8).
     *
     * Counterpart to Solidity's `int8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits.
     *
     * _Available since v3.1._
     */
    function toInt8(int256 value) internal pure returns (int8) {
        require(value >= type(int8).min && value <= type(int8).max, "SafeCast: value doesn't fit in 8 bits");
        return int8(value);
    }

    /**
     * @dev Converts an unsigned uint256 into a signed int256.
     *
     * Requirements:
     *
     * - input must be less than or equal to maxInt256.
     */
    function toInt256(uint256 value) internal pure returns (int256) {
        // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
        require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256");
        return int256(value);
    }
}