// File: contracts/OptionVault.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
import "./lib/OptionLib.sol";
import "./PriceOracle.sol";
import "./interfaces/IOptionVault.sol";
import "./interfaces/IAMM.sol";

/**
 * @notice OptionVault contract is vaults for basic on-chain option contracts.
 * It manages option tokens.
 */
contract OptionVault is IOptionVault, ERC1155, IERC1155Receiver {
    using OptionLib for OptionLib.OptionInfo;

    /// @dev operator address
    address operator;

    /// @dev AMM address
    address ammAddress;

    /// @dev option info
    OptionLib.OptionInfo public optionInfo;

    /// @dev price oracle contract
    PriceOracle priceOracle;

    // events
    event ExpiryCreated(uint256 indexed expiryId, uint64 expiry);
    event SeriesCreated(uint256 indexed expiryId, uint256 seriesId, uint128 strike, bool isPut);

    event AccountCreated(uint256 accountId, address indexed account);
    event VaultDeposited(uint256 indexed accountId, uint256 expiryId, uint128 amount);
    event VaultWithdrawn(uint256 indexed accountId, uint256 expiryId, uint128 amount);
    event Written(uint256 indexed accountId, uint256 seriesId, uint128 amount, address recipient);
    event Unlocked(uint256 indexed accountId, uint256 seriesId, uint128 amount, address holder);
    event Claimed(uint256 indexed seriesId, uint128 amount, uint128 profit);
    event Settled(uint256 accountId, uint256 indexed seriesId, uint128 profit);
    event Hedged(uint32 tickId, int256 tickDelta, int256 hedgePosition);
    event Liquidated(uint256 accountId, uint256 seriesId);
    event ConfigUpdated(uint8 key, uint128 value);

    // modifiers
    modifier onlyVaultOwner(uint256 _accountId) {
        // if accountId is id of trader's vaults, check owner
        // if AMM vaults, check caller is AMM contract
        require(
            (msg.sender == optionInfo.getVaultOwner(_accountId) &&
                _accountId >= OptionLib.MIN_VAULT_ID &&
                _accountId < optionInfo.vaultCount) ||
                (msg.sender == ammAddress && _accountId < OptionLib.MIN_VAULT_ID),
            "V1"
        );
        _;
    }

    modifier onlyAMMVault(uint256 _accountId) {
        require(_accountId < OptionLib.MIN_VAULT_ID, "V2");
        _;
    }

    modifier notAMMVault(uint256 _accountId) {
        require(_accountId >= OptionLib.MIN_VAULT_ID, "V3");
        _;
    }

    modifier onlyOperator() {
        require(msg.sender == operator, "V4");
        _;
    }

    modifier onlyAMM() {
        require(msg.sender == ammAddress, "V5");
        _;
    }

    constructor(
        string memory _uri,
        address _aggregator,
        address _collateral,
        address _underlying,
        address _priceOracle,
        address _operator,
        address _lendingPool
    ) ERC1155(_uri) {
        optionInfo.aggregator = _aggregator;
        priceOracle = PriceOracle(_priceOracle);

        operator = _operator;

        optionInfo.init(_collateral, _underlying, _lendingPool);
    }

    function onERC1155Received(
        address _operator,
        address from,
        uint256 id,
        uint256 value,
        bytes memory data
    ) external override(IERC1155Receiver) returns (bytes4) {
        return bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"));
    }

    function onERC1155BatchReceived(
        address _operator,
        address from,
        uint256[] memory ids,
        uint256[] memory values,
        bytes memory data
    ) external override(IERC1155Receiver) returns (bytes4) {
        return bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"));
    }

    function isApprovedForAll(address account, address _operator) public view virtual override returns (bool) {
        return super.isApprovedForAll(account, _operator) || _operator == address(this) || _operator == ammAddress;
    }

    /**
     * @notice set AMM address
     * @param _ammAddress amm address
     */
    function setAMMAddress(address _ammAddress) external {
        require(ammAddress == address(0));
        ammAddress = _ammAddress;
        setApprovalForAll(ammAddress, true);
    }

    /**
     * @notice set IV by AMM contract
     * @param _seriesId series id
     * @param _iv new implied volatility
     */
    function setIV(uint256 _seriesId, uint128 _iv) external override(IOptionVault) onlyAMM {
        optionInfo.setIV(_seriesId, _iv);
    }

    /**
     * @notice create new vault
     */
    function createAccount() public override(IOptionVault) returns (uint256) {
        uint256 accountId = optionInfo.createAccount(msg.sender);

        emit AccountCreated(accountId, msg.sender);

        return accountId;
    }

    /**
     * @notice deposit collateral to the vault
     * @param _accountId vault id
     * @param _collateral amount to deposit scaled by 1e6
     */
    function deposit(
        uint256 _accountId,
        uint256 _expiryId,
        uint128 _collateral
    ) external override(IOptionVault) onlyVaultOwner(_accountId) {
        require(_collateral > 0, "V9");

        optionInfo.deposit(_accountId, _expiryId, _collateral);

        IERC20(optionInfo.tokens.collateral).transferFrom(msg.sender, address(this), _collateral);

        emit VaultDeposited(_accountId, _expiryId, _collateral);
    }

    /**
     * @notice withdraw collateral from the vault
     * @param _accountId vault id
     * @param _expiryId expiry id
     * @param _collateral amount to withdraw scaled by 1e6
     */
    function withdraw(
        uint256 _accountId,
        uint256 _expiryId,
        uint128 _collateral
    ) external override(IOptionVault) onlyVaultOwner(_accountId) {
        require(_collateral > 0, "V9");

        uint128 spot = getPrice();

        optionInfo.withdraw(_accountId, _expiryId, _collateral, spot);

        IERC20(optionInfo.tokens.collateral).transfer(msg.sender, _collateral);

        emit VaultWithdrawn(_accountId, _expiryId, _collateral);
    }

    /**
     * @notice close short position and withdraw unrequired collateral from the vault
     * @param _accountId account id
     * @param _seriesId series id to close
     * @param _amount the amount to close
     * @param _cRatio the final IM ratio(IM / collateral)
     */
    function closeShortPosition(
        uint256 _accountId,
        uint256 _seriesId,
        uint128 _amount,
        uint128 _cRatio
    ) external override(IOptionVault) onlyVaultOwner(_accountId) returns (uint128) {
        require(0 < _cRatio && _cRatio <= 1e6, "V6");

        if (_amount > 0) {
            require(balanceOf(msg.sender, _seriesId) >= _amount, "V7");

            // unlock options
            optionInfo.unlock(_accountId, _seriesId, _amount);

            _burn(msg.sender, _seriesId, _amount);

            emit Unlocked(_accountId, _seriesId, _amount, msg.sender);
        }

        bool isPool = _accountId < OptionLib.MIN_VAULT_ID;

        uint256 expiryId = optionInfo.serieses[_seriesId].expiryId;
        uint128 unrequiredCollateral = optionInfo.withdrawUnrequiredCollateral(
            _accountId,
            expiryId,
            getPrice(),
            _cRatio,
            isPool
        );

        if (unrequiredCollateral > 0) {
            IERC20(optionInfo.tokens.collateral).transfer(msg.sender, unrequiredCollateral);

            emit VaultWithdrawn(_accountId, expiryId, unrequiredCollateral);
        }

        return unrequiredCollateral;
    }

    /**
     * @notice lock collateral and mint option tokens
     * @param _accountId vault id
     * @param _seriesId option series id
     * @param _amount amount to write scaled by 1e8
     * @param _recipient recipient of option tokens
     */
    function write(
        uint256 _accountId,
        uint256 _seriesId,
        uint128 _amount,
        address _recipient
    ) external override(IOptionVault) onlyVaultOwner(_accountId) {
        require(_amount > 0, "V9");

        uint128 spot = getPrice();

        // write options
        optionInfo.write(_accountId, _seriesId, _amount, spot);

        _mint(_recipient, _seriesId, _amount, "");

        emit Written(_accountId, _seriesId, _amount, _recipient);
    }

    /**
     * @notice deposit collateral and write options in one transaction
     * @param _accountId vault id
     * @param _seriesId option series id
     * @param _cRatio _collateral ratio of Initial Margin
     * @param _amount amount to write scaled by 1e8
     * @param _recipient recipient of option tokens
     */
    function depositAndWrite(
        uint256 _accountId,
        uint256 _seriesId,
        uint128 _cRatio,
        uint128 _amount,
        address _recipient
    ) public override(IOptionVault) onlyVaultOwner(_accountId) returns (uint128) {
        require(_amount > 0, "V9");
        require(0 < _cRatio && _cRatio <= 1e6, "V6");

        bool isPool = _accountId < OptionLib.MIN_VAULT_ID;

        // deposit collateral and write options
        (uint256 expiryId, uint128 collateral) = optionInfo.depositAndWrite(
            _accountId,
            _seriesId,
            _amount,
            _cRatio,
            getPrice(),
            isPool
        );

        IERC20(optionInfo.tokens.collateral).transferFrom(msg.sender, address(this), collateral);

        _mint(_recipient, _seriesId, _amount, "");

        emit VaultDeposited(_accountId, expiryId, collateral);
        emit Written(_accountId, _seriesId, _amount, _recipient);

        return collateral;
    }

    /**
     * @notice add long position to calculate net delta
     */
    function addLong(
        uint256 _accountId,
        uint256 _expiryId,
        uint256 _seriesId,
        uint128 _amount
    ) external override(IOptionVault) onlyVaultOwner(_accountId) {
        require(_amount > 0, "V9");

        uint128 longSize = optionInfo.addLong(_accountId, _expiryId, _seriesId, _amount);

        require(balanceOf(msg.sender, _seriesId) >= longSize, "V10");
    }

    /**
     * @notice remove long position to calculate net delta
     */
    function removeLong(
        uint256 _accountId,
        uint256 _expiryId,
        uint256 _seriesId,
        uint128 _amount
    ) external override(IOptionVault) onlyVaultOwner(_accountId) {
        require(_amount > 0, "V9");

        optionInfo.removeLong(_accountId, _expiryId, _seriesId, _amount);
    }

    /**
     * @notice swap collateral asset for underling asset to hedge vault's delta
     * @param _accountId the id of target vault
     * @param _underlyingAmount amount of underlying asset scaled by 1e8
     * @param _collateralAmount amount of collateral asset scaled by 1e6
     */
    function addUnderlyingLong(
        uint32 _accountId,
        uint256 _expiryId,
        uint256 _underlyingAmount,
        uint256 _collateralAmount
    ) external onlyAMMVault(_accountId) {
        uint128 spot = getPrice();

        // calculate vault's net delta
        int256 vaultDelta = optionInfo.calculateVaultDelta(_accountId, _expiryId, spot);

        // swap collateral asset for underling asset
        int256 hedgePosition = optionInfo.addUnderlyingLong(
            _accountId,
            _expiryId,
            spot,
            vaultDelta,
            _underlyingAmount,
            _collateralAmount
        );

        emit Hedged(_accountId, vaultDelta, hedgePosition);
    }

    /**
     * @notice swap underling asset for collateral asset to hedge vaults' delta
     * @param _accountId the id of target vault
     * @param _underlyingAmount amount of underlying asset scaled by 1e8
     * @param _collateralAmount amount of collateral asset scaled by 1e6
     */
    function addUnderlyingShort(
        uint32 _accountId,
        uint256 _expiryId,
        uint256 _underlyingAmount,
        uint256 _collateralAmount
    ) external onlyAMMVault(_accountId) {
        uint128 spot = getPrice();

        // calculate vault's net delta
        int256 vaultDelta = optionInfo.calculateVaultDelta(_accountId, _expiryId, spot);

        // swap underling asset for collateral asset
        int256 hedgePosition = optionInfo.addUnderlyingShort(
            _accountId,
            _expiryId,
            spot,
            vaultDelta,
            _underlyingAmount,
            _collateralAmount
        );

        emit Hedged(_accountId, vaultDelta, hedgePosition);
    }

    /**
     * @notice repay all underlying asset and withdraw collateral from AAVE
     * @param _repayAmount amount to repay
     */
    function redeemCollateralFromLendingPool(uint128 _repayAmount) external onlyOperator {
        optionInfo.redeemCollateralFromLendingPool(
            _repayAmount,
            getPrice(),
            msg.sender,
            address(IAMM(ammAddress).feePool())
        );
    }

    /**
     * @notice burn options and get collateral from a vault that requires liquidation
     * to save the profit of option holders.
     * @param _accountId vault id
     * @param _seriesId option series id
     * @param _amount amount to liquidate scaled by 1e8
     */
    function liquidate(
        uint256 _accountId,
        uint256 _seriesId,
        uint128 _amount
    ) external notAMMVault(_accountId) {
        require(_amount > 0, "V9");

        require(balanceOf(msg.sender, _seriesId) >= _amount, "V7");

        uint128 price = getPrice();

        uint128 reward = optionInfo.liquidate(_accountId, _seriesId, _amount, price);

        IERC20(optionInfo.tokens.collateral).transfer(msg.sender, reward);

        _burn(msg.sender, _seriesId, _amount);

        emit Liquidated(_accountId, _seriesId);
    }

    /**
     * @notice claim profit of some amount of expired option contracts.
     * @param _seriesId option series id
     * @param _size option size to claim profit scaled by 1e8
     */
    function claim(uint256 _seriesId, uint128 _size) external override(IOptionVault) returns (uint128) {
        IOptionVault.OptionSeries storage series = optionInfo.serieses[_seriesId];

        require(balanceOf(msg.sender, _seriesId) >= _size, "V7");

        uint256 price = getExpiryPrice(series.expiryId);

        uint128 payout = optionInfo.claimProfit(_seriesId, _size, uint128(price));

        // burn options
        _burn(msg.sender, _seriesId, _size);

        // send payout to option holder
        IERC20(optionInfo.tokens.collateral).transfer(msg.sender, payout);

        emit Claimed(_seriesId, _size, payout);

        return payout;
    }

    /**
     * @notice settle a vault
     * fix the payout of an expired vault and send collateral in the vault to vault's owner
     * @param _accountId vault id
     * @param _expiryId option series id
     */
    function settleVault(uint256 _accountId, uint256 _expiryId) external override(IOptionVault) returns (uint128) {
        // anyone can settle trader's vault, but only AMM contract can settle AMM's vault
        require(
            (_accountId >= OptionLib.MIN_VAULT_ID && _accountId < optionInfo.vaultCount) ||
                (msg.sender == ammAddress && _accountId < OptionLib.MIN_VAULT_ID),
            "V8"
        );

        uint256 price = getExpiryPrice(_expiryId);

        uint128 settledAmount = optionInfo.settle(_accountId, _expiryId, uint128(price));

        if (settledAmount > 0) {
            // send all vault's collateral to owner
            if (_accountId < OptionLib.MIN_VAULT_ID) {
                IERC20(optionInfo.tokens.collateral).transfer(ammAddress, settledAmount);
            } else {
                IERC20(optionInfo.tokens.collateral).transfer(optionInfo.getVaultOwner(_accountId), settledAmount);
            }
        }

        emit Settled(_accountId, _expiryId, settledAmount);

        return settledAmount;
    }

    ////////////////////////
    // Wrapper Functions //
    ////////////////////////

    function makeShortPosition(
        uint256 _accountId,
        uint256 _seriesId,
        uint128 _cRatio,
        uint128 _amount,
        uint128 _minFee
    ) external {
        uint256 accountId = _accountId;

        if (accountId == 0) {
            accountId = createAccount();
        }

        depositAndWrite(accountId, _seriesId, _cRatio, _amount, address(this));

        uint128 premium = IAMM(ammAddress).sell(_seriesId, _amount, _minFee);

        IERC20(optionInfo.tokens.collateral).transfer(msg.sender, premium);
    }

    ////////////////////////
    // Operator Functions //
    ////////////////////////

    /**
     * @notice create new expiration
     * @param _expiry expiration
     * @param _strikes strike prices
     * @param _callIVs initial call ivs
     * @param _putIVs initial put ivs
     */
    function createExpiry(
        uint64 _expiry,
        uint64[] memory _strikes,
        uint64[] memory _callIVs,
        uint64[] memory _putIVs
    ) external onlyOperator {
        uint128 expiryId = optionInfo.createExpiry(_expiry);

        for (uint256 i = 0; i < _strikes.length; i++) {
            createSeries(expiryId, _strikes[i], false, _callIVs[i]);
            createSeries(expiryId, _strikes[i], true, _putIVs[i]);
        }

        emit ExpiryCreated(expiryId, _expiry);
    }

    /**
     * @notice update a config value
     */
    function setConfig(uint8 _key, uint128 _value) external onlyOperator {
        require(_value > 0);
        optionInfo.setConfig(_key, _value);

        // emit event
        emit ConfigUpdated(_key, _value);
    }

    /**
     * @notice set new operator
     * @param _operator operator address
     */
    function setNewOperator(address _operator) external onlyOperator {
        operator = _operator;
    }

    //////////////////////
    // Getter Functions //
    //////////////////////

    /**
     * @notice get an expiration
     */
    function getExpiration(uint256 _expiryId)
        external
        view
        override(IOptionVault)
        returns (IOptionVault.Expiration memory)
    {
        return optionInfo.expiries[_expiryId];
    }

    /**
     * @notice get an option series
     */
    function getOptionSeries(uint256 _seriesId)
        external
        view
        override(IOptionVault)
        returns (IOptionVault.OptionSeriesView memory)
    {
        return optionInfo.getOptionSeriesView(_seriesId);
    }

    /**
     * @notice get list of live option serieses
     */
    function getLiveOptionSerieses() external view override(IOptionVault) returns (IOptionVault.Expiration[] memory) {
        return optionInfo.getLiveOptionSerieses();
    }

    /**
     * @notice get timestamp of the last expiry
     */
    function getLastExpiry() external view override(IOptionVault) returns (uint64) {
        return optionInfo.expiries[optionInfo.expiryCount - 1].expiry;
    }

    /**
     * @notice get total collateral value of a vault
     * @param _accountId vault id
     */
    function getCollateralValueQuote(uint256 _accountId) external view override(IOptionVault) returns (uint128) {
        return optionInfo.getCollateralValueQuote(_accountId, getPrice());
    }

    /**
     * @notice get required margin of a vault
     * there are 3 margin levels
     * 1. Maintenance Margin: vaults can be liquidated if the collateral is lower than MM
     * 2. Initial Margin: collateral must be greater than IM to write options
     * 3. Safe Margin: enough margin for delta hedging
     * @param _accountId vault id
     * @param _expiryId expiry id
     * @param _marginLevel margin level
     */
    function getRequiredMargin(
        uint256 _accountId,
        uint256 _expiryId,
        IOptionVault.MarginLevel _marginLevel
    ) external view override(IOptionVault) returns (uint128) {
        uint128 price = getPrice();

        return optionInfo.getRequiredMargin(_accountId, _expiryId, price, _marginLevel);
    }

    /**
     * @notice calculate required margin for a series
     * @param _seriesId series id
     * @param _amount amount of options. plus for short and minus for long
     * @param _marginLevel margin level
     */
    function calRequiredMarginForASeries(
        uint256 _seriesId,
        int128 _amount,
        IOptionVault.MarginLevel _marginLevel
    ) external view override(IOptionVault) returns (uint128) {
        IOptionVault.OptionSeries memory series = optionInfo.serieses[_seriesId];

        return optionInfo.getRequiredMarginForASeries(series.expiryId, _seriesId, getPrice(), _amount, _marginLevel);
    }

    /**
     * @notice get total payout of a vault
     * @param _accountId vault id
     * @param _expiryId expiry id
     */
    function getTotalPayout(uint256 _accountId, uint256 _expiryId)
        external
        view
        override(IOptionVault)
        returns (uint128)
    {
        return optionInfo.getTotalPayout(_accountId, _expiryId, getPrice(), false);
    }

    /**
     * @notice get liquidatable amount of a vault
     * @param _accountId vault id
     * @param _seriesId series id
     */
    function getLiquidatableAmount(uint256 _accountId, uint256 _seriesId) external view returns (uint128 limit) {
        return optionInfo.getLiquidatableAmount(_accountId, _seriesId, getPrice());
    }

    function getAccount(uint256 _accountId) external view override(IOptionVault) returns (AccountView memory) {
        Account storage account = optionInfo.accounts[_accountId];
        return AccountView(account.owner, account.settledCount);
    }

    function getVault(uint256 _accountId, uint256 _expiryId)
        external
        view
        override(IOptionVault)
        returns (VaultView memory)
    {
        Vault storage vault = optionInfo.accounts[_accountId].vaults[_expiryId];
        return
            VaultView(
                optionInfo.accounts[_accountId].owner,
                vault.isSettled,
                vault.collateral,
                vault.hedgePosition,
                vault.shortLiquidity
            );
    }

    /**
     * @notice get position size
     * @param _accountId vault id
     * @param _seriesId option series id
     * @return (short size, long size)
     */
    function getPositionSize(uint256 _accountId, uint256 _seriesId)
        external
        view
        override(IOptionVault)
        returns (uint128, uint128)
    {
        return optionInfo.getPositionSize(_accountId, _seriesId);
    }

    /**
     * @notice get vault's net delta
     * @param _accountId vault id
     */
    function calculateVaultDelta(uint256 _accountId, uint256 _expiryId)
        external
        view
        override(IOptionVault)
        returns (int256)
    {
        return optionInfo.calculateVaultDelta(_accountId, _expiryId, getPrice());
    }

    /**
     * @notice get a config value
     */
    function getConfig(uint8 _key) external view returns (uint128) {
        return optionInfo.configs[_key];
    }

    ///////////////////////
    // Private Functions //
    ///////////////////////

    function createSeries(
        uint128 _expiryId,
        uint64 _strike,
        bool _isPut,
        uint64 _iv
    ) internal {
        uint256 seriesId = optionInfo.createSeries(_expiryId, _strike, _isPut, _iv);
        emit SeriesCreated(_expiryId, seriesId, _strike, _isPut);
    }

    function getExpiryPrice(uint256 _expiryId) internal view returns (uint256) {
        IOptionVault.Expiration storage expiration = optionInfo.expiries[_expiryId];

        (uint256 price, bool isFinalized) = priceOracle.getExpiryPrice(optionInfo.aggregator, expiration.expiry);

        require(isFinalized, "V11");

        return price;
    }

    function getPrice() internal view returns (uint128) {
        (uint256 spot, ) = priceOracle.getPrice(optionInfo.aggregator);
        return uint128(spot);
    }
}

