ETH Price: $1,787.56 (+4.88%)

Contract

0xafebbb512Ff4093E757D836EB408Df6edf089453

Overview

ETH Balance

0 ETH

ETH Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To

There are no matching entries

4 Internal Transactions found.

Latest 4 internal transactions

Parent Transaction Hash Block From To
96807992024-02-05 3:50:38443 days ago1707105038
0xafebbb51...edf089453
 Contract Creation0 ETH
96804282024-02-05 3:27:34443 days ago1707103654
0xafebbb51...edf089453
 Contract Creation0 ETH
96802902024-02-05 3:19:36443 days ago1707103176
0xafebbb51...edf089453
 Contract Creation0 ETH
96799052024-02-05 2:55:05443 days ago1707101705
0xafebbb51...edf089453
 Contract Creation0 ETH
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
DenebOrbiter

Compiler Version
v0.8.17+commit.8df45f5f

Optimization Enabled:
Yes with 800 runs

Other Settings:
default evmVersion
File 1 of 32 : DenebOrbiter.sol
//  SPDX-License-Identifier: AGPL-3.0-or-later
//
//  DenebOrbiter.sol
//
//  Copyright (C) 2023 CygnusDAO
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.

/*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════════  .
    .               .            .               .      🛰️     .           .                 *              .
           █████████           ---======*.                                                 .           ⠀
          ███░░░░░███                                               📡                🌔                       . 
         ███     ░░░  █████ ████  ███████ ████████   █████ ████  █████        ⠀
        ░███         ░░███ ░███  ███░░███░░███░░███ ░░███ ░███  ███░░      .     .⠀           .           .
        ░███          ░███ ░███ ░███ ░███ ░███ ░███  ░███ ░███ ░░█████       ⠀
        ░░███     ███ ░███ ░███ ░███ ░███ ░███ ░███  ░███ ░███  ░░░░███              .             .⠀
         ░░█████████  ░░███████ ░░███████ ████ █████ ░░████████ ██████     .----===*  ⠀
          ░░░░░░░░░    ░░░░░███  ░░░░░███░░░░ ░░░░░   ░░░░░░░░ ░░░░░░            .                            .⠀
                       ███ ░███  ███ ░███                .                 .                 .  ⠀
     🛰️  .             ░░██████  ░░██████                                             .                 .           
                       ░░░░░░    ░░░░░░      -------=========*                      .                     ⠀
           .                            .       .          .            .                          .             .⠀
    
        COLLATERAL DEPLOYER V1 - `Deneb`                                                           
    ═══════════════════════════════════════════════════════════════════════════════════════════════════════════  */
pragma solidity >=0.8.17;

// Dependencies
import {IDenebOrbiter} from "./interfaces/IDenebOrbiter.sol";

// Bytecode
import {CygnusCollateral} from "./CygnusCollateral.sol";

/**
 *  @title  DenebOrbiter Contract that deploys the Cygnus Collateral arm of the lending pool
 *  @author CygnusDAO
 *  @notice The Collateral Deployer which deploys the collateral arm of the lending pool. It deploys the collateral
 *          contract with the factory, underlying LP token, corresponding Cygnus Borrow contract address, the 
 *          oracle for this lending pool and the unique shuttle ID.
 *          We pass structs to avoid having to set constructors in the core contracts, being able to calculate
 *          addresses of lending pools with CREATE2
 */
contract DenebOrbiter is IDenebOrbiter {
    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════
            2. STORAGE
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── Internal ───────────────────────────────────────────────  */

    /**
     *  @custom:struct CollateralParameters Important parameters for the collateral contracts
     *  @custom:member factory The address of the Cygnus factory
     *  @custom:member underlying The address of the underlying LP Token
     *  @custom:member borrowable The address of the Cygnus borrow contract for this collateral
     *  @custom:member oracle The address of the oracle for this lending pool
     *  @custom:member shuttleId The unique id of this lending pool (shared by borrowable)
     */
    struct CollateralParameters {
        address factory;
        address underlying;
        address borrowable;
        address oracle;
        uint256 shuttleId;
    }

    /*  ─────────────────────────────────────────────── Public ────────────────────────────────────────────────  */

    /**
     *  @inheritdoc IDenebOrbiter
     */
    CollateralParameters public override shuttleParameters;

    /**
     *  @inheritdoc IDenebOrbiter
     */
    bytes32 public immutable override collateralInitCodeHash = keccak256(type(CygnusCollateral).creationCode);

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════
            6. NON-CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @inheritdoc IDenebOrbiter
     */
    function deployDeneb(
        address underlying,
        address borrowable,
        address oracle,
        uint256 shuttleId
    ) external override returns (address collateral) {
        // Assign important addresses to pass to collateral contracts
        shuttleParameters = CollateralParameters({
            factory: msg.sender,
            underlying: underlying,
            borrowable: borrowable,
            oracle: oracle,
            shuttleId: shuttleId
        });

        // Create Collateral contract
        collateral = address(new CygnusCollateral{salt: keccak256(abi.encode(underlying, msg.sender))}());

        // Delete and refund some gas
        delete shuttleParameters;
    }
}

File 2 of 32 : CygnusCollateral.sol
//  SPDX-License-Identifier: AGPL-3.0-or-later
//
//  CygnusCollateral.sol
//
//  Copyright (C) 2023 CygnusDAO
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.

/*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════════  
    .              .            .               .      🛰️     .           .                .           .
           █████████     🛰️      ---======*.                                                 .           ⠀
          ███░░░░░███                                               📡                🌔                      . 
         ███     ░░░  █████ ████  ███████ ████████   █████ ████  █████        ⠀
        ░███         ░░███ ░███  ███░░███░░███░░███ ░░███ ░███  ███░░      .     .⠀           .           .
        ░███          ░███ ░███ ░███ ░███ ░███ ░███  ░███ ░███ ░░█████       ⠀
        ░░███     ███ ░███ ░███ ░███ ░███ ░███ ░███  ░███ ░███  ░░░░███              .             .⠀
         ░░█████████  ░░███████ ░░███████ ████ █████ ░░████████ ██████     .----===*  ⠀
          ░░░░░░░░░    ░░░░░███  ░░░░░███░░░░ ░░░░░   ░░░░░░░░ ░░░░░░            .                           .⠀
                       ███ ░███  ███ ░███                .                 .                 .⠀
        .             ░░██████  ░░██████        🛰️                        🛰️             .                 .     
                       ░░░░░░    ░░░░░░      -------=========*                      .                     ⠀
           .                            .       .          .            .                        .             .⠀
        
        COLLATERAL (CygLP) - https://cygnusdao.finance                                                          .                     .
    ═══════════════════════════════════════════════════════════════════════════════════════════════════════════

     Smart contracts to `go long` on your liquidity.

     Deposit liquidity, borrow USD.

     Structure of all Cygnus Contracts:

     Contract                        ⠀Interface                                             
        ├ 1. Libraries                   ├ 1. Custom Errors                                               
        ├ 2. Storage                     ├ 2. Custom Events
        │     ├ Private             ⠀    ├ 3. Constant Functions                          ⠀        
        │     ├ Internal                 │     ├ Public                            ⠀       
        │     └ Public                   │     └ External                        ⠀⠀⠀              
        ├ 3. Constructor                 └ 4. Non-Constant Functions  
        ├ 4. Modifiers              ⠀          ├ Public
        ├ 5. Constant Functions     ⠀          └ External
        │     ├ Private             ⠀                      
        │     ├ Internal            
        │     ├ Public              
        │     └ External            
        └ 6. Non-Constant Functions 
              ├ Private             
              ├ Internal            
              ├ Public              
              └ External            

    @dev: Inspired by Impermax, follows similar architecture and code but with significant edits. It should 
          only be tested with Solidity >=0.8 as some functions don't check for overflow/underflow and all errors
          are handled with the new `custom errors` feature among other small things...                           */
pragma solidity >=0.8.17;

// Dependencies
import {ICygnusCollateral} from "./interfaces/ICygnusCollateral.sol";
import {CygnusCollateralVoid} from "./CygnusCollateralVoid.sol";

// Libraries
import {SafeTransferLib} from "./libraries/SafeTransferLib.sol";
import {FixedPointMathLib} from "./libraries/FixedPointMathLib.sol";

// Interfaces
import {ICygnusBorrow} from "./interfaces/ICygnusBorrow.sol";
import {ICygnusAltairCall} from "./interfaces/ICygnusAltairCall.sol";

// Overrides
import {ERC20} from "./ERC20.sol";

/**
 *  @title  CygnusCollateral Main Collateral contract handles transfers and seizings of collateral
 *  @author CygnusDAO
 *  @notice This is the main Collateral contract which is used for liquidations and for flash redeeming the
 *          underlying. It also overrides the `burn` internal function, calling the borrowable arm to query
 *          the redeemer's current borrow balance to check if the user can redeem the LP Tokens.
 *
 *          When a user's position gets liquidated, it is initially called by the borrow arm. The liquidator
 *          first repays back stables to the borrowable arm and then calls `liquidate` which then calls
 *         `seizeCygLP` in this contract to seize the equivalent of the repaid amount + the liquidation
 *          incentive in CygLP.
 *
 *          There is a liquidation fee which can be set by the hangar18 admin that goes to the DAO Reserves,
 *          taken directly from the user being liquidated. This fee is set to 0 as default.
 *
 */
contract CygnusCollateral is ICygnusCollateral, CygnusCollateralVoid {
    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            1. LIBRARIES
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @custom:library SafeTransferLib ERC20 transfer library that gracefully handles missing return values.
     */
    using SafeTransferLib for address;

    /**
     *  @custom:library FixedPointMathLib Arithmetic library with operations for fixed-point numbers.
     */
    using FixedPointMathLib for uint256;

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            6. NON-CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── Internal ───────────────────────────────────────────────  */

    /**
     *  @notice ERC20 Overrides transfers of CygLP
     *  @notice Before any token transfer we check whether the user has sufficient liquidity (no debt) to transfer
     *  @inheritdoc ERC20
     */
    function _beforeTokenTransfer(address from, address, uint256 amount) internal override(ERC20) {
        // Escape in case of `flashRedeemAltair()`
        // This contract should never have CygLP outside of flash redeeming. If a user is flash redeeming it requires them
        // to `transfer()` or `transferFrom()` to this address first, and it will check `canRedeem` before transfer.
        if (from == address(this)) return;

        // Even though we use borrow indices we still try to accrue on any transfer
        ICygnusBorrow(twinstar).accrueInterest();

        /// @custom:error InsufficientLiquidity Avoid transfers or burns if there's shortfall
        if (!canRedeem(from, amount)) revert CygnusCollateral__InsufficientLiquidity();
    }

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @notice No reason to update since there are no new balance updates
     *  @inheritdoc ICygnusCollateral
     *  @custom:security non-reentrant
     */
    function seizeCygLP(
        address liquidator,
        address borrower,
        uint256 repayAmount
    ) external override nonReentrant returns (uint256 liquidatorAmount) {
        /// @custom:error MsgSenderNotBorrowable Avoid unless msg sender is this shuttle's CygnusBorrow contract
        if (msg.sender != twinstar) revert CygnusCollateral__MsgSenderNotBorrowable();
        /// @custom:erro CantLiquidateZero Avoid liquidating 0 repayAmount
        else if (repayAmount == 0) revert CygnusCollateral__CantLiquidateZero();

        // Get user's shortfall (if any)
        (, uint256 shortfall) = _accountLiquidity(borrower, type(uint256).max);

        // @custom:error NotLiquidatable Avoid unless borrower's loan is in liquidatable state
        if (shortfall == 0) revert CygnusCollateral__NotLiquidatable();

        // Get price from oracle
        uint256 lpTokenPrice = getLPTokenPrice();

        // Get the equivalent of the repaid amount in the underlying LP
        uint256 lpEquivalent = repayAmount.divWad(lpTokenPrice);

        // The equivalent of the LP in CygLP to seize from bororwers
        uint256 cygLPAmount = _convertToShares(lpEquivalent);

        // Add the liquidation incentive to the CygLP seized
        liquidatorAmount = cygLPAmount.mulWad(liquidationIncentive);

        // Transfer amount + incentive to liquidator, escapes canRedeem
        _transfer(borrower, liquidator, liquidatorAmount);

        // Initialize and check if liquidation fee is set
        uint256 daoFee;

        // Check for protocol fee
        if (liquidationFee > 0) {
            // Get the liquidation fee amount that is kept by the protocol from the `cygLPAmount`
            daoFee = cygLPAmount.mulWad(liquidationFee);

            // Get latest DAO reserves from Hangar18
            address daoReserves = hangar18.daoReserves();

            // If applicable, seize daoFee from the borrower, escapes can redeem
            _transfer(borrower, daoReserves, daoFee);
        }

        // Total CygLP seized from the borrower
        uint256 totalSeized = liquidatorAmount + daoFee;

        /// @custom:event SeizeCygLP
        emit SeizeCygLP(liquidator, borrower, cygLPAmount, liquidatorAmount, daoFee, totalSeized);
    }

    /**
     *  @dev This low level function should be called from a periphery contract only
     *  @inheritdoc ICygnusCollateral
     *  @custom:security non-reentrant
     */
    function flashRedeemAltair(
        address redeemer,
        uint256 assets,
        bytes calldata data
    ) external override nonReentrant update returns (uint256 usdAmount) {
        /// @custom:error CantRedeemZero Avoid redeem no LP
        if (assets == 0) revert CygnusCollateral__CantRedeemZero();

        // Compute shares of assets redeemed. Same as `_convertToShares` rounding up
        uint256 shares = assets.fullMulDivUp(totalSupply(), _totalAssets(false));

        // Withdraw assets from the strategy
        _beforeWithdraw(assets);

        // Optimistically transfer LP amount to redeemer
        underlying.safeTransfer(redeemer, assets);

        // If data exists then pass to router - `usdAmount` return var is helpful when flash redeeming via a router
        // with a staticCall before hand, it has no effect on the function itself. In case of deleveraging
        // (converting LP to USDC), the router would first call this function and flashRedeem the LP, sell the LP for USDC,
        // repay user loans (if any) and transfer back the equivalent of the LP redeemed in CygLP to this contract.
        if (data.length > 0) usdAmount = ICygnusAltairCall(msg.sender).altairRedeem_u91A(msg.sender, assets, data);

        // CygLP tokens received by this contract
        uint256 cygLPReceived = balanceOf(address(this));

        /// @custom:error InsufficientCygLPReceived Avoid if we have received less CygLP than declared
        if (cygLPReceived < shares) revert CygnusCollateral__InsufficientCygLPReceived();

        // Burn tokens and emit a Transfer event
        // Escapes `canRedeem` since we are burning tokens from this address
        _burn(address(this), cygLPReceived);
    }
}

File 3 of 32 : CygnusCollateralControl.sol
//  SPDX-License-Identifier: AGPL-3.0-or-later
//
//  CygnusCollateralControl.sol
//
//  Copyright (C) 2023 CygnusDAO
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
pragma solidity >=0.8.17;

// Dependencies
import {ICygnusCollateralControl} from "./interfaces/ICygnusCollateralControl.sol";
import {CygnusTerminal} from "./CygnusTerminal.sol";

// Interfaces
import {IERC20} from "./interfaces/IERC20.sol";

// Overrides
import {ERC20} from "./ERC20.sol";

/**
 *  @title  CygnusCollateralControl Contract for controlling collateral settings like debt ratios/liq. incentives
 *  @author CygnusDAO
 *  @notice Initializes Collateral Arm. Assigns name, symbol and decimals to CygnusTerminal for the CygLP Token.
 *          This contract should be the only contract the Admin has control of (along with the strateyg contract)
 *          specifically to set liquidation fees for the protocol, liquidation incentives for the liquidators and
 *          setting and the max debt ratio.
 */
contract CygnusCollateralControl is ICygnusCollateralControl, CygnusTerminal {
    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            2. STORAGE
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── Private ────────────────────────────────────────────────  */

    // ─────────────────────── Min/Max this pool allows

    /**
     *  @notice Minimum debt ratio at which the collateral becomes liquidatable
     */
    uint256 private constant DEBT_RATIO_MIN = 0.80e18;

    /**
     *  @notice Maximum debt ratio at which the collateral becomes liquidatable
     */
    uint256 private constant DEBT_RATIO_MAX = 0.99e18;

    /**
     *  @notice Minimum liquidation incentive for liquidators that can be set
     */
    uint256 private constant LIQUIDATION_INCENTIVE_MIN = 1.00e18;

    /**
     *  @notice Maximum liquidation incentive for liquidators that can be set
     */
    uint256 private constant LIQUIDATION_INCENTIVE_MAX = 1.15e18;

    /**
     *  @notice Maximum fee the protocol is keeps from each liquidation
     */
    uint256 private constant LIQUIDATION_FEE_MAX = 0.10e18;

    /*  ─────────────────────────────────────────────── Public ────────────────────────────────────────────────  */

    /**
     *  @inheritdoc ICygnusCollateralControl
     */
    uint256 public override debtRatio = 0.90e18;

    /**
     *  @inheritdoc ICygnusCollateralControl
     */
    uint256 public override liquidationIncentive = 1.03e18;

    /**
     *  @inheritdoc ICygnusCollateralControl
     */
    uint256 public override liquidationFee = 0.01e18;

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            5. CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── Private ────────────────────────────────────────────────  */

    /**
     *  @notice Checks if new parameter is within range when updating collateral settings
     *  @param min The minimum value allowed for this parameter
     *  @param max The maximum value allowed for this parameter
     *  @param value The value for the parameter that is being updated
     */
    function _validRange(uint256 min, uint256 max, uint256 value) private pure {
        /// @custom:error ParameterNotInRange Avoid setting important variables outside range
        if (value < min || value > max) revert CygnusCollateralControl__ParameterNotInRange();
    }

    /*  ─────────────────────────────────────────────── Public ────────────────────────────────────────────────  */

    /**
     *  @notice Overrides the name function from the ERC20
     *  @inheritdoc ERC20
     */
    function name() public pure override(ERC20, IERC20) returns (string memory) {
        // Name of the collateral arm
        return "Cygnus: Collateral";
    }

    /**
     *  @notice Overrides the symbol function to represent the underlying LP (Most dexes use 2 tokens, ie 'CygLP: ETH/USDC')
     *  @inheritdoc ERC20
     */
    function symbol() public view override(ERC20, IERC20) returns (string memory) {
        // Symbol of the Collateral (ie `CygLP: ETH/OP`)
        return string(abi.encodePacked("CygLP: ", IERC20(underlying).symbol()));
    }

    /**
     *  @notice Overrides the decimal function to use the same decimals as underlying
     *  @inheritdoc ERC20
     */
    function decimals() public view override(ERC20, IERC20) returns (uint8) {
        // Override decimals for LP tokens
        return IERC20(underlying).decimals();
    }

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @inheritdoc ICygnusCollateralControl
     */
    function borrowable() external view returns (address) {
        // Read the stored internal variable from terminal
        return twinstar;
    }

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            6. NON-CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @inheritdoc ICygnusCollateralControl
     *  @custom:security only-admin 👽
     */
    function setDebtRatio(uint256 newDebtRatio) external override cygnusAdmin {
        // Checks if parameter is within bounds
        _validRange(DEBT_RATIO_MIN, DEBT_RATIO_MAX, newDebtRatio);

        // Debt ratio until now
        uint256 oldDebtRatio = debtRatio;

        /// @custom:event newDebtRatio
        emit NewDebtRatio(oldDebtRatio, debtRatio = newDebtRatio);
    }

    /**
     *  @inheritdoc ICygnusCollateralControl
     *  @custom:security only-admin 👽
     */
    function setLiquidationIncentive(uint256 newLiquidationIncentive) external override cygnusAdmin {
        // Checks if parameter is within bounds
        _validRange(LIQUIDATION_INCENTIVE_MIN, LIQUIDATION_INCENTIVE_MAX, newLiquidationIncentive);

        // Liquidation incentive until now
        uint256 oldLiquidationIncentive = liquidationIncentive;

        /// @custom:event NewLiquidationIncentive
        emit NewLiquidationIncentive(oldLiquidationIncentive, liquidationIncentive = newLiquidationIncentive);
    }

    /**
     *  @inheritdoc ICygnusCollateralControl
     *  @custom:security only-admin 👽
     */
    function setLiquidationFee(uint256 newLiquidationFee) external override cygnusAdmin {
        // Checks if parameter is within bounds
        _validRange(0, LIQUIDATION_FEE_MAX, newLiquidationFee);

        // Liquidation fee until now
        uint256 oldLiquidationFee = liquidationFee;

        /// @custom:event newLiquidationFee
        emit NewLiquidationFee(oldLiquidationFee, liquidationFee = newLiquidationFee);
    }
}

File 4 of 32 : CygnusCollateralModel.sol
//  SPDX-License-Identifier: AGPL-3.0-or-later
//
//  CygnusCollateralModel.sol
//
//  Copyright (C) 2023 CygnusDAO
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
pragma solidity >=0.8.17;

// Dependencies
import {ICygnusCollateralModel} from "./interfaces/ICygnusCollateralModel.sol";
import {CygnusCollateralControl} from "./CygnusCollateralControl.sol";

// Libraries
import {FixedPointMathLib} from "./libraries/FixedPointMathLib.sol";

// Interfaces
import {ICygnusBorrow} from "./interfaces/ICygnusBorrow.sol";

/**
 *  @title  CygnusCollateralModel Main contract in Cygnus that calculates a borrower's liquidity or shortfall in
 *          borrowable`s underlying (stablecoins). All functions are marked as view to be queried by borrowers
 *          to check their positions
 *  @author CygnusDAO
 *  @notice There are 2 main functions in the modelto calculate the liquidity of a user:
 *          `getBorrowerPosition` and `getAccountLiquidity`
 *
 *          `getBorrowerPosition` will return all the data related to the borrower's current position, including
 *          amount of CygLP, collateral value in USD, LP price and Health. The health is the percentage of the
 *          borrowed amount divided by the user's collateral (ie. Debt Ratio), Note that this health is scaled
 *          to the current `debtRatio` param. If `health` returns higher than 100% (or 1e18) then the user
 *          has shortfall and can be liquidated. If `health` returns lower than 100% then the user can borrow
 *          more.
 *
 *          The same can be calculated with `getAccountLiquidity`, but instead of returning a percentage will
 *          return the total amount of USD that the borrower can borrow before their position is in shortfall.
 *
 *          The last function `canBorrow` is called by the `borrowable` contract during borrows to confirm if a
 *          user can borrow or not and can be called by anyone, returning `false` if the account has shortfall,
 *          otherwise will return `true`.
 */
contract CygnusCollateralModel is ICygnusCollateralModel, CygnusCollateralControl {
    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            1. LIBRARIES
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @custom:library SafeTransferLib ERC20 transfer library that gracefully handles missing return values.
     */
    using FixedPointMathLib for uint256;

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════
            5. CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── Private ────────────────────────────────────────────────  */

    /**
     *  @notice Calculate collateral needed for a loan factoring in debt ratio and liq incentive
     *  @param amountCollateral The collateral amount the borrower has deposited (CygLP * exchangeRate)
     *  @param borrowedAmount The total amount of stablecoins the user has borrowed (can be 0)
     */
    function collateralNeededPrivate(uint256 amountCollateral, uint256 borrowedAmount) private view returns (uint256, uint256) {
        // User LP deposited * LP Token price
        // ie. convertToAssets(cygLPBalance) * lpPrice
        uint256 collateralInUsd = amountCollateral.mulWad(getLPTokenPrice());

        // Adjust the collateral by the pool`s debt ratio and liquidation incentives to get the max liquidity
        uint256 maxLiquidity = collateralInUsd.fullMulDiv(debtRatio, liquidationIncentive + liquidationFee);

        // Never underflows
        unchecked {
            // If account has collateral available to borrow against, return liquidity and 0 shortfall
            if (maxLiquidity >= borrowedAmount) return (maxLiquidity - borrowedAmount, 0);
            // else, return 0 liquidity and the account's shortfall, position can be liquidated
            else return (0, borrowedAmount - maxLiquidity);
        }
    }

    /*  ────────────────────────────────────────────── Internal ───────────────────────────────────────────────  */

    /**
     *  @notice Called by CygnusCollateral when a liquidation takes place
     *  @param borrower Address of the borrower
     *  @param borrowBalance Borrowed amount of stablecoins by `borrower`
     *  @return liquidity The user's current LP liquidity priced in USD
     *  @return shortfall The user's current LP shortfall priced in USD (if positive they can be liquidated)
     */
    function _accountLiquidity(address borrower, uint256 borrowBalance) internal view returns (uint256, uint256) {
        // Borrower can never be address zero or Collateral. When doing a `borrow` from the borrowable contract, this function
        // gets called to check for account liquidity. If the borrower passed is either, we revert the tx
        /// @custom:error InvalidBorrower Avoid borrower zero address or this contract
        if (borrower == address(0) || borrower == address(this)) revert CygnusCollateralModel__InvalidBorrower();

        // Check if called externally or from borrowable. If called externally (via `getAccountLiquidity`) then borrowedAmount
        // is always MaxUint256. If called by borrowable ( `borrow` function calls the `canBorrow` function below) then its the
        // account's total borrows (including the new tx borrow amount if any).
        // Simulate accrue as borrowable calls this function with borrower's account borrows and not max uint256
        if (borrowBalance == type(uint256).max) (, borrowBalance) = ICygnusBorrow(twinstar).getBorrowBalance(borrower);

        // Get the CygLP balance of `borrower` and adjust with exchange rate
        uint256 amountCollateral = _convertToAssets(balanceOf(borrower));

        // Calculate user's liquidity or shortfall internally
        return collateralNeededPrivate(amountCollateral, borrowBalance);
    }

    /*  ─────────────────────────────────────────────── Public ────────────────────────────────────────────────  */

    /**
     *  @inheritdoc ICygnusCollateralModel
     */
    function getLPTokenPrice() public view override returns (uint256 price) {
        // Get the price of 1 amount of the underlying, denominated in the borrowable's underlying (a stablecoin).
        // It returns the price in the borrowable`s decimals. ie If USDC, price in 6 deicmals, if DAI/BUSD in 18.
        // Note that price returned can be unexpectedly high depending on the liquidity token's assets decimals.
        price = nebula.lpTokenPriceUsd(underlying);

        // The oracle is already initialized or else the deployment of the lending pool would have failed.
        // We check for invalid price in case something goes wrong with the oracle's price feeds, reverting
        // any borrow or liquidation.
        /// @custom:error PriceCantBeZero Avoid invalid price from oracle
        if (price == 0) revert CygnusCollateralModel__PriceCantBeZero();
    }

    /**
     *  @inheritdoc ICygnusCollateralModel
     */
    function canRedeem(address borrower, uint256 redeemAmount) public view override returns (bool) {
        // Gas savings
        uint256 cygLPBalance = balanceOf(borrower);

        // Redeem amount can't be higher than account balance, return false
        if (redeemAmount > cygLPBalance || redeemAmount == 0) return false;

        // The borrower's final CygLP balance after redeeming `redeemAmount`
        uint256 finalBalance = cygLPBalance - redeemAmount;

        // Calculate the amount of underlying LPs the final balance is worth
        uint256 amountCollateral = _convertToAssets(finalBalance);

        // Get borrower's borrow balance from borrowable contract
        (, uint256 borrowBalance) = ICygnusBorrow(twinstar).getBorrowBalance(borrower);

        // Get the LP price and calculate the needed collateral
        (, uint256 shortfall) = collateralNeededPrivate(amountCollateral, borrowBalance);

        // If user has no shortfall after redeeming return true
        return shortfall == 0;
    }

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @inheritdoc ICygnusCollateralModel
     */
    function getAccountLiquidity(address borrower) external view override returns (uint256 liquidity, uint256 shortfall) {
        // Calculate if `borrower` has liquidity or shortfall
        return _accountLiquidity(borrower, type(uint256).max);
    }

    /**
     *  @inheritdoc ICygnusCollateralModel
     */
    function canBorrow(address borrower, uint256 borrowAmount) external view override returns (bool) {
        // Called by CygnusBorrow at the end of the `borrow` function to check if a `borrower` can borrow `borrowAmount`
        (, uint256 shortfall) = _accountLiquidity(borrower, borrowAmount);

        // User has no shortfall and can borrow
        return shortfall == 0;
    }

    /**
     *  @inheritdoc ICygnusCollateralModel
     */
    function getBorrowerPosition(address borrower) external view override returns (uint256 lpBalance, uint256 positionUsd, uint256 health) {
        // The amount of LP tokens that is owned by the borrower's position
        lpBalance = balanceOf(borrower).mulWad(exchangeRate());

        // Borrower's position in USD
        positionUsd = lpBalance.mulWad(getLPTokenPrice());

        // Max user's liquidity (in USD) = The position's USD adjusted by the debt ratio and liquidation incentives
        uint256 maxLiquidity = positionUsd.fullMulDiv(debtRatio, liquidationIncentive + liquidationFee);

        // Get the latest borrow balance (uses borrow indices)
        (, uint256 borrowBalance) = ICygnusBorrow(twinstar).getBorrowBalance(borrower);

        // The position's health is borrowBalance / maxLiquidity, liquidatable at 100% (ie 1e18)
        health = positionUsd == 0 ? 0 : borrowBalance.divWad(maxLiquidity);
    }
}

File 5 of 32 : CygnusCollateralVoid.sol
//  SPDX-License-Identifier: AGPL-3.0-or-later
//
//  CygnusCollateralVoid.sol
//
//  Copyright (C) 2023 CygnusDAO
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
pragma solidity >=0.8.17;

// Dependencies
import {ICygnusCollateralVoid} from "./interfaces/ICygnusCollateralVoid.sol";
import {CygnusCollateralModel} from "./CygnusCollateralModel.sol";

// Libraries
import {SafeTransferLib} from "./libraries/SafeTransferLib.sol";

// Interfaces
import {IERC20} from "./interfaces/IERC20.sol";

// Strategy
import {IMiniChefV2} from "./interfaces/CollateralVoid/IMiniChef.sol";
import {IMerklClaimer} from "./interfaces/CollateralVoid/IMerkleClaimer.sol";

// Overrides
import {CygnusTerminal} from "./CygnusTerminal.sol";

/**
 *  @title  CygnusCollateralVoid The strategy contract for the underlying LP Tokens
 *  @author CygnusDAO
 *  @notice Strategy for the underlying LP deposits.
 */