// File: @openzeppelin/contracts/token/ERC1155/ERC1155.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./IERC1155.sol";
import "./IERC1155Receiver.sol";
import "./extensions/IERC1155MetadataURI.sol";
import "../../utils/Address.sol";
import "../../utils/Context.sol";
import "../../utils/introspection/ERC165.sol";

/**
 * @dev Implementation of the basic standard multi-token.
 * See https://eips.ethereum.org/EIPS/eip-1155
 * Originally based on code by Enjin: https://github.com/enjin/erc-1155
 *
 * _Available since v3.1._
 */
contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
    using Address for address;

    // Mapping from token ID to account balances
    mapping (uint256 => mapping(address => uint256)) private _balances;

    // Mapping from account to operator approvals
    mapping (address => mapping(address => bool)) private _operatorApprovals;

    // Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json
    string private _uri;

    /**
     * @dev See {_setURI}.
     */
    constructor (string memory uri_) {
        _setURI(uri_);
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return interfaceId == type(IERC1155).interfaceId
            || interfaceId == type(IERC1155MetadataURI).interfaceId
            || super.supportsInterface(interfaceId);
    }

    /**
     * @dev See {IERC1155MetadataURI-uri}.
     *
     * This implementation returns the same URI for *all* token types. It relies
     * on the token type ID substitution mechanism
     * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
     *
     * Clients calling this function must replace the `\{id\}` substring with the
     * actual token type ID.
     */
    function uri(uint256) public view virtual override returns (string memory) {
        return _uri;
    }

    /**
     * @dev See {IERC1155-balanceOf}.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function balanceOf(address account, uint256 id) public view virtual override returns (uint256) {
        require(account != address(0), "ERC1155: balance query for the zero address");
        return _balances[id][account];
    }

    /**
     * @dev See {IERC1155-balanceOfBatch}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(
        address[] memory accounts,
        uint256[] memory ids
    )
        public
        view
        virtual
        override
        returns (uint256[] memory)
    {
        require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch");

        uint256[] memory batchBalances = new uint256[](accounts.length);

        for (uint256 i = 0; i < accounts.length; ++i) {
            batchBalances[i] = balanceOf(accounts[i], ids[i]);
        }

        return batchBalances;
    }

    /**
     * @dev See {IERC1155-setApprovalForAll}.
     */
    function setApprovalForAll(address operator, bool approved) public virtual override {
        require(_msgSender() != operator, "ERC1155: setting approval status for self");

        _operatorApprovals[_msgSender()][operator] = approved;
        emit ApprovalForAll(_msgSender(), operator, approved);
    }

    /**
     * @dev See {IERC1155-isApprovedForAll}.
     */
    function isApprovedForAll(address account, address operator) public view virtual override returns (bool) {
        return _operatorApprovals[account][operator];
    }

    /**
     * @dev See {IERC1155-safeTransferFrom}.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    )
        public
        virtual
        override
    {
        require(to != address(0), "ERC1155: transfer to the zero address");
        require(
            from == _msgSender() || isApprovedForAll(from, _msgSender()),
            "ERC1155: caller is not owner nor approved"
        );

        address operator = _msgSender();

        _beforeTokenTransfer(operator, from, to, _asSingletonArray(id), _asSingletonArray(amount), data);

        uint256 fromBalance = _balances[id][from];
        require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
        _balances[id][from] = fromBalance - amount;
        _balances[id][to] += amount;

        emit TransferSingle(operator, from, to, id, amount);

        _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data);
    }

    /**
     * @dev See {IERC1155-safeBatchTransferFrom}.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    )
        public
        virtual
        override
    {
        require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
        require(to != address(0), "ERC1155: transfer to the zero address");
        require(
            from == _msgSender() || isApprovedForAll(from, _msgSender()),
            "ERC1155: transfer caller is not owner nor approved"
        );

        address operator = _msgSender();

        _beforeTokenTransfer(operator, from, to, ids, amounts, data);

        for (uint256 i = 0; i < ids.length; ++i) {
            uint256 id = ids[i];
            uint256 amount = amounts[i];

            uint256 fromBalance = _balances[id][from];
            require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
            _balances[id][from] = fromBalance - amount;
            _balances[id][to] += amount;
        }

        emit TransferBatch(operator, from, to, ids, amounts);

        _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data);
    }

    /**
     * @dev Sets a new URI for all token types, by relying on the token type ID
     * substitution mechanism
     * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
     *
     * By this mechanism, any occurrence of the `\{id\}` substring in either the
     * URI or any of the amounts in the JSON file at said URI will be replaced by
     * clients with the token type ID.
     *
     * For example, the `https://token-cdn-domain/\{id\}.json` URI would be
     * interpreted by clients as
     * `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json`
     * for token type ID 0x4cce0.
     *
     * See {uri}.
     *
     * Because these URIs cannot be meaningfully represented by the {URI} event,
     * this function emits no events.
     */
    function _setURI(string memory newuri) internal virtual {
        _uri = newuri;
    }

    /**
     * @dev Creates `amount` tokens of token type `id`, and assigns them to `account`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - If `account` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function _mint(address account, uint256 id, uint256 amount, bytes memory data) internal virtual {
        require(account != address(0), "ERC1155: mint to the zero address");

        address operator = _msgSender();

        _beforeTokenTransfer(operator, address(0), account, _asSingletonArray(id), _asSingletonArray(amount), data);

        _balances[id][account] += amount;
        emit TransferSingle(operator, address(0), account, id, amount);

        _doSafeTransferAcceptanceCheck(operator, address(0), account, id, amount, data);
    }

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function _mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) internal virtual {
        require(to != address(0), "ERC1155: mint to the zero address");
        require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");

        address operator = _msgSender();

        _beforeTokenTransfer(operator, address(0), to, ids, amounts, data);

        for (uint i = 0; i < ids.length; i++) {
            _balances[ids[i]][to] += amounts[i];
        }

        emit TransferBatch(operator, address(0), to, ids, amounts);

        _doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data);
    }

    /**
     * @dev Destroys `amount` tokens of token type `id` from `account`
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens of token type `id`.
     */
    function _burn(address account, uint256 id, uint256 amount) internal virtual {
        require(account != address(0), "ERC1155: burn from the zero address");

        address operator = _msgSender();

        _beforeTokenTransfer(operator, account, address(0), _asSingletonArray(id), _asSingletonArray(amount), "");

        uint256 accountBalance = _balances[id][account];
        require(accountBalance >= amount, "ERC1155: burn amount exceeds balance");
        _balances[id][account] = accountBalance - amount;

        emit TransferSingle(operator, account, address(0), id, amount);
    }

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     */
    function _burnBatch(address account, uint256[] memory ids, uint256[] memory amounts) internal virtual {
        require(account != address(0), "ERC1155: burn from the zero address");
        require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");

        address operator = _msgSender();

        _beforeTokenTransfer(operator, account, address(0), ids, amounts, "");

        for (uint i = 0; i < ids.length; i++) {
            uint256 id = ids[i];
            uint256 amount = amounts[i];

            uint256 accountBalance = _balances[id][account];
            require(accountBalance >= amount, "ERC1155: burn amount exceeds balance");
            _balances[id][account] = accountBalance - amount;
        }

        emit TransferBatch(operator, account, address(0), ids, amounts);
    }

    /**
     * @dev Hook that is called before any token transfer. This includes minting
     * and burning, as well as batched variants.
     *
     * The same hook is called on both single and batched variants. For single
     * transfers, the length of the `id` and `amount` arrays will be 1.
     *
     * Calling conditions (for each `id` and `amount` pair):
     *
     * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * of token type `id` will be  transferred to `to`.
     * - When `from` is zero, `amount` tokens of token type `id` will be minted
     * for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens of token type `id`
     * will be burned.
     * - `from` and `to` are never both zero.
     * - `ids` and `amounts` have the same, non-zero length.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    )
        internal
        virtual
    { }

    function _doSafeTransferAcceptanceCheck(
        address operator,
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    )
        private
    {
        if (to.isContract()) {
            try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) {
                if (response != IERC1155Receiver(to).onERC1155Received.selector) {
                    revert("ERC1155: ERC1155Receiver rejected tokens");
                }
            } catch Error(string memory reason) {
                revert(reason);
            } catch {
                revert("ERC1155: transfer to non ERC1155Receiver implementer");
            }
        }
    }

    function _doSafeBatchTransferAcceptanceCheck(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    )
        private
    {
        if (to.isContract()) {
            try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns (bytes4 response) {
                if (response != IERC1155Receiver(to).onERC1155BatchReceived.selector) {
                    revert("ERC1155: ERC1155Receiver rejected tokens");
                }
            } catch Error(string memory reason) {
                revert(reason);
            } catch {
                revert("ERC1155: transfer to non ERC1155Receiver implementer");
            }
        }
    }

    function _asSingletonArray(uint256 element) private pure returns (uint256[] memory) {
        uint256[] memory array = new uint256[](1);
        array[0] = element;

        return array;
    }
}

// File: @openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev _Available since v3.1._
 */
interface IERC1155Receiver is IERC165 {

    /**
        @dev Handles the receipt of a single ERC1155 token type. This function is
        called at the end of a `safeTransferFrom` after the balance has been updated.
        To accept the transfer, this must return
        `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
        (i.e. 0xf23a6e61, or its own function selector).
        @param operator The address which initiated the transfer (i.e. msg.sender)
        @param from The address which previously owned the token
        @param id The ID of the token being transferred
        @param value The amount of tokens being transferred
        @param data Additional data with no specified format
        @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
    */
    function onERC1155Received(
        address operator,
        address from,
        uint256 id,
        uint256 value,
        bytes calldata data
    )
        external
        returns(bytes4);

    /**
        @dev Handles the receipt of a multiple ERC1155 token types. This function
        is called at the end of a `safeBatchTransferFrom` after the balances have
        been updated. To accept the transfer(s), this must return
        `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
        (i.e. 0xbc197c81, or its own function selector).
        @param operator The address which initiated the batch transfer (i.e. msg.sender)
        @param from The address which previously owned the token
        @param ids An array containing ids of each token being transferred (order and length must match values array)
        @param values An array containing amounts of each token being transferred (order and length must match ids array)
        @param data Additional data with no specified format
        @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
    */
    function onERC1155BatchReceived(
        address operator,
        address from,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata data
    )
        external
        returns(bytes4);
}

// File: contracts/lib/OptionLib.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "./PriceCalculator.sol";
import "../interfaces/IAave.sol";
import "../interfaces/IHedge.sol";
import "../interfaces/IOptionVault.sol";
import "../interfaces/IWETH.sol";
import "../interfaces/IFeePool.sol";
import "./PredyMath.sol";

/**
 * @title OptionLib
 */