contract CygnusCollateralVoid is ICygnusCollateralVoid, CygnusCollateralModel {
    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            1. LIBRARIES
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @custom:library SafeTransferLib ERC20 transfer library that gracefully handles missing return values.
     */
    using SafeTransferLib for address;

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            2. STORAGE
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── Private ────────────────────────────────────────────────  */

    /*  ─────────── Strategy */

    /**
     *  @notice Gamma's MiniChef
     */
    IMiniChefV2 private constant REWARDER = IMiniChefV2(0x1e2D8f84605D32a2CBf302E30bFd2387bAdF35dD);

    /**
     *  @notice Pool ID this lpTokenPair corresponds to in `rewarder`
     */
    uint256 private gammaId = type(uint256).max;

    /*  ─────────── Merkle */

    /**
     *  @notice Angle's Merkl Claimer contract
     */
    IMerklClaimer private constant MERKL_REWARDER = IMerklClaimer(0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae);

    /*  ─────────────────────────────────────────────── Public ────────────────────────────────────────────────  */

    /**
     *  @inheritdoc ICygnusCollateralVoid
     */
    address[] public override allRewardTokens;

    /**
     *  @inheritdoc ICygnusCollateralVoid
     */
    address public override harvester;

    /**
     *  @inheritdoc ICygnusCollateralVoid
     */
    uint256 public override lastHarvest;

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            3. CONSTRUCTOR
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @notice Constructs the Cygnus Void contract which handles the strategy for the collateral`s underlying.
     */
    constructor() {}

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            5. CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── Internal ───────────────────────────────────────────────  */

    /**
     *  @notice Preview total balance from the LP strategy
     *  @notice Cygnus Terminal Override
     *  @inheritdoc CygnusTerminal
     */
    function _previewTotalBalance() internal view override(CygnusTerminal) returns (uint256 balance) {
        // Get this contracts deposited LP amount from Velo gauge
        (balance, ) = REWARDER.userInfo(gammaId, address(this));
    }

    /**
     *  @notice Checks the `token` balance of this contract
     *  @param token The token to view balance of
     *  @return amount This contract's `token` balance
     */
    function _checkBalance(address token) internal view returns (uint256) {
        // Our balance of `token`
        return token.balanceOf(address(this));
    }

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @inheritdoc ICygnusCollateralVoid
     */
    function rewarder() external pure override returns (address) {
        // Return the contract that rewards us with `rewardsToken`
        return address(REWARDER);
    }

    /**
     *  @inheritdoc ICygnusCollateralVoid
     */
    function rewardTokensLength() external view override returns (uint256) {
        // Return total reward tokens length
        return allRewardTokens.length;
    }

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            6. NON-CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── Private ────────────────────────────────────────────────  */

    /**
     *  @notice Removes allowances from the harvester
     *  @param _harvester The address of the harvester
     *  @param tokens The old reward tokens
     */
    function removeHarvesterPrivate(address _harvester, address[] memory tokens) private {
        // If no harvester then return
        if (_harvester == address(0)) return;

        // Loop through each token
        for (uint256 i = 0; i < tokens.length; i++) {
            // Remove the harvester's allowance of old tokens
            tokens[i].safeApprove(_harvester, 0);
        }
    }

    /**
     *  @notice Add allowances to the new harvester
     *  @param _harvester The address of the new harvester
     *  @param tokens The new reward tokens
     */
    function addHarvesterPrivate(address _harvester, address[] calldata tokens) private {
        // If no harvester then return
        if (_harvester == address(0)) return;

        // Loop through each token
        for (uint256 i = 0; i < tokens.length; i++) {
            // Check for underlying
            if (tokens[i] != underlying) {
                // Approve harvester in token
                tokens[i].safeApprove(_harvester, type(uint256).max);
            }
        }
    }

    /**
     *  @notice Harvest the rewards from the Merkl contract
     */
    function harvestRewardsMerkl(address[] calldata _tokens, uint256[] calldata _amounts, bytes32[][] calldata _proofs) private {
        // Escape if merkl is not active
        if (_tokens.length == 0) return;

        // Create users array
        address[] memory users = new address[](_tokens.length);

        // Users must match tokens being claimed, user is always the vault
        for (uint256 i = 0; i < users.length; ) {
            // Vault address
            users[i] = address(this);

            unchecked {
                ++i;
            }
        }

        // Claim all rewards
        MERKL_REWARDER.claim(users, _tokens, _amounts, _proofs);
    }

    /**
     *  @notice Harvest the rewards from the strategy
     */
    function harvestRewardsPrivate(address[] calldata _tokens, uint256[] calldata _amounts, bytes32[][] calldata _proofs) private {
        // Harvest rewards from the masterchef first
        REWARDER.harvest(gammaId, address(this));

        // Harvest from Merkl (if any)
        harvestRewardsMerkl(_tokens, _amounts, _proofs);
    }

    /**
     *  @notice Harvest and return the pending reward tokens and mounts interally, used by reinvest function.
     *  @return tokens Array of reward token addresses
     *  @return amounts Array of reward token amounts
     */
    function getRewardsPrivate(
        address[] calldata _tokens,
        uint256[] calldata _amounts,
        bytes32[][] calldata _proofs
    ) private returns (address[] memory tokens, uint256[] memory amounts) {
        // Harvest the rewards from the strategy + Merkl
        harvestRewardsPrivate(_tokens, _amounts, _proofs);

        // Assign reward tokens and gas savings
        tokens = allRewardTokens;

        // Create array of amounts
        amounts = new uint256[](tokens.length);

        // Loop over each reward token and return balance
        for (uint256 i = 0; i < tokens.length; ) {
            // Assign balance of reward token `i`
            amounts[i] = _checkBalance(tokens[i]);

            // Next iteration
            unchecked {
                ++i;
            }
        }

        /// @custom:event RechargeVoid
        emit RechargeVoid(msg.sender, tokens, amounts, lastHarvest = block.timestamp);
    }

    /*  ────────────────────────────────────────────── Internal ───────────────────────────────────────────────  */

    /**
     *  @notice Cygnus Terminal Override
     *  @param assets The amount of assets to deposit in the strategy
     *  @inheritdoc CygnusTerminal
     */
    function _afterDeposit(uint256 assets) internal override(CygnusTerminal) {
        // Deposit assets into the strategy
        REWARDER.deposit(gammaId, assets, address(this));
    }

    /**
     *  @notice Cygnus Terminal Override
     *  @param assets The amount of assets to withdraw from the strategy
     *  @inheritdoc CygnusTerminal
     */
    function _beforeWithdraw(uint256 assets) internal override(CygnusTerminal) {
        // Withdraw assets from the strategy
        REWARDER.withdraw(gammaId, assets, address(this));
    }

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @inheritdoc ICygnusCollateralVoid
     *  @custom:security non-reentrant
     */
    function getRewards(
        address[] calldata _tokens,
        uint256[] calldata _amounts,
        bytes32[][] calldata proofs
    ) external override nonReentrant returns (address[] memory tokens, uint256[] memory amounts) {
        // The harvester contract calls this function to harvest the rewards. Anyone can call
        // this function, but the rewards can only be moved by the harvester contract itself.
        return getRewardsPrivate(_tokens, _amounts, proofs);
    }

    /**
     *  @inheritdoc ICygnusCollateralVoid
     *  @custom:security non-reentrant only-harvester
     */
    function reinvestRewards_y7b(uint256 liquidity) external override nonReentrant update {
        /// @custom:error OnlyHarvesterAllowed Avoid call if msg.sender is not the harvester
        if (msg.sender != harvester) revert CygnusCollateralVoid__OnlyHarvesterAllowed();

        // After deposit hook, doesn't mint any shares. The contract should have already received
        // the underlying LP from the harvester.
        _afterDeposit(liquidity);
    }

    /*  ────────  Admin  ────────  */

    /**
     *  @inheritdoc ICygnusCollateralVoid
     *  @custom:security only-admin 👽
     */
    function chargeVoid() external override cygnusAdmin {
        // Charge with pool ID only once (pool Id is never -1)
        if (gammaId == type(uint256).max) {
            // Get total length
            uint256 rewarderLength = REWARDER.poolLength();

            // Loop through total length and get underlying LP
            for (uint256 i = 0; i < rewarderLength; i++) {
                // Get the underlying LP in rewarder at length `i`
                address _underlying = REWARDER.lpToken(i);

                // If same LP then assign pool ID to `i`
                if (_underlying == underlying) {
                    // Assign pool Id;
                    gammaId = i;

                    // Exit
                    break;
                }
            }
        }

        // Allow rewarder to access our underlying
        underlying.safeApprove(address(REWARDER), type(uint256).max);

        /// @custom:event ChargeVoid
        emit ChargeVoid(underlying, gammaId, address(REWARDER));
    }

    /**
     *  @inheritdoc ICygnusCollateralVoid
     *  @custom:security only-admin 👽
     */
    function setHarvester(address newHarvester, address[] calldata rewardTokens) external override cygnusAdmin {
        // Old harvester
        address oldHarvester = harvester;

        // Remove allowances from the harvester for `allRewardTokens` up to this point
        removeHarvesterPrivate(oldHarvester, allRewardTokens);

        // Allow new harvester to access the new reward tokens passed
        addHarvesterPrivate(newHarvester, rewardTokens);

        /// @custom:event NewHarvester
        emit NewHarvester(oldHarvester, harvester = newHarvester, allRewardTokens = rewardTokens);
    }

    /**
     *  @inheritdoc ICygnusCollateralVoid
     *  @custom:security only-admin 👽
     */
    function sweepToken(address token, address to) external override cygnusAdmin {
        /// @custom;error CantSweepUnderlying Avoid sweeping underlying
        if (token == underlying) revert CygnusCollateralVoid__TokenIsUnderlying();

        // Get balance of token
        uint256 balance = _checkBalance(token);

        // Transfer token balance to `to`
        token.safeTransfer(to, balance);
    }
}

File 6 of 32 : CygnusTerminal.sol
//  SPDX-License-Identifier: AGPL-3.0-or-later
//
//  CygnusTerminal.sol
//
//  Copyright (C) 2023 CygnusDAO
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.

/*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════════
    
           █████████                🛸         🛸                              🛸          .                    
     🛸   ███░░░░░███                                              📡                                     🌔   
         ███     ░░░  █████ ████  ███████ ████████   █████ ████  █████        ⠀
        ░███         ░░███ ░███  ███░░███░░███░░███ ░░███ ░███  ███░░      .     .⠀        🛰️   .             
        ░███          ░███ ░███ ░███ ░███ ░███ ░███  ░███ ░███ ░░█████       ⠀
        ░░███     ███ ░███ ░███ ░███ ░███ ░███ ░███  ░███ ░███  ░░░░███              .             .           
         ░░█████████  ░░███████ ░░███████ ████ █████ ░░████████ ██████       -----========*⠀
          ░░░░░░░░░    ░░░░░███  ░░░░░███░░░░ ░░░░░   ░░░░░░░░ ░░░░░░            .                            .
                       ███ ░███  ███ ░███                .                 .         🛸           ⠀             
         .    🛸*     ░░██████  ░░██████   .    🛸                     🛰️            -----=========*                 
                       ░░░░░░    ░░░░░░                                               🛸  ⠀
           .                            .       .             🛰️         .                          
    
        POOL TOKEN (CygUSD/CygLP) - https://cygnusdao.finance                                                          .                     .
    ═══════════════════════════════════════════════════════════════════════════════════════════════════════════ */
pragma solidity >=0.8.17;

// Dependencies
import {ICygnusTerminal} from "./interfaces/ICygnusTerminal.sol";
import {ERC20} from "./ERC20.sol";
import {ReentrancyGuard} from "./utils/ReentrancyGuard.sol";

// Libraries
import {SafeCastLib} from "./libraries/SafeCastLib.sol";
import {SafeTransferLib} from "./libraries/SafeTransferLib.sol";
import {FixedPointMathLib} from "./libraries/FixedPointMathLib.sol";

// Interfaces
import {IOrbiter} from "./interfaces/IOrbiter.sol";
import {IHangar18} from "./interfaces/IHangar18.sol";
import {ICygnusNebula} from "./interfaces/ICygnusNebula.sol";
import {IAllowanceTransfer} from "./interfaces/IAllowanceTransfer.sol";

/**
 *  @title  CygnusTerminal
 *  @author CygnusDAO
 *  @notice Contract used to mint Collateral and Borrow tokens. Both Collateral/Borrow arms of Cygnus mint here
 *          to get the vault token (CygUSD for stablecoin deposits and CygLP for Liquidity deposits).
 *  @notice As the borrowable arm is a stablecoin vault which has assets deposited in strategies, the exchange
 *          rate for CygUSD should be the cash deposited in the strategy + current borrows. Therefore we use an
 *          internal `_totalAssets(bool)` to take into account latest borrows. The bool dictates whether we should
 *          simulate accruals or not, helpful for the contract to always display data in real time.
 *
 *  @notice Functions overridden in Strategy contracts (CygnusBorrowVoid.sol & CygnusCollateralVoid.sol):
 *            _afterDeposit        - borrowable/collateral - Underlying deposits into the strategy
 *            _beforeWithdraw      - borrowable/collateral - Underlying withdrawals from the strategy
 *            _previewTotalBalance - borrowable/collateral - The balance of USDC/LP deposited in the strategy
 *
 *          Functions overriden to include Borrows and accrue intersest in borrowable (CygnusBorrowModel.sol)
 *            _totalAssets         - borrowable            - Includes total borrows + total balance
 *            update (modifier)    - borrowable            - Interest accruals before any payable actions
 */
abstract contract CygnusTerminal is ICygnusTerminal, ERC20, ReentrancyGuard {
    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            1. LIBRARIES
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @custom:library SafeTransferLib ERC20 transfer library that gracefully handles missing return values.
     */
    using SafeTransferLib for address;

    /**
     *  @custom:library FixedPointMathLib Arithmetic library with operations for fixed-point numbers.
     */
    using FixedPointMathLib for uint256;

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            2. STORAGE
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── Internal ───────────────────────────────────────────────  */

    /**
     *  @notice The address of this contract`s opposite arm. For collateral pools, this is the borrowable address.
     *          For borrowable pools, this is the collateral address. Getters in child contract.
     */
    address internal immutable twinstar;

    /*  ─────────────────────────────────────────────── Public ────────────────────────────────────────────────  */

    /**
     *  @inheritdoc ICygnusTerminal
     */
    IAllowanceTransfer public constant override PERMIT2 = IAllowanceTransfer(0x000000000022D473030F116dDEE9F6B43aC78BA3);

    /**
     *  @inheritdoc ICygnusTerminal
     */
    IHangar18 public immutable override hangar18;

    /**
     *  @inheritdoc ICygnusTerminal
     */
    address public immutable override underlying;

    /**
     *  @inheritdoc ICygnusTerminal
     */
    ICygnusNebula public immutable override nebula;

    /**
     *  @inheritdoc ICygnusTerminal
     */
    uint256 public immutable override shuttleId;

    /**
     *  @notice The total balance held of the underlying in the strategy (USD for borrowable, LP for collateral)
     *  @inheritdoc ICygnusTerminal
     */
    uint160 public override totalBalance;

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            3. CONSTRUCTOR
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @notice Constructs tokens for both Collateral and Borrow arms
     */
    constructor() {
        // Get immutables from deployer contracts (AlbireoOrbiter for Borrowable, DenebOrbiter for Collateral)
        // Factory, asset, borrow/collateral, oracle, lending pool ID
        (hangar18, underlying, twinstar, nebula, shuttleId) = IOrbiter(msg.sender).shuttleParameters();
    }

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            4. MODIFIERS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @custom:modifier cygnusAdmin Controls important parameters in both Collateral and Borrow contracts 👽
     */
    modifier cygnusAdmin() {
        _checkAdmin();
        _;
    }

    /**
     *  @notice We override in borrowable arm to accrue interest before any state changing action.
     *  @custom:modifier update Updates `totalBalance` in terms of its underlying
     *  @custom:override CygnusBorrowModel
     */
    modifier update() virtual {
        _;
        _update();
    }

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            5. CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── Private ────────────────────────────────────────────────  */

    /**
     *  @notice Checks that the msg.sender is Hangar18's current admin 👽
     */
    function _checkAdmin() private view {
        // Current admin from the factory
        address admin = hangar18.admin();

        /// @custom:error MsgSenderNotAdmin Avoid unless caller is Cygnus Admin
        if (msg.sender != admin) revert CygnusTerminal__MsgSenderNotAdmin();
    }

    /*  ────────────────────────────────────────────── Internal ───────────────────────────────────────────────  */

    /**
     *  @notice The total assets owned by the vault. Overridden by the borrowable arm to include borrows.
     *  @notice The bool argument is to check if we should simulate interest accrual or not in borrowable. If the
     *          contract is in sync with the latest balance and it has already accrued, we use false. For Collateral
     *          this has no effect.
     *  @custom:override CygnusBorrowModel
     */
    function _totalAssets(bool) internal view virtual returns (uint256) {
        return totalBalance;
    }

    /**
     *  @notice Converts assets to shares
     *  @notice We always pass false to `_totalAssets()` to not simulate accrual and to avoid extra SLOADs. This is
     *          because stored variables are in sync since deposit/redeem use the `update` modifier which updates
     *          balances and accrue interest (see `update` modifier above).
     */
    function _convertToShares(uint256 assets) internal view returns (uint256) {
        // Gas savings if non-zero
        uint256 _totalSupply = totalSupply();

        // Compute shares given an amount of stablecoin or LP token assets
        return _totalSupply == 0 ? assets : assets.fullMulDiv(_totalSupply, _totalAssets(false));
    }

    /**
     *  @notice Convert shares to assets. Same as above, pass false to `_totalAssets()` as balances are in sync and
     *          we have already accrued.
     */
    function _convertToAssets(uint256 shares) internal view returns (uint256) {
        // Gas savings if non-zero
        uint256 _totalSupply = totalSupply();

        // Compute assets given an amount of CygUSD or CygLP shares
        return _totalSupply == 0 ? shares : shares.fullMulDiv(_totalAssets(false), _totalSupply);
    }

    /*  ─────────────────────────────────────────────── Public ────────────────────────────────────────────────  */

    /**
     *  @notice Computes the exchange rate between 1 unit of the vault token and the underlying asset
     *  @inheritdoc ICygnusTerminal
     */
    function exchangeRate() public view override returns (uint256) {
        // Gas savings if non-zero
        uint256 _totalSupply = totalSupply();

        // Return the exchange rate between 1 unit of CygUSD/CygLP and underlying - Always simulate accruals.
        // This is kept here for reporting purposes.
        return _totalSupply == 0 ? 1e18 : totalAssets().divWad(_totalSupply);
    }

    /**
     *  @notice Total assets managed by the vault. For borrowable this is the stablecoin balance deposited in
     *          the strategy + the current borrows (simulates accruals). For collateral this is the LP token
     *          balance deposited in the strategy.
     *  @inheritdoc ICygnusTerminal
     */
    function totalAssets() public view override returns (uint256) {
        // Always simulate accrual for borrowable when called externally, for collateral this has no effect and
        // reads cached balance.
        return _totalAssets(true);
    }

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            6. NON-CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── Internal ───────────────────────────────────────────────  */

    /**
     *  @notice Updates this contract's balance in terms of its underlying, triggered after any payable function.
     */
    function _update() internal {
        // Preview the total assets of stablecoin or LP we own
        uint256 balance = _previewTotalBalance();

        /// @custom:event Sync
        emit Sync(totalBalance = SafeCastLib.toUint160(balance));
    }

    // Should always be overridden in strategy contracts

    /**
     *  @notice Previews our balance of the underlying asset in the strategy, does not update totalBalance
     *  @notice Not marked as view as some strategies require state update (for example cToken's `balanceOfUnderlying`)
     *  @custom:override CygnusBorrowVoid
     *  @custom:override CygnusCollateralVoid
     */
    function _previewTotalBalance() internal virtual returns (uint256) {}

    /**
     *  @notice Internal hook for deposits into strategies
     *  @param assets The amount of assets to deposit in the strategy
     *  @custom:override CygnusBorrowVoid
     *  @custom:override CygnusCollateralVoid
     */
    function _afterDeposit(uint256 assets) internal virtual {}

    /**
     *  @notice Internal hook for withdrawals from strategies
     *  @param assets The amount of assets to withdraw from the strategy
     *  @custom:override CygnusBorrowVoid
     *  @custom:override CygnusCollateralVoid
     */
    function _beforeWithdraw(uint256 assets) internal virtual {}

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @inheritdoc ICygnusTerminal
     *  @custom:security non-reentrant
     */
    function deposit(
        uint256 assets,
        address recipient,
        IAllowanceTransfer.PermitSingle calldata _permit,
        bytes calldata signature
    ) external override nonReentrant update returns (uint256 shares) {
        // Convert assets deposited into shares
        shares = _convertToShares(assets);

        /// @custom:error CantMintZeroShares Avoid minting 0 shares
        if (shares == 0) revert CygnusTerminal__CantMintZeroShares();

        // Check for permit to approve and deposit in 1 tx. Users can just approve this contract in
        // permit2 and skip this by passing an empty signature).
        if (signature.length > 0) {
            // Set allowance using permit
            PERMIT2.permit(
                // The owner of the tokens being approved.
                // We only allow the owner of the tokens to be the depositor, but
                // recipient can be set to another address
                msg.sender,
                // Data signed over by the owner specifying the terms of approval
                _permit,
                // The owner's signature over the permit data that was the result
                // of signing the EIP712 hash of `_permit`
                signature
            );
        }

        // Transfer underlying to vault
        PERMIT2.transferFrom(msg.sender, address(this), SafeCastLib.toUint160(assets), underlying);

        // Avoid inflation attack on the vault - This is only for the first pool depositor as after there will always
        // be 1000 shares locked in zero address
        if (totalSupply() == 0) {
            // Update shares for first depositor
            shares -= 1000;

            // Lock initial tokens
            _mint(address(0), 1000);
        }

        // Mint shares and emit Transfer event
        _mint(recipient, shares);

        // Deposit assets in the strategy
        _afterDeposit(assets);

        /// @custom:event Deposit
        emit Deposit(msg.sender, recipient, assets, shares);
    }

    /**
     *  @inheritdoc ICygnusTerminal
     *  @custom:security non-reentrant
     */
    function redeem(uint256 shares, address recipient, address owner) external override nonReentrant update returns (uint256 assets) {
        // Withdraw flow
        if (msg.sender != owner) _spendAllowance(owner, msg.sender, shares);

        // Convert shares redeemed into underlying assets
        assets = _convertToAssets(shares);

        /// @custom:error CantRedeemZeroAssets Avoid redeeming 0 assets
        if (assets == 0) revert CygnusTerminal__CantRedeemZeroAssets();

        // Withdraw assets from the strategy
        _beforeWithdraw(assets);

        // Burn shares and emit transfer event
        _burn(owner, shares);

        // Transfer assets to recipient
        underlying.safeTransfer(recipient, assets);

        /// @custom:event Withdraw
        emit Withdraw(msg.sender, recipient, owner, assets, shares);
    }

    /**
     *  @notice Manually updates `totalBalance` in terms of its underlying, and accrues interest in borrowable.
     *  @inheritdoc ICygnusTerminal
     *  @custom:security non-reentrant
     */
    function sync() external override nonReentrant update {}
}

File 7 of 32 : ERC20.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

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

// IMPORTANT: - Removed all hooks from the internal `_transfer` function (used to seize collateral from underwater accounts)
//            - Removed the `_beforeTokenTransfer` from `_mint` to save gas on `canRedeem`

/// @notice Simple ERC20 + EIP-2612 implementation.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol)
///
/// @dev Note:
/// The ERC20 standard allows minting and transferring to and from the zero address,
/// minting and transferring zero tokens, as well as self-approvals.
/// For performance, this implementation WILL NOT revert for such actions.
/// Please add any checks with overrides if desired.
abstract contract ERC20 is IERC20Permit {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The total supply has overflowed.
    error TotalSupplyOverflow();

    /// @dev The allowance has overflowed.
    error AllowanceOverflow();

    /// @dev The allowance has underflowed.
    error AllowanceUnderflow();

    /// @dev Insufficient balance.
    error InsufficientBalance();

    /// @dev Insufficient allowance.
    error InsufficientAllowance();

    /// @dev The permit is invalid.
    error InvalidPermit();

    /// @dev The permit has expired.
    error PermitExpired();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                           EVENTS                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Emitted when `amount` tokens is transferred from `from` to `to`.
    event Transfer(address indexed from, address indexed to, uint256 amount);

    /// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`.
    event Approval(address indexed owner, address indexed spender, uint256 amount);

    /// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
    uint256 private constant _TRANSFER_EVENT_SIGNATURE = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;

    /// @dev `keccak256(bytes("Approval(address,address,uint256)"))`.
    uint256 private constant _APPROVAL_EVENT_SIGNATURE = 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                          STORAGE                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The storage slot for the total supply.
    uint256 private constant _TOTAL_SUPPLY_SLOT = 0x05345cdf77eb68f44c;

    /// @dev The balance slot of `owner` is given by:
    /// ```
    ///     mstore(0x0c, _BALANCE_SLOT_SEED)
    ///     mstore(0x00, owner)
    ///     let balanceSlot := keccak256(0x0c, 0x20)
    /// ```
    uint256 private constant _BALANCE_SLOT_SEED = 0x87a211a2;

    /// @dev The allowance slot of (`owner`, `spender`) is given by:
    /// ```
    ///     mstore(0x20, spender)
    ///     mstore(0x0c, _ALLOWANCE_SLOT_SEED)
    ///     mstore(0x00, owner)
    ///     let allowanceSlot := keccak256(0x0c, 0x34)
    /// ```
    uint256 private constant _ALLOWANCE_SLOT_SEED = 0x7f5e9f20;

    /// @dev The nonce slot of `owner` is given by:
    /// ```
    ///     mstore(0x0c, _NONCES_SLOT_SEED)
    ///     mstore(0x00, owner)
    ///     let nonceSlot := keccak256(0x0c, 0x20)
    /// ```
    uint256 private constant _NONCES_SLOT_SEED = 0x38377508;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev `(_NONCES_SLOT_SEED << 16) | 0x1901`.
    uint256 private constant _NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX = 0x383775081901;

    /// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`.
    bytes32 private constant _DOMAIN_TYPEHASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;

    /// @dev `keccak256("1")`.
    bytes32 private constant _VERSION_HASH = 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6;

    /// @dev `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`.
    bytes32 private constant _PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       ERC20 METADATA                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns the name of the token.
    function name() public view virtual returns (string memory);

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

    /// @dev Returns the decimals places of the token.
    function decimals() public view virtual returns (uint8) {
        return 18;
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                           ERC20                            */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns the amount of tokens in existence.
    function totalSupply() public view virtual returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := sload(_TOTAL_SUPPLY_SLOT)
        }
    }

    /// @dev Returns the amount of tokens owned by `owner`.
    function balanceOf(address owner) public view virtual returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x0c, _BALANCE_SLOT_SEED)
            mstore(0x00, owner)
            result := sload(keccak256(0x0c, 0x20))
        }
    }

    /// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`.
    function allowance(address owner, address spender) public view virtual returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x20, spender)
            mstore(0x0c, _ALLOWANCE_SLOT_SEED)
            mstore(0x00, owner)
            result := sload(keccak256(0x0c, 0x34))
        }
    }

    /// @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
    ///
    /// Emits a {Approval} event.
    function approve(address spender, uint256 amount) public virtual returns (bool) {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute the allowance slot and store the amount.
            mstore(0x20, spender)
            mstore(0x0c, _ALLOWANCE_SLOT_SEED)
            mstore(0x00, caller())
            sstore(keccak256(0x0c, 0x34), amount)
            // Emit the {Approval} event.
            mstore(0x00, amount)
            log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c)))
        }
        return true;
    }

    /// @dev Transfer `amount` tokens from the caller to `to`.
    ///
    /// Requirements:
    /// - `from` must at least have `amount`.
    ///
    /// Emits a {Transfer} event.
    function transfer(address to, uint256 amount) public virtual returns (bool) {
        _beforeTokenTransfer(msg.sender, to, amount);
        /// @solidity memory-safe-assembly
        assembly {
            // Compute the balance slot and load its value.
            mstore(0x0c, _BALANCE_SLOT_SEED)
            mstore(0x00, caller())
            let fromBalanceSlot := keccak256(0x0c, 0x20)
            let fromBalance := sload(fromBalanceSlot)
            // Revert if insufficient balance.
            if gt(amount, fromBalance) {
                mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
                revert(0x1c, 0x04)
            }
            // Subtract and store the updated balance.
            sstore(fromBalanceSlot, sub(fromBalance, amount))
            // Compute the balance slot of `to`.
            mstore(0x00, to)
            let toBalanceSlot := keccak256(0x0c, 0x20)
            // Add and store the updated balance of `to`.
            // Will not overflow because the sum of all user balances
            // cannot exceed the maximum uint256 value.
            sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
            // Emit the {Transfer} event.
            mstore(0x20, amount)
            log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c)))
        }
        _afterTokenTransfer(msg.sender, to, amount);
        return true;
    }

    /// @dev Transfers `amount` tokens from `from` to `to`.
    ///
    /// Note: Does not update the allowance if it is the maximum uint256 value.
    ///
    /// Requirements:
    /// - `from` must at least have `amount`.
    /// - The caller must have at least `amount` of allowance to transfer the tokens of `from`.
    ///
    /// Emits a {Transfer} event.
    function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
        _beforeTokenTransfer(from, to, amount);
        /// @solidity memory-safe-assembly
        assembly {
            let from_ := shl(96, from)
            // Compute the allowance slot and load its value.
            mstore(0x20, caller())
            mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED))
            let allowanceSlot := keccak256(0x0c, 0x34)
            let allowance_ := sload(allowanceSlot)
            // If the allowance is not the maximum uint256 value.
            if add(allowance_, 1) {
                // Revert if the amount to be transferred exceeds the allowance.
                if gt(amount, allowance_) {
                    mstore(0x00, 0x13be252b) // `InsufficientAllowance()`.
                    revert(0x1c, 0x04)
                }
                // Subtract and store the updated allowance.
                sstore(allowanceSlot, sub(allowance_, amount))
            }
            // Compute the balance slot and load its value.
            mstore(0x0c, or(from_, _BALANCE_SLOT_SEED))
            let fromBalanceSlot := keccak256(0x0c, 0x20)
            let fromBalance := sload(fromBalanceSlot)
            // Revert if insufficient balance.
            if gt(amount, fromBalance) {
                mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
                revert(0x1c, 0x04)
            }
            // Subtract and store the updated balance.
            sstore(fromBalanceSlot, sub(fromBalance, amount))
            // Compute the balance slot of `to`.
            mstore(0x00, to)
            let toBalanceSlot := keccak256(0x0c, 0x20)
            // Add and store the updated balance of `to`.
            // Will not overflow because the sum of all user balances
            // cannot exceed the maximum uint256 value.
            sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
            // Emit the {Transfer} event.
            mstore(0x20, amount)
            log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c)))
        }
        _afterTokenTransfer(from, to, amount);
        return true;
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                          EIP-2612                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev For more performance, override to return the constant value
    /// of `keccak256(bytes(name()))` if `name()` will never change.
    function _constantNameHash() internal view virtual returns (bytes32 result) {}

    /// @dev Returns the current nonce for `owner`.
    /// This value is used to compute the signature for EIP-2612 permit.
    function nonces(address owner) public view virtual returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute the nonce slot and load its value.
            mstore(0x0c, _NONCES_SLOT_SEED)
            mstore(0x00, owner)
            result := sload(keccak256(0x0c, 0x20))
        }
    }

    /// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`,
    /// authorized by a signed approval by `owner`.
    ///
    /// Emits a {Approval} event.
    function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public virtual {
        bytes32 nameHash = _constantNameHash();
        //  We simply calculate it on-the-fly to allow for cases where the `name` may change.
        if (nameHash == bytes32(0)) nameHash = keccak256(bytes(name()));
        /// @solidity memory-safe-assembly
        assembly {
            // Revert if the block timestamp is greater than `deadline`.
            if gt(timestamp(), deadline) {
                mstore(0x00, 0x1a15a3cc) // `PermitExpired()`.
                revert(0x1c, 0x04)
            }
            let m := mload(0x40) // Grab the free memory pointer.
            // Clean the upper 96 bits.
            owner := shr(96, shl(96, owner))
            spender := shr(96, shl(96, spender))
            // Compute the nonce slot and load its value.
            mstore(0x0e, _NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX)
            mstore(0x00, owner)
            let nonceSlot := keccak256(0x0c, 0x20)
            let nonceValue := sload(nonceSlot)
            // Prepare the domain separator.
            mstore(m, _DOMAIN_TYPEHASH)
            mstore(add(m, 0x20), nameHash)
            mstore(add(m, 0x40), _VERSION_HASH)
            mstore(add(m, 0x60), chainid())
            mstore(add(m, 0x80), address())
            mstore(0x2e, keccak256(m, 0xa0))
            // Prepare the struct hash.
            mstore(m, _PERMIT_TYPEHASH)
            mstore(add(m, 0x20), owner)
            mstore(add(m, 0x40), spender)
            mstore(add(m, 0x60), value)
            mstore(add(m, 0x80), nonceValue)
            mstore(add(m, 0xa0), deadline)
            mstore(0x4e, keccak256(m, 0xc0))
            // Prepare the ecrecover calldata.
            mstore(0x00, keccak256(0x2c, 0x42))
            mstore(0x20, and(0xff, v))
            mstore(0x40, r)
            mstore(0x60, s)
            let t := staticcall(gas(), 1, 0, 0x80, 0x20, 0x20)
            // If the ecrecover fails, the returndatasize will be 0x00,
            // `owner` will be checked if it equals the hash at 0x00,
            // which evaluates to false (i.e. 0), and we will revert.
            // If the ecrecover succeeds, the returndatasize will be 0x20,
            // `owner` will be compared against the returned address at 0x20.
            if iszero(eq(mload(returndatasize()), owner)) {
                mstore(0x00, 0xddafbaef) // `InvalidPermit()`.
                revert(0x1c, 0x04)
            }
            // Increment and store the updated nonce.
            sstore(nonceSlot, add(nonceValue, t)) // `t` is 1 if ecrecover succeeds.
            // Compute the allowance slot and store the value.
            // The `owner` is already at slot 0x20.
            mstore(0x40, or(shl(160, _ALLOWANCE_SLOT_SEED), spender))
            sstore(keccak256(0x2c, 0x34), value)
            // Emit the {Approval} event.
            log3(add(m, 0x60), 0x20, _APPROVAL_EVENT_SIGNATURE, owner, spender)
            mstore(0x40, m) // Restore the free memory pointer.
            mstore(0x60, 0) // Restore the zero pointer.
        }
    }

    /// @dev Returns the EIP-712 domain separator for the EIP-2612 permit.
    function DOMAIN_SEPARATOR() public view virtual returns (bytes32 result) {
        bytes32 nameHash = _constantNameHash();
        //  We simply calculate it on-the-fly to allow for cases where the `name` may change.
        if (nameHash == bytes32(0)) nameHash = keccak256(bytes(name()));
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Grab the free memory pointer.
            mstore(m, _DOMAIN_TYPEHASH)
            mstore(add(m, 0x20), nameHash)
            mstore(add(m, 0x40), _VERSION_HASH)
            mstore(add(m, 0x60), chainid())
            mstore(add(m, 0x80), address())
            result := keccak256(m, 0xa0)
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                  INTERNAL MINT FUNCTIONS                   */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Mints `amount` tokens to `to`, increasing the total supply.
    ///
    /// Emits a {Transfer} event.
    function _mint(address to, uint256 amount) internal virtual {
        /// @solidity memory-safe-assembly
        assembly {
            let totalSupplyBefore := sload(_TOTAL_SUPPLY_SLOT)
            let totalSupplyAfter := add(totalSupplyBefore, amount)
            // Revert if the total supply overflows.
            if lt(totalSupplyAfter, totalSupplyBefore) {
                mstore(0x00, 0xe5cfe957) // `TotalSupplyOverflow()`.
                revert(0x1c, 0x04)
            }
            // Store the updated total supply.
            sstore(_TOTAL_SUPPLY_SLOT, totalSupplyAfter)
            // Compute the balance slot and load its value.
            mstore(0x0c, _BALANCE_SLOT_SEED)
            mstore(0x00, to)
            let toBalanceSlot := keccak256(0x0c, 0x20)
            // Add and store the updated balance.
            sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
            // Emit the {Transfer} event.
            mstore(0x20, amount)
            log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, mload(0x0c)))
        }
        _afterTokenTransfer(address(0), to, amount);
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                  INTERNAL BURN FUNCTIONS                   */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Burns `amount` tokens from `from`, reducing the total supply.
    ///
    /// Emits a {Transfer} event.
    function _burn(address from, uint256 amount) internal virtual {
        _beforeTokenTransfer(from, address(0), amount);
        /// @solidity memory-safe-assembly
        assembly {
            // Compute the balance slot and load its value.
            mstore(0x0c, _BALANCE_SLOT_SEED)
            mstore(0x00, from)
            let fromBalanceSlot := keccak256(0x0c, 0x20)
            let fromBalance := sload(fromBalanceSlot)
            // Revert if insufficient balance.
            if gt(amount, fromBalance) {
                mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
                revert(0x1c, 0x04)
            }
            // Subtract and store the updated balance.
            sstore(fromBalanceSlot, sub(fromBalance, amount))
            // Subtract and store the updated total supply.
            sstore(_TOTAL_SUPPLY_SLOT, sub(sload(_TOTAL_SUPPLY_SLOT), amount))
            // Emit the {Transfer} event.
            mstore(0x00, amount)
            log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0)
        }
        _afterTokenTransfer(from, address(0), amount);
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                INTERNAL TRANSFER FUNCTIONS                 */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Moves `amount` of tokens from `from` to `to`.
    function _transfer(address from, address to, uint256 amount) internal virtual {
        /// @solidity memory-safe-assembly
        assembly {
            let from_ := shl(96, from)
            // Compute the balance slot and load its value.
            mstore(0x0c, or(from_, _BALANCE_SLOT_SEED))
            let fromBalanceSlot := keccak256(0x0c, 0x20)
            let fromBalance := sload(fromBalanceSlot)
            // Revert if insufficient balance.
            if gt(amount, fromBalance) {
                mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
                revert(0x1c, 0x04)
            }
            // Subtract and store the updated balance.
            sstore(fromBalanceSlot, sub(fromBalance, amount))
            // Compute the balance slot of `to`.
            mstore(0x00, to)
            let toBalanceSlot := keccak256(0x0c, 0x20)
            // Add and store the updated balance of `to`.
            // Will not overflow because the sum of all user balances
            // cannot exceed the maximum uint256 value.
            sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
            // Emit the {Transfer} event.
            mstore(0x20, amount)
            log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c)))
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                INTERNAL ALLOWANCE FUNCTIONS                */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Updates the allowance of `owner` for `spender` based on spent `amount`.
    function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute the allowance slot and load its value.
            mstore(0x20, spender)
            mstore(0x0c, _ALLOWANCE_SLOT_SEED)
            mstore(0x00, owner)
            let allowanceSlot := keccak256(0x0c, 0x34)
            let allowance_ := sload(allowanceSlot)
            // If the allowance is not the maximum uint256 value.
            if add(allowance_, 1) {
                // Revert if the amount to be transferred exceeds the allowance.
                if gt(amount, allowance_) {
                    mstore(0x00, 0x13be252b) // `InsufficientAllowance()`.
                    revert(0x1c, 0x04)
                }
                // Subtract and store the updated allowance.
                sstore(allowanceSlot, sub(allowance_, amount))
            }
        }
    }

    /// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`.
    ///
    /// Emits a {Approval} event.
    function _approve(address owner, address spender, uint256 amount) internal virtual {
        /// @solidity memory-safe-assembly
        assembly {
            let owner_ := shl(96, owner)
            // Compute the allowance slot and store the amount.
            mstore(0x20, spender)
            mstore(0x0c, or(owner_, _ALLOWANCE_SLOT_SEED))
            sstore(keccak256(0x0c, 0x34), amount)
            // Emit the {Approval} event.
            mstore(0x00, amount)
            log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, owner_), shr(96, mload(0x2c)))
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                     HOOKS TO OVERRIDE                      */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Hook that is called before any transfer of tokens.
    /// This includes minting and burning.
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}

    /// @dev Hook that is called after any transfer of tokens.
    /// This includes minting and burning.
    function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
}