library OptionLib {
    using PredyMath for uint128;

    struct TokenContracts {
        // collateral token
        address collateral;
        // underlying token
        address underlying;
    }

    /// @dev option information
    struct OptionInfo {
        address aggregator;
        TokenContracts tokens;
        // counter of serieses
        uint128 seriesCount;
        uint128 expiryCount;
        uint128 expiredCount;
        // expiration => seriesId => option series
        mapping(uint256 => IOptionVault.Expiration) expiries;
        //
        mapping(uint256 => IOptionVault.OptionSeries) serieses;
        // counter of vaults
        uint256 vaultCount;
        // accountId => IOptionVault.Account
        uint128 totalDepositedToLendingPool;
        mapping(uint256 => IOptionVault.Account) accounts;
        // config
        mapping(uint8 => uint128) configs;
        //
        LendingPool lendingPool;
    }

    uint8 public constant MM_RATIO = 1;
    uint8 public constant IM_RATIO = 2;
    uint8 public constant CALL_SAFE_RATIO = 3;
    uint8 public constant PUT_SAFE_RATIO = 4;
    uint8 public constant SLIPPAGE_TOLERANCE = 5;
    uint8 public constant BASE_LIQ_REWARD = 8;
    uint8 public constant REWARD_PER_SIZE_RATIO = 9;

    /// @dev minimum vault id.
    /// trader's vault id is larger than MIN_VAULT_ID
    uint128 public constant MIN_VAULT_ID = 100;

    uint256 constant MAX_UINT256 = 2**256 - 1;

    modifier existsExpiry(uint256 _expiryId, uint128 _expiryCount) {
        require(_expiryId > 0 && _expiryId < _expiryCount, "OptionLib: expiry not found");
        _;
    }

    modifier existsSeries(uint256 _seriesId, uint128 _seriesCount) {
        require(_seriesId > 0 && _seriesId < _seriesCount, "OptionLib: series not found");
        _;
    }

    /**
     * @notice initialize OptionInfo
     */
    function init(
        OptionInfo storage _optionInfo,
        address _collateral,
        address _underlying,
        address _lendingPool
    ) external {
        // initialize states
        _optionInfo.expiredCount = 0;
        _optionInfo.expiryCount = 1;
        _optionInfo.seriesCount = 1;
        _optionInfo.vaultCount = OptionLib.MIN_VAULT_ID;

        // initialize configs
        // 10%
        _optionInfo.configs[MM_RATIO] = 100;
        // 20%
        _optionInfo.configs[IM_RATIO] = 200;
        // 120%
        _optionInfo.configs[CALL_SAFE_RATIO] = 1200;
        // 120%
        _optionInfo.configs[PUT_SAFE_RATIO] = 1200;
        // 0.5%
        _optionInfo.configs[SLIPPAGE_TOLERANCE] = 50;
        // $100
        _optionInfo.configs[BASE_LIQ_REWARD] = 100 * 1e6;
        // 9%
        _optionInfo.configs[REWARD_PER_SIZE_RATIO] = 90;

        // set contract addresses
        _optionInfo.tokens.collateral = _collateral;
        _optionInfo.tokens.underlying = _underlying;
        _optionInfo.lendingPool = LendingPool(_lendingPool);
    }

    function setIV(
        OptionInfo storage _optionInfo,
        uint256 _seriesId,
        uint128 _iv
    ) external {
        _optionInfo.serieses[_seriesId].iv = uint64(_iv);
    }

    /**
     * @notice create new option series
     * the option series must have later expiry than existence serieses
     */
    function createExpiry(OptionInfo storage _optionInfo, uint64 _expiry) external returns (uint128 expiryId) {
        // check expiry is greater than or equal to last created
        uint64 lastExpiry;
        if (_optionInfo.expiryCount > 0) {
            lastExpiry = _optionInfo.expiries[_optionInfo.expiryCount - 1].expiry;
        }

        require(_expiry >= block.timestamp, "OptionLib: expiry must be greater than now");
        require(_expiry >= lastExpiry, "OptionLib: expiry must be greater than or equal to last created");
        require(_expiry % 1 hours == 0, "OptionLib: expiry must be formatted");

        // create series
        expiryId = _optionInfo.expiryCount;
        _optionInfo.expiries[expiryId].expiryId = expiryId;
        _optionInfo.expiries[expiryId].expiry = _expiry;

        _optionInfo.expiryCount += 1;
    }

    function createSeries(
        OptionInfo storage _optionInfo,
        uint128 _expiryId,
        uint64 _strike,
        bool _isPut,
        uint64 _iv
    ) external returns (uint256 seriesId) {
        require(_strike > 0 && _strike < 1e16, "OptionLib: strike must be greater than 0 and less than $100M");
        require(_iv > 0 && _iv < 1000 * 1e6, "OptionLib: iv must be greater than 0 and less than 1000%");

        seriesId = _optionInfo.seriesCount;

        // create series
        _optionInfo.serieses[seriesId] = IOptionVault.OptionSeries(_strike, _isPut, _iv, _expiryId);
        _optionInfo.expiries[_expiryId].seriesIds.push(seriesId);

        _optionInfo.seriesCount += 1;
    }

    function createAccount(OptionInfo storage _optionInfo, address owner) external returns (uint256) {
        uint256 id = _optionInfo.vaultCount;

        IOptionVault.Account storage account = _optionInfo.accounts[id];
        account.owner = owner;
        account.settledCount = _optionInfo.expiredCount;

        _optionInfo.vaultCount += 1;

        return id;
    }

    function deposit(
        OptionInfo storage _optionInfo,
        uint256 _accountId,
        uint256 _expiryId,
        uint128 _amount
    ) external existsExpiry(_expiryId, _optionInfo.expiryCount) {
        IOptionVault.Vault storage vault = _optionInfo.accounts[_accountId].vaults[_expiryId];

        require(!vault.isSettled, "Vault already settled");

        increaseCollateral(vault, _amount);
    }

    function withdraw(
        OptionInfo storage _optionInfo,
        uint256 _accountId,
        uint256 _expiryId,
        uint128 _amount,
        uint128 _spot
    ) external existsExpiry(_expiryId, _optionInfo.expiryCount) {
        IOptionVault.Vault storage vault = _optionInfo.accounts[_accountId].vaults[_expiryId];

        checkOptionSeriesIsLive(_optionInfo.expiries[_expiryId]);

        decreaseCollateral(vault, _amount);

        checkCollateral(_optionInfo, _accountId, _expiryId, _spot);
    }

    function withdrawUnrequiredCollateral(
        OptionInfo storage _optionInfo,
        uint256 _accountId,
        uint256 _expiryId,
        uint128 _spot,
        uint128 _cRatio,
        bool _isPool
    ) external existsExpiry(_expiryId, _optionInfo.expiryCount) returns (uint128) {
        IOptionVault.Vault storage vault = _optionInfo.accounts[_accountId].vaults[_expiryId];

        checkOptionSeriesIsLive(_optionInfo.expiries[_expiryId]);

        uint128 collateralValue = getCollateralValue(_optionInfo, _accountId, _expiryId, _spot);

        uint128 requiredCollateral = (1e6 *
            getRequiredMargin(
                _optionInfo,
                _accountId,
                _expiryId,
                _spot,
                _isPool ? IOptionVault.MarginLevel.Safe : IOptionVault.MarginLevel.Initial
            )) / _cRatio;

        if (collateralValue > requiredCollateral) {
            return decreaseCollateral(vault, collateralValue - requiredCollateral);
        } else {
            return 0;
        }
    }

    function write(
        OptionInfo storage _optionInfo,
        uint256 _accountId,
        uint256 _seriesId,
        uint128 _amount,
        uint128 _spot
    ) external existsSeries(_seriesId, _optionInfo.seriesCount) {
        IOptionVault.Account storage account = _optionInfo.accounts[_accountId];
        IOptionVault.OptionSeries storage series = _optionInfo.serieses[_seriesId];

        checkOptionSeriesIsLive(_optionInfo.expiries[series.expiryId]);

        account.vaults[series.expiryId].shorts[_seriesId] += _amount;

        checkCollateral(_optionInfo, _accountId, series.expiryId, _spot);
    }

    function unlock(
        OptionInfo storage _optionInfo,
        uint256 _accountId,
        uint256 _seriesId,
        uint128 _amount
    ) external existsSeries(_seriesId, _optionInfo.seriesCount) {
        IOptionVault.OptionSeries storage series = _optionInfo.serieses[_seriesId];

        _optionInfo.accounts[_accountId].vaults[series.expiryId].shorts[_seriesId] -= _amount;
    }

    function depositAndWrite(
        OptionInfo storage _optionInfo,
        uint256 _accountId,
        uint256 _seriesId,
        uint128 _amount,
        uint128 _cRatio,
        uint128 _spot,
        bool _isPool
    ) external existsSeries(_seriesId, _optionInfo.seriesCount) returns (uint256 expiryId, uint128 collateral) {
        expiryId = _optionInfo.serieses[_seriesId].expiryId;

        IOptionVault.Account storage account = _optionInfo.accounts[_accountId];
        IOptionVault.Vault storage vault = account.vaults[expiryId];

        checkOptionSeriesIsLive(_optionInfo.expiries[expiryId]);

        account.vaults[expiryId].shorts[_seriesId] += _amount;

        collateral = getCollateralForASeries(_optionInfo, expiryId, _seriesId, _spot, _amount, _cRatio, _isPool);

        increaseCollateral(vault, collateral);
    }

    function addLong(
        OptionInfo storage _optionInfo,
        uint256 _accountId,
        uint256 _expiryId,
        uint256 _seriesId,
        uint128 _amount
    ) external existsSeries(_seriesId, _optionInfo.seriesCount) returns (uint128) {
        IOptionVault.Account storage account = _optionInfo.accounts[_accountId];

        account.vaults[_expiryId].longs[_seriesId] += _amount;

        return account.vaults[_expiryId].longs[_seriesId];
    }

    function removeLong(
        OptionInfo storage _optionInfo,
        uint256 _accountId,
        uint256 _expiryId,
        uint256 _seriesId,
        uint128 _amount
    ) external existsSeries(_seriesId, _optionInfo.seriesCount) {
        IOptionVault.Account storage account = _optionInfo.accounts[_accountId];

        account.vaults[_expiryId].longs[_seriesId] -= _amount;
    }

    /**
     * @notice liquidate a vault
     * the amount that liquidator can burn is '(BASE_REWRD + MM - C) / (mm - premium - REWARD_RATIO * (St or K))'
     * where C = vault.collateral
     * , MM current maintenance margin
     * and mm is maintenance margin per size
     */
    function liquidate(
        OptionInfo storage _optionInfo,
        uint256 _accountId,
        uint256 _seriesId,
        uint128 _amount,
        uint128 _spot
    ) external existsSeries(_seriesId, _optionInfo.seriesCount) returns (uint128) {
        IOptionVault.OptionSeries memory option = _optionInfo.serieses[_seriesId];
        IOptionVault.Vault storage vault = _optionInfo.accounts[_accountId].vaults[option.expiryId];

        uint128 shortAmount = vault.shorts[_seriesId];

        require(shortAmount >= _amount, "OptionLib: amount exceeds vault size");

        uint128 collateralValue = getCollateralValue(_optionInfo, _accountId, option.expiryId, _spot);

        uint128 limit;
        {
            uint128 maintenanceMargin = getRequiredMargin(
                _optionInfo,
                _accountId,
                option.expiryId,
                _spot,
                IOptionVault.MarginLevel.Maintenance
            );
            require(collateralValue < maintenanceMargin, "OptionLib: collateral must be less than MM");

            limit = calLiquidatableAmount(
                _optionInfo,
                maintenanceMargin,
                collateralValue,
                option.isPut ? option.strike : _spot
            );
        }

        require(limit >= _amount, "OptionLib: amount exceeds liquidatable limit");

        vault.shorts[_seriesId] -= _amount;

        uint128 reward;
        {
            uint128 maintenanceMargin = getRequiredMargin(
                _optionInfo,
                _accountId,
                option.expiryId,
                _spot,
                IOptionVault.MarginLevel.Maintenance
            );

            require(collateralValue >= maintenanceMargin, "OptionLib: margin must be safe");
            reward = collateralValue - maintenanceMargin;
        }

        return decreaseCollateral(vault, reward);
    }

    /**
     * @notice calculate profit of some amount of option contracts
     */
    function claimProfit(
        OptionInfo storage _optionInfo,
        uint256 _seriesId,
        uint128 _amount,
        uint128 _price
    ) external view existsSeries(_seriesId, _optionInfo.seriesCount) returns (uint128) {
        uint128 payout = calculatePayout(
            _amount,
            _price,
            _optionInfo.serieses[_seriesId].strike,
            _optionInfo.serieses[_seriesId].isPut,
            false
        );

        return payout;
    }

    /**
     * @notice fix payout of a vault and remove the payout from vault's collaterals
     * @return settledAmount the amount that the vault owner can redeem
     */
    function settle(
        OptionInfo storage _optionInfo,
        uint256 _accountId,
        uint256 _expiryId,
        uint128 _price
    ) external existsExpiry(_expiryId, _optionInfo.expiryCount) returns (uint128 settledAmount) {
        IOptionVault.Account storage account = _optionInfo.accounts[_accountId];
        IOptionVault.Vault storage vault = account.vaults[_expiryId];

        uint128 payout = getTotalPayout(_optionInfo, _accountId, _expiryId, _price, true);

        require(!vault.isSettled, "OptionLib: vault already settled");

        require(vault.hedgePosition == 0, "OptionLib: hedge position must be neutral");

        // all collaterals in Aave must be redeemed before settlement
        require(vault.shortLiquidity == 0, "OptionLib: all collaterals must be withdrawn");

        require(account.settledCount < _expiryId, "OptionLib: vault already settled");

        for (uint256 i = account.settledCount + 1; i < _expiryId; i++) {
            uint128 skippedCollateral = account.vaults[i].collateral;
            require(skippedCollateral == 0, "OptionLib: can not skip expiry");
        }

        vault.isSettled = true;
        account.settledCount = _expiryId;

        updateExpiredCount(_optionInfo, _expiryId);

        decreaseCollateral(vault, payout);

        settledAmount = vault.collateral;
    }

    /*
     * addUnderlyingLong and addUnderlyingShort are functions to achieve delta neutral.
     * vault's net delta is calculated as vaultDelta, and the protocol wanna make (vaultDelta + vault.hedgePosition) zero.
     * hedgePosition exactly represents how many underlying asset the vault has,
     * and negative hedgePosition means short position of underlying asset.
     */

    /**
     * @notice receive underlying asset and send collateral asset to sender
     * @param _vaultDelta net delta of the vault
     * @param _underlyingAmountE8 amount of underlying asset scaled by 1e8
     */
    function addUnderlyingLong(
        OptionInfo storage _optionInfo,
        uint256 _accountId,
        uint256 _expiryId,
        uint128 _spot,
        int256 _vaultDelta,
        uint256 _underlyingAmountE8,
        uint256 _collateralAmount
    ) external existsExpiry(_expiryId, _optionInfo.expiryCount) returns (int256) {
        IOptionVault.Vault storage vault = _optionInfo.accounts[_accountId].vaults[_expiryId];
        ERC20 underlying = ERC20(_optionInfo.tokens.underlying);

        uint256 decimals = underlying.decimals();

        require(_vaultDelta + vault.hedgePosition < 0, "OptionLib: net delta must be negative");

        require(
            -(_vaultDelta + vault.hedgePosition) >= int256(_underlyingAmountE8),
            "OptionLib: underlying amount is too large"
        );

        if (-(_vaultDelta + vault.hedgePosition) < int256(_underlyingAmountE8)) {
            _underlyingAmountE8 = uint256(-(_vaultDelta + vault.hedgePosition));
        }

        require(
            (PredyMath.scale(_spot * _underlyingAmountE8, 16, 6) * (10000 + _optionInfo.configs[SLIPPAGE_TOLERANCE])) /
                10000 >=
                _collateralAmount,
            "OptionLib: collateral amount is too large"
        );

        uint256 uAmount = PredyMath.scale(_underlyingAmountE8, 8, decimals);

        int256 hedgePosition = vault.hedgePosition;

        vault.hedgePosition += int256(_underlyingAmountE8);

        underlying.transferFrom(msg.sender, address(this), uAmount);

        if (hedgePosition < -int256(_underlyingAmountE8)) {
            repayUnderlyingInternal(_optionInfo, vault, _spot, uAmount, uint256(-hedgePosition) - _underlyingAmountE8);
        } else if (hedgePosition < 0) {
            repayUnderlyingInternal(
                _optionInfo,
                vault,
                _spot,
                PredyMath.scale(uint256(-hedgePosition), 8, decimals),
                0
            );
        }

        require(vault.collateral >= uint128(_collateralAmount), "OptionLib: no enough collateral");

        decreaseCollateral(vault, uint128(_collateralAmount));

        IERC20(_optionInfo.tokens.collateral).transfer(msg.sender, _collateralAmount);

        return vault.hedgePosition;
    }

    /**
     * @notice receive collateral asset and send underlying asset to sender
     * @param _vaultDelta net delta of the vault
     * @param _underlyingAmountE8 amount of underlying asset scaled by 1e8
     */
    function addUnderlyingShort(
        OptionInfo storage _optionInfo,
        uint256 _accountId,
        uint256 _expiryId,
        uint128 _spot,
        int256 _vaultDelta,
        uint256 _underlyingAmountE8,
        uint256 _collateralAmount
    ) external existsExpiry(_expiryId, _optionInfo.expiryCount) returns (int256) {
        IOptionVault.Account storage account = _optionInfo.accounts[_accountId];
        IOptionVault.Vault storage vault = account.vaults[_expiryId];
        ERC20 underlying = ERC20(_optionInfo.tokens.underlying);

        uint256 decimals = underlying.decimals();

        require(_vaultDelta + vault.hedgePosition > 0, "OptionLib: net delta must be positive");

        require(
            (_vaultDelta + vault.hedgePosition) >= int256(_underlyingAmountE8),
            "OptionLib: underlying amount is too large"
        );

        require(
            (PredyMath.scale(_spot * _underlyingAmountE8, 16, 6) * (10000 - _optionInfo.configs[SLIPPAGE_TOLERANCE])) /
                10000 <=
                _collateralAmount,
            "OptionLib: collateral amount is too small"
        );

        uint256 uAmount = PredyMath.scale(_underlyingAmountE8, 8, decimals);

        increaseCollateral(vault, uint128(_collateralAmount));

        IERC20(_optionInfo.tokens.collateral).transferFrom(msg.sender, address(this), _collateralAmount);

        if (vault.hedgePosition <= 0) {
            borrowUnderlyingInternal(_optionInfo, vault, _spot, uAmount);
        } else if (vault.hedgePosition < int256(_underlyingAmountE8)) {
            borrowUnderlyingInternal(
                _optionInfo,
                vault,
                _spot,
                uAmount - PredyMath.scale(uint256(vault.hedgePosition), 8, decimals)
            );
        }

        vault.hedgePosition -= int256(_underlyingAmountE8);

        underlying.transfer(msg.sender, uAmount);

        return vault.hedgePosition;
    }

    /**
     * @notice redeem collateral from LendingPool
     */
    function redeemCollateralFromLendingPool(
        OptionInfo storage _optionInfo,
        uint128 _repayAmount,
        uint128 _price,
        address _caller,
        address _feePool
    ) external {
        // there are no live option serieses
        require(_optionInfo.expiryCount == _optionInfo.expiredCount + 1);
        // check total short liquidity is 0
        require(_optionInfo.totalDepositedToLendingPool == 0);

        IERC20(_optionInfo.tokens.underlying).transferFrom(_caller, address(this), _repayAmount);

        IERC20(_optionInfo.tokens.underlying).approve(address(_optionInfo.lendingPool), _repayAmount);
        uint128 repaidAmount = repayBorrow(_optionInfo, MAX_UINT256);

        // redeem underlying tokens
        uint128 withdrawnAmount = uint128(
            _optionInfo.lendingPool.withdraw(_optionInfo.tokens.collateral, MAX_UINT256, address(this))
        );

        uint128 reward = (_price * repaidAmount) / 1e20;
        reward = (reward * (10000 + _optionInfo.configs[SLIPPAGE_TOLERANCE])) / 10000;

        if (reward < withdrawnAmount) {
            uint128 rewardForFeePool = withdrawnAmount - reward;

            // send USDC to fee pool
            IFeePool feePool = IFeePool(_feePool);

            IERC20(_optionInfo.tokens.collateral).approve(address(feePool), rewardForFeePool);
            feePool.sendProfitERC20(address(this), rewardForFeePool);
        } else {
            reward = withdrawnAmount;
        }

        // send USDC and WETH to caller
        IERC20(_optionInfo.tokens.collateral).transfer(_caller, reward);
        IERC20(_optionInfo.tokens.underlying).transfer(_caller, _repayAmount - repaidAmount);
    }

    function setConfig(
        OptionInfo storage _optionInfo,
        uint8 _key,
        uint128 _value
    ) external {
        _optionInfo.configs[_key] = _value;
    }

    //////////////////////
    // Getter Functions //
    //////////////////////

    function getLiveOptionSerieses(OptionInfo storage _optionInfo)
        external
        view
        returns (IOptionVault.Expiration[] memory)
    {
        IOptionVault.Expiration[] memory expirations = new IOptionVault.Expiration[](
            _optionInfo.expiryCount - _optionInfo.expiredCount - 1
        );

        for (uint128 i = _optionInfo.expiredCount + 1; i < _optionInfo.expiryCount; i++) {
            expirations[i - _optionInfo.expiredCount - 1] = IOptionVault.Expiration(
                i,
                _optionInfo.expiries[i].expiry,
                _optionInfo.expiries[i].seriesIds
            );
        }

        return expirations;
    }

    function getOptionSeriesView(OptionInfo storage _optionInfo, uint256 _seriesId)
        external
        view
        returns (IOptionVault.OptionSeriesView memory)
    {
        IOptionVault.OptionSeries memory optionSeries = _optionInfo.serieses[_seriesId];
        IOptionVault.Expiration memory expiration = _optionInfo.expiries[optionSeries.expiryId];

        return
            IOptionVault.OptionSeriesView(
                optionSeries.expiryId,
                _seriesId,
                expiration.expiry,
                OptionLib.getMaturity(expiration.expiry),
                optionSeries.strike,
                optionSeries.isPut,
                optionSeries.iv
            );
    }

    function getCollateralValueQuote(
        OptionInfo storage _optionInfo,
        uint256 _accountId,
        uint128 price
    ) external view returns (uint128 total) {
        for (uint256 i = _optionInfo.accounts[_accountId].settledCount + 1; i < _optionInfo.seriesCount; i++) {
            total += getCollateralValue(_optionInfo, _accountId, i, price);
        }
    }

    /**
     * @notice get position size
     * @param _accountId vault id
     * @param _seriesId option series id
     * @return (short size, long size)
     */
    function getPositionSize(
        OptionInfo storage _optionInfo,
        uint256 _accountId,
        uint256 _seriesId
    ) external view returns (uint128, uint128) {
        IOptionVault.OptionSeries memory series = _optionInfo.serieses[_seriesId];
        IOptionVault.Vault storage vault = _optionInfo.accounts[_accountId].vaults[series.expiryId];

        return (vault.shorts[_seriesId], vault.longs[_seriesId]);
    }

    function getVaultOwner(OptionInfo storage _optionInfo, uint256 _accountId) external view returns (address) {
        return _optionInfo.accounts[_accountId].owner;
    }

    /**
     * @notice get the amount that can be liquidated
     */
    function getLiquidatableAmount(
        OptionInfo storage _optionInfo,
        uint256 _accountId,
        uint256 _seriesId,
        uint128 _spot
    ) external view returns (uint128) {
        IOptionVault.OptionSeries memory option = _optionInfo.serieses[_seriesId];
        IOptionVault.Account storage account = _optionInfo.accounts[_accountId];
        IOptionVault.Vault storage vault = account.vaults[option.expiryId];

        uint128 maintenanceMargin = getRequiredMargin(
            _optionInfo,
            _accountId,
            option.expiryId,
            _spot,
            IOptionVault.MarginLevel.Maintenance
        );

        uint128 collateralValue = getCollateralValue(_optionInfo, _accountId, option.expiryId, _spot);

        if (collateralValue >= maintenanceMargin) {
            return 0;
        }

        uint128 liquidatableAmount = calLiquidatableAmount(
            _optionInfo,
            maintenanceMargin,
            collateralValue,
            option.isPut ? option.strike : _spot
        );

        if (vault.shorts[_seriesId] < liquidatableAmount) {
            return vault.shorts[_seriesId];
        }

        return liquidatableAmount;
    }

    int256 internal constant SQRT_YEAR_E8 = 5615.69229926 * 10**8;

    /**
     * @notice calculate vault's net delta
     * @param _optionInfo pool info
     * @param _accountId vault id to calculate net delta
     * @param _spot spot price
     * @return tickDelta vault's net delta scaled by 1e8
     */
    function calculateVaultDelta(
        OptionInfo storage _optionInfo,
        uint256 _accountId,
        uint256 _expiryId,
        uint128 _spot
    ) external view returns (int256 tickDelta) {
        IOptionVault.Vault storage vault = _optionInfo.accounts[_accountId].vaults[_expiryId];

        // calculate serieses that is not expired
        IOptionVault.Expiration memory expiration = _optionInfo.expiries[_expiryId];

        // option serieses before maturity are included in the calculation.
        if (expiration.expiry <= block.timestamp) {
            return 0;
        }

        // uint64 maturity = getMaturity(expiration.expiry);
        int256 sqrtMaturity = PriceCalculator.getSqrtMaturity(getMaturity(expiration.expiry));

        for (uint256 j = 0; j < expiration.seriesIds.length; j++) {
            uint256 seriesId = expiration.seriesIds[j];

            int128 position = vault.longs[seriesId].toInt128() - vault.shorts[seriesId].toInt128();
            tickDelta += calculateDelta(sqrtMaturity, _optionInfo.serieses[seriesId], position, _spot);
        }
        return tickDelta;
    }

    ///////////////////////
    // Private Functions //
    ///////////////////////

    function increaseCollateral(IOptionVault.Vault storage _vault, uint128 _amount) internal {
        _vault.collateral += _amount;
    }

    function decreaseCollateral(IOptionVault.Vault storage _vault, uint128 _amount) internal returns (uint128) {
        if (_vault.collateral >= _amount) {
            _vault.collateral -= _amount;
            return _amount;
        } else {
            uint128 a = _vault.collateral;
            _vault.collateral = 0;
            return a;
        }
    }

    /**
     * @notice get USD value of collateral
     */
    function getCollateralValue(
        OptionInfo storage _optionInfo,
        uint256 _accountId,
        uint256 _expiryId,
        uint128 _spot
    ) internal view returns (uint128) {
        IOptionVault.Account storage account = _optionInfo.accounts[_accountId];
        IOptionVault.Vault storage vault = account.vaults[_expiryId];

        int256 hedgedValue = vault.shortLiquidity.toInt128() + (_spot.toInt128() * vault.hedgePosition) / 1e10;

        return uint128(uint256(hedgedValue)) + vault.collateral;
    }

    /**
     * @notice get the required margin of a vault
     */
    function getRequiredMargin(
        OptionInfo storage _optionInfo,
        uint256 _accountId,
        uint256 _expiryId,
        uint128 _spot,
        IOptionVault.MarginLevel _marginLevel
    ) public view returns (uint128 requiredMargin) {
        IOptionVault.Account storage account = _optionInfo.accounts[_accountId];
        IOptionVault.Vault storage vault = account.vaults[_expiryId];
        IOptionVault.Expiration storage expiration = _optionInfo.expiries[_expiryId];

        if (vault.isSettled) {
            return 0;
        }

        for (uint256 i = 0; i < expiration.seriesIds.length; i++) {
            uint256 seriesId = expiration.seriesIds[i];
            uint128 shortAmount = vault.shorts[seriesId];
            uint128 longAmount = vault.longs[seriesId];

            if (shortAmount == 0 && longAmount == 0) {
                continue;
            }

            IOptionVault.OptionSeriesParams memory seriesParams = getOptionSeriesParams(
                _optionInfo.serieses[seriesId],
                expiration.expiry
            );

            if (_marginLevel == IOptionVault.MarginLevel.Safe) {
                requiredMargin += calMargin(
                    _optionInfo,
                    shortAmount.toInt128() - longAmount.toInt128(),
                    _spot,
                    seriesParams,
                    _marginLevel
                );
            } else {
                requiredMargin += calMargin(_optionInfo, shortAmount.toInt128(), _spot, seriesParams, _marginLevel);
            }
        }
    }

    function getRequiredMarginForASeries(
        OptionInfo storage _optionInfo,
        uint256 _expiryId,
        uint256 _seriesId,
        uint128 _spot,
        int128 _amount,
        IOptionVault.MarginLevel _marginLevel
    ) public view returns (uint128) {
        uint64 expiry = _optionInfo.expiries[_expiryId].expiry;

        IOptionVault.OptionSeries memory series = _optionInfo.serieses[_seriesId];

        return calMargin(_optionInfo, _amount, _spot, getOptionSeriesParams(series, expiry), _marginLevel);
    }

    /**
     * @notice get the total payout of a vault
     */
    function getTotalPayout(
        OptionInfo storage _optionInfo,
        uint256 _accountId,
        uint256 _expiryId,
        uint128 _spot,
        bool _roundUp
    ) public view returns (uint128 totalPayout) {
        IOptionVault.Account storage account = _optionInfo.accounts[_accountId];
        IOptionVault.Vault storage vault = account.vaults[_expiryId];

        IOptionVault.Expiration memory expiration = _optionInfo.expiries[_expiryId];

        for (uint256 i = 0; i < expiration.seriesIds.length; i++) {
            uint256 seriesId = expiration.seriesIds[i];

            if (vault.shorts[seriesId] == 0) {
                continue;
            }

            totalPayout += calculatePayout(
                vault.shorts[seriesId],
                _spot,
                _optionInfo.serieses[seriesId].strike,
                _optionInfo.serieses[seriesId].isPut,
                _roundUp
            );
        }
    }

    /**
     * @notice calculate liquidatable amount
     * Calculate the liquidatable size so that the collateral of the size's premium + BASE_REWARD remains in vault
     */
    function calLiquidatableAmount(
        OptionInfo storage _optionInfo,
        uint128 _maintenanceMargin,
        uint128 _collateralValue,
        uint128 _spotOrStrike
    ) internal view returns (uint128 limit) {
        // maintenance margin - premium
        uint128 rewardDiffPerSize = (_spotOrStrike * _optionInfo.configs[REWARD_PER_SIZE_RATIO]) / (1000);

        return
            (1e10 * (_optionInfo.configs[BASE_LIQ_REWARD] + _maintenanceMargin - _collateralValue)) / rewardDiffPerSize;
    }

    /**
     * @notice get maturity
     */
    function getMaturity(uint64 _expiry) internal view returns (uint64 maturity) {
        maturity = _expiry > block.timestamp ? _expiry - uint64(block.timestamp) : 0;
    }

    function getOptionSeriesParams(IOptionVault.OptionSeries memory _series, uint64 _expiry)
        internal
        view
        returns (IOptionVault.OptionSeriesParams memory)
    {
        uint64 maturity = getMaturity(_expiry);

        return IOptionVault.OptionSeriesParams(_series.expiryId, maturity, _series.strike, _series.isPut, _series.iv);
    }

    function checkOptionSeriesIsLive(IOptionVault.Expiration memory _expiration) internal view {
        require(_expiration.expiry > block.timestamp, "OptionLib: option series has been expired");
    }

    function checkCollateral(
        OptionInfo storage _optionInfo,
        uint256 _accountId,
        uint256 _expiryId,
        uint128 _spot
    ) internal view {
        require(
            getCollateralValue(_optionInfo, _accountId, _expiryId, _spot) >=
                getRequiredMargin(_optionInfo, _accountId, _expiryId, _spot, IOptionVault.MarginLevel.Initial),
            "OptionLib: collateral is not enough"
        );
    }

    function getCollateralForASeries(
        OptionInfo storage _optionInfo,
        uint256 _expiryId,
        uint256 _seriesId,
        uint128 _spot,
        uint128 _amount,
        uint128 _cRatio,
        bool _isPool
    ) internal view returns (uint128 collateral) {
        uint128 requiredMargin = getRequiredMarginForASeries(
            _optionInfo,
            _expiryId,
            _seriesId,
            _spot,
            _amount.toInt128(),
            _isPool ? IOptionVault.MarginLevel.Safe : IOptionVault.MarginLevel.Initial
        );

        collateral = (1e6 * requiredMargin) / _cRatio;
    }

    /**
     * @notice calculate required margin
     * there are 3 margin levels, Maintenance Margin, Initial Margin and Safe Margin.
     * Maintenance Margin: premium + mmRatio * spot
     * Initial Margin: premium + imRatio * spot
     * Safe Margin:
     *   120% of spot for short call
     *   200% of min(strike, spot) for short put
     *   200% of min(strike, spot) for long call
     *   120% of spot for long put
     */
    function calMargin(
        OptionInfo storage _optionInfo,
        int128 _u,
        uint128 _spot,
        IOptionVault.OptionSeriesParams memory _series,
        IOptionVault.MarginLevel _marginLevel
    ) internal view returns (uint128) {
        if (_marginLevel == IOptionVault.MarginLevel.Maintenance || _marginLevel == IOptionVault.MarginLevel.Initial) {
            uint128 size = uint128(_u);
            // calculate as ATM if option is OTM

            uint256 p = PriceCalculator.calculatePrice(
                _spot,
                _series.strike,
                _series.maturity,
                _series.iv,
                _series.isPut
            );

            if (_marginLevel == IOptionVault.MarginLevel.Initial) {
                // initial margin
                if (_series.isPut) {
                    p += (_series.strike * _optionInfo.configs[IM_RATIO]) / (1000);
                } else {
                    p += (_spot * _optionInfo.configs[IM_RATIO]) / (1000);
                }
            } else {
                // maintenance margin
                if (_series.isPut) {
                    p += (_series.strike * _optionInfo.configs[MM_RATIO]) / (1000);
                } else {
                    p += (_spot * _optionInfo.configs[MM_RATIO]) / (1000);
                }
            }

            return uint128(size * p) / 1e10;
        } else if (_marginLevel == IOptionVault.MarginLevel.Safe) {
            bool collateralForShort;
            uint128 size;

            if (_u > 0) {
                size = uint128(_u);
                collateralForShort = _series.isPut;
            } else {
                size = uint128(-_u);
                collateralForShort = !_series.isPut;
            }

            if (collateralForShort) {
                return
                    (size * PredyMath.min(_series.strike, _spot) * _optionInfo.configs[PUT_SAFE_RATIO]) / (1e10 * 1000);
            } else {
                return (size * _spot * _optionInfo.configs[CALL_SAFE_RATIO]) / (1e10 * 1000);
            }
        }

        return 0;
    }

    /**
     * @notice calculate payout
     * put: amount * max(K - S, 0)
     * call: amount * max(S - K, 0)
     */
    function calculatePayout(
        uint128 _u,
        uint128 _spot,
        uint128 _strike,
        bool _isPut,
        bool _isRoundUp
    ) internal pure returns (uint128) {
        uint128 r;
        if (_isPut && _strike > _spot) {
            r = _strike - _spot;
        } else if (!_isPut && _strike < _spot) {
            r = _spot - _strike;
        } else {
            return 0;
        }
        return PredyMath.mulDiv(_u, r, 1e10, _isRoundUp);
    }

    function updateExpiredCount(OptionInfo storage _optionInfo, uint256 _expiryId) internal {
        IOptionVault.Expiration storage expiration = _optionInfo.expiries[_expiryId];

        require(expiration.expiry < block.timestamp, "expiry must have been passed");

        if (_optionInfo.expiredCount < _expiryId) {
            _optionInfo.expiredCount = uint128(_expiryId);
        }
    }

    function calculateDelta(
        int256 _maturity,
        IOptionVault.OptionSeries memory _option,
        int256 _position,
        uint128 _spot
    ) internal pure returns (int256 delta) {
        return
            (_position * PriceCalculator.calculateDelta(_spot, _option.strike, _maturity, _option.iv, _option.isPut)) /
            int256(1e8);
    }

    /**
     * @notice deposits USDC and borrows underlying asset from compound
     */
    function borrowUnderlyingInternal(
        OptionInfo storage _optionInfo,
        IOptionVault.Vault storage vault,
        uint128 _spot,
        uint256 _underlyingAmount
    ) internal {
        ERC20 underlying = ERC20(_optionInfo.tokens.underlying);

        uint256 decimals = underlying.decimals();

        uint128 depositCollateral = uint128(PredyMath.scale(_underlyingAmount * _spot * 2.0, decimals + 8, 6));

        // deposit USDC to compound
        IERC20(_optionInfo.tokens.collateral).approve(address(_optionInfo.lendingPool), depositCollateral);
        _optionInfo.lendingPool.deposit(_optionInfo.tokens.collateral, depositCollateral, address(this), 0);

        // borrow underling
        borrow(_optionInfo, _underlyingAmount);

        require(vault.collateral >= depositCollateral, "OptionLib: no enough collateral");

        depositCollateral = decreaseCollateral(vault, depositCollateral);
        vault.shortLiquidity += depositCollateral;
        _optionInfo.totalDepositedToLendingPool += depositCollateral;
    }

    /**
     * @notice repays underlying asset and withdraws USDC from compound
     * @param _optionInfo option vault object
     * @param _vault the vault repaying underlying asset
     * @param _spot spot price
     * @param _underlyingAmount amount to repay
     * @param _remainingDebt The remaining debt after the repayment, scaled by 1e8.
     */
    function repayUnderlyingInternal(
        OptionInfo storage _optionInfo,
        IOptionVault.Vault storage _vault,
        uint128 _spot,
        uint256 _underlyingAmount,
        uint256 _remainingDebt
    ) internal {
        IERC20(_optionInfo.tokens.underlying).approve(address(_optionInfo.lendingPool), _underlyingAmount);
        repayBorrow(_optionInfo, _underlyingAmount);

        // calculate unrequired collateral
        // if maturity is large, the protocol have to pay borrowing interest, and it can not redeem all shortLiquidity.
        // In this case, repay underling from outside of protocol.
        uint128 redeemCollateralAmount = _vault.shortLiquidity - (uint128(_remainingDebt) * _spot * 2) / 1e10;

        _optionInfo.lendingPool.withdraw(_optionInfo.tokens.collateral, redeemCollateralAmount, address(this));

        increaseCollateral(_vault, redeemCollateralAmount);

        _vault.shortLiquidity -= redeemCollateralAmount;
        _optionInfo.totalDepositedToLendingPool -= redeemCollateralAmount;
    }

    /**
     * @notice borrow underlying asset with Variable type debt
     */
    function borrow(OptionInfo storage _optionInfo, uint256 _amount) internal {
        _optionInfo.lendingPool.borrow(_optionInfo.tokens.underlying, _amount, 2, 0, address(this));
    }

    /**
     * @notice repay borrowing underlying asset with Variable type debt
     */
    function repayBorrow(OptionInfo storage _optionInfo, uint256 _amount) internal returns (uint128) {
        return uint128(_optionInfo.lendingPool.repay(_optionInfo.tokens.underlying, _amount, 2, address(this)));
    }
}

// File: contracts/PriceOracle.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";

/**
 * @notice chainlink price oracle.
 * This is the contract to fix the price at the nearest time after expiry.
 */
contract PriceOracle is Ownable {
    /// @dev expiry price with timestamp
    struct ExpiryPrice {
        uint256 price;
        uint256 timestamp;
    }

    /// @dev chainlink aggregator address of the underlying asset price
    mapping(address => AggregatorV3Interface) aggregators;

    /// @dev aggregator address => timestamp => expiry price
    mapping(address => mapping(uint256 => ExpiryPrice)) internal expiryPrices;

    event ExpiryPriceUpdated(address aggregator, uint256 expiry, uint256 price);

    uint256 public constant DISPUTE_PERIOD = 2 hours;

    /**
     * @notice set aggregator
     * only owner can set new aggregator
     */
    function setAggregator(address _aggregatorAddress) external onlyOwner {
        require(address(aggregators[_aggregatorAddress]) == address(0));
        aggregators[_aggregatorAddress] = AggregatorV3Interface(_aggregatorAddress);
    }

    /**
     * @notice set expiry price
     * anyone can set price if the price has not been setted.
     * Also timestamp must be later than expiration.
     */
    function setExpiryPrice(address _aggregator, uint256 _expiryTimestamp) external {
        (uint256 price, uint256 timestamp) = getPrice(_aggregator);

        require(_expiryTimestamp < timestamp, "PriceOracle: price timestamp must be later than expiry");

        ExpiryPrice storage expiryPrice = expiryPrices[_aggregator][_expiryTimestamp];

        require(expiryPrice.timestamp == 0, "PriceOracle: already setted");

        expiryPrice.price = price;
        expiryPrice.timestamp = timestamp;

        emit ExpiryPriceUpdated(_aggregator, _expiryTimestamp, price);
    }

    /**
     * @notice update expiry price
     * anyone can update price if the price has not been setted
     * or if new price's timestamp is earlier than previous one.
     * Also timestamp must be later than expiration.
     */
    function updateExpiryPrice(
        address _aggregator,
        uint256 _expiryTimestamp,
        uint80 _roundId
    ) external {
        (uint256 price, uint256 timestamp) = getHistoricalPrice(_aggregator, _roundId);

        require(_expiryTimestamp < timestamp, "PriceOracle: price timestamp must be later than expiry");

        ExpiryPrice storage expiryPrice = expiryPrices[_aggregator][_expiryTimestamp];

        require(
            expiryPrice.timestamp == 0 || expiryPrice.timestamp > timestamp,
            "PriceOracle: new price's timestamp must be close to expiry"
        );

        expiryPrice.price = price;
        expiryPrice.timestamp = timestamp;

        emit ExpiryPriceUpdated(_aggregator, _expiryTimestamp, price);
    }

    /**
     * @notice get price for an expiration
     * @return price price scaled by 1e8
     * @return _isFinalized returns true if price has been finalized, if not returns false
     */
    function getExpiryPrice(address _aggregator, uint256 _expiryTimestamp)
        external
        view
        returns (uint256 price, bool _isFinalized)
    {
        price = expiryPrices[_aggregator][_expiryTimestamp].price;
        _isFinalized = isFinalized(_aggregator, _expiryTimestamp);
    }

    /**
     * @notice return flag if price is finalized or not
     * true if dispute period has been passed and price has been setted more than once
     */
    function isFinalized(address _aggregator, uint256 _expiryTimestamp) internal view returns (bool) {
        return
            (_expiryTimestamp + DISPUTE_PERIOD < block.timestamp) &&
            expiryPrices[_aggregator][_expiryTimestamp].timestamp != 0;
    }

    /**
     * @notice get price scaled by 1e8
     */
    function getPrice(address _aggregatorAddress) public view returns (uint256, uint256) {
        (, int256 answer, , uint256 roundTimestamp, ) = aggregators[_aggregatorAddress].latestRoundData();

        require(answer > 0, "PriceOracle: price is lower than 0");

        return (uint256(answer), roundTimestamp);
    }

    function getHistoricalPrice(address _aggregatorAddress, uint80 _roundId) public view returns (uint256, uint256) {
        (, int256 answer, , uint256 roundTimestamp, ) = aggregators[_aggregatorAddress].getRoundData(_roundId);

        require(answer > 0, "PriceOracle: price is lower than 0");

        return (uint256(answer), roundTimestamp);
    }
}