File 8 of 32 : AggregatorV3Interface.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.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);
}

File 9 of 32 : IMerkleClaimer.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IMerklClaimer {
    function claim(address[] calldata users, address[] calldata tokens, uint256[] calldata amounts, bytes32[][] calldata proofs) external;
}

File 10 of 32 : IMiniChef.sol
//  SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity >=0.8.17;

// Interface for masterchef
interface IMiniChefV2 {
    /// @notice Info of each MCV2 user.
    /// `amount` LP token amount the user has provided.
    /// `rewardDebt` The amount of SUSHI entitled to the user.
    struct UserInfo {
        uint256 amount;
        int256 rewardDebt;
    }

    /// @notice Info of each MCV2 pool.
    /// `allocPoint` The amount of allocation points assigned to the pool.
    /// Also known as the amount of SUSHI to distribute per block.
    struct PoolInfo {
        uint128 accSushiPerShare;
        uint64 lastRewardTime;
        uint64 allocPoint;
        IRewarder[] rewarders;
    }

    function poolInfo(uint256) external view returns (PoolInfo memory);

    function rewarder(uint256) external view returns (address);

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

    function userInfo(uint256, address) external view returns (uint256, int256);

    /// @notice Returns the number of MCV2 pools.
    function poolLength() external view returns (uint256 pools);

    /// @notice View function to see pending SUSHI on frontend.
    /// @param _pid The index of the pool. See `poolInfo`.
    /// @param _user Address of user.
    /// @return pending SUSHI reward for a given user.
    function pendingSushi(uint256 _pid, address _user) external view returns (uint256 pending);

    /// @notice Update reward variables for all pools. Be careful of gas spending!
    /// @param pids Pool IDs of all to be updated. Make sure to update all active pools.
    function massUpdatePools(uint256[] calldata pids) external;

    /// @notice Update reward variables of the given pool.
    /// @param pid The index of the pool. See `poolInfo`.
    /// @return pool Returns the pool that was updated.
    function updatePool(uint256 pid) external returns (PoolInfo memory pool);

    /// @notice Deposit LP tokens to MCV2 for SUSHI allocation.
    /// @param pid The index of the pool. See `poolInfo`.
    /// @param amount LP token amount to deposit.
    /// @param to The receiver of `amount` deposit benefit.
    function deposit(uint256 pid, uint256 amount, address to) external;

    /// @notice Withdraw LP tokens from MCV2.
    /// @param pid The index of the pool. See `poolInfo`.
    /// @param amount LP token amount to withdraw.
    /// @param to Receiver of the LP tokens.
    function withdraw(uint256 pid, uint256 amount, address to) external;

    /// @notice Harvest proceeds for transaction sender to `to`.
    /// @param pid The index of the pool. See `poolInfo`.
    /// @param to Receiver of SUSHI rewards.
    function harvest(uint256 pid, address to) external;

    /// @notice Withdraw LP tokens from MCV2 and harvest proceeds for transaction sender to `to`.
    /// @param pid The index of the pool. See `poolInfo`.
    /// @param amount LP token amount to withdraw.
    /// @param to Receiver of the LP tokens and SUSHI rewards.
    function withdrawAndHarvest(uint256 pid, uint256 amount, address to) external;

    /// @notice Withdraw without caring about rewards. EMERGENCY ONLY.
    /// @param pid The index of the pool. See `poolInfo`.
    /// @param to Receiver of the LP tokens.
    function emergencyWithdraw(uint256 pid, address to) external;

    function lpToken(uint256) external view returns (address);

    function SUSHI() external pure returns (address);
}

interface IRewarder {
    function pendingTokens(uint256 pid, address user, uint256 sushiAmount) external view returns (address[] memory, uint256[] memory);

    function pendingToken(uint256 pid, address user) external view returns (uint256);

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

File 11 of 32 : IAlbireoOrbiter.sol
//  SPDX-License-Identifier: AGPL-3.0-or-later
//
//  IAlbireoOrbiter.sol
//
//  Copyright (C) 2023 CygnusDAO
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
pragma solidity >=0.8.17;

/**
 *  @title ICygnusAlbireo The interface the Cygnus borrow deployer
 *  @notice A contract that constructs a Cygnus borrow pool must implement this to pass arguments to the pool
 */
interface IAlbireoOrbiter {
    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            4. NON-CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @notice Passing the struct parameters to the borrow contracts avoids setting constructor parameters
     *  @return factory The address of the Cygnus factory assigned to `Hangar18`
     *  @return underlying The address of the underlying borrow token (address of USDC)
     *  @return collateral The address of the Cygnus collateral contract for this borrow contract
     *  @return oracle The address of the oracle for this lending pool
     *  @return shuttleId The lending pool ID
     */
    function shuttleParameters()
        external
        returns (address factory, address underlying, address collateral, address oracle, uint256 shuttleId);

    /**
     *  @return The init code hash of the borrow contract for this deployer
     */
    function borrowableInitCodeHash() external view returns (bytes32);

    /**
     *  @notice Function to deploy the borrow contract of a lending pool
     *  @param underlying The address of the underlying borrow token (address of USDc)
     *  @param collateral The address of the Cygnus collateral contract for this borrow contract
     *  @param shuttleId The ID of the shuttle we are deploying (shared by borrow and collateral)
     *  @return borrowable The address of the new borrow contract
     */
    function deployAlbireo(address underlying, address collateral, address oracle, uint256 shuttleId) external returns (address borrowable);
}

File 12 of 32 : IAllowanceTransfer.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.17;

/// @title AllowanceTransfer
/// @notice Handles ERC20 token permissions through signature based allowance setting and ERC20 token transfers by checking allowed amounts
/// @dev Requires user's token approval on the Permit2 contract
interface IAllowanceTransfer {
    /// @notice Thrown when an allowance on a token has expired.
    /// @param deadline The timestamp at which the allowed amount is no longer valid
    error AllowanceExpired(uint256 deadline);

    /// @notice Thrown when an allowance on a token has been depleted.
    /// @param amount The maximum amount allowed
    error InsufficientAllowance(uint256 amount);

    /// @notice Thrown when too many nonces are invalidated.
    error ExcessiveInvalidation();

    /// @notice Emits an event when the owner successfully invalidates an ordered nonce.
    event NonceInvalidation(address indexed owner, address indexed token, address indexed spender, uint48 newNonce, uint48 oldNonce);

    /// @notice Emits an event when the owner successfully sets permissions on a token for the spender.
    event Approval(address indexed owner, address indexed token, address indexed spender, uint160 amount, uint48 expiration);

    /// @notice Emits an event when the owner successfully sets permissions using a permit signature on a token for the spender.
    event Permit(address indexed owner, address indexed token, address indexed spender, uint160 amount, uint48 expiration, uint48 nonce);

    /// @notice Emits an event when the owner sets the allowance back to 0 with the lockdown function.
    event Lockdown(address indexed owner, address token, address spender);

    /// @notice The permit data for a token
    struct PermitDetails {
        // ERC20 token address
        address token;
        // the maximum amount allowed to spend
        uint160 amount;
        // timestamp at which a spender's token allowances become invalid
        uint48 expiration;
        // an incrementing value indexed per owner,token,and spender for each signature
        uint48 nonce;
    }

    /// @notice The permit message signed for a single token allownce
    struct PermitSingle {
        // the permit data for a single token alownce
        PermitDetails details;
        // address permissioned on the allowed tokens
        address spender;
        // deadline on the permit signature
        uint256 sigDeadline;
    }

    /// @notice The permit message signed for multiple token allowances
    struct PermitBatch {
        // the permit data for multiple token allowances
        PermitDetails[] details;
        // address permissioned on the allowed tokens
        address spender;
        // deadline on the permit signature
        uint256 sigDeadline;
    }

    /// @notice The saved permissions
    /// @dev This info is saved per owner, per token, per spender and all signed over in the permit message
    /// @dev Setting amount to type(uint160).max sets an unlimited approval
    struct PackedAllowance {
        // amount allowed
        uint160 amount;
        // permission expiry
        uint48 expiration;
        // an incrementing value indexed per owner,token,and spender for each signature
        uint48 nonce;
    }

    /// @notice A token spender pair.
    struct TokenSpenderPair {
        // the token the spender is approved
        address token;
        // the spender address
        address spender;
    }

    /// @notice Details for a token transfer.
    struct AllowanceTransferDetails {
        // the owner of the token
        address from;
        // the recipient of the token
        address to;
        // the amount of the token
        uint160 amount;
        // the token to be transferred
        address token;
    }

    /// @notice A mapping from owner address to token address to spender address to PackedAllowance struct, which contains details and conditions of the approval.
    /// @notice The mapping is indexed in the above order see: allowance[ownerAddress][tokenAddress][spenderAddress]
    /// @dev The packed slot holds the allowed amount, expiration at which the allowed amount is no longer valid, and current nonce thats updated on any signature based approvals.
    function allowance(address, address, address) external view returns (uint160, uint48, uint48);

    /// @notice Approves the spender to use up to amount of the specified token up until the expiration
    /// @param token The token to approve
    /// @param spender The spender address to approve
    /// @param amount The approved amount of the token
    /// @param expiration The timestamp at which the approval is no longer valid
    /// @dev The packed allowance also holds a nonce, which will stay unchanged in approve
    /// @dev Setting amount to type(uint160).max sets an unlimited approval
    function approve(address token, address spender, uint160 amount, uint48 expiration) external;

    /// @notice Permit a spender to a given amount of the owners token via the owner's EIP-712 signature
    /// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce
    /// @param owner The owner of the tokens being approved
    /// @param permitSingle Data signed over by the owner specifying the terms of approval
    /// @param signature The owner's signature over the permit data
    function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external;

    /// @notice Permit a spender to the signed amounts of the owners tokens via the owner's EIP-712 signature
    /// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce
    /// @param owner The owner of the tokens being approved
    /// @param permitBatch Data signed over by the owner specifying the terms of approval
    /// @param signature The owner's signature over the permit data
    function permit(address owner, PermitBatch memory permitBatch, bytes calldata signature) external;

    /// @notice Transfer approved tokens from one address to another
    /// @param from The address to transfer from
    /// @param to The address of the recipient
    /// @param amount The amount of the token to transfer
    /// @param token The token address to transfer
    /// @dev Requires the from address to have approved at least the desired amount
    /// of tokens to msg.sender.
    function transferFrom(address from, address to, uint160 amount, address token) external;

    /// @notice Transfer approved tokens in a batch
    /// @param transferDetails Array of owners, recipients, amounts, and tokens for the transfers
    /// @dev Requires the from addresses to have approved at least the desired amount
    /// of tokens to msg.sender.
    function transferFrom(AllowanceTransferDetails[] calldata transferDetails) external;

    /// @notice Enables performing a "lockdown" of the sender's Permit2 identity
    /// by batch revoking approvals
    /// @param approvals Array of approvals to revoke.
    function lockdown(TokenSpenderPair[] calldata approvals) external;

    /// @notice Invalidate nonces for a given (token, spender) pair
    /// @param token The token to invalidate nonces for
    /// @param spender The spender to invalidate nonces for
    /// @param newNonce The new nonce to set. Invalidates all nonces less than it.
    /// @dev Can't invalidate more than 2**16 nonces per transaction.
    function invalidateNonces(address token, address spender, uint48 newNonce) external;
}

File 13 of 32 : ICygnusAltairCall.sol
//  SPDX-License-Identifier: AGPL-3.0-or-later
//
//  ICygnusAltairCall.sol
//
//  Copyright (C) 2023 CygnusDAO
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
pragma solidity >=0.8.17;

/**
 *  @title ICygnusAltairCall
 *  @notice Simple callee contract for leverage, deleverage and flash liquidations
 */
interface ICygnusAltairCall {
    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            4. NON-CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @notice Function that is called by the CygnusBorrow contract and decodes data to carry out the leverage
     *  @notice Will only succeed if: Caller is borrow contract & Borrow contract was called by router
     *
     *  @param sender Address of the contract that initialized the borrow transaction (address of the router)
     *  @param borrowAmount The amount to leverage
     *  @param data The encoded byte data passed from the CygnusBorrow contract to the router
     */
    function altairBorrow_O9E(address sender, uint256 borrowAmount, bytes calldata data) external returns (uint256);

    /**
     *  @notice Function that is called by the CygnusCollateral contract and decodes data to carry out the deleverage
     *  @notice Will only succeed if: Caller is collateral contract & collateral contract was called by router
     *
     *  @param sender Address of the contract that initialized the redeem transaction (address of the router)
     *  @param redeemAmount The amount to deleverage
     *  @param data The encoded byte data passed from the CygnusCollateral contract to the router
     */
    function altairRedeem_u91A(address sender, uint256 redeemAmount, bytes calldata data) external returns (uint256);

    /**
     *  @notice Function that is called by the CygnusBorrow contract and decodes data to carry out the liquidation
     *  @notice Will only succeed if: Caller is borrow contract & Borrow contract was called by router
     *
     *  @param sender Address of the contract that initialized the borrow transaction (address of the router)
     *  @param cygLPAmount The cygLP Amount seized
     *  @param actualRepayAmount The usd amount the contract must have for the liquidate function to finish
     *  @param data The encoded byte data passed from the CygnusBorrow contract to the router
     */
    function altairLiquidate_f2x(address sender, uint256 cygLPAmount, uint256 actualRepayAmount, bytes calldata data) external;
}

File 14 of 32 : ICygnusBorrow.sol
//  SPDX-License-Identifier: AGPL-3.0-or-later
//
//  ICygnusBorrow.sol
//
//  Copyright (C) 2023 CygnusDAO
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
pragma solidity >=0.8.17;

// Dependencies
import {ICygnusBorrowVoid} from "./ICygnusBorrowVoid.sol";

// Overrides
import {ICygnusTerminal} from "./ICygnusTerminal.sol";

/**
 *  @title ICygnusBorrow Interface for the main Borrow contract which handles borrows/liquidations
 *  @notice Main interface to borrow against collateral or liquidate positions
 */
interface ICygnusBorrow is ICygnusBorrowVoid {
    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            1. CUSTOM ERRORS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @dev Reverts if the borrower has insufficient liquidity for this borrow
     *  @custom:error InsufficientLiquidity
     */
    error CygnusBorrow__InsufficientLiquidity();

    /**
     *  @dev Reverts if usd received is less than repaid after liquidating
     *  @custom:error InsufficientUsdReceived
     */
    error CygnusBorrow__InsufficientUsdReceived();

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            2. CUSTOM EVENTS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @dev Logs when a borrower borrows or repays a loan.
     *  @param sender Indexed address of msg.sender
     *  @param borrower Indexed address of the borrower
     *  @param receiver Indexed address of receiver
     *  @param borrowAmount The amount of stablecoins borrowed (if any)
     *  @param repayAmount The amount of stablecoins repaid (if any)
     *  @custom:event Borrow
     */
    event Borrow(address indexed sender, address indexed borrower, address indexed receiver, uint256 borrowAmount, uint256 repayAmount);

    /**
     *  @dev Logs when a liquidator repays and seizes collateral
     *  @param sender Indexed address of msg.sender (should be `Altair` address)
     *  @param borrower Indexed address of the borrower
     *  @param receiver Indexed address of receiver
     *  @param repayAmount The amount of USD repaid
     *  @param cygLPAmount The amount of CygLP seized
     *  @param usdAmount The total amount of underlying deposited
     *  @custom:event Liquidate
     */
    event Liquidate(
        address indexed sender,
        address indexed borrower,
        address indexed receiver,
        uint256 repayAmount,
        uint256 cygLPAmount,
        uint256 usdAmount
    );

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            4. NON-CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @notice This low level function should be called from a periphery contract only
     *  @notice Main function used to borrow stablecoins or repay loans.
     *  @param borrower The address of the borrower
     *  @param receiver The address of the receiver of the borrow amount.
     *  @param borrowAmount The amount of the underlying asset to borrow.
     *  @param data Calldata passed to a router contract
     *  @custom:security non-reentrant
     */
    function borrow(address borrower, address receiver, uint256 borrowAmount, bytes calldata data) external returns (uint256);

    /**
     *  @notice This low level function should be called from a periphery contract only
     *  @notice Main function used to liquidate or flash liquidation positions.
     *  @param borrower The address of the borrower being liquidated
     *  @param receiver The address of the receiver of the collateral
     *  @param repayAmount USD amount covering the loan
     *  @param data Calldata passed to a router contract
     *  @return usdAmount The amount of USD deposited after taking into account liq. incentive
     *  @custom:security non-reentrant
     */
    function liquidate(address borrower, address receiver, uint256 repayAmount, bytes calldata data) external returns (uint256 usdAmount);
}

File 15 of 32 : ICygnusBorrowControl.sol
//  SPDX-License-Identifier: AGPL-3.0-or-later
//
//  ICygnusBorrowControl.sol
//
//  Copyright (C) 2023 CygnusDAO
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
pragma solidity >=0.8.17;

// Dependencies
import {ICygnusTerminal} from "./ICygnusTerminal.sol";

/**
 *  @title  ICygnusBorrowControl Interface for the control of borrow contracts (interest rate params, reserves, etc.)
 *  @notice Admin contract for Cygnus Borrow contract 👽
 */
interface ICygnusBorrowControl is ICygnusTerminal {
    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            1. CUSTOM ERRORS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @dev Reverts when attempting to set a parameter outside the min/max ranges allowed in the Control contract
     *  @custom:error ParameterNotInRange
     */
    error CygnusBorrowControl__ParameterNotInRange();

    /**
     *  @dev Reverts when setting the collateral if the msg.sender is not the hangar18 contract
     *  @custom:error MsgSenderNotHangar
     */
    error CygnusBorrowControl__MsgSenderNotHangar();

    /**
     *  @dev Reverts wehn attempting to set a collateral that has already been set
     *  @custom:error CollateralAlreadySet
     */
    error CygnusBorrowControl__CollateralAlreadySet();

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            2. CUSTOM EVENTS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @dev Logs when a new interest rate curve is set for this shuttle
     *  @param baseRatePerYear The approximate target base APR, as a mantissa (scaled by 1e18)
     *  @param multiplierPerYear The rate of increase in interest rate wrt utilization (scaled by 1e18)
     *  @param kinkMultiplier_ The increase to multiplier per year once kink utilization is reached
     *  @param kinkUtilizationRate_ The rate at which the jump interest rate takes effect
     *  custom:event NewInterestRateParameters
     */
    event NewInterestRateParameters(
        uint256 baseRatePerYear,
        uint256 multiplierPerYear,
        uint256 kinkMultiplier_,
        uint256 kinkUtilizationRate_
    );

    /**
     *  @dev Logs when a new rewarder contract is set to reward borrowers and lenders.
     *  @param oldRewarder The address of the rewarder up until this point used for CYG distribution
     *  @param newRewarder The address of the new rewarder
     *  @custom:event NewCygnusBorrowRewarder
     */
    event NewPillarsOfCreation(address oldRewarder, address newRewarder);

    /**
     *  @dev Logs when a new reserve factor is set.
     *  @param oldReserveFactor The reserve factor used in this shuttle until this point
     *  @param newReserveFactor The new reserve factor set
     *  @custom:event NewReserveFactor
     */
    event NewReserveFactor(uint256 oldReserveFactor, uint256 newReserveFactor);

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            3. CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── Internal ───────────────────────────────────────────────  */

    /**
     *  @custom:struct InterestRateModel The interest rate params for this pool
     *  @custom:member baseRatePerSecond The base interest rate which is the y-intercept when utilization rate is 0
     *  @custom:member multiplierPerSecond The multiplier of utilization rate that gives the slope of the interest rate
     *  @custom:member jumpMultiplierPerSecond The multiplierPerSecond after hitting a specified utilization point
     *  @custom:member kink The utilization point at which the jump multiplier is applied
     */
    struct InterestRateModel {
        uint64 baseRatePerSecond;
        uint64 multiplierPerSecond;
        uint64 jumpMultiplierPerSecond;
        uint64 kink;
    }

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @return The address of the Cygnus Collateral contract for this borrowable
     */
    function collateral() external view returns (address);

    /**
     *  @return Address that rewards borrowers and lenders in CYG
     */
    function pillarsOfCreation() external view returns (address);

    /**
     *  @notice The current interest rate params set for this pool
     *  @return baseRatePerSecond The base interest rate which is the y-intercept when utilization rate is 0
     *  @return multiplierPerSecond The multiplier of utilization rate that gives the slope of the interest rate
     *  @return jumpMultiplierPerSecond The multiplierPerSecond after hitting a specified utilization point
     *  @return kink The utilization point at which the jump multiplier is applied
     */
    function interestRateModel()
        external
        view
        returns (uint64 baseRatePerSecond, uint64 multiplierPerSecond, uint64 jumpMultiplierPerSecond, uint64 kink);

    /**
     *  @return reserveFactor Percentage of interest that is routed to this market's Reserve Pool
     */
    function reserveFactor() external view returns (uint256);

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            4. NON-CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */
    /**
     *  @notice Admin 👽
     *  @notice Updates the interest rate model with annualized rates, scaled by 1e18 (ie 1% = 0.01e18)
     *  @param baseRatePerYear The new annualized base rate
     *  @param multiplierPerYear The new annualized rate of increase in interest rate wrt utilization
     *  @param kinkMultiplier_ The increase to the slope once kink utilization is reached
     *  @param kinkUtilizationRate_ The new max utilization rate
     *  @custom:security only-admin
     */
    function setInterestRateModel(
        uint256 baseRatePerYear,
        uint256 multiplierPerYear,
        uint256 kinkMultiplier_,
        uint256 kinkUtilizationRate_
    ) external;

    /**
     *  @notice Admin 👽
     *  @notice Updates the reserve factor
     *  @param newReserveFactor The new reserve factor for this shuttle
     *  @custom:security only-admin
     */
    function setReserveFactor(uint256 newReserveFactor) external;

    /**
     *  @notice Admin 👽
     *  @notice Updates the pillars of creation contract. This can be updated to the zero address in case we need
     *          to remove rewards from pools saving us gas instead of calling the contract.
     *  @param newPillars The address of the new CYG rewarder
     *  @custom:security only-admin
     */
    function setPillarsOfCreation(address newPillars) external;
}

File 16 of 32 : ICygnusBorrowModel.sol
//  SPDX-License-Identifier: AGPL-3.0-or-later
//
//  ICygnusBorrowModel.sol
//
//  Copyright (C) 2023 CygnusDAO
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
pragma solidity >=0.8.17;

// Dependencies
import {ICygnusBorrowControl} from "./ICygnusBorrowControl.sol";

/**
 *  @title ICygnusBorrowModel Interface of the contract that implements the interest rate model and interest accruals
 */
interface ICygnusBorrowModel is ICygnusBorrowControl {
    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            2. CUSTOM EVENTS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @dev Logs when interest is accrued to borrows and reserves
     *  @param cash Total balance of the underlying in the strategy
     *  @param borrows Latest total borrows stored
     *  @param interest Interest accumulated since last accrual
     *  @param reserves The amount of CygUSD minted to the DAO
     *  @custom:event AccrueInterest
     */
    event AccrueInterest(uint256 cash, uint256 borrows, uint256 interest, uint256 reserves);

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            3. CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── Internal ───────────────────────────────────────────────  */

    /**
     *  @custom:struct BorrowSnapshot Container for individual user's borrow balance information
     *  @custom:member principal The total borrowed amount without interest accrued
     *  @custom:member interestIndex Borrow index as of the most recent balance-changing action
     */
    struct BorrowSnapshot {
        uint128 principal;
        uint128 interestIndex;
    }

    /*  ─────────────────────────────────────────────── Public ────────────────────────────────────────────────  */

    /**
     *  @return Total borrows of the lending pool (uses borrow indices to simulate interest rate accruals)
     */
    function totalBorrows() external view returns (uint256);

    /**
     *  @return Borrow index stored of this lending pool (uses borrow indices)
     */
    function borrowIndex() external view returns (uint256);

    /**
     *  @return The unix timestamp stored of the last interest rate accrual
     */
    function lastAccrualTimestamp() external view returns (uint256);

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @return The price of the denomination token in 18 decimals, used for reporting purposes only
     */
    function getUsdPrice() external view returns (uint256);

    /**
     *  @return The total amount of borrowed funds divided by the total vault assets
     */
    function utilizationRate() external view returns (uint256);

    /**
     *  @return The current per-second borrow rate
     */
    function borrowRate() external view returns (uint256);

    /**
     *  @return The current per-second supply rate
     */
    function supplyRate() external view returns (uint256);

    /**
     *  @notice Function used to get the borrow balance of users and their principal.
     *  @param borrower The address whose balance should be calculated
     *  @return principal The stablecoin amount borrowed without interests
     *  @return borrowBalance The stablecoin amount borrowed with interests  (ie. what borrowers must pay back)
     */
    function getBorrowBalance(address borrower) external view returns (uint256 principal, uint256 borrowBalance);

    /**
     *  @notice Gets the lender`s full position
     *  @param lender The address of the lender
     *  @return usdBalance The amount of stablecoins that the lender owns
     *  @return positionInUsd The position of the lender in USD
     */
    function getLenderPosition(address lender) external view returns (uint256 usdBalance, uint256 positionInUsd);

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            4. NON-CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @notice Applies interest accruals to borrows and reserves
     */
    function accrueInterest() external;

    /**
     *  @notice Manually track the user's CygUSD shares to pass to the rewarder contract
     *  @param lender The address of the lender
     */
    function trackLender(address lender) external;

    /**
     *  @notice Manually track the user's borrows
     *  @param borrower The address of the borrower
     */
    function trackBorrower(address borrower) external;
}

File 17 of 32 : ICygnusBorrowVoid.sol
//  SPDX-License-Identifier: AGPL-3.0-or-later
//
//  ICygnusBorrowVoid.sol
//
//  Copyright (C) 2023 CygnusDAO
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
pragma solidity >=0.8.17;

// Dependencies
import {ICygnusBorrowModel} from "./ICygnusBorrowModel.sol";

/**
 *  @title  ICygnusBorrowVoid
 *  @notice Interface for `CygnusBorrowVoid` which is in charge of connecting the stablecoin Token with
 *          a specified strategy (for example connect to a rewarder contract to stake the USDC, etc.)
 */
interface ICygnusBorrowVoid is ICygnusBorrowModel {
    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            1. CUSTOM ERRORS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @dev Reverts if msg.sender is not the harvester
     *
     *  @custom:error OnlyHarvesterAllowed
     */
    error CygnusBorrowVoid__OnlyHarvesterAllowed();

    /**
     *  @dev Reverts if the token we are sweeping is underlying
     *
     *  @custom:error TokenIsUnderlying
     */
    error CygnusBorrowVoid__TokenIsUnderlying();

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            2. CUSTOM EVENTS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @dev Logs when the strategy is first initialized or re-approves contracts
     *
     *  @param underlying The address of the underlying LP
     *  @param shuttleId The unique ID of the lending pool
     *  @param whitelisted The contract we approved to use our underlying
     *
     *  @custom:event ChargeVoid
     */
    event ChargeVoid(address underlying, uint256 shuttleId, address whitelisted);

    /**
     *  @dev Logs when rewards are harvested
     *
     *  @param sender The address of the caller who harvested the rewards
     *  @param tokens Total reward tokens harvested
     *  @param amounts Amounts of reward tokens harvested
     *  @param timestamp The timestamp of the harvest
     *
     *  @custom:event RechargeVoid
     */
    event RechargeVoid(address indexed sender, address[] tokens, uint256[] amounts, uint256 timestamp);

    /**
     *  @dev Logs when admin sets a new harvester to reinvest rewards
     *
     *  @param oldHarvester The address of the old harvester
     *  @param newHarvester The address of the new harvester
     *  @param rewardTokens The reward tokens added for the new harvester
     *
     *  @custom:event NewHarvester
     */
    event NewHarvester(address oldHarvester, address newHarvester, address[] rewardTokens);

    /**
     *  @dev Logs when admin sets a new reward token for the harvester (if needed)
     *
     *  @param _token Address of the token we are allowing the harvester to move
     *  @param _harvester Address of the harvester
     *
     *  @custom:event NewBonusHarvesterToken
     */
    event NewBonusHarvesterToken(address _token, address _harvester);

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            3. CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ─────────────────────────────────────────────── Public ────────────────────────────────────────────────  */

    /**
     *  @return harvester The address of the harvester contract
     */
    function harvester() external view returns (address);

    /**
     *  @return lastHarvest Timestamp of the last reinvest performed by the harvester contract
     */
    function lastHarvest() external view returns (uint256);

    /**
     *  @notice Array of reward tokens for this pool
     *  @param index The index of the token in the array
     *  @return rewardToken The reward token
     */
    function allRewardTokens(uint256 index) external view returns (address rewardToken);

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @return rewarder The address of the rewarder contract
     */
    function rewarder() external view returns (address);

    /**
     *  @return rewardTokensLength Length of reward tokens
     */
    function rewardTokensLength() external view returns (uint256);

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            4. NON-CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @notice Get the pending rewards manually - helpful to get rewards through static calls
     *
     *  @return tokens The addresses of the reward tokens earned by harvesting rewards
     *  @return amounts The amounts of each token received
     *
     *  @custom:security non-reentrant
     */
    function getRewards() external returns (address[] memory tokens, uint256[] memory amounts);

    /**
     *  @notice Only the harvester can reinvest
     *  @notice Reinvests all rewards from the rewarder to buy more USD to then deposit back into the rewarder
     *          This makes underlying balance increase in this contract, increasing the exchangeRate between
     *          CygUSD and underlying and thus lowering utilization rate and borrow rate
     *
     *  @custom:security non-reentrant only-harvester
     */
    function reinvestRewards_y7b(uint256 liquidity) external;

    /**
     *  @notice Admin 👽
     *  @notice Charges approvals needed for deposits and withdrawals, and any other function
     *          needed to get the vault started. ie, setting a pool ID from a MasterChef, a gauge, etc.
     *
     *  @custom:security only-admin
     */
    function chargeVoid() external;

    /**
     *  @notice Admin 👽
     *  @notice Sets the harvester address to harvest and reinvest rewards into more underlying
     *
     *  @param _harvester The address of the new harvester contract
     *  @param rewardTokens Array of reward tokens
     *
     *  @custom:security only-admin
     */
    function setHarvester(address _harvester, address[] calldata rewardTokens) external;

    /**
     *  @notice Admin 👽
     *  @notice Sweeps a token that was sent to this address by mistake, or a bonus reward token we are not tracking. Cannot
     *          sweep the underlying USD or USD LP token (like Comp USDC, etc.)
     *
     *  @custom:security only-admin
     */
    function sweepToken(address token, address to) external;
}

File 18 of 32 : ICygnusCollateral.sol
//  SPDX-License-Identifier: AGPL-3.0-or-later
//
//  ICygnusCollateral.sol
//
//  Copyright (C) 2023 CygnusDAO
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
pragma solidity >=0.8.17;

// Dependencies
import {ICygnusCollateralVoid} from "./ICygnusCollateralVoid.sol";

/**
 *  @title ICygnusCollateral
 *  @notice Interface for the main collateral contract which handles collateral seizes and flash redeems
 */
interface ICygnusCollateral is ICygnusCollateralVoid {
    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            1. CUSTOM ERRORS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @dev Reverts when the user doesn't have enough liquidity to redeem
     *  @custom:error InsufficientLiquidity
     */
    error CygnusCollateral__InsufficientLiquidity();

    /**
     *  @dev Reverts when the msg.sender of the liquidation is not this contract`s borrowable
     *  @custom:error MsgSenderNotBorrowable
     */
    error CygnusCollateral__MsgSenderNotBorrowable();

    /**
     *  @dev Reverts when the repayAmount in a liquidation is 0
     *  @custom:error CantLiquidateZero
     */
    error CygnusCollateral__CantLiquidateZero();

    /**
     *  @dev Reverts when trying to redeem 0 tokens
     *  @custom:error CantRedeemZero
     */
    error CygnusCollateral__CantRedeemZero();

    /**
     * @dev Reverts when liquidating an account that has no shortfall
     * @custom:error NotLiquidatable
     */
    error CygnusCollateral__NotLiquidatable();

    /**
     *  @dev Reverts when redeeming more shares than CygLP in this contract
     *  @custom:error InsufficientRedeemAmount
     */
    error CygnusCollateral__InsufficientCygLPReceived();

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            2. CUSTOM EVENTS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @dev Logs when collateral is seized from the borrower and sent to the liquidator
     *  @param liquidator The address of the liquidator
     *  @param borrower The address of the borrower being liquidated
     *  @param cygLPAmount The amount of CygLP seized without taking into account incentive or fee
     *  @param liquidatorAmount The aamount of CygLP seized sent to the liquidator (with the liq. incentive)
     *  @param daoFee The amount of CygLP sent to the DAO Reserves
     *  @param totalSeized The total amount of CygLP seized from the borrower
     */
    event SeizeCygLP(
        address indexed liquidator,
        address indexed borrower,
        uint256 cygLPAmount,
        uint256 liquidatorAmount,
        uint256 daoFee,
        uint256 totalSeized
    );

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            4. NON-CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @notice Seizes CygLP from borrower and adds it to the liquidator's account. This function can only be called 
     *          from the borrowable contract, else it reverts.
     *  @param liquidator The address repaying the borrow amount and seizing the collateral
     *  @param borrower The address of the borrower
     *  @param repayAmount The number of collateral tokens to seize
     *  @return liquidatorAmount The amount of CygLP that the liquidator received for liquidating the position
     *  @custom:security non-reentrant
     */
    function seizeCygLP(address liquidator, address borrower, uint256 repayAmount) external returns (uint256 liquidatorAmount);

    /**
     *  @notice Flash redeems the underlying LP Token - Low level function which should only be called by a router.
     *  @param redeemer The address redeeming the tokens (Altair contract)
     *  @param assets The amount of the underlying assets to redeem
     *  @param data Calldata passed from and back to router contract
     *  @custom:security non-reentrant
     */
    function flashRedeemAltair(address redeemer, uint256 assets, bytes calldata data) external returns (uint256 usdAmount);
}

File 19 of 32 : ICygnusCollateralControl.sol
//  SPDX-License-Identifier: AGPL-3.0-or-later
//
//  ICygnusCollateralControl.sol
//
//  Copyright (C) 2023 CygnusDAO
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
pragma solidity >=0.8.17;

// Dependencies
import {ICygnusTerminal} from "./ICygnusTerminal.sol";

/**
 *  @title  ICygnusCollateralControl Interface for the admin control of collateral contracts (incentives, debt ratios)
 *  @notice Admin contract for Cygnus Collateral contract 👽
 */
interface ICygnusCollateralControl is ICygnusTerminal {
    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            1. CUSTOM ERRORS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @dev Reverts when attempting to set a parameter outside the min/max ranges allowed in the Control contract
     *  @custom:error ParameterNotInRange
     */
    error CygnusCollateralControl__ParameterNotInRange();

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════  
            2. CUSTOM EVENTS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @dev Logs when the max debt ratio is updated for this shuttle
     *  @param oldDebtRatio The old debt ratio at which the collateral was liquidatable in this shuttle
     *  @param newDebtRatio The new debt ratio for this shuttle
     *  @custom:event NewDebtRatio
     */
    event NewDebtRatio(uint256 oldDebtRatio, uint256 newDebtRatio);

    /**
     *  @dev Logs when a new liquidation incentive is set for liquidators
     *  @param oldLiquidationIncentive The old incentive for liquidators taken from the collateral
     *  @param newLiquidationIncentive The new liquidation incentive for this shuttle
     *  @custom:event NewLiquidationIncentive
     */
    event NewLiquidationIncentive(uint256 oldLiquidationIncentive, uint256 newLiquidationIncentive);

    /**
     *  @dev Logs when a new liquidation fee is set, which the protocol keeps from each liquidation
     *  @param oldLiquidationFee The previous fee the protocol kept as reserves from each liquidation
     *  @param newLiquidationFee The new liquidation fee for this shuttle
     *  @custom:event NewLiquidationFee
     */
    event NewLiquidationFee(uint256 oldLiquidationFee, uint256 newLiquidationFee);

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            3. CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @return The address of the Cygnus Borrow contract for this collateral
     */
    function borrowable() external view returns (address);

    /**
     *  @return The current max debt ratio for this shuttle, after which positions become liquidatable
     */
    function debtRatio() external view returns (uint256);

    /**
     *  @return The current liquidation incentive for this shuttle
     */
    function liquidationIncentive() external view returns (uint256);

    /**
     *  @return The current liquidation fee the protocol keeps from each liquidation
     */
    function liquidationFee() external view returns (uint256);

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            5. NON-CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @notice Admin 👽
     *  @notice Updates the shuttle's debt ratio
     *  @param  newDebtRatio The new max debt ratio at which positions become liquidatable.
     *  @custom:security only-admin
     */
    function setDebtRatio(uint256 newDebtRatio) external;

    /**
     *  @notice Admin 👽
     *  @notice Updates the liquidation bonus for liquidators
     *  @param  newLiquidationIncentive The new incentive that the liquidators receive for liquidating positions.
     *  @custom:security only-admin
     */
    function setLiquidationIncentive(uint256 newLiquidationIncentive) external;

    /**
     *  @notice Admin 👽
     *  @notice Updates the liquidation fee that the protocol keeps for every liquidation
     *  @param newLiquidationFee The new fee that the protocol keeps from every liquidation.
     *  @custom:security only-admin
     */
    function setLiquidationFee(uint256 newLiquidationFee) external;
}

File 20 of 32 : ICygnusCollateralModel.sol
//  SPDX-License-Identifier: AGPL-3.0-or-later
//
//  ICygnusCollateralModel.sol
//
//  Copyright (C) 2023 CygnusDAO
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
pragma solidity >=0.8.17;

// Dependencies
import {ICygnusCollateralControl} from "./ICygnusCollateralControl.sol";

/**
 *  @title ICygnusCollateralModel The contract that implements the collateralization model
 */
interface ICygnusCollateralModel is ICygnusCollateralControl {
    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            1. CUSTOM ERRORS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @dev Reverts when the borrower is the zero address or this collateral
     *  @custom:error InvalidBorrower
     */
    error CygnusCollateralModel__InvalidBorrower();

    /**
     *  @dev Reverts when the price returned from the oracle is 0
     *  @custom:error PriceCantBeZero
     */
    error CygnusCollateralModel__PriceCantBeZero();

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            4. NON-CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ─────────────────────────────────────────────── Public ────────────────────────────────────────────────  */

    /**
     *  @notice Checks if the given user is able to redeem the specified amount of LP tokens.
     *  @param borrower The address of the user to check.
     *  @param redeemAmount The amount of LP tokens to be redeemed.
     *  @return True if the user can redeem, false otherwise.
     *
     */
    function canRedeem(address borrower, uint256 redeemAmount) external view returns (bool);

    /**
     *  @notice Get the price of 1 amount of the underlying in stablecoins. Note: It returns the price in the borrowable`s
     *          decimals. ie If USDC, returns price in 6 deicmals, if DAI/BUSD in 18
     *  @notice Calls the oracle to return the price of 1 unit of the underlying LP Token of this shuttle
     *  @return The price of 1 LP Token denominated in the Borrowable's underlying stablecoin
     */
    function getLPTokenPrice() external view returns (uint256);

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @notice Gets an account's liquidity or shortfall
     *  @param borrower The address of the borrower
     *  @return liquidity The account's liquidity denominated in the borrowable's underlying stablecoin.
     *  @return shortfall The account's shortfall denominated in the borrowable's underlying stablecoin. If positive 
     *                    then the account can be liquidated.
     */
    function getAccountLiquidity(address borrower) external view returns (uint256 liquidity, uint256 shortfall);

    /**
     *  @notice Check if a borrower can borrow a specified amount of stablecoins from the borrowable contract.
     *  @param borrower The address of the borrower
     *  @param borrowAmount The amount of stablecoins that borrower wants to borrow.
     *  @return A boolean indicating whether the borrower can borrow the specified amount
     */
    function canBorrow(address borrower, uint256 borrowAmount) external view returns (bool);

    /**
     *  @notice Quick view function to get a borrower's latest position
     *  @param borrower The address of the borrower
     *  @return lpBalance The borrower`s position in LP Tokens
     *  @return positionUsd The borrower's position in USD (ie. CygLP Balance * Exchange Rate * LP Token Price)
     *  @return health The user's current loan health (once it reaches 100% the user becomes liquidatable)
     */
    function getBorrowerPosition(address borrower) external view returns (uint256 lpBalance, uint256 positionUsd, uint256 health);
}

File 21 of 32 : ICygnusCollateralVoid.sol
//  SPDX-License-Identifier: AGPL-3.0-or-later
//
//  ICygnusCollateralVoid.sol
//
//  Copyright (C) 2023 CygnusDAO
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
pragma solidity >=0.8.17;

// Dependencies
import {ICygnusCollateralModel} from "./ICygnusCollateralModel.sol";

/**
 *  @title ICygnusCollateralVoid
 *  @notice Interface for `CygnusCollateralVoid` which is in charge of connecting the collateral LP Tokens with
 *          a specified strategy (for example connect to a rewarder contract to stake the LP Token, etc.)
 */
interface ICygnusCollateralVoid is ICygnusCollateralModel {
    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            1. CUSTOM ERRORS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @dev Reverts if msg.sender is not the harvester
     *
     *  @custom:error OnlyHarvesterAllowed
     */
    error CygnusCollateralVoid__OnlyHarvesterAllowed();

    /**
     *  @dev Reverts if the token we are sweeping is the underlying LP
     *
     *  @custom:error TokenIsUnderlying
     */
    error CygnusCollateralVoid__TokenIsUnderlying();

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            2. CUSTOM EVENTS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @dev Logs when the strategy is first initialized or re-approves contracts
     *
     *  @param underlying The address of the underlying stablecoin
     *  @param shuttleId The unique ID of the lending pool
     *  @param whitelisted The contract we approved to use our underlying
     *
     *  @custom:event ChargeVoid
     */
    event ChargeVoid(address underlying, uint256 shuttleId, address whitelisted);

    /**
     *  @dev Logs when rewards are harvested
     *
     *  @param sender The address of the caller who harvested the rewards
     *  @param tokens Total reward tokens harvested
     *  @param amounts Amounts of reward tokens harvested
     *  @param timestamp The timestamp of the harvest
     *
     *  @custom:event RechargeVoid
     */
    event RechargeVoid(address indexed sender, address[] tokens, uint256[] amounts, uint256 timestamp);

    /**
     *  @dev Logs when admin sets a new harvester to reinvest rewards
     *
     *  @param oldHarvester The address of the old harvester
     *  @param newHarvester The address of the new harvester
     *  @param rewardTokens The reward tokens added for the new harvester
     *
     *  @custom:event NewHarvester
     */
    event NewHarvester(address oldHarvester, address newHarvester, address[] rewardTokens);

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            3. CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ─────────────────────────────────────────────── Public ────────────────────────────────────────────────  */

    /**
     *  @return harvester The address of the harvester contract
     */
    function harvester() external view returns (address);

    /**
     *  @return lastHarvest Timestamp of the last harvest performed by the harvester contract
     */
    function lastHarvest() external view returns (uint256);

    /**
     *  @notice Array of reward tokens for this pool
     *  @param index The index of the token in the array
     *  @return rewardToken The reward token
     */
    function allRewardTokens(uint256 index) external view returns (address rewardToken);

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @return rewarder The address of the rewarder contract
     */
    function rewarder() external view returns (address);

    /**
     *  @return rewardTokensLength Length of reward tokens
     */
    function rewardTokensLength() external view returns (uint256);

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            4. NON-CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @notice Get the pending rewards manually - helpful to get rewards through static calls
     *
     *  @return tokens The addresses of the reward tokens earned by harvesting rewards
     *  @return amounts The amounts of each token received
     *
     *  @custom:security non-reentrant
     */
    function getRewards(
        address[] calldata _tokens,
        uint256[] calldata _amounts,
        bytes32[][] calldata _proofs
    ) external returns (address[] memory tokens, uint256[] memory amounts);

    /**
     *  @notice Only the harvester can reinvest
     *  @notice Reinvests all rewards from the rewarder to buy more LP to then deposit back into the rewarder
     *          This makes underlying LP balance increase in this contract, increasing the exchangeRate between
     *          CygLP and underlying and thus lowering debt ratio for all borrwers in the pool as they own more LP.
     *
     *  @custom:security non-reentrant only-harvester
     */
    function reinvestRewards_y7b(uint256 liquidity) external;

    /**
     *  @notice Admin 👽
     *  @notice Charges approvals needed for deposits and withdrawals, and any other function
     *          needed to get the vault started. ie, setting a pool ID from a MasterChef, a gauge, etc.
     *
     *  @custom:security only-admin
     */
    function chargeVoid() external;

    /**
     *  @notice Admin 👽
     *  @notice Sets the harvester address to harvest and reinvest rewards into more underlying
     *
     *  @param _harvester The address of the new harvester contract
     *
     *  @custom:security only-admin
     */
    function setHarvester(address _harvester, address[] calldata rewardTokens) external;

    /**
     *  @notice Admin 👽
     *  @notice Sweeps a token that was sent to this address by mistake, or a bonus reward token we are not tracking. Cannot
     *          sweep the underlying LP
     *
     *  @custom:security only-admin
     */
    function sweepToken(address token, address to) external;
}

File 22 of 32 : ICygnusNebula.sol
//  SPDX-License-Identifier: AGPL-3.0-or-later
//
//  CygnusNebulaOracle.sol
//
//  Copyright (C) 2023 CygnusDAO
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
pragma solidity ^0.8.17;

// Interfaces
import {AggregatorV3Interface} from "./AggregatorV3Interface.sol";
import {IERC20} from "./IERC20.sol";

/**
 *  @title ICygnusNebula Interface to interact with Cygnus' LP Oracle
 *  @author CygnusDAO
 */
interface ICygnusNebula {
    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            1. CUSTOM ERRORS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @dev Reverts when attempting to initialize an already initialized LP Token
     *
     *  @param lpTokenPair The address of the LP Token we are initializing
     *
     *  @custom:error PairIsInitialized
     */
    error CygnusNebulaOracle__PairAlreadyInitialized(address lpTokenPair);

    /**
     *  @dev Reverts when attempting to get the price of an LP Token that is not initialized
     *
     *  @param lpTokenPair THe address of the LP Token we are getting the price for
     *
     *  @custom:error PairNotInitialized
     */
    error CygnusNebulaOracle__PairNotInitialized(address lpTokenPair);

    /**
     *  @dev Reverts when attempting to access admin only methods
     *
     *  @param sender The address of msg.sender
     *
     *  @custom:error MsgSenderNotAdmin
     */
    error CygnusNebulaOracle__MsgSenderNotAdmin(address sender);

    /**
     *  @dev Reverts when attempting to set the admin if the pending admin is the zero address
     *
     *  @param pendingAdmin The address of the pending oracle admin
     *
     *  @custom:error AdminCantBeZero
     */
    error CygnusNebulaOracle__AdminCantBeZero(address pendingAdmin);

    /**
     *  @dev Reverts when attempting to set the same pending admin twice
     *
     *  @param pendingAdmin The address of the pending oracle admin
     *
     *  @custom:error PendingAdminAlreadySet
     */
    error CygnusNebulaOracle__PendingAdminAlreadySet(address pendingAdmin);

    /**
     *  @dev Reverts when getting a record if not initialized
     *
     *  @param lpTokenPair The address of the LP Token for the record
     *
     *  @custom:error NebulaRecordNotInitialized
     */
    error CygnusNebulaOracle__NebulaRecordNotInitialized(address lpTokenPair);

    /**
     *  @dev Reverts when re-initializing a record
     *
     *  @param lpTokenPair The address of the LP Token for the record
     *
     *  @custom:error NebulaRecordAlreadyInitialized
     */
    error CygnusNebulaOracle__NebulaRecordAlreadyInitialized(address lpTokenPair);

    /**
     *  @dev Reverts when the price of an initialized `lpTokenPair` is 0
     *
     *  @param lpTokenPair The address of the LP Token for the record
     *
     *  @custom:error NebulaRecordAlreadyInitialized
     */
    error CygnusNebulaOracle__PriceCantBeZero(address lpTokenPair);

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            2. CUSTOM EVENTS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @dev Logs when an LP Token pair's price starts being tracked
     *
     *  @param initialized Whether or not the LP Token is initialized
     *  @param oracleId The ID for this oracle
     *  @param lpTokenPair The address of the LP Token
     *  @param poolTokens The addresses of the tokens for this LP Token
     *  @param poolTokensDecimals The decimals of each pool token
     *  @param priceFeeds The addresses of the price feeds for the tokens
     *  @param priceFeedsDecimals The decimals of each price feed
     *
     *  @custom:event InitializeCygnusNebula
     */
    event InitializeNebulaOracle(
        bool initialized,
        uint88 oracleId,
        address lpTokenPair,
        IERC20[] poolTokens,
        uint256[] poolTokensDecimals,
        AggregatorV3Interface[] priceFeeds,
        uint256[] priceFeedsDecimals
    );

    /**
     *  @dev Logs when a new pending admin is set, to be accepted by admin
     *
     *  @param oracleCurrentAdmin The address of the current oracle admin
     *  @param oraclePendingAdmin The address of the pending oracle admin
     *
     *  @custom:event NewNebulaPendingAdmin
     */
    event NewOraclePendingAdmin(address oracleCurrentAdmin, address oraclePendingAdmin);

    /**
     *  @dev Logs when the pending admin is confirmed as the new oracle admin
     *
     *  @param oracleOldAdmin The address of the old oracle admin
     *  @param oracleNewAdmin The address of the new oracle admin
     *
     *  @custom:event NewNebulaAdmin
     */
    event NewOracleAdmin(address oracleOldAdmin, address oracleNewAdmin);

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            3. CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── Internal ───────────────────────────────────────────────  */

    /**
     *  @notice The struct record of each oracle used by Cygnus
     *  @custom:member initialized Whether an LP Token is being tracked or not
     *  @custom:member oracleId The ID of the LP Token tracked by the oracle
     *  @custom:member name User friendly name of the underlying
     *  @custom:member underlying The address of the LP Token
     *  @custom:member poolTokens Array of all the pool tokens
     *  @custom:member poolTokensDecimals Array of the decimals of each pool token
     *  @custom:member priceFeeds Array of all the Chainlink price feeds for the pool tokens
     *  @custom:member priceFeedsDecimals Array of the decimals of each price feed
     */
    struct NebulaOracle {
        bool initialized;
        uint88 oracleId;
        string name;
        address underlying;
        IERC20[] poolTokens;
        uint256[] poolTokensDecimals;
        AggregatorV3Interface[] priceFeeds;
        uint256[] priceFeedsDecimals;
    }

    /*  ─────────────────────────────────────────────── Public ────────────────────────────────────────────────  */

    /**
     *  @notice Returns the struct record of each oracle used by Cygnus
     *
     *  @param lpTokenPair The address of the LP Token
     *  @return nebulaOracle Struct of the oracle for the LP Token
     */
    function getNebulaOracle(address lpTokenPair) external view returns (NebulaOracle memory nebulaOracle);

    /**
     *  @notice Gets the address of the LP Token that (if) is being tracked by this oracle
     *
     *  @param id The ID of each LP Token that is being tracked by this oracle
     *  @return The address of the LP Token if it is being tracked by this oracle, else returns address zero
     */
    function allNebulas(uint256 id) external view returns (address);

    /**
     *  @return The name for this Cygnus-Chainlink Nebula oracle
     */
    function name() external view returns (string memory);

    /**
     *  @return The address of the Cygnus admin
     */
    function admin() external view returns (address);

    /**
     *  @return The address of the new requested admin
     */
    function pendingAdmin() external view returns (address);

    /**
     *  @return The version of this oracle
     */
    function version() external view returns (string memory);

    /**
     *  @return SECONDS_PER_YEAR The number of seconds in year assumed by the oracle
     */
    function SECONDS_PER_YEAR() external view returns (uint256);

    /**
     *  @notice We use a constant to set the chainlink aggregator decimals. As stated by chainlink all decimals for tokens
     *          denominated in USD are 8 decimals. And all decimals for tokens denominated in ETH are 18 decimals. We use
     *          tokens denominated in USD, so we set the constant to 8 decimals.
     *  @return AGGREGATOR_DECIMALS The decimals used by Chainlink (8 for all tokens priced in USD, 18 for priced in ETH)
     */
    function AGGREGATOR_DECIMALS() external pure returns (uint256);

    /**
     *  @return The scalar used to price the token in 18 decimals (ie. 10 ** (18 - AGGREGATOR_DECIMALS))
     */
    function AGGREGATOR_SCALAR() external pure returns (uint256);

    /**
     *  @return How many LP Token pairs' prices are being tracked by this oracle
     */
    function nebulaSize() external view returns (uint88);

    /**
     *  @return The denomination token this oracle returns the price in
     */
    function denominationToken() external view returns (IERC20);

    /**
     *  @return The decimals for this Cygnus-Chainlink Nebula oracle
     */
    function decimals() external view returns (uint8);

    /**
     *  @return The address of Chainlink's denomination oracle
     */
    function denominationAggregator() external view returns (AggregatorV3Interface);

    /**
     *  @return nebulaRegistry The address of the nebula registry
     */
    function nebulaRegistry() external view returns (address);

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @return The price of the denomination token in oracle decimals
     */
    function denominationTokenPrice() external view returns (uint256);

    /**
     *  @notice Get the APR given 2 exchange rates and the time elapsed between them. This is helpful for tokens
     *          that meet x*y=k such as UniswapV2 since exchange rates should never decrease (else LPs lose cash).
     *          Uses the natural log to avoid overflowing when we annualize the log difference.
     *
     *  @param exchangeRateLast The previous exchange rate
     *  @param exchangeRateNow The current exchange rate
     *  @param timeElapsed Time elapsed between the exchange rates
     *  @return apr The estimated base rate (APR excluding any token rewards)
     */
    function getAnnualizedBaseRate(
        uint256 exchangeRateLast,
        uint256 exchangeRateNow,
        uint256 timeElapsed
    ) external pure returns (uint256 apr);

    /**
     *  @notice Gets the latest price of the LP Token denominated in denomination token
     *  @notice LP Token pair must be initialized, else reverts with custom error
     *
     *  @param lpTokenPair The address of the LP Token
     *  @return lpTokenPrice The price of the LP Token denominated in denomination token
     */
    function lpTokenPriceUsd(address lpTokenPair) external view returns (uint256 lpTokenPrice);

    /**
     *  @notice Gets the latest price of the LP Token's token0 and token1 denominated in denomination token
     *  @notice Used by Cygnus Altair contract to calculate optimal amount of leverage
     *
     *  @param lpTokenPair The address of the LP Token
     *  @return Array of the LP's asset prices
     */
    function assetPricesUsd(address lpTokenPair) external view returns (uint256[] memory);

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            4. NON-CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @notice Admin 👽
     *  @notice Initialize an LP Token pair, only admin
     *
     *  @param lpTokenPair The contract address of the LP Token
     *  @param aggregators Array of Chainlink aggregators for this LP token's tokens
     *
     *  @custom:security non-reentrant only-admin
     */
    function initializeNebulaOracle(address lpTokenPair, AggregatorV3Interface[] calldata aggregators) external;
}

File 23 of 32 : ICygnusTerminal.sol
//  SPDX-License-Identifier: AGPL-3.0-or-later
//
//  ICygnusTerminal.sol
//
//  Copyright (C) 2023 CygnusDAO
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
pragma solidity >=0.8.17;

// Dependencies
import {IERC20Permit} from "./IERC20Permit.sol";

// Interfaces
import {IHangar18} from "./IHangar18.sol";
import {IAllowanceTransfer} from "./IAllowanceTransfer.sol";
import {ICygnusNebula} from "./ICygnusNebula.sol";

/**
 *  @title ICygnusTerminal
 *  @notice The interface to mint/redeem pool tokens (CygLP and CygUSD)
 */
interface ICygnusTerminal is IERC20Permit {
    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            1. CUSTOM ERRORS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @dev Reverts when attempting to mint zero shares
     *  @custom:error CantMintZeroShares
     */
    error CygnusTerminal__CantMintZeroShares();

    /**
     *  @dev Reverts when attempting to redeem zero assets
     *  @custom:error CantBurnZeroAssets
     */
    error CygnusTerminal__CantRedeemZeroAssets();

    /**
     *  @dev Reverts when attempting to call Admin-only functions
     *  @custom:error MsgSenderNotAdmin
     */
    error CygnusTerminal__MsgSenderNotAdmin();

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            2. CUSTOM EVENTS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @dev Logs when totalBalance syncs with the underlying contract's balanceOf.
     *  @param totalBalance Total balance in terms of the underlying
     *  @custom:event Sync
     */
    event Sync(uint160 totalBalance);

    /**
     *  @dev Logs when CygLP or CygUSD pool tokens are minted
     *  @param sender The address of `CygnusAltair` or the sender of the function call
     *  @param recipient Address of the minter
     *  @param assets Amount of assets being deposited
     *  @param shares Amount of pool tokens being minted
     *  @custom:event Mint
     */
    event Deposit(address indexed sender, address indexed recipient, uint256 assets, uint256 shares);

    /**
     *  @dev Logs when CygLP or CygUSD are redeemed
     *  @param sender The address of the redeemer of the shares
     *  @param recipient The address of the recipient of assets
     *  @param owner The address of the owner of the pool tokens
     *  @param assets The amount of assets to redeem
     *  @param shares The amount of pool tokens burnt
     *  @custom:event Redeem
     */
    event Withdraw(address indexed sender, address indexed recipient, address indexed owner, uint256 assets, uint256 shares);

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
           3. CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ─────────────────────────────────────────────── Public ────────────────────────────────────────────────  */

    /**
     *  @return Address of the Permit2 router on this chain. We use the AllowanceTransfer instead of SignatureTransfer 
     *          to allow deposits from other smart contracts.
     *        
     */
    function PERMIT2() external view returns (IAllowanceTransfer);

    /**
     *  @return The address of the Cygnus Factory contract used to deploy this shuttle
     */
    function hangar18() external view returns (IHangar18);

    /**
     *  @return The address of the underlying asset (stablecoin for Borrowable, LP Token for collateral)
     */
    function underlying() external view returns (address);

    /**
     *  @return The address of the oracle for this lending pool
     */
    function nebula() external view returns (ICygnusNebula);

    /**
     *  @return The unique ID of the lending pool, shared by Borrowable and Collateral
     */
    function shuttleId() external view returns (uint256);

    /**
     *  @return Total available cash deposited in the strategy (stablecoin for Borrowable, LP Token for collateral)
     */
    function totalBalance() external view returns (uint160);

    /**
     *  @return The total assets owned by the vault. Same as total balance, but includes total borrows for Borrowable.
     */
    function totalAssets() external view returns (uint256);

    /**
     *  @return The exchange rate between 1 vault share (CygUSD/CygLP) and the underlying asset
     */
    function exchangeRate() external view returns (uint256);

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            4. NON-CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @notice Deposits the underlying asset into the vault (stablecoins for borrowable, LP tokens for collateral).
     *          Users must approve the Permit2 router in the underlying before depositing. Users can bypass
     *          the permit and signature arguments by also approving the vault contract in the Permit2 router
     *          and pass an empty permit and signature.
     *  @param assets Amount of the underlying asset to deposit.
     *  @param recipient Address that will receive the corresponding amount of shares.
     *  @param _permit Data signed over by the owner specifying the terms of approval
     *  @param _signature The owner's signature over the permit data
     *  @return shares Amount of Cygnus Vault shares minted and transferred to the `recipient`.
     *  @custom:security non-reentrant
     */
    function deposit(
        uint256 assets,
        address recipient,
        IAllowanceTransfer.PermitSingle calldata _permit,
        bytes calldata _signature
    ) external returns (uint256 shares);

    /**
     *  @notice Redeems vault shares and transfers out assets (stablecoins for borrowable, LP tokens for collateral).
     *  @param shares The number of shares to redeem for the underlying asset.
     *  @param recipient The address that will receive the underlying asset.
     *  @param owner The address that owns the shares.
     *  @return assets The amount of underlying assets received by the `recipient`.
     *  @custom:security non-reentrant
     */
    function redeem(uint256 shares, address recipient, address owner) external returns (uint256 assets);

    /**
     *  @notice Syncs `totalBalance` in terms of its underlying
     *  @custom:security non-reentrant
     */
    function sync() external;
}

File 24 of 32 : IDenebOrbiter.sol
//  SPDX-License-Identifier: AGPL-3.0-or-later
//
//  IDenebOrbiter.sol
//
//  Copyright (C) 2023 CygnusDAO
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
pragma solidity >=0.8.17;

/**
 *  @title ICygnusDeneb The interface for a contract that is capable of deploying Cygnus collateral pools
 *  @notice A contract that constructs a Cygnus collateral pool must implement this to pass arguments to the pool
 */
interface IDenebOrbiter {
    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            4. NON-CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @notice Passing the struct parameters to the collateral contract avoids setting constructor
     *  @return factory The address of the Cygnus factory
     *  @return underlying The address of the underlying LP Token
     *  @return borrowable The address of the Cygnus borrow contract for this collateral
     *  @return oracle The address of the oracle for this lending pool
     *  @return shuttleId The ID of the lending pool
     */
    function shuttleParameters()
        external
        returns (address factory, address underlying, address borrowable, address oracle, uint256 shuttleId);

    /**
     *  @return The init code hash of the collateral contract for this deployer
     */
    function collateralInitCodeHash() external view returns (bytes32);

    /**
     *  @notice Function to deploy the collateral contract of a lending pool
     *  @param underlying The address of the underlying LP Token
     *  @param borrowable The address of the Cygnus borrow contract for this collateral
     *  @param oracle The address of the oracle for this lending pool
     *  @param shuttleId The ID of the lending pool
     *  @return collateral The address of the new deployed Cygnus collateral contract
     */
    function deployDeneb(address underlying, address borrowable, address oracle, uint256 shuttleId) external returns (address collateral);
}

File 25 of 32 : IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity >=0.8.17;

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

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

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

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

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

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

    /**
     * @dev 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 26 of 32 : IERC20Permit.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity >=0.8.17;

import {IERC20} from "./IERC20.sol";

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
interface IERC20Permit is IERC20 {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     */
    function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

File 27 of 32 : IHangar18.sol
//  SPDX-License-Identifier: AGPL-3.0-or-later
//
//  IHangar18.sol
//
//  Copyright (C) 2023 CygnusDAO
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
pragma solidity >=0.8.17;

// Orbiters
import {IDenebOrbiter} from "./IDenebOrbiter.sol";
import {IAlbireoOrbiter} from "./IAlbireoOrbiter.sol";

// Oracles

/**
 *  @title The interface for the Cygnus Factory
 *  @notice The Cygnus factory facilitates creation of collateral and borrow pools
 */
interface IHangar18 {
    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            1. CUSTOM ERRORS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @dev Reverts when caller is not Admin
     *
     *  @param sender The address of the account that invoked the function and caused the error
     *  @param admin The address of the Admin that is allowed to perform the function
     *
     *  @custom:error CygnusAdminOnly
     */
    error Hangar18__CygnusAdminOnly(address sender, address admin);

    /**
     *  @dev Reverts when the orbiter pair already exists
     *
     *  @custom:error OrbitersAlreadySet
     */
    error Hangar18__OrbitersAlreadySet();

    /**
     *  @dev Reverts when trying to deploy a shuttle that already exists
     *
     *  @custom:error ShuttleAlreadyDeployed
     */
    error Hangar18__ShuttleAlreadyDeployed();

    /**
     *  @dev Reverts when deploying a shuttle with orbiters that are inactive or dont exist
     *
     *  @custom:error OrbitersAreInactive
     */
    error Hangar18__OrbitersAreInactive();

    /**
     *  @dev Reverts when predicted collateral address doesn't match with deployed
     *
     *  @custom:error CollateralAddressMismatch
     */
    error Hangar18__CollateralAddressMismatch();

    /**
     *  @dev Reverts when trying to deploy a shuttle with an unsupported LP Pair
     *
     *  @custom:error LiquidityTokenNotSupported
     */
    error Hangar18__LiquidityTokenNotSupported();

    /**
     *  @dev Reverts when the CYG rewarder contract is zero
     *
     *  @custom:error PillarsCantBeZero
     */
    error Hangar18__PillarsCantBeZero();

    /**
     *  @dev Reverts when the CYG rewarder contract is zero
     *
     *  @custom:error PillarsCantBeZero
     */
    error Hangar18__AltairCantBeZero();

    /**
     *  @dev Reverts when the oracle set is the same as the new one we are assigning
     *
     *  @param priceOracle The address of the existing price oracle
     *  @param newPriceOracle The address of the new price oracle that was attempted to be set
     *
     *  @custom:error CygnusNebulaAlreadySet
     */
    error Hangar18__CygnusNebulaAlreadySet(address priceOracle, address newPriceOracle);

    /**
     *  @dev Reverts when the admin is the same as the new one we are assigning
     *
     *  @custom:error AdminAlreadySet
     */
    error Hangar18__AdminAlreadySet();

    /**
     *  @dev Reverts when the pending admin is the same as the new one we are assigning
     *
     *  @param newPendingAdmin The address of the new pending admin
     *  @param pendingAdmin The address of the existing pending admin
     *
     *  @custom:error PendingAdminAlreadySet
     */
    error Hangar18__PendingAdminAlreadySet(address newPendingAdmin, address pendingAdmin);

    /**
     *  @dev Reverts when the pending dao reserves is already the dao reserves
     *
     *  @custom:error DaoReservesAlreadySet
     */
    error Hangar18__DaoReservesAlreadySet();

    /**
     *  @dev Reverts when the pending address is the same as the new pending
     *
     *  @custom:error PendingDaoReservesAlreadySet
     */
    error Hangar18__PendingDaoReservesAlreadySet();

    /**
     *  @dev Reverts when msg.sender is not the pending admin
     *
     *  @custom:error SenderNotPendingAdmin
     */
    error Hangar18__SenderNotPendingAdmin();

    /**
     *  @dev Reverts when pending reserves contract address is the zero address
     *
     *  @custom:error DaoReservesCantBeZero
     */
    error Hangar18__DaoReservesCantBeZero();

    /**
     *  @dev Reverts when setting a new vault as the 0 address
     *
     *  @custom:error X1VaultCantBeZero
     */
    error Hangar18__X1VaultCantBeZero();

    /**
     *  @dev Reverts when deploying a pool with an inactive orbiter
     *
     *  @custom:error OrbiterInactive
     */
    error Hangar18__OrbiterInactive();

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            2. CUSTOM EVENTS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /**
     *  @dev Logs when a new lending pool is launched
     *
     *  @param lpTokenPair The address of the LP Token pair
     *  @param orbiterId The ID of the orbiter used to deploy this lending pool
     *  @param shuttleId The ID of the lending pool
     *  @param borrowable The address of the Cygnus borrow contract
     *  @param collateral The address of the Cygnus collateral contract
     *
     *  @custom:event NewShuttle
     */
    event NewShuttle(address indexed lpTokenPair, uint256 indexed orbiterId, uint256 shuttleId, address borrowable, address collateral);

    /**
     *  @dev Logs when a new Cygnus admin is requested
     *
     *  @param pendingAdmin Address of the requested admin
     *  @param _admin Address of the present admin
     *
     *  @custom:event NewPendingCygnusAdmin
     */
    event NewPendingCygnusAdmin(address pendingAdmin, address _admin);

    /**
     *  @dev Logs when a new Cygnus admin is confirmed
     *
     *  @param oldAdmin Address of the old admin
     *  @param newAdmin Address of the new confirmed admin
     *
     *  @custom:event NewCygnusAdmin
     */
    event NewCygnusAdmin(address oldAdmin, address newAdmin);

    /**
     *  @dev Logs when a new implementation contract is requested
     *
     *  @param oldPendingdaoReservesContract Address of the current `daoReserves` contract
     *  @param newPendingdaoReservesContract Address of the requested new `daoReserves` contract
     *
     *  @custom:event NewPendingDaoReserves
     */
    event NewPendingDaoReserves(address oldPendingdaoReservesContract, address newPendingdaoReservesContract);

    /**
     *  @dev Logs when a new implementation contract is confirmed
     *
     *  @param oldDaoReserves Address of old `daoReserves` contract
     *  @param daoReserves Address of the new confirmed `daoReserves` contract
     *
     *  @custom:event NewDaoReserves
     */
    event NewDaoReserves(address oldDaoReserves, address daoReserves);

    /**
     *  @dev Logs when a new pillars is confirmed
     *
     *  @param oldPillars Address of old `pillars` contract
     *  @param newPillars Address of the new pillars contract
     *
     *  @custom:event NewPillarsOfCreation
     */
    event NewPillarsOfCreation(address oldPillars, address newPillars);