// File: contracts/interfaces/IOptionVault.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.8.0;

interface IOptionVault {
    enum MarginLevel {
        Maintenance,
        Initial,
        Safe
    }

    struct Expiration {
        uint128 expiryId;
        uint64 expiry;
        uint256[] seriesIds;
    }

    struct OptionSeries {
        uint64 strike;
        bool isPut;
        uint64 iv;
        uint128 expiryId;
    }

    struct Account {
        address owner;
        uint256 settledCount;
        mapping(uint256 => Vault) vaults;
    }

    struct Vault {
        // is settled or not
        bool isSettled;
        // seriesId => short amount
        mapping(uint256 => uint128) shorts;
        // seriesId => long amount
        mapping(uint256 => uint128) longs;
        //
        uint128 collateral;
        //
        int256 hedgePosition;
        //
        uint128 shortLiquidity;
    }

    // view struct

    struct OptionSeriesParams {
        uint256 id;
        uint64 maturity;
        uint128 strike;
        bool isPut;
        uint128 iv;
    }

    struct OptionSeriesView {
        uint256 expiryId;
        uint256 seriesId;
        uint64 expiry;
        uint64 maturity;
        uint128 strike;
        bool isPut;
        uint64 iv;
    }

    struct AccountView {
        address owner;
        uint256 settledCount;
    }