    /**
     *  @dev Logs when a new router is confirmed
     *
     *  @param oldRouter Address of the old base router contract
     *  @param newRouter Address of the new router contract
     *
     *  @custom:event NewAltairRouter
     */
    event NewAltairRouter(address oldRouter, address newRouter);

    /**
     *  @dev Logs when orbiters are initialized in the factory
     *
     *  @param status Whether or not these orbiters are active and usable
     *  @param orbitersLength How many orbiter pairs we have (equals the amount of Dexes cygnus is using)
     *  @param borrowOrbiter The address of the borrow orbiter for this dex
     *  @param denebOrbiter The address of the collateral orbiter for this dex
     *  @param orbitersName The name of the dex for these orbiters
     *  @param uniqueHash The keccack256 hash of the collateral init code hash and borrowable init code hash
     *
     *  @custom:event InitializeOrbiters
     */
    event InitializeOrbiters(
        bool status,
        uint256 orbitersLength,
        IAlbireoOrbiter borrowOrbiter,
        IDenebOrbiter denebOrbiter,
        bytes32 uniqueHash,
        string orbitersName
    );

    /**
     *  @dev Logs when admins switch orbiters off for future deployments
     *
     *  @param status Bool representing whether or not these orbiters are usable
     *  @param orbiterId The ID of the collateral & borrow orbiters
     *  @param albireoOrbiter The address of the deleted borrow orbiter
     *  @param denebOrbiter The address of the deleted collateral orbiter
     *  @param orbiterName The name of the dex these orbiters were for
     *
     *  @custom:event SwitchOrbiterStatus
     */
    event SwitchOrbiterStatus(
        bool status,
        uint256 orbiterId,
        IAlbireoOrbiter albireoOrbiter,
        IDenebOrbiter denebOrbiter,
        string orbiterName
    );

    /**
     *  @dev Logs when a new vault is set which accumulates rewards from lending pools
     *
     *  @param oldVault The address of the old vault
     *  @param newVault The address of the new vault
     *
     *  @custom:event NewX1Vault
     */
    event NewX1Vault(address oldVault, address newVault);

    /**
     *  @dev Logs when an owner allows or disallows spender to borrow on their behalf
     *
     *  @param owner The address of msg.sender (owner of the CygLP)
     *  @param spender The address of the user the owner is allowing/disallowing
     *  @param status Whether or not the spender can borrow after this transaction
     *
     *  @custom:event NewMasterBorrowApproval
     */
    event NewMasterBorrowApproval(address owner, address spender, bool status);

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            3. CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── Internal ───────────────────────────────────────────────  */

    /**
     * @custom:struct Official record of all collateral and borrow deployer contracts, unique per dex
     * @custom:member status Whether or not these orbiters are active and usable
     * @custom:member orbiterId The ID for this pair of orbiters
     * @custom:member albireoOrbiter The address of the borrow deployer contract
     * @custom:member denebOrbiter The address of the collateral deployer contract
     * @custom:member borrowableInitCodeHash The hash of the borrowable contract's initialization code
     * @custom:member collateralInitCodeHash The hash of the collateral contract's initialization code
     * @custom:member uniqueHash The unique hash of the orbiter
     * @custom:member orbiterName Huamn friendly name for the orbiters
     */
    struct Orbiter {
        bool status;
        uint88 orbiterId;
        IAlbireoOrbiter albireoOrbiter;
        IDenebOrbiter denebOrbiter;
        bytes32 borrowableInitCodeHash;
        bytes32 collateralInitCodeHash;
        bytes32 uniqueHash;
        string orbiterName;
    }

    /**
     *  @custom:struct Shuttle Official record of pools deployed by this factory
     *  @custom:member launched Whether or not the lending pool is initialized
     *  @custom:member shuttleId The ID of the lending pool
     *  @custom:member borrowable The address of the borrowing contract
     *  @custom:member collateral The address of the Cygnus collateral
     *  @custom:member orbiterId The ID of the orbiters used to deploy lending pool
     */
    struct Shuttle {
        bool launched;
        uint88 shuttleId;
        address borrowable;
        address collateral;
        uint96 orbiterId;
    }

    /*  ─────────────────────────────────────────────── Public ────────────────────────────────────────────────  */

    /**
     *  @notice Array of structs containing all orbiters deployed
     *  @param _orbiterId The ID of the orbiter pair
     *  @return status Whether or not these orbiters are active and usable
     *  @return orbiterId The ID for these orbiters (ideally should be 1 per dex)
     *  @return albireoOrbiter The address of the borrow deployer contract
     *  @return denebOrbiter The address of the collateral deployer contract
     *  @return borrowableInitCodeHash The init code hash of the borrowable
     *  @return collateralInitCodeHash The init code hash of the collateral
     *  @return uniqueHash The keccak256 hash of collateralInitCodeHash and borrowableInitCodeHash
     *  @return orbiterName The name of the dex
     */
    function allOrbiters(
        uint256 _orbiterId
    )
        external
        view
        returns (
            bool status,
            uint88 orbiterId,
            IAlbireoOrbiter albireoOrbiter,
            IDenebOrbiter denebOrbiter,
            bytes32 borrowableInitCodeHash,
            bytes32 collateralInitCodeHash,
            bytes32 uniqueHash,
            string memory orbiterName
        );

    /**
     *  @notice Array of LP Token pairs deployed
     *  @param _shuttleId The ID of the shuttle deployed
     *  @return launched Whether this pair exists or not
     *  @return shuttleId The ID of this shuttle
     *  @return borrowable The address of the borrow contract
     *  @return collateral The address of the collateral contract
     *  @return orbiterId The ID of the orbiters used to deploy this lending pool
     */
    function allShuttles(
        uint256 _shuttleId
    ) external view returns (bool launched, uint88 shuttleId, address borrowable, address collateral, uint96 orbiterId);

    /**
     *  @notice Checks if a pair of orbiters has been added to the Hangar
     *  @param orbiterHash The keccak hash of the creation code of each orbiter
     *  @return Whether this par of orbiters has been added or not
     */
    function orbitersExist(bytes32 orbiterHash) external view returns (bool);

    /**
     *  @notice Official record of all lending pools deployed
     *  @param _lpTokenPair The address of the LP Token
     *  @param _orbiterId The ID of the orbiter for this LP Token
     *  @return launched Whether this pair exists or not
     *  @return shuttleId The ID of this shuttle
     *  @return borrowable The address of the borrow contract
     *  @return collateral The address of the collateral contract
     *  @return orbiterId The ID of the orbiters used to deploy this lending pool
     */
    function getShuttles(
        address _lpTokenPair,
        uint256 _orbiterId
    ) external view returns (bool launched, uint88 shuttleId, address borrowable, address collateral, uint96 orbiterId);

    /**
     *  @return Human friendly name for this contract
     */
    function name() external view returns (string memory);

    /**
     *  @return The version of this contract
     */
    function version() external view returns (string memory);

    /**
     *  @return usd The address of the borrowable token (stablecoin)
     */
    function usd() external view returns (address);

    /**
     *  @return nativeToken The address of the chain's native token
     */
    function nativeToken() external view returns (address);

    /**
     *  @notice The address of the nebula registry on this chain
     */
    function nebulaRegistry() external view returns (address);

    /**
     *  @return admin The address of the Cygnus Admin which grants special permissions in collateral/borrow contracts
     */
    function admin() external view returns (address);

    /**
     *  @return pendingAdmin The address of the requested account to be the new Cygnus Admin
     */
    function pendingAdmin() external view returns (address);

    /**
     *  @return daoReserves The address that handles Cygnus reserves from all pools
     */
    function daoReserves() external view returns (address);

    /**
     *  @dev Returns the address of the CygnusDAO revenue vault.
     *  @return cygnusX1Vault The address of the CygnusDAO revenue vault.
     */
    function cygnusX1Vault() external view returns (address);

    /**
     *  @dev Returns the address of the CygnusDAO base router.
     *  @return cygnusAltair Latest address of the base router on this chain.
     */
    function cygnusAltair() external view returns (address);

    /**
     *  @dev Returns the address of the CYG rewarder
     *  @return cygnusPillars The address of the CYG rewarder on this chain
     */
    function cygnusPillars() external view returns (address);

    /**
     * @dev Returns the total number of orbiter pairs deployed (1 collateral + 1 borrow = 1 orbiter).
     * @return orbitersDeployed The total number of orbiter pairs deployed.
     */
    function orbitersDeployed() external view returns (uint256);

    /**
     *  @dev Returns the total number of shuttles deployed.
     *  @return shuttlesDeployed The total number of shuttles deployed.
     */
    function shuttlesDeployed() external view returns (uint256);

    /**
     *  @dev Returns the chain ID
     */
    function chainId() external view returns (uint256);

    /**
     *  @dev Returns the borrowable TVL (Total Value Locked) in USD for a specific shuttle.
     *  @param shuttleId The ID of the shuttle for which the borrowable TVL is requested.
     *  @return The borrowable TVL in USD for the specified shuttle.
     */
    function borrowableTvlUsd(uint256 shuttleId) external view returns (uint256);

    /**
     *  @dev Returns the collateral TVL (Total Value Locked) in USD for a specific shuttle.
     *  @param shuttleId The ID of the shuttle for which the collateral TVL is requested.
     *  @return The collateral TVL in USD for the specified shuttle.
     */
    function collateralTvlUsd(uint256 shuttleId) external view returns (uint256);

    /**
     *  @dev Returns the total TVL (Total Value Locked) in USD for a specific shuttle.
     *  @param shuttleId The ID of the shuttle for which the total TVL is requested.
     *  @return The total TVL in USD for the specified shuttle.
     */
    function shuttleTvlUsd(uint256 shuttleId) external view returns (uint256);

    /**
     *  @dev Returns the total borrowable TVL in USD for all shuttles.
     *  @return The total borrowable TVL in USD.
     */
    function allBorrowablesTvlUsd() external view returns (uint256);

    /**
     *  @dev Returns the total collateral TVL in USD for all shuttles.
     *  @return The total collateral TVL in USD.
     */
    function allCollateralsTvlUsd() external view returns (uint256);

    /**
     *  @dev Returns the USD value of the DAO Cyg USD reserves.
     *  @return The USD value of the DAO Cyg USD reserves.
     */
    function daoCygUsdReservesUsd() external view returns (uint256);

    /**
     *  @dev Returns the USD value of the DAO Cyg LP reserves.
     *  @return The USD value of the DAO Cyg LP reserves.
     */
    function daoCygLPReservesUsd() external view returns (uint256);

    /**
     *  @dev Returns the total USD value of CygnusDAO reserves.
     *  @return The total USD value of CygnusDAO reserves.
     */
    function cygnusTotalReservesUsd() external view returns (uint256);

    /**
     *  @dev Returns the total TVL in USD for CygnusDAO.
     *  @return The total TVL in USD for CygnusDAO.
     */
    function cygnusTvlUsd() external view returns (uint256);

    /**
     *  @dev Returns the total amount borrowed for all shuttles
     *  @return The total amount borrowed in USD.
     */
    function cygnusTotalBorrows() external view returns (uint256);

    /*  ═══════════════════════════════════════════════════════════════════════════════════════════════════════ 
            4. NON-CONSTANT FUNCTIONS
        ═══════════════════════════════════════════════════════════════════════════════════════════════════════  */

    /*  ────────────────────────────────────────────── External ───────────────────────────────────────────────  */

    /**
     *  @notice Admin 👽
     *  @notice Turns off orbiters making them not able for deployment of pools
     *
     *  @param orbiterId The ID of the orbiter pairs we want to switch the status of
     *
     *  @custom:security only-admin
     */
    function switchOrbiterStatus(uint256 orbiterId) external;

    /**
     *  @notice Admin 👽
     *  @notice Initializes both Borrow arms and the collateral arm
     *
     *  @param lpTokenPair The address of the underlying LP Token this pool is for
     *  @param orbiterId The ID of the orbiters we want to deploy to (= dex Id)
     *  @return borrowable The address of the Cygnus borrow contract for this pool
     *  @return collateral The address of the Cygnus collateral contract for both borrow tokens
     *
     *  @custom:security non-reentrant only-admin 👽
     */
    function deployShuttle(address lpTokenPair, uint256 orbiterId) external returns (address borrowable, address collateral);

    /**
     *  @notice Admin 👽
     *  @notice Sets the new orbiters to deploy collateral and borrow contracts and stores orbiters in storage
     *
     *  @param name The name of the strategy OR the dex these orbiters are for
     *  @param albireoOrbiter the address of this orbiter's borrow deployer
     *  @param denebOrbiter The address of this orbiter's collateral deployer
     *
     *  @custom:security non-reentrant only-admin
     */
    function initializeOrbiter(string memory name, IAlbireoOrbiter albireoOrbiter, IDenebOrbiter denebOrbiter) external;

    /**
     *  @notice Admin 👽
     *  @notice Sets a new pending admin for Cygnus
     *
     *  @param newCygnusAdmin Address of the requested Cygnus admin
     *
     *  @custom:security only-admin
     */
    function setPendingAdmin(address newCygnusAdmin) external;

    /**
     *  @notice Approves the pending admin and is the new Cygnus admin
     *
     *  @custom:security only-pending-admin
     */
    function acceptCygnusAdmin() external;

    /**
     *  @notice Admin 👽
     *  @notice Accepts the new implementation contract
     *
     *  @param newReserves The address of the new DAO reserves
     *
     *  @custom:security only-admin
     */
    function setDaoReserves(address newReserves) external;

    /**
     *  @notice Admin 👽
     *  @notice Sets the address of the new x1 vault which accumulates rewards over time
     *
     *  @custom:security only-admin
     */
    function setCygnusX1Vault(address newX1Vault) external;

    /**
     *  @notice Admin 👽
     *  @notice Sets the address of the new pillars of creation
     *
     *  @custom:security only-admin
     */
    function setCygnusPillars(address newPillars) external;

    /**
     *  @notice Admin 👽
     *  @notice Sets the address of the new base router
     *
     *  @custom:security only-admin
     */
    function setCygnusAltair(address newAltair) external;
}

File 28 of 32 : IOrbiter.sol
//  SPDX-License-Identifier: AGPL-3.0-or-later
//
//  IOrbiter.sol
//
//  Copyright (C) 2023 CygnusDAO
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
pragma solidity >=0.8.17;

import {IHangar18} from "./IHangar18.sol";
import {ICygnusNebula} from "./ICygnusNebula.sol";

/**
 *  @notice Interface used by core contracts to read variables from the deployers (AlbireoOrbiter.sol and
 *          DenebOrbiter.sol)
 */
interface IOrbiter {
    /**
     *  @notice Simple interface of both borrow/collateral orbiters that gets read during deployment of pools
     *          in the constructor of CygnusTerminal.sol
     *  @return factory    The address of the Cygnus factory-like contract, assigned to `hangar18`
     *  @return underlying The address of the underlying borrow token (stablecoin) or collateral token (LP Token)
     *  @return twinstar   The opposite contract to the one being deployed. IE. If collateral is being deployed,
     *                     then it is the address of the borrowable. If borrowable is being deployed, it is the
     *                     address of the collateral.
     *  @return oracle     The address of the oracle
     *  @return shuttleId  The lending pool ID
     */
    function shuttleParameters()
        external
        returns (IHangar18 factory, address underlying, address twinstar, ICygnusNebula oracle, uint256 shuttleId);
}

File 29 of 32 : FixedPointMathLib.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
library FixedPointMathLib {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The operation failed, as the output exceeds the maximum value of uint256.
    error ExpOverflow();

    /// @dev The operation failed, as the output exceeds the maximum value of uint256.
    error FactorialOverflow();

    /// @dev The operation failed, due to an multiplication overflow.
    error MulWadFailed();

    /// @dev The operation failed, either due to a
    /// multiplication overflow, or a division by a zero.
    error DivWadFailed();

    /// @dev The multiply-divide operation failed, either due to a
    /// multiplication overflow, or a division by a zero.
    error MulDivFailed();

    /// @dev The division failed, as the denominator is zero.
    error DivFailed();

    /// @dev The full precision multiply-divide operation failed, either due
    /// to the result being larger than 256 bits, or a division by a zero.
    error FullMulDivFailed();

    /// @dev The output is undefined, as the input is less-than-or-equal to zero.
    error LnWadUndefined();

    /// @dev The output is undefined, as the input is zero.
    error Log2Undefined();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The scalar of ETH and most ERC20s.
    uint256 internal constant WAD = 1e18;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*              SIMPLIFIED FIXED POINT OPERATIONS             */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Equivalent to `(x * y) / WAD` rounded down.
    function mulWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to `require(y == 0 || x <= type(uint256).max / y)`.
            if mul(y, gt(x, div(not(0), y))) {
                // Store the function selector of `MulWadFailed()`.
                mstore(0x00, 0xbac65e5b)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            z := div(mul(x, y), WAD)
        }
    }

    /// @dev Equivalent to `(x * y) / WAD` rounded up.
    function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to `require(y == 0 || x <= type(uint256).max / y)`.
            if mul(y, gt(x, div(not(0), y))) {
                // Store the function selector of `MulWadFailed()`.
                mstore(0x00, 0xbac65e5b)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            z := add(iszero(iszero(mod(mul(x, y), WAD))), div(mul(x, y), WAD))
        }
    }

    /// @dev Equivalent to `(x * WAD) / y` rounded down.
    function divWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to `require(y != 0 && (WAD == 0 || x <= type(uint256).max / WAD))`.
            if iszero(mul(y, iszero(mul(WAD, gt(x, div(not(0), WAD)))))) {
                // Store the function selector of `DivWadFailed()`.
                mstore(0x00, 0x7c5f487d)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            z := div(mul(x, WAD), y)
        }
    }

    /// @dev Equivalent to `(x * WAD) / y` rounded up.
    function divWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to `require(y != 0 && (WAD == 0 || x <= type(uint256).max / WAD))`.
            if iszero(mul(y, iszero(mul(WAD, gt(x, div(not(0), WAD)))))) {
                // Store the function selector of `DivWadFailed()`.
                mstore(0x00, 0x7c5f487d)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            z := add(iszero(iszero(mod(mul(x, WAD), y))), div(mul(x, WAD), y))
        }
    }

    /// @dev Equivalent to `x` to the power of `y`.
    /// because `x ** y = (e ** ln(x)) ** y = e ** (ln(x) * y)`.
    function powWad(int256 x, int256 y) internal pure returns (int256) {
        // Using `ln(x)` means `x` must be greater than 0.
        return expWad((lnWad(x) * y) / int256(WAD));
    }

    /// @dev Returns `exp(x)`, denominated in `WAD`.
    function expWad(int256 x) internal pure returns (int256 r) {
        unchecked {
            // When the result is < 0.5 we return zero. This happens when
            // x <= floor(log(0.5e18) * 1e18) ~ -42e18
            if (x <= -42139678854452767551) return r;

            /// @solidity memory-safe-assembly
            assembly {
                // When the result is > (2**255 - 1) / 1e18 we can not represent it as an
                // int. This happens when x >= floor(log((2**255 - 1) / 1e18) * 1e18) ~ 135.
                if iszero(slt(x, 135305999368893231589)) {
                    // Store the function selector of `ExpOverflow()`.
                    mstore(0x00, 0xa37bfec9)
                    // Revert with (offset, size).
                    revert(0x1c, 0x04)
                }
            }

            // x is now in the range (-42, 136) * 1e18. Convert to (-42, 136) * 2**96
            // for more intermediate precision and a binary basis. This base conversion
            // is a multiplication by 1e18 / 2**96 = 5**18 / 2**78.
            x = (x << 78) / 5 ** 18;

            // Reduce range of x to (-½ ln 2, ½ ln 2) * 2**96 by factoring out powers
            // of two such that exp(x) = exp(x') * 2**k, where k is an integer.
            // Solving this gives k = round(x / log(2)) and x' = x - k * log(2).
            int256 k = ((x << 96) / 54916777467707473351141471128 + 2 ** 95) >> 96;
            x = x - k * 54916777467707473351141471128;

            // k is in the range [-61, 195].

            // Evaluate using a (6, 7)-term rational approximation.
            // p is made monic, we'll multiply by a scale factor later.
            int256 y = x + 1346386616545796478920950773328;
            y = ((y * x) >> 96) + 57155421227552351082224309758442;
            int256 p = y + x - 94201549194550492254356042504812;
            p = ((p * y) >> 96) + 28719021644029726153956944680412240;
            p = p * x + (4385272521454847904659076985693276 << 96);

            // We leave p in 2**192 basis so we don't need to scale it back up for the division.
            int256 q = x - 2855989394907223263936484059900;
            q = ((q * x) >> 96) + 50020603652535783019961831881945;
            q = ((q * x) >> 96) - 533845033583426703283633433725380;
            q = ((q * x) >> 96) + 3604857256930695427073651918091429;
            q = ((q * x) >> 96) - 14423608567350463180887372962807573;
            q = ((q * x) >> 96) + 26449188498355588339934803723976023;

            /// @solidity memory-safe-assembly
            assembly {
                // Div in assembly because solidity adds a zero check despite the unchecked.
                // The q polynomial won't have zeros in the domain as all its roots are complex.
                // No scaling is necessary because p is already 2**96 too large.
                r := sdiv(p, q)
            }

            // r should be in the range (0.09, 0.25) * 2**96.

            // We now need to multiply r by:
            // * the scale factor s = ~6.031367120.
            // * the 2**k factor from the range reduction.
            // * the 1e18 / 2**96 factor for base conversion.
            // We do this all at once, with an intermediate result in 2**213
            // basis, so the final right shift is always by a positive amount.
            r = int256((uint256(r) * 3822833074963236453042738258902158003155416615667) >> uint256(195 - k));
        }
    }

    /// @dev Returns `ln(x)`, denominated in `WAD`.
    function lnWad(int256 x) internal pure returns (int256 r) {
        unchecked {
            /// @solidity memory-safe-assembly
            assembly {
                if iszero(sgt(x, 0)) {
                    // Store the function selector of `LnWadUndefined()`.
                    mstore(0x00, 0x1615e638)
                    // Revert with (offset, size).
                    revert(0x1c, 0x04)
                }
            }

            // We want to convert x from 10**18 fixed point to 2**96 fixed point.
            // We do this by multiplying by 2**96 / 10**18. But since
            // ln(x * C) = ln(x) + ln(C), we can simply do nothing here
            // and add ln(2**96 / 10**18) at the end.

            // Compute k = log2(x) - 96.
            int256 k;
            /// @solidity memory-safe-assembly
            assembly {
                let v := x
                k := shl(7, lt(0xffffffffffffffffffffffffffffffff, v))
                k := or(k, shl(6, lt(0xffffffffffffffff, shr(k, v))))
                k := or(k, shl(5, lt(0xffffffff, shr(k, v))))

                // For the remaining 32 bits, use a De Bruijn lookup.
                // See: https://graphics.stanford.edu/~seander/bithacks.html
                v := shr(k, v)
                v := or(v, shr(1, v))
                v := or(v, shr(2, v))
                v := or(v, shr(4, v))
                v := or(v, shr(8, v))
                v := or(v, shr(16, v))

                // forgefmt: disable-next-item
                k := sub(
                    or(k, byte(shr(251, mul(v, shl(224, 0x07c4acdd))), 0x0009010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f)),
                    96
                )
            }

            // Reduce range of x to (1, 2) * 2**96
            // ln(2^k * x) = k * ln(2) + ln(x)
            x <<= uint256(159 - k);
            x = int256(uint256(x) >> 159);

            // Evaluate using a (8, 8)-term rational approximation.
            // p is made monic, we will multiply by a scale factor later.
            int256 p = x + 3273285459638523848632254066296;
            p = ((p * x) >> 96) + 24828157081833163892658089445524;
            p = ((p * x) >> 96) + 43456485725739037958740375743393;
            p = ((p * x) >> 96) - 11111509109440967052023855526967;
            p = ((p * x) >> 96) - 45023709667254063763336534515857;
            p = ((p * x) >> 96) - 14706773417378608786704636184526;
            p = p * x - (795164235651350426258249787498 << 96);

            // We leave p in 2**192 basis so we don't need to scale it back up for the division.
            // q is monic by convention.
            int256 q = x + 5573035233440673466300451813936;
            q = ((q * x) >> 96) + 71694874799317883764090561454958;
            q = ((q * x) >> 96) + 283447036172924575727196451306956;
            q = ((q * x) >> 96) + 401686690394027663651624208769553;
            q = ((q * x) >> 96) + 204048457590392012362485061816622;
            q = ((q * x) >> 96) + 31853899698501571402653359427138;
            q = ((q * x) >> 96) + 909429971244387300277376558375;
            /// @solidity memory-safe-assembly
            assembly {
                // Div in assembly because solidity adds a zero check despite the unchecked.
                // The q polynomial is known not to have zeros in the domain.
                // No scaling required because p is already 2**96 too large.
                r := sdiv(p, q)
            }

            // r is in the range (0, 0.125) * 2**96

            // Finalization, we need to:
            // * multiply by the scale factor s = 5.549…
            // * add ln(2**96 / 10**18)
            // * add k * ln(2)
            // * multiply by 10**18 / 2**96 = 5**18 >> 78

            // mul s * 5e18 * 2**96, base is now 5**18 * 2**192
            r *= 1677202110996718588342820967067443963516166;
            // add ln(2) * k * 5e18 * 2**192
            r += 16597577552685614221487285958193947469193820559219878177908093499208371 * k;
            // add ln(2**96 / 10**18) * 5e18 * 2**192
            r += 600920179829731861736702779321621459595472258049074101567377883020018308;
            // base conversion: mul 2**18 / 2**192
            r >>= 174;
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                  GENERAL NUMBER UTILITIES                  */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Calculates `floor(a * b / d)` with full precision.
    /// Throws if result overflows a uint256 or when `d` is zero.
    /// Credit to Remco Bloemen under MIT license: https://2π.com/21/muldiv
    function fullMulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            // forgefmt: disable-next-item
            for {

            } 1 {

            } {
                // 512-bit multiply `[prod1 prod0] = x * y`.
                // Compute the product mod `2**256` and mod `2**256 - 1`
                // then use the Chinese Remainder Theorem to reconstruct
                // the 512 bit result. The result is stored in two 256
                // variables such that `product = prod1 * 2**256 + prod0`.

                // Least significant 256 bits of the product.
                let prod0 := mul(x, y)
                let mm := mulmod(x, y, not(0))
                // Most significant 256 bits of the product.
                let prod1 := sub(mm, add(prod0, lt(mm, prod0)))

                // Handle non-overflow cases, 256 by 256 division.
                if iszero(prod1) {
                    if iszero(d) {
                        // Store the function selector of `FullMulDivFailed()`.
                        mstore(0x00, 0xae47f702)
                        // Revert with (offset, size).
                        revert(0x1c, 0x04)
                    }
                    result := div(prod0, d)
                    break
                }

                // Make sure the result is less than `2**256`.
                // Also prevents `d == 0`.
                if iszero(gt(d, prod1)) {
                    // Store the function selector of `FullMulDivFailed()`.
                    mstore(0x00, 0xae47f702)
                    // Revert with (offset, size).
                    revert(0x1c, 0x04)
                }

                ///////////////////////////////////////////////
                // 512 by 256 division.
                ///////////////////////////////////////////////

                // Make division exact by subtracting the remainder from `[prod1 prod0]`.
                // Compute remainder using mulmod.
                let remainder := mulmod(x, y, d)
                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
                // Factor powers of two out of `d`.
                // Compute largest power of two divisor of `d`.
                // Always greater or equal to 1.
                let twos := and(d, sub(0, d))
                // Divide d by power of two.
                d := div(d, twos)
                // Divide [prod1 prod0] by the factors of two.
                prod0 := div(prod0, twos)
                // Shift in bits from `prod1` into `prod0`. For this we need
                // to flip `twos` such that it is `2**256 / twos`.
                // If `twos` is zero, then it becomes one.
                prod0 := or(prod0, mul(prod1, add(div(sub(0, twos), twos), 1)))
                // Invert `d mod 2**256`
                // Now that `d` is an odd number, it has an inverse
                // modulo `2**256` such that `d * inv = 1 mod 2**256`.
                // Compute the inverse by starting with a seed that is correct
                // correct for four bits. That is, `d * inv = 1 mod 2**4`.
                let inv := xor(mul(3, d), 2)
                // Now use Newton-Raphson iteration to improve the precision.
                // Thanks to Hensel's lifting lemma, this also works in modular
                // arithmetic, doubling the correct bits in each step.
                inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**8
                inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**16
                inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**32
                inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**64
                inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**128
                result := mul(prod0, mul(inv, sub(2, mul(d, inv)))) // inverse mod 2**256
                break
            }
        }
    }

    /// @dev Calculates `floor(x * y / d)` with full precision, rounded up.
    /// Throws if result overflows a uint256 or when `d` is zero.
    /// Credit to Uniswap-v3-core under MIT license:
    /// https://github.com/Uniswap/v3-core/blob/contracts/libraries/FullMath.sol
    function fullMulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 result) {
        result = fullMulDiv(x, y, d);
        /// @solidity memory-safe-assembly
        assembly {
            if mulmod(x, y, d) {
                if iszero(add(result, 1)) {
                    // Store the function selector of `FullMulDivFailed()`.
                    mstore(0x00, 0xae47f702)
                    // Revert with (offset, size).
                    revert(0x1c, 0x04)
                }
                result := add(result, 1)
            }
        }
    }

    /// @dev Returns `floor(x * y / d)`.
    /// Reverts if `x * y` overflows, or `d` is zero.
    function mulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to require(d != 0 && (y == 0 || x <= type(uint256).max / y))
            if iszero(mul(d, iszero(mul(y, gt(x, div(not(0), y)))))) {
                // Store the function selector of `MulDivFailed()`.
                mstore(0x00, 0xad251c27)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            z := div(mul(x, y), d)
        }
    }

    /// @dev Returns `ceil(x * y / d)`.
    /// Reverts if `x * y` overflows, or `d` is zero.
    function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to require(d != 0 && (y == 0 || x <= type(uint256).max / y))
            if iszero(mul(d, iszero(mul(y, gt(x, div(not(0), y)))))) {
                // Store the function selector of `MulDivFailed()`.
                mstore(0x00, 0xad251c27)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            z := add(iszero(iszero(mod(mul(x, y), d))), div(mul(x, y), d))
        }
    }

    /// @dev Returns `ceil(x / d)`.
    /// Reverts if `d` is zero.
    function divUp(uint256 x, uint256 d) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(d) {
                // Store the function selector of `DivFailed()`.
                mstore(0x00, 0x65244e4e)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            z := add(iszero(iszero(mod(x, d))), div(x, d))
        }
    }

    /// @dev Returns `max(0, x - y)`.
    function zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := mul(gt(x, y), sub(x, y))
        }
    }

    /// @dev Returns the square root of `x`.
    function sqrt(uint256 x) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // `floor(sqrt(2**15)) = 181`. `sqrt(2**15) - 181 = 2.84`.
            z := 181 // The "correct" value is 1, but this saves a multiplication later.

            // This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
            // start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.

            // Let `y = x / 2**r`.
            // We check `y >= 2**(k + 8)` but shift right by `k` bits
            // each branch to ensure that if `x >= 256`, then `y >= 256`.
            let r := shl(7, lt(0xffffffffffffffffffffffffffffffffff, x))
            r := or(r, shl(6, lt(0xffffffffffffffffff, shr(r, x))))
            r := or(r, shl(5, lt(0xffffffffff, shr(r, x))))
            r := or(r, shl(4, lt(0xffffff, shr(r, x))))
            z := shl(shr(1, r), z)

            // Goal was to get `z*z*y` within a small factor of `x`. More iterations could
            // get y in a tighter range. Currently, we will have y in `[256, 256*(2**16))`.
            // We ensured `y >= 256` so that the relative difference between `y` and `y+1` is small.
            // That's not possible if `x < 256` but we can just verify those cases exhaustively.

            // Now, `z*z*y <= x < z*z*(y+1)`, and `y <= 2**(16+8)`, and either `y >= 256`, or `x < 256`.
            // Correctness can be checked exhaustively for `x < 256`, so we assume `y >= 256`.
            // Then `z*sqrt(y)` is within `sqrt(257)/sqrt(256)` of `sqrt(x)`, or about 20bps.

            // For `s` in the range `[1/256, 256]`, the estimate `f(s) = (181/1024) * (s+1)`
            // is in the range `(1/2.84 * sqrt(s), 2.84 * sqrt(s))`,
            // with largest error when `s = 1` and when `s = 256` or `1/256`.

            // Since `y` is in `[256, 256*(2**16))`, let `a = y/65536`, so that `a` is in `[1/256, 256)`.
            // Then we can estimate `sqrt(y)` using
            // `sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2**18`.

            // There is no overflow risk here since `y < 2**136` after the first branch above.
            z := shr(18, mul(z, add(shr(r, x), 65536))) // A `mul()` is saved from starting `z` at 181.

            // Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))

            // If `x+1` is a perfect square, the Babylonian method cycles between
            // `floor(sqrt(x))` and `ceil(sqrt(x))`. This statement ensures we return floor.
            // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
            // Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
            // If you don't care whether the floor or ceil square root is returned, you can remove this statement.
            z := sub(z, lt(div(x, z), z))
        }
    }

    /// @dev Returns the factorial of `x`.
    function factorial(uint256 x) internal pure returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            for {

            } 1 {

            } {
                if iszero(lt(10, x)) {
                    // forgefmt: disable-next-item
                    result := and(shr(mul(22, x), 0x375f0016260009d80004ec0002d00001e0000180000180000200000400001), 0x3fffff)
                    break
                }
                if iszero(lt(57, x)) {
                    let end := 31
                    result := 8222838654177922817725562880000000
                    if iszero(lt(end, x)) {
                        end := 10
                        result := 3628800
                    }
                    for {
                        let w := not(0)
                    } 1 {

                    } {
                        result := mul(result, x)
                        x := add(x, w)
                        if eq(x, end) {
                            break
                        }
                    }
                    break
                }
                // Store the function selector of `FactorialOverflow()`.
                mstore(0x00, 0xaba0f2a2)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Returns the log2 of `x`.
    /// Equivalent to computing the index of the most significant bit (MSB) of `x`.
    function log2(uint256 x) internal pure returns (uint256 r) {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(x) {
                // Store the function selector of `Log2Undefined()`.
                mstore(0x00, 0x5be3aa5c)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
            r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
            r := or(r, shl(5, lt(0xffffffff, shr(r, x))))

            // For the remaining 32 bits, use a De Bruijn lookup.
            // See: https://graphics.stanford.edu/~seander/bithacks.html
            x := shr(r, x)
            x := or(x, shr(1, x))
            x := or(x, shr(2, x))
            x := or(x, shr(4, x))
            x := or(x, shr(8, x))
            x := or(x, shr(16, x))

            // forgefmt: disable-next-item
            r := or(r, byte(shr(251, mul(x, shl(224, 0x07c4acdd))), 0x0009010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f))
        }
    }

    /// @dev Returns the log2 of `x`, rounded up.
    function log2Up(uint256 x) internal pure returns (uint256 r) {
        unchecked {
            uint256 isNotPo2;
            assembly {
                isNotPo2 := iszero(iszero(and(x, sub(x, 1))))
            }
            return log2(x) + isNotPo2;
        }
    }

    /// @dev Returns the average of `x` and `y`.
    function avg(uint256 x, uint256 y) internal pure returns (uint256 z) {
        unchecked {
            z = (x & y) + ((x ^ y) >> 1);
        }
    }

    /// @dev Returns the average of `x` and `y`.
    function avg(int256 x, int256 y) internal pure returns (int256 z) {
        unchecked {
            z = (x >> 1) + (y >> 1) + (((x & 1) + (y & 1)) >> 1);
        }
    }

    /// @dev Returns the absolute value of `x`.
    function abs(int256 x) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            let mask := sub(0, shr(255, x))
            z := xor(mask, add(mask, x))
        }
    }

    /// @dev Returns the absolute distance between `x` and `y`.
    function dist(int256 x, int256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            let a := sub(y, x)
            z := xor(a, mul(xor(a, sub(x, y)), sgt(x, y)))
        }
    }

    /// @dev Returns the minimum of `x` and `y`.
    function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := xor(x, mul(xor(x, y), lt(y, x)))
        }
    }

    /// @dev Returns the minimum of `x` and `y`.
    function min(int256 x, int256 y) internal pure returns (int256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := xor(x, mul(xor(x, y), slt(y, x)))
        }
    }

    /// @dev Returns the maximum of `x` and `y`.
    function max(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := xor(x, mul(xor(x, y), gt(y, x)))
        }
    }

    /// @dev Returns the maximum of `x` and `y`.
    function max(int256 x, int256 y) internal pure returns (int256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := xor(x, mul(xor(x, y), sgt(y, x)))
        }
    }

    /// @dev Returns `x`, bounded to `minValue` and `maxValue`.
    function clamp(uint256 x, uint256 minValue, uint256 maxValue) internal pure returns (uint256 z) {
        z = min(max(x, minValue), maxValue);
    }

    /// @dev Returns `x`, bounded to `minValue` and `maxValue`.
    function clamp(int256 x, int256 minValue, int256 maxValue) internal pure returns (int256 z) {
        z = min(max(x, minValue), maxValue);
    }

    /// @dev Returns greatest common divisor of `x` and `y`.
    function gcd(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // forgefmt: disable-next-item
            for {
                z := x
            } y {

            } {
                let t := y
                y := mod(z, y)
                z := t
            }
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                   RAW NUMBER OPERATIONS                    */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns `x + y`, without checking for overflow.
    function rawAdd(uint256 x, uint256 y) internal pure returns (uint256 z) {
        unchecked {
            z = x + y;
        }
    }

    /// @dev Returns `x + y`, without checking for overflow.
    function rawAdd(int256 x, int256 y) internal pure returns (int256 z) {
        unchecked {
            z = x + y;
        }
    }

    /// @dev Returns `x - y`, without checking for underflow.
    function rawSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
        unchecked {
            z = x - y;
        }
    }

    /// @dev Returns `x - y`, without checking for underflow.
    function rawSub(int256 x, int256 y) internal pure returns (int256 z) {
        unchecked {
            z = x - y;
        }
    }

    /// @dev Returns `x * y`, without checking for overflow.
    function rawMul(uint256 x, uint256 y) internal pure returns (uint256 z) {
        unchecked {
            z = x * y;
        }
    }

    /// @dev Returns `x * y`, without checking for overflow.
    function rawMul(int256 x, int256 y) internal pure returns (int256 z) {
        unchecked {
            z = x * y;
        }
    }

    /// @dev Returns `x / y`, returning 0 if `y` is zero.
    function rawDiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := div(x, y)
        }
    }

    /// @dev Returns `x / y`, returning 0 if `y` is zero.
    function rawSDiv(int256 x, int256 y) internal pure returns (int256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := sdiv(x, y)
        }
    }

    /// @dev Returns `x % y`, returning 0 if `y` is zero.
    function rawMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := mod(x, y)
        }
    }

    /// @dev Returns `x % y`, returning 0 if `y` is zero.
    function rawSMod(int256 x, int256 y) internal pure returns (int256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := smod(x, y)
        }
    }

    /// @dev Returns `(x + y) % d`, return 0 if `d` if zero.
    function rawAddMod(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := addmod(x, y, d)
        }
    }

    /// @dev Returns `(x * y) % d`, return 0 if `d` if zero.
    function rawMulMod(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := mulmod(x, y, d)
        }
    }
}