    struct VaultView {
        address owner;
        bool isSettled;
        uint128 collateral;
        int256 hedgePosition;
        uint128 shortLiquidity;
    }

    function getExpiration(uint256 _expiryId) external view returns (IOptionVault.Expiration memory);

    function getOptionSeries(uint256 _seriesId) external view returns (IOptionVault.OptionSeriesView memory);

    function getAccount(uint256 _vaultId) external view returns (AccountView memory);

    function getVault(uint256 _vaultId, uint256 _expiryId) external view returns (VaultView memory);

    function getCollateralValueQuote(uint256 _vaultId) external view returns (uint128);

    function getRequiredMargin(
        uint256 _vaultId,
        uint256 _expiryId,
        IOptionVault.MarginLevel _marginLevel
    ) external view returns (uint128);

    function calRequiredMarginForASeries(
        uint256 _seriesId,
        int128 _amount,
        IOptionVault.MarginLevel _marginLevel
    ) external view returns (uint128);

    function getTotalPayout(uint256 _vaultId, uint256 _expiryId) external view returns (uint128);

    function getPositionSize(uint256 _vaultId, uint256 _seriesId) external view returns (uint128, uint128);

    function getLiveOptionSerieses() external view returns (IOptionVault.Expiration[] memory);

    function getLastExpiry() external view returns (uint64);

    function createAccount() external returns (uint256);

    function setIV(uint256 _seriesId, uint128 _iv) external;

    function deposit(
        uint256 _vaultId,
        uint256 _expiryId,
        uint128 _collateral
    ) external;

    function withdraw(
        uint256 _vaultId,
        uint256 _expiryId,
        uint128 _collateral
    ) external;

    function closeShortPosition(
        uint256 _accountId,
        uint256 _seriesId,
        uint128 _amount,
        uint128 _cRatio
    ) external returns (uint128);

    function write(
        uint256 _vaultId,
        uint256 _seriesId,
        uint128 _amount,
        address _recepient
    ) external;

    function depositAndWrite(
        uint256 _vaultId,
        uint256 _seriesId,
        uint128 _collateral,
        uint128 _amount,
        address _recepient
    ) external returns (uint128);

    function settleVault(uint256 _vaultId, uint256 _expiryId) external returns (uint128);

    function claim(uint256 _seriesId, uint128 _size) external returns (uint128);

    function addLong(
        uint256 _vaultId,
        uint256 _expiryId,
        uint256 _seriesId,
        uint128 _amount
    ) external;

    function removeLong(
        uint256 _vaultId,
        uint256 _expiryId,
        uint256 _seriesId,
        uint128 _amount
    ) external;

    function calculateVaultDelta(uint256 _vaultId, uint256 _expiryId) external view returns (int256);
}

// File: contracts/interfaces/IAMM.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.8.0;

import "./IFeePool.sol";

interface IAMM {
    function sell(
        uint256 _seriesId,
        uint128 _amount,
        uint128 _minFee
    ) external returns (uint128);

    function feePool() external returns (IFeePool);
}

// File: @openzeppelin/contracts/token/ERC1155/IERC1155.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC1155 compliant contract, as defined in the
 * https://eips.ethereum.org/EIPS/eip-1155[EIP].
 *
 * _Available since v3.1._
 */
interface IERC1155 is IERC165 {
    /**
     * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`.
     */
    event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);

    /**
     * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
     * transfers.
     */
    event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values);

    /**
     * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
     * `approved`.
     */
    event ApprovalForAll(address indexed account, address indexed operator, bool approved);

    /**
     * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
     *
     * If an {URI} event was emitted for `id`, the standard
     * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
     * returned by {IERC1155MetadataURI-uri}.
     */
    event URI(string value, uint256 indexed id);

    /**
     * @dev Returns the amount of tokens of token type `id` owned by `account`.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function balanceOf(address account, uint256 id) external view returns (uint256);

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) external view returns (uint256[] memory);

    /**
     * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
     *
     * Emits an {ApprovalForAll} event.
     *
     * Requirements:
     *
     * - `operator` cannot be the caller.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
     *
     * See {setApprovalForAll}.
     */
    function isApprovedForAll(address account, address operator) external view returns (bool);

    /**
     * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If the caller is not `from`, it must be have been approved to spend ``from``'s tokens via {setApprovalForAll}.
     * - `from` must have a balance of tokens of type `id` of at least `amount`.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external;

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function safeBatchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data) external;
}

// File: @openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../IERC1155.sol";

/**
 * @dev Interface of the optional ERC1155MetadataExtension interface, as defined
 * in the https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[EIP].
 *
 * _Available since v3.1._
 */
interface IERC1155MetadataURI is IERC1155 {
    /**
     * @dev Returns the URI for token type `id`.
     *
     * If the `\{id\}` substring is present in the URI, it must be replaced by
     * clients with the actual token type ID.
     */
    function uri(uint256 id) external view returns (string memory);
}

// 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;
        // solhint-disable-next-line no-inline-assembly
        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");

        // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
        (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");

        // solhint-disable-next-line avoid-low-level-calls
        (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");

        // solhint-disable-next-line avoid-low-level-calls
        (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");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private 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

                // solhint-disable-next-line no-inline-assembly
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

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



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/*
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
        return msg.data;
    }
}

// File: @openzeppelin/contracts/utils/introspection/ERC165.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./IERC165.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 *
 * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}

// File: @openzeppelin/contracts/utils/introspection/IERC165.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

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



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * We have followed general OpenZeppelin guidelines: functions revert instead
 * of returning `false` on failure. This behavior is nonetheless conventional
 * and does not conflict with the expectations of ERC20 applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20 is Context, IERC20, IERC20Metadata {
    mapping (address => uint256) private _balances;

    mapping (address => mapping (address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * The defaut value of {decimals} is 18. To select a different value for
     * {decimals} you should overload it.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    constructor (string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5,05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the value {ERC20} uses, unless this function is
     * overridden;
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual override returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `recipient` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * Requirements:
     *
     * - `sender` and `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     * - the caller must have allowance for ``sender``'s tokens of at least
     * `amount`.
     */
    function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(sender, recipient, amount);

        uint256 currentAllowance = _allowances[sender][_msgSender()];
        require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
        _approve(sender, _msgSender(), currentAllowance - amount);

        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue);
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        uint256 currentAllowance = _allowances[_msgSender()][spender];
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        _approve(_msgSender(), spender, currentAllowance - subtractedValue);

        return true;
    }

    /**
     * @dev Moves tokens `amount` from `sender` to `recipient`.
     *
     * This is internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `sender` cannot be the zero address.
     * - `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     */
    function _transfer(address sender, address recipient, uint256 amount) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(sender, recipient, amount);

        uint256 senderBalance = _balances[sender];
        require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
        _balances[sender] = senderBalance - amount;
        _balances[recipient] += amount;

        emit Transfer(sender, recipient, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        _balances[account] += amount;
        emit Transfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        _balances[account] = accountBalance - amount;
        _totalSupply -= amount;

        emit Transfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be to transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
}

// File: contracts/lib/PriceCalculator.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.8.0;

import "./AdvancedMath.sol";

/**
 * @notice Option price calculator using Black-Scholes formula
 */
library PriceCalculator {
    /// @dev sqrt(365 * 86400)
    int256 internal constant SQRT_YEAR_E8 = 5615.69229926 * 10**8;
    /// @dev range size of linear function
    int256 internal constant RANGE = 20 * 1e6;

    struct Parameters {
        // spot price
        int256 spot;
        // strike price
        int256 strike;
        // log(spot / strike)
        int256 logSigE4;
        // sqrt(maturity)
        int256 sqrtMaturity;
    }

    /**
     * @notice calculate option price at a IV point
     * @param _spot spot price scaled 1e8
     * @param _strike strike price scaled 1e8
     * @param _maturity maturity in seconds
     * @param _iv IV
     * @param _isPut option type
     * @return premium per amount
     */
    function calculatePrice(
        uint256 _spot,
        uint256 _strike,
        uint256 _maturity,
        uint256 _iv,
        bool _isPut
    ) external pure returns (uint256 premium) {
        validateParameters(_spot, _strike);
        require(0 < _iv && _iv < 1000 * 1e6, "PriceCalculator: implied volatility must be between 0 and 1000%");

        int256 sqrtMaturity = getSqrtMaturity(_maturity);
        int256 logSigE4;
        {
            int256 spotPerStrikeE4 = int256((_spot * 1e4) / _strike);
            logSigE4 = AdvancedMath.logTaylor(spotPerStrikeE4);
        }

        return
            uint256(
                calOptionPrice(
                    Parameters(int256(_spot), int256(_strike), logSigE4, sqrtMaturity),
                    int256(_iv),
                    _isPut,
                    // calculatePrice function never reverts by delta cut-off
                    0
                )
            );
    }

    /**
     * @notice calculate option price with two IV points
     * @param _spot spot price scaled 1e8
     * @param _strike strike price scaled 1e8
     * @param _maturity maturity in seconds
     * @param _x0 start IV
     * @param _x1 end IV
     * @param _isPut option type
     * @param _minDelta minimum delta. if delta is less than minDelta or greater than (100% - minDelta), calculation reverts
     * @return premium per amount
     */
    function calculatePrice2(
        uint256 _spot,
        uint256 _strike,
        uint256 _maturity,
        uint256 _x0,
        uint256 _x1,
        bool _isPut,
        uint256 _minDelta
    ) external pure returns (uint256 premium) {
        validateParameters(_spot, _strike);
        require(0 < _x0 && _x0 < 10 * 1e8, "PriceCalculator: 0 < x0 < 1000%");
        require(0 < _x1 && _x1 < 10 * 1e8, "PriceCalculator: 0 < x1 < 1000%");
        require(_x0 < _x1, "PriceCalculator: _x0 < _x1");

        int256 sqrtMaturity = getSqrtMaturity(_maturity);

        int256 logSigE4;
        {
            int256 spotPerStrikeE4 = int256((_spot * 1e4) / _strike);
            logSigE4 = AdvancedMath.logTaylor(spotPerStrikeE4);
        }

        return
            uint256(
                calculatePriceOfRanges(
                    Parameters(int256(_spot), int256(_strike), logSigE4, sqrtMaturity),
                    int256(_x0),
                    int256(_x1),
                    _isPut,
                    _minDelta
                )
            );
    }

    /**
     * @notice calculate option's delta
     * @param _spot spot price scaled 1e8
     * @param _strike strike price scaled 1e8
     * @param sqrtMaturity maturity in seconds
     * @param _iv IV
     * @param _isPut option type
     * @return delta
     */
    function calculateDelta(
        uint256 _spot,
        uint256 _strike,
        int256 sqrtMaturity,
        uint256 _iv,
        bool _isPut
    ) external pure returns (int256) {
        validateParameters(_spot, _strike);
        require(0 < _iv && _iv < 1000 * 1e6, "PriceCalculator: implied volatility must be between 0 and 1000%");

        int256 logSigE4;
        {
            int256 spotPerStrikeE4 = int256((_spot * 1e4) / _strike);
            logSigE4 = AdvancedMath.logTaylor(spotPerStrikeE4);
        }
        (int256 d1E4, ) = _calD1D2(logSigE4, sqrtMaturity, int256(_iv));
        if (_isPut) {
            return -AdvancedMath.calStandardNormalCDF(-d1E4);
        } else {
            return AdvancedMath.calStandardNormalCDF(d1E4);
        }
    }

    function getSqrtMaturity(uint256 _maturity) public pure returns (int256) {
        require(
            _maturity > 0 && _maturity < 31536000,
            "PriceCalculator: maturity must not have expired and less than 1 year"
        );

        return (AdvancedMath.sqrt(int256(_maturity)) * 1e16) / SQRT_YEAR_E8;
    }

    function calculatePriceOfRanges(
        Parameters memory _params,
        int256 _x0,
        int256 _x1,
        bool _isPut,
        uint256 _minDelta
    ) internal pure returns (int256 premium) {
        int256 lower = _x0 / RANGE;
        int256 upper = _x1 / RANGE;
        int256 cache;
        for (int256 i = lower; i <= upper; i++) {
            int256 x0 = 0;
            int256 x1 = RANGE;
            if (i == lower) {
                x0 = _x0 - i * RANGE;
            }
            if (i == upper) {
                x1 = _x1 - i * RANGE;
            }
            int256 p;
            (p, cache) = calculatePriceOfRange(_params, i, x0, x1, _isPut, cache, _minDelta);
            premium += p;
        }
        premium /= upper - lower + 1;
    }

    function calculatePriceOfRange(
        Parameters memory _params,
        int256 _tick,
        int256 _x0,
        int256 _x1,
        bool _isPut,
        int256 _start,
        uint256 _minDelta
    ) internal pure returns (int256, int256) {
        if (_start == 0) {
            _start = calOptionPrice(_params, _tick * RANGE, _isPut, _minDelta);
        }
        int256 end = calOptionPrice(_params, (_tick + 1) * RANGE, _isPut, _minDelta);
        // y = (end - start)/RANGE * x + start + instrict
        return ((_start + ((end - _start) * (_x1 + _x0)) / (2 * RANGE)), end);
    }

    function _calD1D2(
        int256 _logSigE4,
        int256 _sqrtMaturity,
        int256 _volatilityE8
    ) internal pure returns (int256 d1E4, int256 d2E4) {
        int256 sigE8 = (_volatilityE8 * _sqrtMaturity) / (1e8);
        d1E4 = ((_logSigE4 * 10**8) / sigE8) + (sigE8 / (2 * 10**4));
        d2E4 = d1E4 - (sigE8 / 10**4);
    }

    function calOptionPrice(
        Parameters memory _params,
        int256 _volatility,
        bool _isPut,
        uint256 _minDelta
    ) internal pure returns (int256 price) {
        int256 nd1E8;

        if (_volatility > 0) {
            (int256 d1E4, int256 d2E4) = _calD1D2(_params.logSigE4, _params.sqrtMaturity, _volatility);
            nd1E8 = AdvancedMath.calStandardNormalCDF(d1E4);
            int256 nd2E8 = AdvancedMath.calStandardNormalCDF(d2E4);
            price = (_params.spot * nd1E8 - _params.strike * nd2E8) / 1e8;
        }
        int256 lowestPrice;
        if (_isPut) {
            price = price - _params.spot + _params.strike;

            lowestPrice = (_params.strike > _params.spot) ? _params.strike - _params.spot : int256(0);
        } else {
            lowestPrice = (_params.spot > _params.strike) ? _params.spot - _params.strike : int256(0);
        }

        // delta cut-off
        // if option type is put, delta is `1 - N(d1)`
        // if option type is call, delta is `N(d1)`
        require((!_isPut && abs(nd1E8) >= _minDelta) || (_isPut && 1e8 - abs(nd1E8) >= _minDelta), "delta is too low");

        if (price < lowestPrice) {
            return lowestPrice;
        }

        return price;
    }

    function validateParameters(uint256 _spot, uint256 _strike) internal pure {
        require(_spot > 0 && _spot < 1e13, "PriceCalculator: spot price must be between 0 and 10^13");
        require(_strike > 0 && _strike < 1e13, "PriceCalculator: strike price must be between 0 and 10^13");
    }

    function abs(int256 x) internal pure returns (uint256) {
        return uint256(x >= 0 ? x : -x);
    }
}

// File: contracts/interfaces/IAave.sol



pragma solidity ^0.8.0;

interface LendingPool {
    function deposit(
        address asset,
        uint256 amount,
        address onBehalfOf,
        uint16 referralCode
    ) external;

    function withdraw(
        address asset,
        uint256 amount,
        address to
    ) external returns (uint256);

    function borrow(
        address asset,
        uint256 amount,
        uint256 interestRateMode,
        uint16 referralCode,
        address onBehalfOf
    ) external;

    function repay(
        address asset,
        uint256 amount,
        uint256 rateMode,
        address onBehalfOf
    ) external returns (uint256);
}

// File: contracts/interfaces/IHedge.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.8.0;

interface IHedge {
    function init(address _ammAddress) external;

    function underlying() external returns (address);

    function shortAsset() external returns (address);

    function buy(uint256 _underlyingAmount, uint256 _amountInMaximum) external returns (uint256 amountIn);

    function sell(uint256 _underlyingAmount, uint256 _amountOutMinimum) external returns (uint256 amountOut);

    function getShortPrice(uint256 _size) external view returns (uint256);

    function buyShort(uint256 _size, uint256 _amountToPay) external returns (uint256 amountIn);

    function sellShort(uint256 _size, uint256 amountToReceive) external returns (uint256 amountOut);

    function getValueQuote(int256 _position) external view returns (uint256);
}

// File: contracts/interfaces/IWETH.sol



pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IWETH is IERC20 {
    function deposit() external payable;

    function withdraw(uint256 value) external;
}

// File: contracts/interfaces/IFeePool.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.8.0;

interface IFeePool {
    function sendProfitERC20(address _account, uint256 _amount) external;
}

// File: contracts/lib/PredyMath.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/math/SafeCast.sol";

library PredyMath {
    function max(uint128 a, uint128 b) internal pure returns (uint128) {
        return a > b ? a : b;
    }

    function min(uint128 a, uint128 b) internal pure returns (uint128) {
        return a > b ? b : a;
    }

    function abs(int128 x) internal pure returns (uint128) {
        return uint128(x >= 0 ? x : -x);
    }

    function mulDiv(
        uint256 _x,
        uint256 _y,
        uint256 _d,
        bool _roundUp
    ) internal pure returns (uint128) {
        uint256 tailing;
        if (_roundUp) {
            uint256 remainer = (_x * _y) % _d;
            if (remainer > 0) {
                tailing = 1;
            }
        }

        uint256 result = (_x * _y) / _d + tailing;

        return SafeCast.toUint128(result);
    }

    function scale(
        uint256 _a,
        uint256 _from,
        uint256 _to
    ) internal pure returns (uint256) {
        if (_from > _to) {
            return _a / 10**(_from - _to);
        } else if (_from < _to) {
            return _a * 10**(_to - _from);
        } else {
            return _a;
        }
    }

    function toInt128(uint256 _a) internal pure returns (int128) {
        return SafeCast.toInt128(SafeCast.toInt256(_a));
    }
}

// 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: @openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

// File: contracts/lib/AdvancedMath.sol



/// from https://github.com/LienFinance/bondmaker
pragma solidity ^0.8.0;

library AdvancedMath {
    /// @dev sqrt(2*PI) * 10^8
    int256 internal constant SQRT_2PI_E8 = 250662827;
    /// @dev PI * 10^8
    int256 internal constant PI_E8 = 314159265;
    /// @dev Napier's constant
    int256 internal constant E_E8 = 271828182;
    /// @dev Inverse of Napier's constant (1/e)
    int256 internal constant INV_E_E8 = 36787944;

    // for CDF
    int256 internal constant p = 23164190;
    int256 internal constant b1 = 31938153;
    int256 internal constant b2 = -35656378;
    int256 internal constant b3 = 178147793;
    int256 internal constant b4 = -182125597;
    int256 internal constant b5 = 133027442;

    /**
     * @dev Calculate an approximate value of the square root of x by Babylonian method.
     */
    function sqrt(int256 x) internal pure returns (int256 y) {
        require(x >= 0, "cannot calculate the square root of a negative number");
        int256 z = (x + 1) / 2;
        y = x;
        while (z < y) {
            y = z;
            z = (x / z + z) / 2;
        }
    }

    /**
     * @dev Returns log(x) for any positive x.
     */
    function logTaylor(int256 inputE4) internal pure returns (int256 outputE4) {
        require(inputE4 > 1, "input should be positive number");
        int256 inputE8 = inputE4 * 1e4;
        // input x for _logTaylor1 is adjusted to 1/e < x < 1.
        while (inputE8 < INV_E_E8) {
            inputE8 = (inputE8 * E_E8) / 1e8;
            outputE4 -= 1e4;
        }
        while (inputE8 > 1e8) {
            inputE8 = (inputE8 * INV_E_E8) / 1e8;
            outputE4 += 1e4;
        }
        outputE4 += logTaylor1(inputE8 / 1e4 - 1e4);
    }

    /**
     * @notice Calculate an approximate value of the logarithm of input value by
     * Taylor expansion around 1.
     * @dev log(x + 1) = x - 1/2 x^2 + 1/3 x^3 - 1/4 x^4 + 1/5 x^5
     *                     - 1/6 x^6 + 1/7 x^7 - 1/8 x^8 + ...
     */
    function logTaylor1(int256 inputE4) internal pure returns (int256 outputE4) {
        outputE4 =
            inputE4 -
            inputE4**2 /
            (2 * 1e4) +
            inputE4**3 /
            (3 * 1e8) -
            inputE4**4 /
            (4 * 1e12) +
            inputE4**5 /
            (5 * 1e16) -
            inputE4**6 /
            (6 * 1e20) +
            inputE4**7 /
            (7 * 1e24) -
            inputE4**8 /
            (8 * 1e28);
    }

    /**
     * @notice Calculate the cumulative distribution function of standard normal
     * distribution.
     * @dev Abramowitz and Stegun, Handbook of Mathematical Functions (1964)
     * http://people.math.sfu.ca/~cbm/aands/
     * errors are less than 0.7% at -3.2
     */
    function calStandardNormalCDF(int256 inputE4) internal pure returns (int256 outputE8) {
        require(inputE4 < 440 * 1e4 && inputE4 > -440 * 1e4, "input is too large");
        int256 _inputE4 = inputE4 > 0 ? inputE4 : inputE4 * (-1);
        int256 t = 1e16 / (1e8 + (p * _inputE4) / 1e4);
        int256 X2 = (inputE4 * inputE4) / 2;
        int256 X3 = (X2 * X2) / 1e8;
        int256 X4 = (X3 * X2) / 1e8;
        int256 exp2X2 = 1e8 +
            X2 +
            (X3 / 2) +
            (X4 / 6) +
            ((X3 * X3) / (24 * 1e8)) +
            ((X2 * (X3 * X3)) / (120 * 1e16)) +
            ((X4 * X4) / (720 * 1e8)) +
            ((X2 * (X4 * X4)) / (5040 * 1e16)) +
            ((X3 * (X4 * X4)) / (40320 * 1e16)) +
            ((X4 * X4 * X4) / (362880 * 1e16)) +
            ((X2 * (X4 * X4 * X4)) / (3628800 * 1e24)) +
            ((X3 * (X4 * X4 * X4)) / (39916800 * 1e24));

        int256 Z = (1e24 / exp2X2) / SQRT_2PI_E8;
        int256 y = (b5 * t) / 1e8;
        y = ((y + b4) * t) / 1e8;
        y = ((y + b3) * t) / 1e8;
        y = ((y + b2) * t) / 1e8;
        y = 1e8 - (Z * ((y + b1) * t)) / 1e16;
        return inputE4 > 0 ? y : 1e8 - y;
    }
}

// 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 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 < 2**128, "SafeCast: value doesn\'t fit in 128 bits");
        return uint128(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 < 2**64, "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 < 2**32, "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 < 2**16, "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 < 2**8, "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 >= -2**127 && value < 2**127, "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 >= -2**63 && value < 2**63, "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 >= -2**31 && value < 2**31, "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 >= -2**15 && value < 2**15, "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 >= -2**7 && value < 2**7, "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) {
        require(value < 2**255, "SafeCast: value doesn't fit in an int256");
        return int256(value);
    }
}

// File: @openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../utils/Context.sol";
/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor () {
        address msgSender = _msgSender();
        _owner = msgSender;
        emit OwnershipTransferred(address(0), msgSender);
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
}

// File: @chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0;

interface AggregatorV3Interface {

  function decimals() external view returns (uint8);
  function description() external view returns (string memory);
  function version() external view returns (uint256);

  // getRoundData and latestRoundData should both raise "No data present"
  // if they do not have data to report, instead of returning unset values
  // which could be misinterpreted as actual reported values.
  function getRoundData(uint80 _roundId)
    external
    view
    returns (
      uint80 roundId,
      int256 answer,
      uint256 startedAt,
      uint256 updatedAt,
      uint80 answeredInRound
    );
  function latestRoundData()
    external
    view
    returns (
      uint80 roundId,
      int256 answer,
      uint256 startedAt,
      uint256 updatedAt,
      uint80 answeredInRound
    );

}