File 30 of 32 : SafeCastLib.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Safe integer casting library that reverts on overflow.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeCastLib.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeCast.sol)
library SafeCastLib {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    error Overflow();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*          UNSIGNED INTEGER SAFE CASTING OPERATIONS          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    function toUint8(uint256 x) internal pure returns (uint8) {
        if (x >= 1 << 8) _revertOverflow();
        return uint8(x);
    }

    function toUint16(uint256 x) internal pure returns (uint16) {
        if (x >= 1 << 16) _revertOverflow();
        return uint16(x);
    }

    function toUint24(uint256 x) internal pure returns (uint24) {
        if (x >= 1 << 24) _revertOverflow();
        return uint24(x);
    }

    function toUint32(uint256 x) internal pure returns (uint32) {
        if (x >= 1 << 32) _revertOverflow();
        return uint32(x);
    }

    function toUint40(uint256 x) internal pure returns (uint40) {
        if (x >= 1 << 40) _revertOverflow();
        return uint40(x);
    }

    function toUint48(uint256 x) internal pure returns (uint48) {
        if (x >= 1 << 48) _revertOverflow();
        return uint48(x);
    }

    function toUint56(uint256 x) internal pure returns (uint56) {
        if (x >= 1 << 56) _revertOverflow();
        return uint56(x);
    }

    function toUint64(uint256 x) internal pure returns (uint64) {
        if (x >= 1 << 64) _revertOverflow();
        return uint64(x);
    }

    function toUint72(uint256 x) internal pure returns (uint72) {
        if (x >= 1 << 72) _revertOverflow();
        return uint72(x);
    }

    function toUint80(uint256 x) internal pure returns (uint80) {
        if (x >= 1 << 80) _revertOverflow();
        return uint80(x);
    }

    function toUint88(uint256 x) internal pure returns (uint88) {
        if (x >= 1 << 88) _revertOverflow();
        return uint88(x);
    }

    function toUint96(uint256 x) internal pure returns (uint96) {
        if (x >= 1 << 96) _revertOverflow();
        return uint96(x);
    }

    function toUint104(uint256 x) internal pure returns (uint104) {
        if (x >= 1 << 104) _revertOverflow();
        return uint104(x);
    }

    function toUint112(uint256 x) internal pure returns (uint112) {
        if (x >= 1 << 112) _revertOverflow();
        return uint112(x);
    }

    function toUint120(uint256 x) internal pure returns (uint120) {
        if (x >= 1 << 120) _revertOverflow();
        return uint120(x);
    }

    function toUint128(uint256 x) internal pure returns (uint128) {
        if (x >= 1 << 128) _revertOverflow();
        return uint128(x);
    }

    function toUint136(uint256 x) internal pure returns (uint136) {
        if (x >= 1 << 136) _revertOverflow();
        return uint136(x);
    }

    function toUint144(uint256 x) internal pure returns (uint144) {
        if (x >= 1 << 144) _revertOverflow();
        return uint144(x);
    }

    function toUint152(uint256 x) internal pure returns (uint152) {
        if (x >= 1 << 152) _revertOverflow();
        return uint152(x);
    }

    function toUint160(uint256 x) internal pure returns (uint160) {
        if (x >= 1 << 160) _revertOverflow();
        return uint160(x);
    }

    function toUint168(uint256 x) internal pure returns (uint168) {
        if (x >= 1 << 168) _revertOverflow();
        return uint168(x);
    }

    function toUint176(uint256 x) internal pure returns (uint176) {
        if (x >= 1 << 176) _revertOverflow();
        return uint176(x);
    }

    function toUint184(uint256 x) internal pure returns (uint184) {
        if (x >= 1 << 184) _revertOverflow();
        return uint184(x);
    }

    function toUint192(uint256 x) internal pure returns (uint192) {
        if (x >= 1 << 192) _revertOverflow();
        return uint192(x);
    }

    function toUint200(uint256 x) internal pure returns (uint200) {
        if (x >= 1 << 200) _revertOverflow();
        return uint200(x);
    }

    function toUint208(uint256 x) internal pure returns (uint208) {
        if (x >= 1 << 208) _revertOverflow();
        return uint208(x);
    }

    function toUint216(uint256 x) internal pure returns (uint216) {
        if (x >= 1 << 216) _revertOverflow();
        return uint216(x);
    }

    function toUint224(uint256 x) internal pure returns (uint224) {
        if (x >= 1 << 224) _revertOverflow();
        return uint224(x);
    }

    function toUint232(uint256 x) internal pure returns (uint232) {
        if (x >= 1 << 232) _revertOverflow();
        return uint232(x);
    }

    function toUint240(uint256 x) internal pure returns (uint240) {
        if (x >= 1 << 240) _revertOverflow();
        return uint240(x);
    }

    function toUint248(uint256 x) internal pure returns (uint248) {
        if (x >= 1 << 248) _revertOverflow();
        return uint248(x);
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*           SIGNED INTEGER SAFE CASTING OPERATIONS           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    function toInt8(int256 x) internal pure returns (int8) {
        int8 y = int8(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt16(int256 x) internal pure returns (int16) {
        int16 y = int16(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt24(int256 x) internal pure returns (int24) {
        int24 y = int24(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt32(int256 x) internal pure returns (int32) {
        int32 y = int32(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt40(int256 x) internal pure returns (int40) {
        int40 y = int40(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt48(int256 x) internal pure returns (int48) {
        int48 y = int48(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt56(int256 x) internal pure returns (int56) {
        int56 y = int56(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt64(int256 x) internal pure returns (int64) {
        int64 y = int64(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt72(int256 x) internal pure returns (int72) {
        int72 y = int72(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt80(int256 x) internal pure returns (int80) {
        int80 y = int80(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt88(int256 x) internal pure returns (int88) {
        int88 y = int88(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt96(int256 x) internal pure returns (int96) {
        int96 y = int96(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt104(int256 x) internal pure returns (int104) {
        int104 y = int104(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt112(int256 x) internal pure returns (int112) {
        int112 y = int112(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt120(int256 x) internal pure returns (int120) {
        int120 y = int120(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt128(int256 x) internal pure returns (int128) {
        int128 y = int128(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt136(int256 x) internal pure returns (int136) {
        int136 y = int136(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt144(int256 x) internal pure returns (int144) {
        int144 y = int144(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt152(int256 x) internal pure returns (int152) {
        int152 y = int152(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt160(int256 x) internal pure returns (int160) {
        int160 y = int160(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt168(int256 x) internal pure returns (int168) {
        int168 y = int168(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt176(int256 x) internal pure returns (int176) {
        int176 y = int176(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt184(int256 x) internal pure returns (int184) {
        int184 y = int184(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt192(int256 x) internal pure returns (int192) {
        int192 y = int192(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt200(int256 x) internal pure returns (int200) {
        int200 y = int200(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt208(int256 x) internal pure returns (int208) {
        int208 y = int208(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt216(int256 x) internal pure returns (int216) {
        int216 y = int216(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt224(int256 x) internal pure returns (int224) {
        int224 y = int224(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt232(int256 x) internal pure returns (int232) {
        int232 y = int232(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt240(int256 x) internal pure returns (int240) {
        int240 y = int240(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt248(int256 x) internal pure returns (int248) {
        int248 y = int248(x);
        if (x != y) _revertOverflow();
        return y;
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                      PRIVATE HELPERS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    function _revertOverflow() private pure {
        /// @solidity memory-safe-assembly
        assembly {
            // Store the function selector of `Overflow()`.
            mstore(0x00, 0x35278d12)
            // Revert with (offset, size).
            revert(0x1c, 0x04)
        }
    }
}

File 31 of 32 : SafeTransferLib.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Caution! This library won't check that a token has code, responsibility is delegated to the caller.
library SafeTransferLib {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The ETH transfer has failed.
    error ETHTransferFailed();

    /// @dev The ERC20 `transferFrom` has failed.
    error TransferFromFailed();

    /// @dev The ERC20 `transfer` has failed.
    error TransferFailed();

    /// @dev The ERC20 `approve` has failed.
    error ApproveFailed();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Suggested gas stipend for contract receiving ETH
    /// that disallows any storage writes.
    uint256 internal constant _GAS_STIPEND_NO_STORAGE_WRITES = 2300;

    /// @dev Suggested gas stipend for contract receiving ETH to perform a few
    /// storage reads and writes, but low enough to prevent griefing.
    /// Multiply by a small constant (e.g. 2), if needed.
    uint256 internal constant _GAS_STIPEND_NO_GRIEF = 100000;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       ETH OPERATIONS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Sends `amount` (in wei) ETH to `to`.
    /// Reverts upon failure.
    function safeTransferETH(address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // Transfer the ETH and check if it succeeded or not.
            if iszero(call(gas(), to, amount, 0, 0, 0, 0)) {
                // Store the function selector of `ETHTransferFailed()`.
                mstore(0x00, 0xb12d13eb)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
    /// The `gasStipend` can be set to a low enough value to prevent
    /// storage writes or gas griefing.
    ///
    /// If sending via the normal procedure fails, force sends the ETH by
    /// creating a temporary contract which uses `SELFDESTRUCT` to force send the ETH.
    ///
    /// Reverts if the current contract has insufficient balance.
    function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // If insufficient balance, revert.
            if lt(selfbalance(), amount) {
                // Store the function selector of `ETHTransferFailed()`.
                mstore(0x00, 0xb12d13eb)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            // Transfer the ETH and check if it succeeded or not.
            if iszero(call(gasStipend, to, amount, 0, 0, 0, 0)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                // We can directly use `SELFDESTRUCT` in the contract creation.
                // Compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758
                if iszero(create(amount, 0x0b, 0x16)) {
                    // For better gas estimation.
                    if iszero(gt(gas(), 1000000)) {
                        revert(0, 0)
                    }
                }
            }
        }
    }

    /// @dev Force sends `amount` (in wei) ETH to `to`, with a gas stipend
    /// equal to `_GAS_STIPEND_NO_GRIEF`. This gas stipend is a reasonable default
    /// for 99% of cases and can be overriden with the three-argument version of this
    /// function if necessary.
    ///
    /// If sending via the normal procedure fails, force sends the ETH by
    /// creating a temporary contract which uses `SELFDESTRUCT` to force send the ETH.
    ///
    /// Reverts if the current contract has insufficient balance.
    function forceSafeTransferETH(address to, uint256 amount) internal {
        // Manually inlined because the compiler doesn't inline functions with branches.
        /// @solidity memory-safe-assembly
        assembly {
            // If insufficient balance, revert.
            if lt(selfbalance(), amount) {
                // Store the function selector of `ETHTransferFailed()`.
                mstore(0x00, 0xb12d13eb)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            // Transfer the ETH and check if it succeeded or not.
            if iszero(call(_GAS_STIPEND_NO_GRIEF, to, amount, 0, 0, 0, 0)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                // We can directly use `SELFDESTRUCT` in the contract creation.
                // Compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758
                if iszero(create(amount, 0x0b, 0x16)) {
                    // For better gas estimation.
                    if iszero(gt(gas(), 1000000)) {
                        revert(0, 0)
                    }
                }
            }
        }
    }

    /// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
    /// The `gasStipend` can be set to a low enough value to prevent
    /// storage writes or gas griefing.
    ///
    /// Simply use `gasleft()` for `gasStipend` if you don't need a gas stipend.
    ///
    /// Note: Does NOT revert upon failure.
    /// Returns whether the transfer of ETH is successful instead.
    function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal returns (bool success) {
        /// @solidity memory-safe-assembly
        assembly {
            // Transfer the ETH and check if it succeeded or not.
            success := call(gasStipend, to, amount, 0, 0, 0, 0)
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                      ERC20 OPERATIONS                      */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
    /// Reverts upon failure.
    ///
    /// The `from` account must have at least `amount` approved for
    /// the current contract to manage.
    function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.

            mstore(0x60, amount) // Store the `amount` argument.
            mstore(0x40, to) // Store the `to` argument.
            mstore(0x2c, shl(96, from)) // Store the `from` argument.
            // Store the function selector of `transferFrom(address,address,uint256)`.
            mstore(0x0c, 0x23b872dd000000000000000000000000)

            if iszero(
                and(
                    // The arguments of `and` are evaluated from right to left.
                    // Set success to whether the call reverted, if not we check it either
                    // returned exactly 1 (can't just be non-zero data), or had no return data.
                    or(eq(mload(0x00), 1), iszero(returndatasize())),
                    call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
                )
            ) {
                // Store the function selector of `TransferFromFailed()`.
                mstore(0x00, 0x7939f424)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends all of ERC20 `token` from `from` to `to`.
    /// Reverts upon failure.
    ///
    /// The `from` account must have at least `amount` approved for
    /// the current contract to manage.
    function safeTransferAllFrom(address token, address from, address to) internal returns (uint256 amount) {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.

            mstore(0x40, to) // Store the `to` argument.
            mstore(0x2c, shl(96, from)) // Store the `from` argument.
            // Store the function selector of `balanceOf(address)`.
            mstore(0x0c, 0x70a08231000000000000000000000000)
            if iszero(
                and(
                    // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                    staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
                )
            ) {
                // Store the function selector of `TransferFromFailed()`.
                mstore(0x00, 0x7939f424)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            // Store the function selector of `transferFrom(address,address,uint256)`.
            mstore(0x00, 0x23b872dd)
            // The `amount` argument is already written to the memory word at 0x6c.
            amount := mload(0x60)

            if iszero(
                and(
                    // The arguments of `and` are evaluated from right to left.
                    // Set success to whether the call reverted, if not we check it either
                    // returned exactly 1 (can't just be non-zero data), or had no return data.
                    or(eq(mload(0x00), 1), iszero(returndatasize())),
                    call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
                )
            ) {
                // Store the function selector of `TransferFromFailed()`.
                mstore(0x00, 0x7939f424)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
    /// Reverts upon failure.
    function safeTransfer(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            // Store the function selector of `transfer(address,uint256)`.
            mstore(0x00, 0xa9059cbb000000000000000000000000)

            if iszero(
                and(
                    // The arguments of `and` are evaluated from right to left.
                    // Set success to whether the call reverted, if not we check it either
                    // returned exactly 1 (can't just be non-zero data), or had no return data.
                    or(eq(mload(0x00), 1), iszero(returndatasize())),
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                // Store the function selector of `TransferFailed()`.
                mstore(0x00, 0x90b8ec18)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            // Restore the part of the free memory pointer that was overwritten.
            mstore(0x34, 0)
        }
    }

    /// @dev Sends all of ERC20 `token` from the current contract to `to`.
    /// Reverts upon failure.
    function safeTransferAll(address token, address to) internal returns (uint256 amount) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
            mstore(0x20, address()) // Store the address of the current contract.
            if iszero(
                and(
                    // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                    staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
                )
            ) {
                // Store the function selector of `TransferFailed()`.
                mstore(0x00, 0x90b8ec18)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            mstore(0x14, to) // Store the `to` argument.
            // The `amount` argument is already written to the memory word at 0x34.
            amount := mload(0x34)
            // Store the function selector of `transfer(address,uint256)`.
            mstore(0x00, 0xa9059cbb000000000000000000000000)

            if iszero(
                and(
                    // The arguments of `and` are evaluated from right to left.
                    // Set success to whether the call reverted, if not we check it either
                    // returned exactly 1 (can't just be non-zero data), or had no return data.
                    or(eq(mload(0x00), 1), iszero(returndatasize())),
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                // Store the function selector of `TransferFailed()`.
                mstore(0x00, 0x90b8ec18)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            // Restore the part of the free memory pointer that was overwritten.
            mstore(0x34, 0)
        }
    }

    /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
    /// Reverts upon failure.
    function safeApprove(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            // Store the function selector of `approve(address,uint256)`.
            mstore(0x00, 0x095ea7b3000000000000000000000000)

            if iszero(
                and(
                    // The arguments of `and` are evaluated from right to left.
                    // Set success to whether the call reverted, if not we check it either
                    // returned exactly 1 (can't just be non-zero data), or had no return data.
                    or(eq(mload(0x00), 1), iszero(returndatasize())),
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                // Store the function selector of `ApproveFailed()`.
                mstore(0x00, 0x3e3f8f73)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            // Restore the part of the free memory pointer that was overwritten.
            mstore(0x34, 0)
        }
    }

    /// @dev Returns the amount of ERC20 `token` owned by `account`.
    /// Returns zero if the `token` does not exist.
    function balanceOf(address token, address account) internal view returns (uint256 amount) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, account) // Store the `account` argument.
            // Store the function selector of `balanceOf(address)`.
            mstore(0x00, 0x70a08231000000000000000000000000)
            amount := mul(
                mload(0x20),
                and(
                    // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                    staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
                )
            )
        }
    }
}

File 32 of 32 : ReentrancyGuard.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @title ReentrancyGuard
/// @author Paul Razvan Berg
/// @notice Contract module that helps prevent reentrant calls to a function.
///
/// Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier available, which can be applied
/// to functions to make sure there are no nested (reentrant) calls to them.
///
/// Note that because there is a single `nonReentrant` guard, functions marked as `nonReentrant` may not
/// call one another. This can be worked around by making those functions `private`, and then adding
/// `external` `nonReentrant` entry points to them.
///
/// @dev Forked from OpenZeppelin
/// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.4.0/contracts/utils/ReentrancyGuard.sol
abstract contract ReentrancyGuard {
    /*//////////////////////////////////////////////////////////////////////////
                                       ERRORS
    //////////////////////////////////////////////////////////////////////////*/

    /// @notice Thrown when there is a reentrancy call.
    error ReentrantCall();

    /*//////////////////////////////////////////////////////////////////////////
                                  PRIVATE STORAGE
    //////////////////////////////////////////////////////////////////////////*/

    bool private notEntered;

    /*//////////////////////////////////////////////////////////////////////////
                                     CONSTRUCTOR
    //////////////////////////////////////////////////////////////////////////*/

    /// Storing an initial non-zero value makes deployment a bit more expensive but in exchange the
    /// refund on every call to nonReentrant will be lower in amount. Since refunds are capped to a
    /// percentage of the total transaction's gas, it is best to keep them low in cases like this one,
    /// to increase the likelihood of the full refund coming into effect.
    constructor() {
        notEntered = true;
    }

    /*//////////////////////////////////////////////////////////////////////////
                                      MODIFIERS
    //////////////////////////////////////////////////////////////////////////*/

    /// @notice Prevents a contract from calling itself, directly or indirectly.
    /// @dev Calling a `nonReentrant` function from another `nonReentrant` function
    /// is not supported. It is possible to prevent this from happening by making
    /// the `nonReentrant` function external, and make it call a `private`
    /// function that does the actual work.
    modifier nonReentrant() {
        // On the first call to nonReentrant, notEntered will be true.
        if (!notEntered) {
            revert ReentrantCall();
        }

        // Any calls to nonReentrant after this point will fail.
        notEntered = false;

        _;

        // By storing the original value once again, a refund is triggered (https://eips.ethereum.org/EIPS/eip-2200).
        notEntered = true;
    }
}

Settings
{
  "viaIR": true,
  "optimizer": {
    "enabled": true,
    "runs": 800,
    "details": {
      "peephole": true,
      "inliner": true,
      "jumpdestRemover": true,
      "orderLiterals": true,
      "deduplicate": true,
      "cse": true,
      "constantOptimizer": true,
      "yulDetails": {
        "stackAllocation": true,
        "optimizerSteps": "dhfoDgvulfnTUtnIf[xa[r]EscLMcCTUtTOntnfDIulLculVcul[j]Tpeulxa[rul]xa[r]cLgvifCTUca[r]LSsTOtfDnca[r]Iulc]jmul[jul]VcTOculjmul"
      }
    }
  },
  "metadata": {
    "bytecodeHash": "none"
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {}
}

Contract Security Audit

Contract ABI

API
[{"inputs":[],"name":"collateralInitCodeHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"underlying","type":"address"},{"internalType":"address","name":"borrowable","type":"address"},{"internalType":"address","name":"oracle","type":"address"},{"internalType":"uint256","name":"shuttleId","type":"uint256"}],"name":"deployDeneb","outputs":[{"internalType":"address","name":"collateral","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"shuttleParameters","outputs":[{"internalType":"address","name":"factory","type":"address"},{"internalType":"address","name":"underlying","type":"address"},{"internalType":"address","name":"borrowable","type":"address"},{"internalType":"address","name":"oracle","type":"address"},{"internalType":"uint256","name":"shuttleId","type":"uint256"}],"stateMutability":"view","type":"function"}]

60a0346100695761314e90603f8201601f191681016001600160401b0381118282101761005357604052818152602081019161343883395190206080526040516133c9908161006f823960805181605c0152f35b634e487b7160e01b600052604160045260246000fd5b600080fdfe608060408181526004918236101561001657600080fd5b600092833560e01c9182631b2b71e4146100de575081635a412e5314610083575063ef703c961461004657600080fd5b3461007f578160031936011261007f57602090517f00000000000000000000000000000000000000000000000000000000000000008152f35b5080fd5b83833461007f578160031936011261007f5760a09273ffffffffffffffffffffffffffffffffffffffff8093541692806001541692816002541691600354169254938151958652602086015284015260608301526080820152f35b9150913461023257608036600319011261023257823573ffffffffffffffffffffffffffffffffffffffff9283821680920361022e5760243584811680910361022a576044359085821680920361022657608092606435938161014360a08794610236565b338152866020820152838882015284606082015201527fffffffffffffffffffffffff00000000000000000000000000000000000000009033828a5416178955848260015416176001558160025416176002556003541617600355845581516020810191825233838201528281526101bc606082610236565b519020815161314e8082019082821067ffffffffffffffff8311176102135790829161026f8339039086f59283156102095793602094818055816001558160025581600355555191168152f35b81513d86823e3d90fd5b634e487b7160e01b885260418752602488fd5b8780fd5b8680fd5b8580fd5b8380fd5b90601f8019910116810190811067ffffffffffffffff82111761025857604052565b634e487b7160e01b600052604160045260246000fdfe6101208060405234620001f4576000805460ff19166001178155635a412e5360e01b825260a09190828260048184335af1918215620001e757818091818091819662000133575b505061010094855260e05260805260c0528252670c7d713b49da0000600155670e4b4b8af6a70000600255662386f26fc1000060035560001960045560405190612f3f92836200020f843960805183818161058c01528181610f5f0152818161116301528181612200015281816123e50152612b560152518281816105d0015281816112750152612915015260c05182818161038101528181610d0801528181610dae0152818161152f0152818161173e01528181611a5601528181611c8101528181611d4b015281816122df015261287b015260e051828181611b63015261230601525181611abc0152f35b94509450505050823d8411620001df575b601f8101601f191682016001600160401b03811183821017620001cb578491839160405281010312620001c7578051916001600160a01b038084168403620001c7576200019460208401620001f9565b92620001a360408201620001f9565b9260608201519283168303620001c457506080015193929190388062000046565b80fd5b5080fd5b634e487b7160e01b84526041600452602484fd5b503d62000144565b50604051903d90823e3d90fd5b600080fd5b51906001600160a01b0382168203620001f45756fe6080604081815260048036101561001557600080fd5b600092833560e01c90816301e1d11414611f405750806306fdde0314611f17578063095ea7b314611ea457806318160ddd14611e7d57806323b872dd14611d98578063258836fe14611d0b578063313ce56714611c545780633644e51514611bcc5780633ba0b9a914611baf5780634bdaeac114611b875780634ecde84914611b435780635130362214611b1f5780635ec88c7914611adf5780635f77f89114611aa45780636afdd85014611a7a5780636f307dc314611a3657806370a08231146119fb5780637ecebe00146119c057806381f5b6f4146119525780638808e71c146116695780638c765e941461164a57806395d89b41146115035780639ee80c5c146114ac578063a36a36301461148d578063a84310811461140f578063a9059cbb14611370578063ad7a672f14611347578063b4ebbda414611113578063ba08765214610ff3578063bb3f673614610ecb578063bcbc332014610cb9578063bf199e6214610c9a578063cc1f8ffa14610c5d578063cea55f5714610c3e578063cf8c8c4414610bfe578063d505accf14610a4e578063d959a01614610890578063dcc3e06e14610861578063dd62ed3e14610816578063ddb19a34146105f4578063e8a24e1d146105b0578063e99a6ac71461056c578063eb6a887d146104ee578063f1a392da146104cb578063fa14fdc2146102e6578063fef0bec8146102725763fff6cae91461022857600080fd5b3461026e578260031936011261026e5782549160ff831615610261578360018460ff198091168355610258612a1b565b82541617815580f35b516306fda65d60e31b8152fd5b8280fd5b503461026e57602036600319011261026e5780359061028f6128f7565b67016345785d8a000082116102d85750907f87272249f1056ec16a68dd9255101358246dd26a796e2f0bc6b3a0ad02f2de4191600354908060035582519182526020820152a180f35b825163058b4cb360e11b8152fd5b50903461026e57606036600319011261026e578135926001600160a01b03928385168503610474576024359060443567ffffffffffffffff81116104c7576103319036908301611fb5565b84549360ff8516156104b75760ff19988986168755869882156104a7576805345cdf77eb68f44c549660081c16610369818885612d9c565b96830961048b575b816103a59161037f82612473565b7f0000000000000000000000000000000000000000000000000000000000000000612e71565b816103f8575b5050506387a211a2600c523083526020600c20549182106103ea57506020946103d660019230612c45565b6103de612a1b565b82541617905551908152f35b83516337eed4ab60e11b8152fd5b60209293975061042d8751948593849363989bebcf60e01b8552338c86015260248501526060604485015260648401916120ff565b038186335af190811561048157839161044b575b50933880806103ab565b90506020813d8211610479575b8161046560209383612085565b81010312610474575138610441565b600080fd5b3d9150610458565b84513d85823e3d90fd5b94600101801561049b5794610371565b8463ae47f7028852601cfd5b8851632a28590560e01b81528690fd5b86516306fda65d60e31b81528490fd5b8380fd5b5050346104ea57816003193601126104ea576020906007549051908152f35b5080fd5b503461026e57602036600319011261026e5780359061050b6128f7565b670b1a2bc2ec5000008210801561055b575b6102d85750907ff25d2243e4128e51523d8537f04f18c3e6390bbfc6323390a9a8e59b921ce04691600154908060015582519182526020820152a180f35b50670dbd2fc137a30000821161051d565b5050346104ea57816003193601126104ea57602090516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b5050346104ea57816003193601126104ea57602090516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b503461026e57606036600319011261026e5767ffffffffffffffff9281358481116104ea576106269036908401611fe3565b6024959195358281116104c7576106409036908601611fe3565b9092604435908111610812576106599036908701611fe3565b93909285549660ff8816156108045760ff1997881687558054731e2d8f84605d32a2cbf302e30bfd2387badf35dd90813b15610800578a51630c7e663b60e11b81529283019081523060208201528891839182908490829060400103925af180156107f6576107dd575b506106d0959697986125a8565b8251926106e9846106e28160056124e3565b0385612085565b8351916106f583612529565b9261070283519485612085565b808452610711601f1991612529565b01366020850137805b855181101561075457600190610743306001600160a01b0361073c848b612541565b5116612f03565b61074d8287612541565b520161071a565b5060016107d9939294426007558551606081527fc5252ef97d1913a56f636a69fa3df5248e702e398f142464175250bb7e9ec1e06107a6610798606084018b612014565b838103602085015287612051565b9142898201528033930390a28254161790556107cb8351948486958652850190612014565b908382036020850152612051565b0390f35b6107e8878092612085565b6107f257386106c3565b8580fd5b89513d89823e3d90fd5b8880fd5b88516306fda65d60e31b8152fd5b8480fd5b503461026e578160031936011261026e57356001600160a01b03928382168203610474576024359384168403610474576020938452637f5e9f20600c52526034600c20549051908152f35b5050346104ea57816003193601126104ea5760209051731e2d8f84605d32a2cbf302e30bfd2387badf35dd8152f35b50903461026e578060031936011261026e578135916001600160a01b039081841691828503610474576024359167ffffffffffffffff808411610a4a576108dd6109159436908401611fe3565b81819692956108ea6128f7565b60065416996109108a5161090a816109038160056124e3565b0382612085565b8c612802565b612866565b847fffffffffffffffffffffffff000000000000000000000000000000000000000060065416176006558311610a3757680100000000000000008311610a375750600554826005558083106109f7575b5060058652855b8281106109bc57867fddd57fd5151dbcab176b5c0606de5d7aa76cbbce6df7e771c62b17b56e97486c876060888881519384526020840152820152806109b66060820160056124e3565b0390a180f35b60019060206109ca846127ee565b930192817f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db001550161096c565b827f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db091820191015b818110610a2c5750610965565b878155600101610a1f565b634e487b7160e01b875260419052602486fd5b8780fd5b509190346104ea5760e03660031901126104ea578235906001600160a01b0380831680930361047457602435908116809103610474576064359260443560843560ff81168103610bfa57610aa061213c565b91825160208094012091874211610bee5786519765383775081901600e52858a5284600c20918254917f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8b52868b01958652828b8a8c82017fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc681528b606084019a468c527f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9608086019530875260a08120602e525252528688525260a08b015260c08a20604e526042602c208b5260ff16855260a435885260c435606052848060808c60015afa90863d5103610be257019055777f5e9f200000000000000000000000000000000000000000851786526034602c20557f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259190a3528060605280f35b8b63ddafbaef8c52601cfd5b89631a15a3cc8a52601cfd5b8680fd5b508234610c3b5782600319360112610c3b575035906001600160a01b038216820361047457610c3260209260243590612196565b91519115825250f35b80fd5b5050346104ea57816003193601126104ea576020906001549051908152f35b508234610c3b5782600319360112610c3b575035906001600160a01b038216820361047457610c9160209260243590612385565b90519015158152f35b5050346104ea57816003193601126104ea576020906005549051908152f35b50903461026e578260031936011261026e57610cd36128f7565b6000199081835414610d63575b916060917f98ccebae89f16f152c93c6a0cdc0d99913b9e62a9fc612471c21ffc299c389fd937f000000000000000000000000000000000000000000000000000000000000000092610d48731e2d8f84605d32a2cbf302e30bfd2387badf35dd938486612eba565b546001600160a01b038251941684526020840152820152a180f35b805163040f1f6d60e11b8152602090731e2d8f84605d32a2cbf302e30bfd2387badf35dd82828781845afa918215610ec1578792610e92575b5086929192916001600160a01b0391827f000000000000000000000000000000000000000000000000000000000000000016935b858110610de3575b505050505050610ce0565b86516378ed5d1f60e01b81528981018290528381602481865afa908115610e8857869186918d91610e5b575b501614610e2457610e1f906127df565b610dd0565b8855508593507f98ccebae89f16f152c93c6a0cdc0d99913b9e62a9fc612471c21ffc299c389fd9250606091503890508080610dd8565b610e7b9150863d8811610e81575b610e738183612085565b8101906120bd565b38610e0f565b503d610e69565b88513d8d823e3d90fd5b9091508281813d8311610eba575b610eaa8183612085565b8101031261047457519038610d9c565b503d610ea0565b84513d89823e3d90fd5b50903461026e57602036600319011261026e578135906001600160a01b039384831692838103610474576387a211a2600c528152610f166020600c2054610f106129e4565b90612d2e565b9382610f29610f236122c0565b87612d2e565b946024610f48600154610f41600254600354906120dc565b9089612d9c565b938351998a938492637d6af07960e01b84528301527f0000000000000000000000000000000000000000000000000000000000000000165afa8015610fe9576060968391610fba575b5084610faa575050915b81519384526020840152820152f35b610fb49250612d59565b91610f9b565b610fda9150843d8611610fe2575b610fd28183612085565b810190612180565b905038610f91565b503d610fc8565b83513d84823e3d90fd5b50903461026e57606036600319011261026e576001600160a01b0392602435848116843581830361047457604435968716928388036104745784549760ff8916156111035760ff1980991686558433036110be575b611051836129b5565b9788156110b05750916110798860209a959361037f846001999761107485612473565b612c45565b865190888252898201527ffbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db873392a46103de612a1b565b875163e3e8d29760e01b8152fd5b33602052637f5e9f20600c528086526034600c208054600181016110e4575b5050611048565b8085116110f757849003905538806110dd565b896313be252b8952601cfd5b86516306fda65d60e31b81528890fd5b509134610c3b5760603660031901126104745782356001600160a01b038082168083036104745760243590828216918281036104745785549360443560ff8616156113375760ff199586168855337f0000000000000000000000000000000000000000000000000000000000000000831614611199578851632cac670b60e21b81528a90fd5b80999394959698979915611327576111b360001984612196565b905015611317576111cf6111d4916111c96122c0565b90612d59565b612981565b926111ec6111e460025486612d2e565b809a85612cc4565b899260035480611251575b60208b8b8e60018d8d8d7f73872d6d3374a1876ef7909cf7f3224e3c5c43971c31d778cf6b6caadb586d8760808f8f611230818c6120dc565b908a519283528b8d8401528a8301526060820152a382541617905551908152f35b602091929394506112629086612d2e565b93895193848092630b60d15f60e31b82527f0000000000000000000000000000000000000000000000000000000000000000165afa91821561130d5793836112e08c9997957f73872d6d3374a1876ef7909cf7f3224e3c5c43971c31d778cf6b6caadb586d879560809560209f9960019c9a926112ed575b50612cc4565b9395979a819597506111f7565b60206113069293503d8111610e8157610e738183612085565b90386112da565b88513d8c823e3d90fd5b875163c381cd1960e01b81528490fd5b875163f3934a2360e01b81528490fd5b88516306fda65d60e31b81528a90fd5b5050346104ea57816003193601126104ea576001600160a01b036020925460081c169051908152f35b509134610c3b5781600319360112610c3b5782356001600160a01b038116810361047457602435916113a28333612b42565b6387a211a2600c5233815260209485600c2090815490818611611404575084900390555282600c208181540190558252600c5160601c337fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8480a35160018152f35b63f4d678b88452601cfd5b503461026e57602036600319011261026e5780359061142c6128f7565b670de0b6b3a76400008210801561147c575b6102d85750907faeba5a6c40a8ac138134bff1aaa65debf25971188a58804bad717f82f0ec131691600254908060025582519182526020820152a180f35b50670ff59ee833b30000821161143e565b5050346104ea57816003193601126104ea576020906003549051908152f35b503461026e57602036600319011261026e573560055481101561026e576001600160a01b03906005602094527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db00154169051908152f35b50903461026e578260031936011261026e5782815180936395d89b4160e01b8252816001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165afa9182156116405783926115c0575b6107d983836115b66027825180947f4379674c503a200000000000000000000000000000000000000000000000000060208301526115a68151809260208686019101611f66565b8101036007810185520183612085565b5191829182611f89565b9091503d8084833e6115d28183612085565b8101906020818303126104c75780519067ffffffffffffffff8211610812570181601f820112156104c75780519361160985612120565b9261161685519485612085565b85845260208684010111610c3b57506107d9936116399160208085019101611f66565b903861155f565b81513d85823e3d90fd5b5050346104ea57816003193601126104ea576020906002549051908152f35b50903461026e5761012036600319011261026e57602435926001600160a01b0380851684358187036104c75760c03660431901126104c75761010495863567ffffffffffffffff81116107f2576116c39036908301611fb5565b94909786549560ff8716156119425760ff1996871688556116e385612981565b998a156119325781611840575b5050506116fc83612e3a565b6e22d473030f116ddee9f6b43ac78ba3803b15610a4a57608488928385938c519687958694631b63c28b60e11b8652338b8701523060248701521660448501527f00000000000000000000000000000000000000000000000000000000000000001660648401525af1801561183657908691611821575b50506805345cdf77eb68f44c54156117d8575b50906001929161179887602099612bd7565b6117a181612789565b855190815286888201527fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7863392a36103de612a1b565b96956103e719810190811161180e576020975090611798600194939280986118026103e889612bd7565b99505050909192611786565b634e487b7160e01b855260118852602485fd5b8161182b91612085565b610812578438611773565b87513d88823e3d90fd5b6e22d473030f116ddee9f6b43ac78ba391823b1561192e578a516302b67b5760e41b81523387820152936044358681169081900361192657602486015260643586811680910361192657604486015260843565ffffffffffff9081811680910361192a57606487015260a43590811680910361192657608486015260c43586811680910361192657856118f281958e97958395899560a486015260e43560c486015261010060e48601528401916120ff565b03925af1801561191c57611908575b80806116f0565b8661191591979297612085565b9438611901565b88513d89823e3d90fd5b8b80fd5b8c80fd5b8980fd5b895163f1ca48b160e01b81528590fd5b88516306fda65d60e31b81528490fd5b509190346104ea5760203660031901126104ea5781549060ff8216156119b15760ff1991821683556006546001600160a01b031633036119a2575061199a6001929335612789565b610258612a1b565b51639f91a89160e01b81528390fd5b516306fda65d60e31b81528390fd5b503461026e57602036600319011261026e57356001600160a01b038116810361026e576020926338377508600c525281600c20549051908152f35b503461026e57602036600319011261026e57356001600160a01b038116810361026e576020926387a211a2600c525281600c20549051908152f35b5050346104ea57816003193601126104ea57602090516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b5050346104ea57816003193601126104ea57602090516e22d473030f116ddee9f6b43ac78ba38152f35b5050346104ea57816003193601126104ea57602090517f00000000000000000000000000000000000000000000000000000000000000008152f35b508290346104ea5760203660031901126104ea5735906001600160a01b0382168203610c3b5750611b139060001990612196565b82519182526020820152f35b5050346104ea57816003193601126104ea57602090611b3c6122c0565b9051908152f35b5050346104ea57816003193601126104ea57602090516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b5050346104ea57816003193601126104ea576020906001600160a01b03600654169051908152f35b5050346104ea57816003193601126104ea57602090611b3c6129e4565b5050346104ea57816003193601126104ea5760209060a0611beb61213c565b8381519101208251907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8252848201527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc683820152466060820152306080820152209051908152f35b509134610c3b5780600319360112610c3b5760208251809463313ce56760e01b8252816001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165afa928315611cff578193611cbe575b60208460ff855191168152f35b9092506020813d8211611cf7575b81611cd960209383612085565b8101031261026e57519160ff83168303610c3b575060ff6020611cb1565b3d9150611ccc565b509051903d90823e3d90fd5b509190346104ea57806003193601126104ea578235906001600160a01b0390818316808403610812576024359280841684036107f257611d496128f7565b7f00000000000000000000000000000000000000000000000000000000000000001614611d89575090611d8691611d803083612f03565b91612e71565b80f35b51630eaba6c760e11b81528490fd5b509134610c3b576060366003190112610c3b576001600160a01b038335818116929183820361026e57602435908116810361026e5760443591611ddb8382612b42565b60601b92602096338852600c94637f5e9f208117865260348620805460018101611e5a575b50506387a211a217855287852090815490818611611404575084900390555284822081815401905584525160601c907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8480a35160018152f35b808711611e715786900390556387a211a238611e00565b836313be252b8652601cfd5b5050346104ea57816003193601126104ea576020906805345cdf77eb68f44c549051908152f35b503461026e578160031936011261026e57356001600160a01b038116810361026e5791602092602435908452637f5e9f20600c52338252806034600c20558152602c5160601c907f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925843392a35160018152f35b5050346104ea57816003193601126104ea576107d990611f3561213c565b905191829182611f89565b8490346104ea57816003193601126104ea576001600160a01b036020925460081c168152f35b60005b838110611f795750506000910152565b8181015183820152602001611f69565b60409160208252611fa98151809281602086015260208686019101611f66565b601f01601f1916010190565b9181601f840112156104745782359167ffffffffffffffff8311610474576020838186019501011161047457565b9181601f840112156104745782359167ffffffffffffffff8311610474576020808501948460051b01011161047457565b90815180825260208080930193019160005b828110612034575050505090565b83516001600160a01b031685529381019392810192600101612026565b90815180825260208080930193019160005b828110612071575050505090565b835185529381019392810192600101612063565b90601f8019910116810190811067ffffffffffffffff8211176120a757604052565b634e487b7160e01b600052604160045260246000fd5b9081602091031261047457516001600160a01b03811681036104745790565b919082018092116120e957565b634e487b7160e01b600052601160045260246000fd5b908060209392818452848401376000828201840152601f01601f1916010190565b67ffffffffffffffff81116120a757601f01601f191660200190565b6121466012612120565b906121546040519283612085565b601282527f4379676e75733a20436f6c6c61746572616c00000000000000000000000000006020830152565b9190826040910312610474576020825192015190565b906001600160a01b03918281168015801561227a575b6122685760001983146121e2575b506121de92506387a211a2600c526000526121d96020600c20546129b5565b612283565b9091565b60409192506024825180958193637d6af07960e01b835260048301527f0000000000000000000000000000000000000000000000000000000000000000165afa801561225c576121de9260009161223c575b5090386121ba565b612254915060403d8111610fe257610fd28183612085565b905038612234565b6040513d6000823e3d90fd5b604051633801164f60e21b8152600490fd5b503081146121ac565b6122926122aa91610f106122c0565b6001546122a4600254600354906120dc565b91612d9c565b8181106122b8570390600090565b600092910390565b604051906317e5a1a160e01b82526020826024816001600160a01b03807f00000000000000000000000000000000000000000000000000000000000000001660048301527f0000000000000000000000000000000000000000000000000000000000000000165afa91821561225c57600092612352575b50811561234057565b60405163586ad3b960e11b8152600490fd5b90916020823d821161237d575b8161236c60209383612085565b81010312610c3b5750519038612337565b3d915061235f565b9190916387a211a2600c526000928184526020600c205490818111801561246b575b612466578103908111612452579060406123c26024936129b5565b91815193848092637d6af07960e01b82526001600160a01b0380911660048301527f0000000000000000000000000000000000000000000000000000000000000000165afa9182156124475761241f939492612425575b50612283565b90501590565b61243e91925060403d8111610fe257610fd28183612085565b90509038612419565b6040513d86823e3d90fd5b634e487b7160e01b84526011600452602484fd5b505050565b5080156123a7565b60045490731e2d8f84605d32a2cbf302e30bfd2387badf35dd91823b1561047457604051630ad58d2f60e01b815260048101919091526024810191909152306044820152906000908290818381606481015b03925af1801561225c576124d65750565b60006124e191612085565b565b90815480825260208092019260005281600020916000905b828210612509575050505090565b83546001600160a01b0316855293840193600193840193909101906124fb565b67ffffffffffffffff81116120a75760051b60200190565b80518210156125555760209160051b010190565b634e487b7160e01b600052603260045260246000fd5b90918281527f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83116104745760209260051b809284830137010190565b9490939192948415612781576125bd85612529565b946125cb6040519687612085565b808652601f1993846125dc83612529565b019260209336858a013760005b88518110156126075760019030612600828c612541565b52016125e9565b509091929394959697733ef3d8ba38ebe18db133cec108f4d14ce00dd9ae97883b15610474579061265a99979593969492916040519a8b996301c7ba5760e61b8b52608060048c015260848b0190612014565b938560031995868c82030160248d015282815201939060005b81811061274357505050908291848a61269295030160448b015261256b565b908682030160648701528281528181016005908385831b8401019680966000945b8786106126db575050505050505050509181600081819503925af1801561225c576124d65750565b91939597995091939597828282030185528935601e198536030181121561047457840188810191903567ffffffffffffffff81116104745780881b360383136104745761272d8a92839260019561256b565b9b01950196019396949290918b999896926126b3565b918096989a97999b50949092939435906001600160a01b0382168092036104745789816001938293520196019101918c9a9896999795949392612673565b505050505050565b60045490731e2d8f84605d32a2cbf302e30bfd2387badf35dd91823b1561047457604051638dbdbe6d60e01b815260048101919091526024810191909152306044820152906000908290818381606481016124c5565b60001981146120e95760010190565b356001600160a01b03811681036104745790565b9190916001600160a01b038082161561285057600093845b8151811015612848578061283e8786866128376128439688612541565b5116612eba565b6127df565b61281a565b505050509050565b50509050565b91908110156125555760051b0190565b91906001600160a01b0392838116156128f1577f0000000000000000000000000000000000000000000000000000000000000000841660005b8481106128ae57505050505050565b8082876128c76128c26128d2958a8a612856565b6127ee565b16036128d7576127df565b61289f565b61283e600019856128ec6128c2858b8b612856565b612eba565b50505050565b6040516303e1469160e61b81526001600160a01b03906020816004817f000000000000000000000000000000000000000000000000000000000000000086165afa90811561225c57600091612963575b5016330361295157565b6040516331d63ad160e11b8152600490fd5b61297b915060203d8111610e8157610e738183612085565b38612947565b6805345cdf77eb68f44c54600091908061299b5750905090565b6001600160a01b036129b2935460081c1691612d9c565b90565b6805345cdf77eb68f44c546000816129cc57505090565b916001600160a01b036129b2935460081c1690612d9c565b6805345cdf77eb68f44c5460009080612a05575050670de0b6b3a764000090565b6001600160a01b036129b2925460081c16612d59565b60048054604080516393f1a40b60e01b815292830191909152306024830152908181604481731e2d8f84605d32a2cbf302e30bfd2387badf35dd5afa908115612b3757600091612aea575b507f3145c2b643834867f2c0a8cac8a48218307687911615c0ed01412a09dd163b66916001600160a01b03612a9c602093612e3a565b916000547fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff008560081b169116176000555191168152a1565b908282813d8311612b30575b612b008183612085565b81010312610c3b5750517f3145c2b643834867f2c0a8cac8a48218307687911615c0ed01412a09dd163b66612a66565b503d612af6565b82513d6000823e3d90fd5b906001600160a01b033081841614612466577f00000000000000000000000000000000000000000000000000000000000000001691823b15610474576000809360046040518096819363a6afed9560e01b83525af192831561225c57612bad93612bc6575b50612385565b15612bb457565b604051631ba566ff60e01b8152600490fd5b6000612bd191612085565b38612ba7565b6805345cdf77eb68f44c805490838201918210612c3757556387a211a2600c526000526020600c20818154019055602052600c5160601c60007fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef602080a3565b63e5cfe9576000526004601cfd5b90612c508183612b42565b6387a211a2600c52816000526020600c2091825491828111612cb657600093816001600160a01b03940390556805345cdf77eb68f44c8181540390558352167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef602083a3565b63f4d678b86000526004601cfd5b91906387a211a28360601b17600c526020600c208054808411612cb65783900390556000526020600c208181540190556020526001600160a01b03600c5160601c91167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef602080a3565b90806000190482118102612d4b57670de0b6b3a764000091020490565b63bac65e5b6000526004601cfd5b670de0b6b3a7640000907812725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f218111820215830215612d8e57020490565b637c5f487d6000526004601cfd5b91600092828102926000198183099184831085018084039314612e1e5782841115612e1157908391099060018386038416808080960460028082600302188083028203028083028203028083028203028083028203028083028203028092029003029703040190848311900302920304170290565b63ae47f70286526004601cfd5b505050918215612e2d57500490565b63ae47f70290526004601cfd5b74010000000000000000000000000000000000000000811015612e63576001600160a01b031690565b6335278d126000526004601cfd5b60109260209260145260345260446000938480936fa9059cbb00000000000000000000000082525af13d156001835114171615612ead57603452565b6390b8ec1890526004601cfd5b60109260209260145260345260446000938480936f095ea7b300000000000000000000000082525af13d156001835114171615612ef657603452565b633e3f8f7390526004601cfd5b602460106020939284936014526f70a082310000000000000000000000006000525afa601f3d1116602051029056fea164736f6c6343000811000aa164736f6c6343000811000a6101208060405234620001f4576000805460ff19166001178155635a412e5360e01b825260a09190828260048184335af1918215620001e757818091818091819662000133575b505061010094855260e05260805260c0528252670c7d713b49da0000600155670e4b4b8af6a70000600255662386f26fc1000060035560001960045560405190612f3f92836200020f843960805183818161058c01528181610f5f0152818161116301528181612200015281816123e50152612b560152518281816105d0015281816112750152612915015260c05182818161038101528181610d0801528181610dae0152818161152f0152818161173e01528181611a5601528181611c8101528181611d4b015281816122df015261287b015260e051828181611b63015261230601525181611abc0152f35b94509450505050823d8411620001df575b601f8101601f191682016001600160401b03811183821017620001cb578491839160405281010312620001c7578051916001600160a01b038084168403620001c7576200019460208401620001f9565b92620001a360408201620001f9565b9260608201519283168303620001c457506080015193929190388062000046565b80fd5b5080fd5b634e487b7160e01b84526041600452602484fd5b503d62000144565b50604051903d90823e3d90fd5b600080fd5b51906001600160a01b0382168203620001f45756fe6080604081815260048036101561001557600080fd5b600092833560e01c90816301e1d11414611f405750806306fdde0314611f17578063095ea7b314611ea457806318160ddd14611e7d57806323b872dd14611d98578063258836fe14611d0b578063313ce56714611c545780633644e51514611bcc5780633ba0b9a914611baf5780634bdaeac114611b875780634ecde84914611b435780635130362214611b1f5780635ec88c7914611adf5780635f77f89114611aa45780636afdd85014611a7a5780636f307dc314611a3657806370a08231146119fb5780637ecebe00146119c057806381f5b6f4146119525780638808e71c146116695780638c765e941461164a57806395d89b41146115035780639ee80c5c146114ac578063a36a36301461148d578063a84310811461140f578063a9059cbb14611370578063ad7a672f14611347578063b4ebbda414611113578063ba08765214610ff3578063bb3f673614610ecb578063bcbc332014610cb9578063bf199e6214610c9a578063cc1f8ffa14610c5d578063cea55f5714610c3e578063cf8c8c4414610bfe578063d505accf14610a4e578063d959a01614610890578063dcc3e06e14610861578063dd62ed3e14610816578063ddb19a34146105f4578063e8a24e1d146105b0578063e99a6ac71461056c578063eb6a887d146104ee578063f1a392da146104cb578063fa14fdc2146102e6578063fef0bec8146102725763fff6cae91461022857600080fd5b3461026e578260031936011261026e5782549160ff831615610261578360018460ff198091168355610258612a1b565b82541617815580f35b516306fda65d60e31b8152fd5b8280fd5b503461026e57602036600319011261026e5780359061028f6128f7565b67016345785d8a000082116102d85750907f87272249f1056ec16a68dd9255101358246dd26a796e2f0bc6b3a0ad02f2de4191600354908060035582519182526020820152a180f35b825163058b4cb360e11b8152fd5b50903461026e57606036600319011261026e578135926001600160a01b03928385168503610474576024359060443567ffffffffffffffff81116104c7576103319036908301611fb5565b84549360ff8516156104b75760ff19988986168755869882156104a7576805345cdf77eb68f44c549660081c16610369818885612d9c565b96830961048b575b816103a59161037f82612473565b7f0000000000000000000000000000000000000000000000000000000000000000612e71565b816103f8575b5050506387a211a2600c523083526020600c20549182106103ea57506020946103d660019230612c45565b6103de612a1b565b82541617905551908152f35b83516337eed4ab60e11b8152fd5b60209293975061042d8751948593849363989bebcf60e01b8552338c86015260248501526060604485015260648401916120ff565b038186335af190811561048157839161044b575b50933880806103ab565b90506020813d8211610479575b8161046560209383612085565b81010312610474575138610441565b600080fd5b3d9150610458565b84513d85823e3d90fd5b94600101801561049b5794610371565b8463ae47f7028852601cfd5b8851632a28590560e01b81528690fd5b86516306fda65d60e31b81528490fd5b8380fd5b5050346104ea57816003193601126104ea576020906007549051908152f35b5080fd5b503461026e57602036600319011261026e5780359061050b6128f7565b670b1a2bc2ec5000008210801561055b575b6102d85750907ff25d2243e4128e51523d8537f04f18c3e6390bbfc6323390a9a8e59b921ce04691600154908060015582519182526020820152a180f35b50670dbd2fc137a30000821161051d565b5050346104ea57816003193601126104ea57602090516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b5050346104ea57816003193601126104ea57602090516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b503461026e57606036600319011261026e5767ffffffffffffffff9281358481116104ea576106269036908401611fe3565b6024959195358281116104c7576106409036908601611fe3565b9092604435908111610812576106599036908701611fe3565b93909285549660ff8816156108045760ff1997881687558054731e2d8f84605d32a2cbf302e30bfd2387badf35dd90813b15610800578a51630c7e663b60e11b81529283019081523060208201528891839182908490829060400103925af180156107f6576107dd575b506106d0959697986125a8565b8251926106e9846106e28160056124e3565b0385612085565b8351916106f583612529565b9261070283519485612085565b808452610711601f1991612529565b01366020850137805b855181101561075457600190610743306001600160a01b0361073c848b612541565b5116612f03565b61074d8287612541565b520161071a565b5060016107d9939294426007558551606081527fc5252ef97d1913a56f636a69fa3df5248e702e398f142464175250bb7e9ec1e06107a6610798606084018b612014565b838103602085015287612051565b9142898201528033930390a28254161790556107cb8351948486958652850190612014565b908382036020850152612051565b0390f35b6107e8878092612085565b6107f257386106c3565b8580fd5b89513d89823e3d90fd5b8880fd5b88516306fda65d60e31b8152fd5b8480fd5b503461026e578160031936011261026e57356001600160a01b03928382168203610474576024359384168403610474576020938452637f5e9f20600c52526034600c20549051908152f35b5050346104ea57816003193601126104ea5760209051731e2d8f84605d32a2cbf302e30bfd2387badf35dd8152f35b50903461026e578060031936011261026e578135916001600160a01b039081841691828503610474576024359167ffffffffffffffff808411610a4a576108dd6109159436908401611fe3565b81819692956108ea6128f7565b60065416996109108a5161090a816109038160056124e3565b0382612085565b8c612802565b612866565b847fffffffffffffffffffffffff000000000000000000000000000000000000000060065416176006558311610a3757680100000000000000008311610a375750600554826005558083106109f7575b5060058652855b8281106109bc57867fddd57fd5151dbcab176b5c0606de5d7aa76cbbce6df7e771c62b17b56e97486c876060888881519384526020840152820152806109b66060820160056124e3565b0390a180f35b60019060206109ca846127ee565b930192817f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db001550161096c565b827f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db091820191015b818110610a2c5750610965565b878155600101610a1f565b634e487b7160e01b875260419052602486fd5b8780fd5b509190346104ea5760e03660031901126104ea578235906001600160a01b0380831680930361047457602435908116809103610474576064359260443560843560ff81168103610bfa57610aa061213c565b91825160208094012091874211610bee5786519765383775081901600e52858a5284600c20918254917f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8b52868b01958652828b8a8c82017fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc681528b606084019a468c527f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9608086019530875260a08120602e525252528688525260a08b015260c08a20604e526042602c208b5260ff16855260a435885260c435606052848060808c60015afa90863d5103610be257019055777f5e9f200000000000000000000000000000000000000000851786526034602c20557f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259190a3528060605280f35b8b63ddafbaef8c52601cfd5b89631a15a3cc8a52601cfd5b8680fd5b508234610c3b5782600319360112610c3b575035906001600160a01b038216820361047457610c3260209260243590612196565b91519115825250f35b80fd5b5050346104ea57816003193601126104ea576020906001549051908152f35b508234610c3b5782600319360112610c3b575035906001600160a01b038216820361047457610c9160209260243590612385565b90519015158152f35b5050346104ea57816003193601126104ea576020906005549051908152f35b50903461026e578260031936011261026e57610cd36128f7565b6000199081835414610d63575b916060917f98ccebae89f16f152c93c6a0cdc0d99913b9e62a9fc612471c21ffc299c389fd937f000000000000000000000000000000000000000000000000000000000000000092610d48731e2d8f84605d32a2cbf302e30bfd2387badf35dd938486612eba565b546001600160a01b038251941684526020840152820152a180f35b805163040f1f6d60e11b8152602090731e2d8f84605d32a2cbf302e30bfd2387badf35dd82828781845afa918215610ec1578792610e92575b5086929192916001600160a01b0391827f000000000000000000000000000000000000000000000000000000000000000016935b858110610de3575b505050505050610ce0565b86516378ed5d1f60e01b81528981018290528381602481865afa908115610e8857869186918d91610e5b575b501614610e2457610e1f906127df565b610dd0565b8855508593507f98ccebae89f16f152c93c6a0cdc0d99913b9e62a9fc612471c21ffc299c389fd9250606091503890508080610dd8565b610e7b9150863d8811610e81575b610e738183612085565b8101906120bd565b38610e0f565b503d610e69565b88513d8d823e3d90fd5b9091508281813d8311610eba575b610eaa8183612085565b8101031261047457519038610d9c565b503d610ea0565b84513d89823e3d90fd5b50903461026e57602036600319011261026e578135906001600160a01b039384831692838103610474576387a211a2600c528152610f166020600c2054610f106129e4565b90612d2e565b9382610f29610f236122c0565b87612d2e565b946024610f48600154610f41600254600354906120dc565b9089612d9c565b938351998a938492637d6af07960e01b84528301527f0000000000000000000000000000000000000000000000000000000000000000165afa8015610fe9576060968391610fba575b5084610faa575050915b81519384526020840152820152f35b610fb49250612d59565b91610f9b565b610fda9150843d8611610fe2575b610fd28183612085565b810190612180565b905038610f91565b503d610fc8565b83513d84823e3d90fd5b50903461026e57606036600319011261026e576001600160a01b0392602435848116843581830361047457604435968716928388036104745784549760ff8916156111035760ff1980991686558433036110be575b611051836129b5565b9788156110b05750916110798860209a959361037f846001999761107485612473565b612c45565b865190888252898201527ffbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db873392a46103de612a1b565b875163e3e8d29760e01b8152fd5b33602052637f5e9f20600c528086526034600c208054600181016110e4575b5050611048565b8085116110f757849003905538806110dd565b896313be252b8952601cfd5b86516306fda65d60e31b81528890fd5b509134610c3b5760603660031901126104745782356001600160a01b038082168083036104745760243590828216918281036104745785549360443560ff8616156113375760ff199586168855337f0000000000000000000000000000000000000000000000000000000000000000831614611199578851632cac670b60e21b81528a90fd5b80999394959698979915611327576111b360001984612196565b905015611317576111cf6111d4916111c96122c0565b90612d59565b612981565b926111ec6111e460025486612d2e565b809a85612cc4565b899260035480611251575b60208b8b8e60018d8d8d7f73872d6d3374a1876ef7909cf7f3224e3c5c43971c31d778cf6b6caadb586d8760808f8f611230818c6120dc565b908a519283528b8d8401528a8301526060820152a382541617905551908152f35b602091929394506112629086612d2e565b93895193848092630b60d15f60e31b82527f0000000000000000000000000000000000000000000000000000000000000000165afa91821561130d5793836112e08c9997957f73872d6d3374a1876ef7909cf7f3224e3c5c43971c31d778cf6b6caadb586d879560809560209f9960019c9a926112ed575b50612cc4565b9395979a819597506111f7565b60206113069293503d8111610e8157610e738183612085565b90386112da565b88513d8c823e3d90fd5b875163c381cd1960e01b81528490fd5b875163f3934a2360e01b81528490fd5b88516306fda65d60e31b81528a90fd5b5050346104ea57816003193601126104ea576001600160a01b036020925460081c169051908152f35b509134610c3b5781600319360112610c3b5782356001600160a01b038116810361047457602435916113a28333612b42565b6387a211a2600c5233815260209485600c2090815490818611611404575084900390555282600c208181540190558252600c5160601c337fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8480a35160018152f35b63f4d678b88452601cfd5b503461026e57602036600319011261026e5780359061142c6128f7565b670de0b6b3a76400008210801561147c575b6102d85750907faeba5a6c40a8ac138134bff1aaa65debf25971188a58804bad717f82f0ec131691600254908060025582519182526020820152a180f35b50670ff59ee833b30000821161143e565b5050346104ea57816003193601126104ea576020906003549051908152f35b503461026e57602036600319011261026e573560055481101561026e576001600160a01b03906005602094527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db00154169051908152f35b50903461026e578260031936011261026e5782815180936395d89b4160e01b8252816001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165afa9182156116405783926115c0575b6107d983836115b66027825180947f4379674c503a200000000000000000000000000000000000000000000000000060208301526115a68151809260208686019101611f66565b8101036007810185520183612085565b5191829182611f89565b9091503d8084833e6115d28183612085565b8101906020818303126104c75780519067ffffffffffffffff8211610812570181601f820112156104c75780519361160985612120565b9261161685519485612085565b85845260208684010111610c3b57506107d9936116399160208085019101611f66565b903861155f565b81513d85823e3d90fd5b5050346104ea57816003193601126104ea576020906002549051908152f35b50903461026e5761012036600319011261026e57602435926001600160a01b0380851684358187036104c75760c03660431901126104c75761010495863567ffffffffffffffff81116107f2576116c39036908301611fb5565b94909786549560ff8716156119425760ff1996871688556116e385612981565b998a156119325781611840575b5050506116fc83612e3a565b6e22d473030f116ddee9f6b43ac78ba3803b15610a4a57608488928385938c519687958694631b63c28b60e11b8652338b8701523060248701521660448501527f00000000000000000000000000000000000000000000000000000000000000001660648401525af1801561183657908691611821575b50506805345cdf77eb68f44c54156117d8575b50906001929161179887602099612bd7565b6117a181612789565b855190815286888201527fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7863392a36103de612a1b565b96956103e719810190811161180e576020975090611798600194939280986118026103e889612bd7565b99505050909192611786565b634e487b7160e01b855260118852602485fd5b8161182b91612085565b610812578438611773565b87513d88823e3d90fd5b6e22d473030f116ddee9f6b43ac78ba391823b1561192e578a516302b67b5760e41b81523387820152936044358681169081900361192657602486015260643586811680910361192657604486015260843565ffffffffffff9081811680910361192a57606487015260a43590811680910361192657608486015260c43586811680910361192657856118f281958e97958395899560a486015260e43560c486015261010060e48601528401916120ff565b03925af1801561191c57611908575b80806116f0565b8661191591979297612085565b9438611901565b88513d89823e3d90fd5b8b80fd5b8c80fd5b8980fd5b895163f1ca48b160e01b81528590fd5b88516306fda65d60e31b81528490fd5b509190346104ea5760203660031901126104ea5781549060ff8216156119b15760ff1991821683556006546001600160a01b031633036119a2575061199a6001929335612789565b610258612a1b565b51639f91a89160e01b81528390fd5b516306fda65d60e31b81528390fd5b503461026e57602036600319011261026e57356001600160a01b038116810361026e576020926338377508600c525281600c20549051908152f35b503461026e57602036600319011261026e57356001600160a01b038116810361026e576020926387a211a2600c525281600c20549051908152f35b5050346104ea57816003193601126104ea57602090516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b5050346104ea57816003193601126104ea57602090516e22d473030f116ddee9f6b43ac78ba38152f35b5050346104ea57816003193601126104ea57602090517f00000000000000000000000000000000000000000000000000000000000000008152f35b508290346104ea5760203660031901126104ea5735906001600160a01b0382168203610c3b5750611b139060001990612196565b82519182526020820152f35b5050346104ea57816003193601126104ea57602090611b3c6122c0565b9051908152f35b5050346104ea57816003193601126104ea57602090516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b5050346104ea57816003193601126104ea576020906001600160a01b03600654169051908152f35b5050346104ea57816003193601126104ea57602090611b3c6129e4565b5050346104ea57816003193601126104ea5760209060a0611beb61213c565b8381519101208251907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8252848201527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc683820152466060820152306080820152209051908152f35b509134610c3b5780600319360112610c3b5760208251809463313ce56760e01b8252816001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165afa928315611cff578193611cbe575b60208460ff855191168152f35b9092506020813d8211611cf7575b81611cd960209383612085565b8101031261026e57519160ff83168303610c3b575060ff6020611cb1565b3d9150611ccc565b509051903d90823e3d90fd5b509190346104ea57806003193601126104ea578235906001600160a01b0390818316808403610812576024359280841684036107f257611d496128f7565b7f00000000000000000000000000000000000000000000000000000000000000001614611d89575090611d8691611d803083612f03565b91612e71565b80f35b51630eaba6c760e11b81528490fd5b509134610c3b576060366003190112610c3b576001600160a01b038335818116929183820361026e57602435908116810361026e5760443591611ddb8382612b42565b60601b92602096338852600c94637f5e9f208117865260348620805460018101611e5a575b50506387a211a217855287852090815490818611611404575084900390555284822081815401905584525160601c907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8480a35160018152f35b808711611e715786900390556387a211a238611e00565b836313be252b8652601cfd5b5050346104ea57816003193601126104ea576020906805345cdf77eb68f44c549051908152f35b503461026e578160031936011261026e57356001600160a01b038116810361026e5791602092602435908452637f5e9f20600c52338252806034600c20558152602c5160601c907f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925843392a35160018152f35b5050346104ea57816003193601126104ea576107d990611f3561213c565b905191829182611f89565b8490346104ea57816003193601126104ea576001600160a01b036020925460081c168152f35b60005b838110611f795750506000910152565b8181015183820152602001611f69565b60409160208252611fa98151809281602086015260208686019101611f66565b601f01601f1916010190565b9181601f840112156104745782359167ffffffffffffffff8311610474576020838186019501011161047457565b9181601f840112156104745782359167ffffffffffffffff8311610474576020808501948460051b01011161047457565b90815180825260208080930193019160005b828110612034575050505090565b83516001600160a01b031685529381019392810192600101612026565b90815180825260208080930193019160005b828110612071575050505090565b835185529381019392810192600101612063565b90601f8019910116810190811067ffffffffffffffff8211176120a757604052565b634e487b7160e01b600052604160045260246000fd5b9081602091031261047457516001600160a01b03811681036104745790565b919082018092116120e957565b634e487b7160e01b600052601160045260246000fd5b908060209392818452848401376000828201840152601f01601f1916010190565b67ffffffffffffffff81116120a757601f01601f191660200190565b6121466012612120565b906121546040519283612085565b601282527f4379676e75733a20436f6c6c61746572616c00000000000000000000000000006020830152565b9190826040910312610474576020825192015190565b906001600160a01b03918281168015801561227a575b6122685760001983146121e2575b506121de92506387a211a2600c526000526121d96020600c20546129b5565b612283565b9091565b60409192506024825180958193637d6af07960e01b835260048301527f0000000000000000000000000000000000000000000000000000000000000000165afa801561225c576121de9260009161223c575b5090386121ba565b612254915060403d8111610fe257610fd28183612085565b905038612234565b6040513d6000823e3d90fd5b604051633801164f60e21b8152600490fd5b503081146121ac565b6122926122aa91610f106122c0565b6001546122a4600254600354906120dc565b91612d9c565b8181106122b8570390600090565b600092910390565b604051906317e5a1a160e01b82526020826024816001600160a01b03807f00000000000000000000000000000000000000000000000000000000000000001660048301527f0000000000000000000000000000000000000000000000000000000000000000165afa91821561225c57600092612352575b50811561234057565b60405163586ad3b960e11b8152600490fd5b90916020823d821161237d575b8161236c60209383612085565b81010312610c3b5750519038612337565b3d915061235f565b9190916387a211a2600c526000928184526020600c205490818111801561246b575b612466578103908111612452579060406123c26024936129b5565b91815193848092637d6af07960e01b82526001600160a01b0380911660048301527f0000000000000000000000000000000000000000000000000000000000000000165afa9182156124475761241f939492612425575b50612283565b90501590565b61243e91925060403d8111610fe257610fd28183612085565b90509038612419565b6040513d86823e3d90fd5b634e487b7160e01b84526011600452602484fd5b505050565b5080156123a7565b60045490731e2d8f84605d32a2cbf302e30bfd2387badf35dd91823b1561047457604051630ad58d2f60e01b815260048101919091526024810191909152306044820152906000908290818381606481015b03925af1801561225c576124d65750565b60006124e191612085565b565b90815480825260208092019260005281600020916000905b828210612509575050505090565b83546001600160a01b0316855293840193600193840193909101906124fb565b67ffffffffffffffff81116120a75760051b60200190565b80518210156125555760209160051b010190565b634e487b7160e01b600052603260045260246000fd5b90918281527f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83116104745760209260051b809284830137010190565b9490939192948415612781576125bd85612529565b946125cb6040519687612085565b808652601f1993846125dc83612529565b019260209336858a013760005b88518110156126075760019030612600828c612541565b52016125e9565b509091929394959697733ef3d8ba38ebe18db133cec108f4d14ce00dd9ae97883b15610474579061265a99979593969492916040519a8b996301c7ba5760e61b8b52608060048c015260848b0190612014565b938560031995868c82030160248d015282815201939060005b81811061274357505050908291848a61269295030160448b015261256b565b908682030160648701528281528181016005908385831b8401019680966000945b8786106126db575050505050505050509181600081819503925af1801561225c576124d65750565b91939597995091939597828282030185528935601e198536030181121561047457840188810191903567ffffffffffffffff81116104745780881b360383136104745761272d8a92839260019561256b565b9b01950196019396949290918b999896926126b3565b918096989a97999b50949092939435906001600160a01b0382168092036104745789816001938293520196019101918c9a9896999795949392612673565b505050505050565b60045490731e2d8f84605d32a2cbf302e30bfd2387badf35dd91823b1561047457604051638dbdbe6d60e01b815260048101919091526024810191909152306044820152906000908290818381606481016124c5565b60001981146120e95760010190565b356001600160a01b03811681036104745790565b9190916001600160a01b038082161561285057600093845b8151811015612848578061283e8786866128376128439688612541565b5116612eba565b6127df565b61281a565b505050509050565b50509050565b91908110156125555760051b0190565b91906001600160a01b0392838116156128f1577f0000000000000000000000000000000000000000000000000000000000000000841660005b8481106128ae57505050505050565b8082876128c76128c26128d2958a8a612856565b6127ee565b16036128d7576127df565b61289f565b61283e600019856128ec6128c2858b8b612856565b612eba565b50505050565b6040516303e1469160e61b81526001600160a01b03906020816004817f000000000000000000000000000000000000000000000000000000000000000086165afa90811561225c57600091612963575b5016330361295157565b6040516331d63ad160e11b8152600490fd5b61297b915060203d8111610e8157610e738183612085565b38612947565b6805345cdf77eb68f44c54600091908061299b5750905090565b6001600160a01b036129b2935460081c1691612d9c565b90565b6805345cdf77eb68f44c546000816129cc57505090565b916001600160a01b036129b2935460081c1690612d9c565b6805345cdf77eb68f44c5460009080612a05575050670de0b6b3a764000090565b6001600160a01b036129b2925460081c16612d59565b60048054604080516393f1a40b60e01b815292830191909152306024830152908181604481731e2d8f84605d32a2cbf302e30bfd2387badf35dd5afa908115612b3757600091612aea575b507f3145c2b643834867f2c0a8cac8a48218307687911615c0ed01412a09dd163b66916001600160a01b03612a9c602093612e3a565b916000547fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff008560081b169116176000555191168152a1565b908282813d8311612b30575b612b008183612085565b81010312610c3b5750517f3145c2b643834867f2c0a8cac8a48218307687911615c0ed01412a09dd163b66612a66565b503d612af6565b82513d6000823e3d90fd5b906001600160a01b033081841614612466577f00000000000000000000000000000000000000000000000000000000000000001691823b15610474576000809360046040518096819363a6afed9560e01b83525af192831561225c57612bad93612bc6575b50612385565b15612bb457565b604051631ba566ff60e01b8152600490fd5b6000612bd191612085565b38612ba7565b6805345cdf77eb68f44c805490838201918210612c3757556387a211a2600c526000526020600c20818154019055602052600c5160601c60007fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef602080a3565b63e5cfe9576000526004601cfd5b90612c508183612b42565b6387a211a2600c52816000526020600c2091825491828111612cb657600093816001600160a01b03940390556805345cdf77eb68f44c8181540390558352167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef602083a3565b63f4d678b86000526004601cfd5b91906387a211a28360601b17600c526020600c208054808411612cb65783900390556000526020600c208181540190556020526001600160a01b03600c5160601c91167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef602080a3565b90806000190482118102612d4b57670de0b6b3a764000091020490565b63bac65e5b6000526004601cfd5b670de0b6b3a7640000907812725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f218111820215830215612d8e57020490565b637c5f487d6000526004601cfd5b91600092828102926000198183099184831085018084039314612e1e5782841115612e1157908391099060018386038416808080960460028082600302188083028203028083028203028083028203028083028203028083028203028092029003029703040190848311900302920304170290565b63ae47f70286526004601cfd5b505050918215612e2d57500490565b63ae47f70290526004601cfd5b74010000000000000000000000000000000000000000811015612e63576001600160a01b031690565b6335278d126000526004601cfd5b60109260209260145260345260446000938480936fa9059cbb00000000000000000000000082525af13d156001835114171615612ead57603452565b6390b8ec1890526004601cfd5b60109260209260145260345260446000938480936f095ea7b300000000000000000000000082525af13d156001835114171615612ef657603452565b633e3f8f7390526004601cfd5b602460106020939284936014526f70a082310000000000000000000000006000525afa601f3d1116602051029056fea164736f6c6343000811000a

Deployed Bytecode

0x608060408181526004918236101561001657600080fd5b600092833560e01c9182631b2b71e4146100de575081635a412e5314610083575063ef703c961461004657600080fd5b3461007f578160031936011261007f57602090517f4aec2a73c2736893d90326e8f53bee654f8ee8f3731f3cf0ca78000896b3b72e8152f35b5080fd5b83833461007f578160031936011261007f5760a09273ffffffffffffffffffffffffffffffffffffffff8093541692806001541692816002541691600354169254938151958652602086015284015260608301526080820152f35b9150913461023257608036600319011261023257823573ffffffffffffffffffffffffffffffffffffffff9283821680920361022e5760243584811680910361022a576044359085821680920361022657608092606435938161014360a08794610236565b338152866020820152838882015284606082015201527fffffffffffffffffffffffff00000000000000000000000000000000000000009033828a5416178955848260015416176001558160025416176002556003541617600355845581516020810191825233838201528281526101bc606082610236565b519020815161314e8082019082821067ffffffffffffffff8311176102135790829161026f8339039086f59283156102095793602094818055816001558160025581600355555191168152f35b81513d86823e3d90fd5b634e487b7160e01b885260418752602488fd5b8780fd5b8680fd5b8580fd5b8380fd5b90601f8019910116810190811067ffffffffffffffff82111761025857604052565b634e487b7160e01b600052604160045260246000fdfe6101208060405234620001f4576000805460ff19166001178155635a412e5360e01b825260a09190828260048184335af1918215620001e757818091818091819662000133575b505061010094855260e05260805260c0528252670c7d713b49da0000600155670e4b4b8af6a70000600255662386f26fc1000060035560001960045560405190612f3f92836200020f843960805183818161058c01528181610f5f0152818161116301528181612200015281816123e50152612b560152518281816105d0015281816112750152612915015260c05182818161038101528181610d0801528181610dae0152818161152f0152818161173e01528181611a5601528181611c8101528181611d4b015281816122df015261287b015260e051828181611b63015261230601525181611abc0152f35b94509450505050823d8411620001df575b601f8101601f191682016001600160401b03811183821017620001cb578491839160405281010312620001c7578051916001600160a01b038084168403620001c7576200019460208401620001f9565b92620001a360408201620001f9565b9260608201519283168303620001c457506080015193929190388062000046565b80fd5b5080fd5b634e487b7160e01b84526041600452602484fd5b503d62000144565b50604051903d90823e3d90fd5b600080fd5b51906001600160a01b0382168203620001f45756fe6080604081815260048036101561001557600080fd5b600092833560e01c90816301e1d11414611f405750806306fdde0314611f17578063095ea7b314611ea457806318160ddd14611e7d57806323b872dd14611d98578063258836fe14611d0b578063313ce56714611c545780633644e51514611bcc5780633ba0b9a914611baf5780634bdaeac114611b875780634ecde84914611b435780635130362214611b1f5780635ec88c7914611adf5780635f77f89114611aa45780636afdd85014611a7a5780636f307dc314611a3657806370a08231146119fb5780637ecebe00146119c057806381f5b6f4146119525780638808e71c146116695780638c765e941461164a57806395d89b41146115035780639ee80c5c146114ac578063a36a36301461148d578063a84310811461140f578063a9059cbb14611370578063ad7a672f14611347578063b4ebbda414611113578063ba08765214610ff3578063bb3f673614610ecb578063bcbc332014610cb9578063bf199e6214610c9a578063cc1f8ffa14610c5d578063cea55f5714610c3e578063cf8c8c4414610bfe578063d505accf14610a4e578063d959a01614610890578063dcc3e06e14610861578063dd62ed3e14610816578063ddb19a34146105f4578063e8a24e1d146105b0578063e99a6ac71461056c578063eb6a887d146104ee578063f1a392da146104cb578063fa14fdc2146102e6578063fef0bec8146102725763fff6cae91461022857600080fd5b3461026e578260031936011261026e5782549160ff831615610261578360018460ff198091168355610258612a1b565b82541617815580f35b516306fda65d60e31b8152fd5b8280fd5b503461026e57602036600319011261026e5780359061028f6128f7565b67016345785d8a000082116102d85750907f87272249f1056ec16a68dd9255101358246dd26a796e2f0bc6b3a0ad02f2de4191600354908060035582519182526020820152a180f35b825163058b4cb360e11b8152fd5b50903461026e57606036600319011261026e578135926001600160a01b03928385168503610474576024359060443567ffffffffffffffff81116104c7576103319036908301611fb5565b84549360ff8516156104b75760ff19988986168755869882156104a7576805345cdf77eb68f44c549660081c16610369818885612d9c565b96830961048b575b816103a59161037f82612473565b7f0000000000000000000000000000000000000000000000000000000000000000612e71565b816103f8575b5050506387a211a2600c523083526020600c20549182106103ea57506020946103d660019230612c45565b6103de612a1b565b82541617905551908152f35b83516337eed4ab60e11b8152fd5b60209293975061042d8751948593849363989bebcf60e01b8552338c86015260248501526060604485015260648401916120ff565b038186335af190811561048157839161044b575b50933880806103ab565b90506020813d8211610479575b8161046560209383612085565b81010312610474575138610441565b600080fd5b3d9150610458565b84513d85823e3d90fd5b94600101801561049b5794610371565b8463ae47f7028852601cfd5b8851632a28590560e01b81528690fd5b86516306fda65d60e31b81528490fd5b8380fd5b5050346104ea57816003193601126104ea576020906007549051908152f35b5080fd5b503461026e57602036600319011261026e5780359061050b6128f7565b670b1a2bc2ec5000008210801561055b575b6102d85750907ff25d2243e4128e51523d8537f04f18c3e6390bbfc6323390a9a8e59b921ce04691600154908060015582519182526020820152a180f35b50670dbd2fc137a30000821161051d565b5050346104ea57816003193601126104ea57602090516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b5050346104ea57816003193601126104ea57602090516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b503461026e57606036600319011261026e5767ffffffffffffffff9281358481116104ea576106269036908401611fe3565b6024959195358281116104c7576106409036908601611fe3565b9092604435908111610812576106599036908701611fe3565b93909285549660ff8816156108045760ff1997881687558054731e2d8f84605d32a2cbf302e30bfd2387badf35dd90813b15610800578a51630c7e663b60e11b81529283019081523060208201528891839182908490829060400103925af180156107f6576107dd575b506106d0959697986125a8565b8251926106e9846106e28160056124e3565b0385612085565b8351916106f583612529565b9261070283519485612085565b808452610711601f1991612529565b01366020850137805b855181101561075457600190610743306001600160a01b0361073c848b612541565b5116612f03565b61074d8287612541565b520161071a565b5060016107d9939294426007558551606081527fc5252ef97d1913a56f636a69fa3df5248e702e398f142464175250bb7e9ec1e06107a6610798606084018b612014565b838103602085015287612051565b9142898201528033930390a28254161790556107cb8351948486958652850190612014565b908382036020850152612051565b0390f35b6107e8878092612085565b6107f257386106c3565b8580fd5b89513d89823e3d90fd5b8880fd5b88516306fda65d60e31b8152fd5b8480fd5b503461026e578160031936011261026e57356001600160a01b03928382168203610474576024359384168403610474576020938452637f5e9f20600c52526034600c20549051908152f35b5050346104ea57816003193601126104ea5760209051731e2d8f84605d32a2cbf302e30bfd2387badf35dd8152f35b50903461026e578060031936011261026e578135916001600160a01b039081841691828503610474576024359167ffffffffffffffff808411610a4a576108dd6109159436908401611fe3565b81819692956108ea6128f7565b60065416996109108a5161090a816109038160056124e3565b0382612085565b8c612802565b612866565b847fffffffffffffffffffffffff000000000000000000000000000000000000000060065416176006558311610a3757680100000000000000008311610a375750600554826005558083106109f7575b5060058652855b8281106109bc57867fddd57fd5151dbcab176b5c0606de5d7aa76cbbce6df7e771c62b17b56e97486c876060888881519384526020840152820152806109b66060820160056124e3565b0390a180f35b60019060206109ca846127ee565b930192817f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db001550161096c565b827f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db091820191015b818110610a2c5750610965565b878155600101610a1f565b634e487b7160e01b875260419052602486fd5b8780fd5b509190346104ea5760e03660031901126104ea578235906001600160a01b0380831680930361047457602435908116809103610474576064359260443560843560ff81168103610bfa57610aa061213c565b91825160208094012091874211610bee5786519765383775081901600e52858a5284600c20918254917f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8b52868b01958652828b8a8c82017fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc681528b606084019a468c527f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9608086019530875260a08120602e525252528688525260a08b015260c08a20604e526042602c208b5260ff16855260a435885260c435606052848060808c60015afa90863d5103610be257019055777f5e9f200000000000000000000000000000000000000000851786526034602c20557f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259190a3528060605280f35b8b63ddafbaef8c52601cfd5b89631a15a3cc8a52601cfd5b8680fd5b508234610c3b5782600319360112610c3b575035906001600160a01b038216820361047457610c3260209260243590612196565b91519115825250f35b80fd5b5050346104ea57816003193601126104ea576020906001549051908152f35b508234610c3b5782600319360112610c3b575035906001600160a01b038216820361047457610c9160209260243590612385565b90519015158152f35b5050346104ea57816003193601126104ea576020906005549051908152f35b50903461026e578260031936011261026e57610cd36128f7565b6000199081835414610d63575b916060917f98ccebae89f16f152c93c6a0cdc0d99913b9e62a9fc612471c21ffc299c389fd937f000000000000000000000000000000000000000000000000000000000000000092610d48731e2d8f84605d32a2cbf302e30bfd2387badf35dd938486612eba565b546001600160a01b038251941684526020840152820152a180f35b805163040f1f6d60e11b8152602090731e2d8f84605d32a2cbf302e30bfd2387badf35dd82828781845afa918215610ec1578792610e92575b5086929192916001600160a01b0391827f000000000000000000000000000000000000000000000000000000000000000016935b858110610de3575b505050505050610ce0565b86516378ed5d1f60e01b81528981018290528381602481865afa908115610e8857869186918d91610e5b575b501614610e2457610e1f906127df565b610dd0565b8855508593507f98ccebae89f16f152c93c6a0cdc0d99913b9e62a9fc612471c21ffc299c389fd9250606091503890508080610dd8565b610e7b9150863d8811610e81575b610e738183612085565b8101906120bd565b38610e0f565b503d610e69565b88513d8d823e3d90fd5b9091508281813d8311610eba575b610eaa8183612085565b8101031261047457519038610d9c565b503d610ea0565b84513d89823e3d90fd5b50903461026e57602036600319011261026e578135906001600160a01b039384831692838103610474576387a211a2600c528152610f166020600c2054610f106129e4565b90612d2e565b9382610f29610f236122c0565b87612d2e565b946024610f48600154610f41600254600354906120dc565b9089612d9c565b938351998a938492637d6af07960e01b84528301527f0000000000000000000000000000000000000000000000000000000000000000165afa8015610fe9576060968391610fba575b5084610faa575050915b81519384526020840152820152f35b610fb49250612d59565b91610f9b565b610fda9150843d8611610fe2575b610fd28183612085565b810190612180565b905038610f91565b503d610fc8565b83513d84823e3d90fd5b50903461026e57606036600319011261026e576001600160a01b0392602435848116843581830361047457604435968716928388036104745784549760ff8916156111035760ff1980991686558433036110be575b611051836129b5565b9788156110b05750916110798860209a959361037f846001999761107485612473565b612c45565b865190888252898201527ffbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db873392a46103de612a1b565b875163e3e8d29760e01b8152fd5b33602052637f5e9f20600c528086526034600c208054600181016110e4575b5050611048565b8085116110f757849003905538806110dd565b896313be252b8952601cfd5b86516306fda65d60e31b81528890fd5b509134610c3b5760603660031901126104745782356001600160a01b038082168083036104745760243590828216918281036104745785549360443560ff8616156113375760ff199586168855337f0000000000000000000000000000000000000000000000000000000000000000831614611199578851632cac670b60e21b81528a90fd5b80999394959698979915611327576111b360001984612196565b905015611317576111cf6111d4916111c96122c0565b90612d59565b612981565b926111ec6111e460025486612d2e565b809a85612cc4565b899260035480611251575b60208b8b8e60018d8d8d7f73872d6d3374a1876ef7909cf7f3224e3c5c43971c31d778cf6b6caadb586d8760808f8f611230818c6120dc565b908a519283528b8d8401528a8301526060820152a382541617905551908152f35b602091929394506112629086612d2e565b93895193848092630b60d15f60e31b82527f0000000000000000000000000000000000000000000000000000000000000000165afa91821561130d5793836112e08c9997957f73872d6d3374a1876ef7909cf7f3224e3c5c43971c31d778cf6b6caadb586d879560809560209f9960019c9a926112ed575b50612cc4565b9395979a819597506111f7565b60206113069293503d8111610e8157610e738183612085565b90386112da565b88513d8c823e3d90fd5b875163c381cd1960e01b81528490fd5b875163f3934a2360e01b81528490fd5b88516306fda65d60e31b81528a90fd5b5050346104ea57816003193601126104ea576001600160a01b036020925460081c169051908152f35b509134610c3b5781600319360112610c3b5782356001600160a01b038116810361047457602435916113a28333612b42565b6387a211a2600c5233815260209485600c2090815490818611611404575084900390555282600c208181540190558252600c5160601c337fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8480a35160018152f35b63f4d678b88452601cfd5b503461026e57602036600319011261026e5780359061142c6128f7565b670de0b6b3a76400008210801561147c575b6102d85750907faeba5a6c40a8ac138134bff1aaa65debf25971188a58804bad717f82f0ec131691600254908060025582519182526020820152a180f35b50670ff59ee833b30000821161143e565b5050346104ea57816003193601126104ea576020906003549051908152f35b503461026e57602036600319011261026e573560055481101561026e576001600160a01b03906005602094527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db00154169051908152f35b50903461026e578260031936011261026e5782815180936395d89b4160e01b8252816001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165afa9182156116405783926115c0575b6107d983836115b66027825180947f4379674c503a200000000000000000000000000000000000000000000000000060208301526115a68151809260208686019101611f66565b8101036007810185520183612085565b5191829182611f89565b9091503d8084833e6115d28183612085565b8101906020818303126104c75780519067ffffffffffffffff8211610812570181601f820112156104c75780519361160985612120565b9261161685519485612085565b85845260208684010111610c3b57506107d9936116399160208085019101611f66565b903861155f565b81513d85823e3d90fd5b5050346104ea57816003193601126104ea576020906002549051908152f35b50903461026e5761012036600319011261026e57602435926001600160a01b0380851684358187036104c75760c03660431901126104c75761010495863567ffffffffffffffff81116107f2576116c39036908301611fb5565b94909786549560ff8716156119425760ff1996871688556116e385612981565b998a156119325781611840575b5050506116fc83612e3a565b6e22d473030f116ddee9f6b43ac78ba3803b15610a4a57608488928385938c519687958694631b63c28b60e11b8652338b8701523060248701521660448501527f00000000000000000000000000000000000000000000000000000000000000001660648401525af1801561183657908691611821575b50506805345cdf77eb68f44c54156117d8575b50906001929161179887602099612bd7565b6117a181612789565b855190815286888201527fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7863392a36103de612a1b565b96956103e719810190811161180e576020975090611798600194939280986118026103e889612bd7565b99505050909192611786565b634e487b7160e01b855260118852602485fd5b8161182b91612085565b610812578438611773565b87513d88823e3d90fd5b6e22d473030f116ddee9f6b43ac78ba391823b1561192e578a516302b67b5760e41b81523387820152936044358681169081900361192657602486015260643586811680910361192657604486015260843565ffffffffffff9081811680910361192a57606487015260a43590811680910361192657608486015260c43586811680910361192657856118f281958e97958395899560a486015260e43560c486015261010060e48601528401916120ff565b03925af1801561191c57611908575b80806116f0565b8661191591979297612085565b9438611901565b88513d89823e3d90fd5b8b80fd5b8c80fd5b8980fd5b895163f1ca48b160e01b81528590fd5b88516306fda65d60e31b81528490fd5b509190346104ea5760203660031901126104ea5781549060ff8216156119b15760ff1991821683556006546001600160a01b031633036119a2575061199a6001929335612789565b610258612a1b565b51639f91a89160e01b81528390fd5b516306fda65d60e31b81528390fd5b503461026e57602036600319011261026e57356001600160a01b038116810361026e576020926338377508600c525281600c20549051908152f35b503461026e57602036600319011261026e57356001600160a01b038116810361026e576020926387a211a2600c525281600c20549051908152f35b5050346104ea57816003193601126104ea57602090516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b5050346104ea57816003193601126104ea57602090516e22d473030f116ddee9f6b43ac78ba38152f35b5050346104ea57816003193601126104ea57602090517f00000000000000000000000000000000000000000000000000000000000000008152f35b508290346104ea5760203660031901126104ea5735906001600160a01b0382168203610c3b5750611b139060001990612196565b82519182526020820152f35b5050346104ea57816003193601126104ea57602090611b3c6122c0565b9051908152f35b5050346104ea57816003193601126104ea57602090516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b5050346104ea57816003193601126104ea576020906001600160a01b03600654169051908152f35b5050346104ea57816003193601126104ea57602090611b3c6129e4565b5050346104ea57816003193601126104ea5760209060a0611beb61213c565b8381519101208251907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8252848201527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc683820152466060820152306080820152209051908152f35b509134610c3b5780600319360112610c3b5760208251809463313ce56760e01b8252816001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165afa928315611cff578193611cbe575b60208460ff855191168152f35b9092506020813d8211611cf7575b81611cd960209383612085565b8101031261026e57519160ff83168303610c3b575060ff6020611cb1565b3d9150611ccc565b509051903d90823e3d90fd5b509190346104ea57806003193601126104ea578235906001600160a01b0390818316808403610812576024359280841684036107f257611d496128f7565b7f00000000000000000000000000000000000000000000000000000000000000001614611d89575090611d8691611d803083612f03565b91612e71565b80f35b51630eaba6c760e11b81528490fd5b509134610c3b576060366003190112610c3b576001600160a01b038335818116929183820361026e57602435908116810361026e5760443591611ddb8382612b42565b60601b92602096338852600c94637f5e9f208117865260348620805460018101611e5a575b50506387a211a217855287852090815490818611611404575084900390555284822081815401905584525160601c907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8480a35160018152f35b808711611e715786900390556387a211a238611e00565b836313be252b8652601cfd5b5050346104ea57816003193601126104ea576020906805345cdf77eb68f44c549051908152f35b503461026e578160031936011261026e57356001600160a01b038116810361026e5791602092602435908452637f5e9f20600c52338252806034600c20558152602c5160601c907f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925843392a35160018152f35b5050346104ea57816003193601126104ea576107d990611f3561213c565b905191829182611f89565b8490346104ea57816003193601126104ea576001600160a01b036020925460081c168152f35b60005b838110611f795750506000910152565b8181015183820152602001611f69565b60409160208252611fa98151809281602086015260208686019101611f66565b601f01601f1916010190565b9181601f840112156104745782359167ffffffffffffffff8311610474576020838186019501011161047457565b9181601f840112156104745782359167ffffffffffffffff8311610474576020808501948460051b01011161047457565b90815180825260208080930193019160005b828110612034575050505090565b83516001600160a01b031685529381019392810192600101612026565b90815180825260208080930193019160005b828110612071575050505090565b835185529381019392810192600101612063565b90601f8019910116810190811067ffffffffffffffff8211176120a757604052565b634e487b7160e01b600052604160045260246000fd5b9081602091031261047457516001600160a01b03811681036104745790565b919082018092116120e957565b634e487b7160e01b600052601160045260246000fd5b908060209392818452848401376000828201840152601f01601f1916010190565b67ffffffffffffffff81116120a757601f01601f191660200190565b6121466012612120565b906121546040519283612085565b601282527f4379676e75733a20436f6c6c61746572616c00000000000000000000000000006020830152565b9190826040910312610474576020825192015190565b906001600160a01b03918281168015801561227a575b6122685760001983146121e2575b506121de92506387a211a2600c526000526121d96020600c20546129b5565b612283565b9091565b60409192506024825180958193637d6af07960e01b835260048301527f0000000000000000000000000000000000000000000000000000000000000000165afa801561225c576121de9260009161223c575b5090386121ba565b612254915060403d8111610fe257610fd28183612085565b905038612234565b6040513d6000823e3d90fd5b604051633801164f60e21b8152600490fd5b503081146121ac565b6122926122aa91610f106122c0565b6001546122a4600254600354906120dc565b91612d9c565b8181106122b8570390600090565b600092910390565b604051906317e5a1a160e01b82526020826024816001600160a01b03807f00000000000000000000000000000000000000000000000000000000000000001660048301527f0000000000000000000000000000000000000000000000000000000000000000165afa91821561225c57600092612352575b50811561234057565b60405163586ad3b960e11b8152600490fd5b90916020823d821161237d575b8161236c60209383612085565b81010312610c3b5750519038612337565b3d915061235f565b9190916387a211a2600c526000928184526020600c205490818111801561246b575b612466578103908111612452579060406123c26024936129b5565b91815193848092637d6af07960e01b82526001600160a01b0380911660048301527f0000000000000000000000000000000000000000000000000000000000000000165afa9182156124475761241f939492612425575b50612283565b90501590565b61243e91925060403d8111610fe257610fd28183612085565b90509038612419565b6040513d86823e3d90fd5b634e487b7160e01b84526011600452602484fd5b505050565b5080156123a7565b60045490731e2d8f84605d32a2cbf302e30bfd2387badf35dd91823b1561047457604051630ad58d2f60e01b815260048101919091526024810191909152306044820152906000908290818381606481015b03925af1801561225c576124d65750565b60006124e191612085565b565b90815480825260208092019260005281600020916000905b828210612509575050505090565b83546001600160a01b0316855293840193600193840193909101906124fb565b67ffffffffffffffff81116120a75760051b60200190565b80518210156125555760209160051b010190565b634e487b7160e01b600052603260045260246000fd5b90918281527f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83116104745760209260051b809284830137010190565b9490939192948415612781576125bd85612529565b946125cb6040519687612085565b808652601f1993846125dc83612529565b019260209336858a013760005b88518110156126075760019030612600828c612541565b52016125e9565b509091929394959697733ef3d8ba38ebe18db133cec108f4d14ce00dd9ae97883b15610474579061265a99979593969492916040519a8b996301c7ba5760e61b8b52608060048c015260848b0190612014565b938560031995868c82030160248d015282815201939060005b81811061274357505050908291848a61269295030160448b015261256b565b908682030160648701528281528181016005908385831b8401019680966000945b8786106126db575050505050505050509181600081819503925af1801561225c576124d65750565b91939597995091939597828282030185528935601e198536030181121561047457840188810191903567ffffffffffffffff81116104745780881b360383136104745761272d8a92839260019561256b565b9b01950196019396949290918b999896926126b3565b918096989a97999b50949092939435906001600160a01b0382168092036104745789816001938293520196019101918c9a9896999795949392612673565b505050505050565b60045490731e2d8f84605d32a2cbf302e30bfd2387badf35dd91823b1561047457604051638dbdbe6d60e01b815260048101919091526024810191909152306044820152906000908290818381606481016124c5565b60001981146120e95760010190565b356001600160a01b03811681036104745790565b9190916001600160a01b038082161561285057600093845b8151811015612848578061283e8786866128376128439688612541565b5116612eba565b6127df565b61281a565b505050509050565b50509050565b91908110156125555760051b0190565b91906001600160a01b0392838116156128f1577f0000000000000000000000000000000000000000000000000000000000000000841660005b8481106128ae57505050505050565b8082876128c76128c26128d2958a8a612856565b6127ee565b16036128d7576127df565b61289f565b61283e600019856128ec6128c2858b8b612856565b612eba565b50505050565b6040516303e1469160e61b81526001600160a01b03906020816004817f000000000000000000000000000000000000000000000000000000000000000086165afa90811561225c57600091612963575b5016330361295157565b6040516331d63ad160e11b8152600490fd5b61297b915060203d8111610e8157610e738183612085565b38612947565b6805345cdf77eb68f44c54600091908061299b5750905090565b6001600160a01b036129b2935460081c1691612d9c565b90565b6805345cdf77eb68f44c546000816129cc57505090565b916001600160a01b036129b2935460081c1690612d9c565b6805345cdf77eb68f44c5460009080612a05575050670de0b6b3a764000090565b6001600160a01b036129b2925460081c16612d59565b60048054604080516393f1a40b60e01b815292830191909152306024830152908181604481731e2d8f84605d32a2cbf302e30bfd2387badf35dd5afa908115612b3757600091612aea575b507f3145c2b643834867f2c0a8cac8a48218307687911615c0ed01412a09dd163b66916001600160a01b03612a9c602093612e3a565b916000547fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff008560081b169116176000555191168152a1565b908282813d8311612b30575b612b008183612085565b81010312610c3b5750517f3145c2b643834867f2c0a8cac8a48218307687911615c0ed01412a09dd163b66612a66565b503d612af6565b82513d6000823e3d90fd5b906001600160a01b033081841614612466577f00000000000000000000000000000000000000000000000000000000000000001691823b15610474576000809360046040518096819363a6afed9560e01b83525af192831561225c57612bad93612bc6575b50612385565b15612bb457565b604051631ba566ff60e01b8152600490fd5b6000612bd191612085565b38612ba7565b6805345cdf77eb68f44c805490838201918210612c3757556387a211a2600c526000526020600c20818154019055602052600c5160601c60007fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef602080a3565b63e5cfe9576000526004601cfd5b90612c508183612b42565b6387a211a2600c52816000526020600c2091825491828111612cb657600093816001600160a01b03940390556805345cdf77eb68f44c8181540390558352167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef602083a3565b63f4d678b86000526004601cfd5b91906387a211a28360601b17600c526020600c208054808411612cb65783900390556000526020600c208181540190556020526001600160a01b03600c5160601c91167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef602080a3565b90806000190482118102612d4b57670de0b6b3a764000091020490565b63bac65e5b6000526004601cfd5b670de0b6b3a7640000907812725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f218111820215830215612d8e57020490565b637c5f487d6000526004601cfd5b91600092828102926000198183099184831085018084039314612e1e5782841115612e1157908391099060018386038416808080960460028082600302188083028203028083028203028083028203028083028203028083028203028092029003029703040190848311900302920304170290565b63ae47f70286526004601cfd5b505050918215612e2d57500490565b63ae47f70290526004601cfd5b74010000000000000000000000000000000000000000811015612e63576001600160a01b031690565b6335278d126000526004601cfd5b60109260209260145260345260446000938480936fa9059cbb00000000000000000000000082525af13d156001835114171615612ead57603452565b6390b8ec1890526004601cfd5b60109260209260145260345260446000938480936f095ea7b300000000000000000000000082525af13d156001835114171615612ef657603452565b633e3f8f7390526004601cfd5b602460106020939284936014526f70a082310000000000000000000000006000525afa601f3d1116602051029056fea164736f6c6343000811000aa164736f6c6343000811000a

Block Transaction Gas Used Reward
view all blocks sequenced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.