ETH Price: $2,353.12 (+0.59%)

Contract

0x835Ba5b1B202773A94Daaa07168b26B22584637a

Overview

ETH Balance

0 ETH

ETH Value

$0.00

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Block
From
To
Execute Trade295316882026-04-15 17:34:5926 secs ago1776274499IN
0x835Ba5b1...22584637a
0 ETH0.000029820.06
Execute Trade295316882026-04-15 17:34:5926 secs ago1776274499IN
0x835Ba5b1...22584637a
0 ETH0.000033850.06
Publish Index Pr...295316882026-04-15 17:34:5926 secs ago1776274499IN
0x835Ba5b1...22584637a
0 ETH0.000032780.06
Execute Trade295316242026-04-15 17:33:551 min ago1776274435IN
0x835Ba5b1...22584637a
0 ETH0.00002970.06
Execute Trade295316242026-04-15 17:33:551 min ago1776274435IN
0x835Ba5b1...22584637a
0 ETH0.000033630.06
Execute Trade295316242026-04-15 17:33:551 min ago1776274435IN
0x835Ba5b1...22584637a
0 ETH0.000029510.06
Execute Trade295316232026-04-15 17:33:541 min ago1776274434IN
0x835Ba5b1...22584637a
0 ETH0.000033540.06
Publish Index Pr...295316232026-04-15 17:33:541 min ago1776274434IN
0x835Ba5b1...22584637a
0 ETH0.00003290.06
Execute Trade295316102026-04-15 17:33:411 min ago1776274421IN
0x835Ba5b1...22584637a
0 ETH0.000010930.02
Execute Trade295316102026-04-15 17:33:411 min ago1776274421IN
0x835Ba5b1...22584637a
0 ETH0.000012240.02
Publish Index Pr...295316102026-04-15 17:33:411 min ago1776274421IN
0x835Ba5b1...22584637a
0 ETH0.000011920.02
Execute Trade295316062026-04-15 17:33:371 min ago1776274417IN
0x835Ba5b1...22584637a
0 ETH0.000011020.02
Execute Trade295316062026-04-15 17:33:371 min ago1776274417IN
0x835Ba5b1...22584637a
0 ETH0.000012310.02
Publish Index Pr...295316062026-04-15 17:33:371 min ago1776274417IN
0x835Ba5b1...22584637a
0 ETH0.000011880.02
Execute Trade295315992026-04-15 17:33:301 min ago1776274410IN
0x835Ba5b1...22584637a
0 ETH0.000010860.02
Execute Trade295315992026-04-15 17:33:301 min ago1776274410IN
0x835Ba5b1...22584637a
0 ETH0.000012210.02
Publish Index Pr...295315992026-04-15 17:33:301 min ago1776274410IN
0x835Ba5b1...22584637a
0 ETH0.0000120.02
Execute Trade295315822026-04-15 17:33:132 mins ago1776274393IN
0x835Ba5b1...22584637a
0 ETH0.000007330.02
Publish Index Pr...295315822026-04-15 17:33:132 mins ago1776274393IN
0x835Ba5b1...22584637a
0 ETH0.000004330.02
Execute Trade295315532026-04-15 17:32:442 mins ago1776274364IN
0x835Ba5b1...22584637a
0 ETH0.000020330.04
Publish Index Pr...295315532026-04-15 17:32:442 mins ago1776274364IN
0x835Ba5b1...22584637a
0 ETH0.000022390.04
Execute Trade295315532026-04-15 17:32:442 mins ago1776274364IN
0x835Ba5b1...22584637a
0 ETH0.000023020.04
Publish Index Pr...295315532026-04-15 17:32:442 mins ago1776274364IN
0x835Ba5b1...22584637a
0 ETH0.000022630.04
Execute Trade295314982026-04-15 17:31:493 mins ago1776274309IN
0x835Ba5b1...22584637a
0 ETH0.000020170.04
Execute Trade295314972026-04-15 17:31:483 mins ago1776274308IN
0x835Ba5b1...22584637a
0 ETH0.000022770.04
View all transactions

View more zero value Internal Transactions in Advanced View mode

Advanced mode:

Cross-Chain Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
Exchange_v1

Compiler Version
v0.8.25+commit.b61c2a91

Optimization Enabled:
Yes with 1 runs

Other Settings:
paris EvmVersion
File 1 of 54 : Exchange_v1.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";

import { Address } from "./libraries/Address.sol";
import { BalanceLoading } from "./libraries/BalanceLoading.sol";
import { BalanceTracking } from "./libraries/BalanceTracking.sol";
import { ClosureDeleveraging } from "./libraries/ClosureDeleveraging.sol";
import { Constants } from "./libraries/Constants.sol";
import { Depositing } from "./libraries/Depositing.sol";
import { ExchangeErrors } from "./libraries/ExchangeErrors.sol";
import { ExchangeEvents } from "./libraries/ExchangeEvents.sol";
import { ExitFund } from "./libraries/ExitFund.sol";
import { Funding } from "./libraries/Funding.sol";
import { IndexPriceMargin } from "./libraries/IndexPriceMargin.sol";
import { ManagedAccounts } from "./libraries/ManagedAccounts.sol";
import { MarketAdmin } from "./libraries/MarketAdmin.sol";
import { NonceInvalidations } from "./libraries/NonceInvalidations.sol";
import { OraclePriceMargin } from "./libraries/OraclePriceMargin.sol";
import { Owned } from "./Owned.sol";
import { PositionBelowMinimumLiquidation } from "./libraries/PositionBelowMinimumLiquidation.sol";
import { PositionInDeactivatedMarketLiquidation } from "./libraries/PositionInDeactivatedMarketLiquidation.sol";
import { Trading } from "./libraries/Trading.sol";
import { Transferring } from "./libraries/Transferring.sol";
import { WalletExitAcquisitionDeleveraging } from "./libraries/WalletExitAcquisitionDeleveraging.sol";
import { WalletExitLiquidation } from "./libraries/WalletExitLiquidation.sol";
import { WalletInMaintenanceAcquisitionDeleveraging } from "./libraries/WalletInMaintenanceAcquisitionDeleveraging.sol";
import { WalletInMaintenanceLiquidation } from "./libraries/WalletInMaintenanceLiquidation.sol";
import { Withdrawing } from "./libraries/Withdrawing.sol";
import {
  AcquisitionDeleverageArguments,
  Balance,
  ClosureDeleverageArguments,
  FundingMultiplierQuartet,
  IndexPricePayload,
  Market,
  MarketOverrides,
  NonceInvalidation,
  Order,
  Trade,
  OverridableMarketFields,
  PositionBelowMinimumLiquidationArguments,
  PositionInDeactivatedMarketLiquidationArguments,
  Transfer,
  WalletExit,
  WalletLiquidationArguments,
  Withdrawal,
  WithdrawalFromManagedAccount
} from "./libraries/Structs.sol";
import { DeleverageType, LiquidationType } from "./libraries/Enums.sol";
import {
  IBridgeAdapter,
  ICustodian,
  IExchange,
  IIndexPriceAdapter,
  IManagedAccountProvider,
  IOraclePriceAdapter
} from "./libraries/Interfaces.sol";

// solhint-disable-next-line contract-name-capwords
contract Exchange_v1 is EIP712, ExchangeErrors, ExchangeEvents, IExchange, Owned {
  using NonceInvalidations for mapping(address => NonceInvalidation[]);

  // State variables //

  // Balance tracking
  BalanceTracking.Storage private _balanceTracking;
  // Mapping of wallet => list of base asset symbols with open positions
  mapping(address => string[]) private _baseAssetSymbolsWithOpenPositionsByWallet;
  // Mapping of order wallet hash => isComplete
  mapping(bytes32 => bool) private _completedOrderHashes;
  // Transfers - mapping of transfer wallet hash => isComplete
  mapping(bytes32 => bool) private _completedTransferHashes;
  // Withdrawals - mapping of withdrawal wallet hash => isComplete
  mapping(bytes32 => bool) private _completedWithdrawalHashes;
  // Withdrawals - mapping of MA withdrawal wallet hash => isComplete
  mapping(bytes32 => bool) private _completedWithdrawalFromManagedAccountHashes;
  // List of whitelisted cross-chain Bridge Adapter contracts
  IBridgeAdapter[] private _bridgeAdapters;
  // Fund custody contract
  ICustodian public custodian;
  // Deposit index
  uint64 public depositIndex;
  // Zero only if Exit Fund has no open positions or quote balance
  uint256 public exitFundPositionOpenedAtBlockTimestamp;
  // List of whitelisted Index Price Adapter contracts
  IIndexPriceAdapter[] private _indexPriceAdapters;
  // Must be true or `deposit` will revert
  bool public isDepositEnabled;
  // If positive (index increases) longs pay shorts; if negative (index decreases) shorts pay longs
  mapping(string => FundingMultiplierQuartet[]) public fundingMultipliersByBaseAssetSymbol;
  // Milliseconds since epoch, always aligned to funding period
  mapping(string => uint64) public lastFundingRatePublishTimestampInMsByBaseAssetSymbol;
  // List of whitelisted Managed Account Provider contracts
  IManagedAccountProvider[] private _managedAccountProviders;
  // Wallet-specific market parameter overrides
  mapping(string => mapping(address => MarketOverrides)) public marketOverridesByBaseAssetSymbolAndWallet;
  // A list of base asset symbols for all markets in addition order
  string[] private _marketBaseAssetSymbols;
  // Mapping of base asset symbol => market struct
  mapping(string => Market) private _marketsByBaseAssetSymbol;
  // Mapping of wallet => last invalidated timestamp in milliseconds
  mapping(address => NonceInvalidation[]) private _nonceInvalidationsByWallet;
  // Currently whitelisted Oracle Price Adapter, used for on-chain exits
  IOraclePriceAdapter public oraclePriceAdapter;
  // Mapping of order hash => filled quantity in pips
  mapping(bytes32 => uint64) private _partiallyFilledOrderQuantities;
  // Mapping of wallet address to total pending deposit quantity
  mapping(address => uint64) public pendingDepositQuantityByWallet;
  // Address of ERC-20 contract used as collateral and quote for all markets
  address public quoteTokenAddress;
  // Exits
  mapping(address => WalletExit) private _walletExits;

  // State variables - tunable parameters //

  uint256 public chainPropagationPeriodInS;
  uint64 public delegateKeyExpirationPeriodInMs;
  // Slippage tolerance to account for rounding errors when validating liquidation prices for very small position sizes
  uint64 public positionBelowMinimumLiquidationPriceToleranceMultiplier;

  // State variables - changeable wallets //

  address public dispatcherWallet;
  address public exitFundWallet;
  address public feeWallet;
  address public insuranceFundWallet;

  // Modifiers //

  modifier onlyAdminOrDispatcher() {
    if (msg.sender != adminWallet && msg.sender != dispatcherWallet) {
      revert SenderMustBeAdminOrDispatcher();
    }
    _;
  }

  modifier onlyDispatcher() {
    _onlyDispatcher();
    _;
  }

  modifier onlyDispatcherWhenExitFundHasNoPositions() {
    _onlyDispatcher();
    if (_baseAssetSymbolsWithOpenPositionsByWallet[exitFundWallet].length > 0) {
      revert ExitFundCannotHaveOpenPosition();
    }
    _;
  }

  modifier onlyWhenExitFundHasOpenPositions() {
    _onlyWhenExitFundHasOpenPositions();
    _;
  }

  modifier onlyDispatcherWhenExitFundHasOpenPositions() {
    _onlyDispatcher();
    _onlyWhenExitFundHasOpenPositions();
    _;
  }

  modifier onlyGovernance() {
    if (msg.sender != custodian.governance()) {
      revert SenderMustBeGovernance();
    }
    _;
  }

  // Functions //

  /**
   * @notice Instantiate a new `Exchange` contract
   *
   * @param balanceMigrationSource Previous Exchange contract to migrate wallet balances from. Not used if zero
   * @param exitFundWallet_ Address of EF wallet
   * @param feeWallet_ Address of Fee wallet
   * @param indexPriceAdapters Addresses of Index Price Adapter contracts whitelisted to validate index price payloads
   * @param insuranceFundWallet_ Address of IF wallet
   * @param oraclePriceAdapter_ Addresses of Oracle Price Adapter contract used for on-chain exit pricing
   * @param quoteTokenAddress_ Address of quote asset ERC20 contract
   *
   * @dev Sets `owner_` and `admin_` to `msg.sender`
   */
  constructor(
    IExchange balanceMigrationSource,
    address exitFundWallet_,
    address feeWallet_,
    IIndexPriceAdapter[] memory indexPriceAdapters,
    address insuranceFundWallet_,
    IOraclePriceAdapter oraclePriceAdapter_,
    address quoteTokenAddress_
  ) EIP712(Constants.EIP_712_DOMAIN_NAME, Constants.EIP_712_DOMAIN_VERSION) Owned() {
    require(
      address(balanceMigrationSource) == address(0x0) || Address.isContract(address(balanceMigrationSource)),
      "Invalid migration source"
    );
    _balanceTracking.migrationSource = IExchange(balanceMigrationSource);

    require(Address.isContract(address(quoteTokenAddress_)), "Invalid quote asset address");
    quoteTokenAddress = quoteTokenAddress_;

    setExitFundWallet(exitFundWallet_);

    setFeeWallet(feeWallet_);

    require(insuranceFundWallet_ != address(0x0), "Invalid IF wallet");
    insuranceFundWallet = insuranceFundWallet_;

    for (uint8 i = 0; i < indexPriceAdapters.length; i++) {
      require(Address.isContract(address(indexPriceAdapters[i])), "Invalid Index Price Adapter address");
    }
    _indexPriceAdapters = indexPriceAdapters;

    require(Address.isContract(address(oraclePriceAdapter_)), "Invalid Oracle Price Adapter address");
    oraclePriceAdapter = oraclePriceAdapter_;

    // Deposits must be manually enabled via `setDepositIndex` and `setDepositEnabled`
    depositIndex = Constants.DEPOSIT_INDEX_NOT_SET;
  }

  // Tunable parameters //

  /**
   * @notice Sets a new Chain Propagation Period - the block timestamp delay after which order nonce invalidations are
   * respected by `executeTrade` and wallet exits are respected by `executeTrade` and `withdraw`
   *
   * @param newChainPropagationPeriodInS The new Chain Propagation Period expressed in seconds. Must be less than
   * `Constants.MAX_CHAIN_PROPAGATION_PERIOD_IN_S`
   */
  function setChainPropagationPeriod(uint256 newChainPropagationPeriodInS) public onlyAdmin {
    if (newChainPropagationPeriodInS > Constants.MAX_CHAIN_PROPAGATION_PERIOD_IN_S) {
      revert NewValueExceedsMaximum();
    }

    uint256 oldChainPropagationPeriodInS = chainPropagationPeriodInS;
    chainPropagationPeriodInS = newChainPropagationPeriodInS;

    emit ChainPropagationPeriodChanged(oldChainPropagationPeriodInS, newChainPropagationPeriodInS);
  }

  /**
   * @notice Sets a new Delegated Key Expiration Period - the delay following a delegated key's nonce timestamp after
   * which it cannot be used to sign orders
   *
   * @param newDelegateKeyExpirationPeriodInMs The new Delegated Key Expiration Period expressed as milliseconds. Must
   * be less than `Constants.MAX_DELEGATE_KEY_EXPIRATION_PERIOD_IN_MS`
   */
  function setDelegatedKeyExpirationPeriod(uint64 newDelegateKeyExpirationPeriodInMs) public onlyAdmin {
    if (newDelegateKeyExpirationPeriodInMs > Constants.MAX_DELEGATE_KEY_EXPIRATION_PERIOD_IN_MS) {
      revert NewValueExceedsMaximum();
    }

    uint64 oldDelegateKeyExpirationPeriodInMs = delegateKeyExpirationPeriodInMs;
    delegateKeyExpirationPeriodInMs = newDelegateKeyExpirationPeriodInMs;

    emit DelegateKeyExpirationPeriodChanged(oldDelegateKeyExpirationPeriodInMs, newDelegateKeyExpirationPeriodInMs);
  }

  /**
   * @notice Sets a new position below minimum liquidation price tolerance multiplier
   *
   * @param newPositionBelowMinimumLiquidationPriceToleranceMultiplier The new position below minimum liquidation price
   * tolerance multiplier. Must be less than `Constants.MAX_FEE_MULTIPLIER`
   */
  function setPositionBelowMinimumLiquidationPriceToleranceMultiplier(
    uint64 newPositionBelowMinimumLiquidationPriceToleranceMultiplier
  ) public onlyAdmin {
    if (newPositionBelowMinimumLiquidationPriceToleranceMultiplier > Constants.MAX_FEE_MULTIPLIER) {
      revert NewValueExceedsMaximum();
    }

    uint64 oldPositionBelowMinimumLiquidationPriceToleranceMultiplier = positionBelowMinimumLiquidationPriceToleranceMultiplier;
    positionBelowMinimumLiquidationPriceToleranceMultiplier = newPositionBelowMinimumLiquidationPriceToleranceMultiplier;

    emit PositionBelowMinimumLiquidationPriceToleranceMultiplierChanged(
      oldPositionBelowMinimumLiquidationPriceToleranceMultiplier,
      newPositionBelowMinimumLiquidationPriceToleranceMultiplier
    );
  }

  /**
   * @notice Sets the address of the `Custodian` contract
   *
   * @dev The `Custodian` accepts `Exchange` and `Governance` addresses in its constructor, after which they can only be
   * changed by the `Governance` contract itself. Therefore the `Custodian` must be deployed last and its address set
   * here on an existing `Exchange` contract. This value is immutable once set and cannot be changed again
   *
   * @param newCustodian The address of the `Custodian` contract deployed against this `Exchange` contract's address
   */
  function setCustodian(ICustodian newCustodian) public onlyAdmin {
    if (custodian != ICustodian(payable(address(0x0)))) {
      revert ValueCanOnlyBetSetOnce();
    }
    if (!Address.isContract(address(newCustodian))) {
      revert InvalidContractAddress();
    }
    custodian = newCustodian;
  }

  /**
   * @notice Enable depositing assets into the Exchange by setting the current deposit index from
   * the old Exchange contract's value. This function can only be called once
   */
  function setDepositIndex() public onlyAdmin {
    if (depositIndex != Constants.DEPOSIT_INDEX_NOT_SET) {
      revert ValueCanOnlyBetSetOnce();
    }

    depositIndex = address(_balanceTracking.migrationSource) == address(0x0)
      ? 0
      : _balanceTracking.migrationSource.depositIndex();
  }

  /**
   * @notice Enables or disables depositing assets into the Exchange
   *
   * @param isEnabled Enables deposit if true, disables if false
   */
  function setDepositEnabled(bool isEnabled) public onlyAdmin {
    if (isEnabled) {
      if (isDepositEnabled) {
        revert NewValueMustBeDifferentFromCurrent();
      }
      emit DepositsEnabled();
    } else {
      if (!isDepositEnabled) {
        revert NewValueMustBeDifferentFromCurrent();
      }
      emit DepositsDisabled();
    }

    isDepositEnabled = isEnabled;
  }

  /**
   * @notice Sets the address of the Exit Fund wallet
   *
   * @dev The current Exit Fund wallet cannot have any open balances
   *
   * @param newExitFundWallet The new Exit Fund wallet. Must be different from the current one
   */
  function setExitFundWallet(address newExitFundWallet) public onlyAdmin {
    ExitFund.validateExitFundWallet_delegatecall(
      exitFundWallet,
      newExitFundWallet,
      _balanceTracking,
      _baseAssetSymbolsWithOpenPositionsByWallet
    );

    exitFundWallet = newExitFundWallet;
  }

  /**
   * @notice Sets the address of the Fee wallet
   *
   * @dev Trade and Withdraw fees will accrue in the `_balanceTracking` quote mapping for this wallet
   *
   * @param newFeeWallet The new Fee wallet. Must be different from the current one
   */
  function setFeeWallet(address newFeeWallet) public onlyAdmin {
    if (newFeeWallet == address(0x0)) {
      revert InvalidWalletAddress();
    }
    if (newFeeWallet == feeWallet) {
      revert NewValueMustBeDifferentFromCurrent();
    }

    address oldFeeWallet = feeWallet;
    feeWallet = newFeeWallet;

    emit FeeWalletChanged(oldFeeWallet, newFeeWallet);
  }

  /**
   * @notice Sets Bridge Adapter contract addresses whitelisted for withdrawals
   *
   * @param newBridgeAdapters An array of Bridge Adapter contract addresses
   */
  function setBridgeAdapters(IBridgeAdapter[] calldata newBridgeAdapters) public onlyGovernance {
    _bridgeAdapters = newBridgeAdapters;
  }

  /**
   * @notice Sets Index Price Adapter contract addresses
   *
   * @param newIndexPriceAdapters An array of contract addresses
   */
  function setIndexPriceAdapters(IIndexPriceAdapter[] calldata newIndexPriceAdapters) public onlyGovernance {
    _indexPriceAdapters = newIndexPriceAdapters;
  }

  /**
   * @notice Sets IF wallet address
   *
   * @param newInsuranceFundWallet The new IF wallet address
   */
  function setInsuranceFundWallet(address newInsuranceFundWallet) public onlyGovernance {
    if (_walletExits[newInsuranceFundWallet].exists) {
      revert NewInsuranceFundWalletCannotBeExited();
    }
    insuranceFundWallet = newInsuranceFundWallet;
  }

  /**
   * @notice Sets Managed Account Provider contract addresses
   *
   * @param newManagedAccountProviders The new contract addresses
   */
  function setManagedAccountProviders(
    IManagedAccountProvider[] calldata newManagedAccountProviders
  ) public onlyGovernance {
    _managedAccountProviders = newManagedAccountProviders;
  }

  /**
   * @notice Sets Oracle Price Adapter contract address used for on-chain exit pricing
   *
   * @param newOraclePriceAdapter The new contract addresses
   */
  function setOraclePriceAdapter(IOraclePriceAdapter newOraclePriceAdapter) public onlyGovernance {
    oraclePriceAdapter = newOraclePriceAdapter;
  }

  /**
   * @notice Migrates all quote asset funds held by Custodian to new token contract and sets new `quoteTokenAddress`
   */
  function migrateQuoteTokenAddress() public onlyAdmin {
    address oldQuoteTokenAddress = quoteTokenAddress;

    address newQuoteTokenAddress = custodian.migrateAsset(quoteTokenAddress);
    quoteTokenAddress = newQuoteTokenAddress;

    emit QuoteTokenAddressChanged(oldQuoteTokenAddress, newQuoteTokenAddress);
  }

  /**
   * @dev Returns the EIP-712 domain separator for the current chain
   */
  function domainSeparatorV4() public view returns (bytes32) {
    return _domainSeparatorV4();
  }

  /**
   * @notice Load a wallet's balance by asset symbol, in pips
   *
   * @param wallet The wallet address to load the balance for. Can be different from `msg.sender`
   * @param assetSymbol The asset symbol to load the wallet's balance for
   *
   * @return balance The quantity denominated in pips of asset at `assetSymbol` currently in an open position or
   * quote balance by `wallet` if base or quote respectively. Result may be negative
   */
  function loadBalanceBySymbol(address wallet, string memory assetSymbol) public view override returns (int64) {
    return
      BalanceLoading.loadBalanceBySymbol_delegatecall(
        wallet,
        assetSymbol,
        _balanceTracking,
        _baseAssetSymbolsWithOpenPositionsByWallet,
        fundingMultipliersByBaseAssetSymbol,
        lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
        _marketsByBaseAssetSymbol,
        pendingDepositQuantityByWallet
      );
  }

  /**
   * @notice Load a wallet's balance-tracking struct by asset symbol
   *
   * @param wallet The wallet address to load the balance for. Can be different from `msg.sender`
   * @param assetSymbol The asset symbol to load the wallet's balance for
   *
   * @return The internal `Balance` struct tracking the asset at `assetSymbol` currently in an open position for or
   * deposited by `wallet`
   */
  function loadBalanceStructBySymbol(
    address wallet,
    string memory assetSymbol
  ) public view override returns (Balance memory) {
    return BalanceLoading.loadBalanceStructBySymbol_delegatecall(wallet, assetSymbol, _balanceTracking);
  }

  /**
   * @notice Loads a list of all currently open positions for a wallet
   *
   * @param wallet The wallet address to load open positions for for. Can be different from `msg.sender`
   *
   * @return A list of base asset symbols corresponding to markets in which the wallet currently has an open position
   */
  function loadBaseAssetSymbolsWithOpenPositionsByWallet(
    address wallet
  ) public view override returns (string[] memory) {
    return _baseAssetSymbolsWithOpenPositionsByWallet[wallet];
  }

  /**
   * @notice Loads the total count of all Bridge Adapters currently whitelisted
   *
   * @return The total count of all Bridge Adapters currently whitelisted
   *
   */
  function loadBridgeAdaptersLength() public view returns (uint256) {
    return _bridgeAdapters.length;
  }

  /**
   * @notice Loads the Bridge Adapter at the given index
   *
   * @param index The index at which to load
   *
   * @return The Bridge Adapter at the given index
   */
  function loadBridgeAdapter(uint8 index) public view returns (IBridgeAdapter) {
    return _bridgeAdapters[index];
  }

  /**
   * @notice Loads the total count of all Index Price Adapters currently whitelisted
   *
   * @return The total count of all Index Price Adapters currently whitelisted
   *
   */
  function loadIndexPriceAdaptersLength() public view returns (uint256) {
    return _indexPriceAdapters.length;
  }

  /**
   * @notice Loads the Index Price Adapter at the given index
   *
   * @param index The index at which to load
   *
   * @return The Index Price Adapter at the given index
   */
  function loadIndexPriceAdapter(uint8 index) public view returns (IIndexPriceAdapter) {
    return _indexPriceAdapters[index];
  }

  /**
   * @notice Loads the total count of all Managed Account Providers currently whitelisted
   *
   * @return The total count of all Managed Account Providers currently whitelisted
   *
   */
  function loadManagedAccountProvidersLength() public view returns (uint256) {
    return _managedAccountProviders.length;
  }

  /**
   * @notice Loads the Managed Account Provider at the given index
   *
   * @param index The index at which to load
   *
   * @return The Managed Account Provider at the given index
   */
  function loadManagedAccountProvider(uint8 index) public view returns (IManagedAccountProvider) {
    return _managedAccountProviders[index];
  }

  /**
   * @notice Loads the total count of all markets added
   *
   * @return The total count of all markets added
   *
   */
  function loadMarketsLength() public view returns (uint256) {
    return _marketBaseAssetSymbols.length;
  }

  /**
   * @notice Loads the Market at the given index by addition order
   *
   * @param index The index at which to load
   *
   * @return The Market at the given index by addition order
   */
  function loadMarket(uint8 index) public view returns (Market memory) {
    return _marketsByBaseAssetSymbol[_marketBaseAssetSymbols[index]];
  }

  /**
   * @notice Loads the last nonce invalidation created by a wallet
   *
   * @param wallet The wallet address
   *
   * @return nonceInvalidation The most recent nonce invalidation struct created by the wallet with `invalidateNonce`. Struct will be
   * empty if wallet has never invalidated a nonce
   */
  function loadLastNonceInvalidationForWallet(
    address wallet
  ) public view returns (NonceInvalidation memory nonceInvalidation) {
    if (_nonceInvalidationsByWallet[wallet].length > 0) {
      nonceInvalidation = _nonceInvalidationsByWallet[wallet][_nonceInvalidationsByWallet[wallet].length - 1];
    }
  }

  /**
   * @notice Load the balance of quote asset the wallet can withdraw after exiting, in pips. Note that due to changing
   * prices the value returned is only an estimate and may not exactly match the value actually transferred after exit
   *
   * @param wallet The wallet address to load the exit quote balance for. Can be different from `msg.sender`
   *
   * @return balance The quantity denominated in pips of quote asset that can be withdrawn after exiting the wallet.
   * Result may be zero, in which case an exit withdrawal would not transfer out any quote but would still close all
   * positions and quote balance. The available quote for exit withdrawal can validly be negative for the EF wallet, in
   * which case this function will return 0 since no withdrawal is possible. For all other wallets, the exit quote
   * calculations are designed such that the result quantity to withdraw is never negative; however the return type is
   * still signed to provide visibility into unforeseen bugs or rounding errors
   */
  function loadQuoteQuantityAvailableForExitWithdrawal(address wallet) public view returns (int64) {
    return
      OraclePriceMargin.loadQuoteQuantityAvailableForExitWithdrawalIncludingOutstandingWalletFunding_delegatecall(
        exitFundWallet,
        oraclePriceAdapter,
        wallet,
        _balanceTracking,
        _baseAssetSymbolsWithOpenPositionsByWallet,
        fundingMultipliersByBaseAssetSymbol,
        lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
        marketOverridesByBaseAssetSymbolAndWallet,
        _marketsByBaseAssetSymbol,
        pendingDepositQuantityByWallet
      );
  }

  /*
   * @notice Loads the exit status for a wallet
   *
   * @param wallet The wallet to load exit status for
   *
   * @return The WalletExit struct corresponding to the wallet
   */
  function loadWalletExitStatus(address wallet) external view returns (WalletExit memory) {
    return _walletExits[wallet];
  }

  // Dispatcher whitelisting //

  /**
   * @notice Sets the wallet whitelisted to dispatch transactions calling the `executeTrade` and `withdraw`
   * functions
   *
   * @param newDispatcherWallet The new whitelisted dispatcher wallet. Must be different from the current one
   */
  function setDispatcher(address newDispatcherWallet) public onlyAdmin {
    if (newDispatcherWallet == address(0x0)) {
      revert InvalidWalletAddress();
    }
    if (newDispatcherWallet == dispatcherWallet) {
      revert NewValueMustBeDifferentFromCurrent();
    }

    emit DispatcherChanged(dispatcherWallet, newDispatcherWallet);

    dispatcherWallet = newDispatcherWallet;
  }

  /**
   * @notice Clears the currently set whitelisted dispatcher wallet, effectively disabling calling any functions
   * restricted by the `onlyDispatcherWhenExitFundHasNoPositions` modifier until a new wallet is set with `setDispatcher`
   */
  function removeDispatcher() public onlyAdmin {
    emit DispatcherChanged(dispatcherWallet, address(0x0));

    dispatcherWallet = address(0x0);
  }

  // Depositing //

  /**
   * @notice Deposit quote token
   *
   * @param quantityInAssetUnits The quantity to deposit. The sending wallet must first call the `approve` method on
   * the token contract for at least this quantity
   * @param depositorWallet The wallet which will be credited for the new balance. Defaults to sending wallet if zero
   */
  function deposit(uint256 quantityInAssetUnits, address depositorWallet) public {
    address depositorWallet_ = depositorWallet == address(0x0) ? msg.sender : depositorWallet;

    Depositing.deposit_delegatecall(
      depositorWallet_,
      msg.sender,
      quantityInAssetUnits,
      custodian,
      depositIndex,
      exitFundWallet,
      isDepositEnabled,
      quoteTokenAddress,
      _balanceTracking,
      pendingDepositQuantityByWallet,
      _walletExits
    );

    depositIndex++;
  }

  /**
   * @notice Apply pending deposits
   *
   * @param quantity The quantity to apply. Must be less than or equal to the total amount pending for the wallet
   * @param wallet The wallet for which to apply pending deposits
   */
  function applyPendingDepositsForWallet(uint64 quantity, address wallet) public onlyAdminOrDispatcher {
    Depositing.applyPendingDepositsForWallet_delegatecall(
      quantity,
      wallet,
      _balanceTracking,
      pendingDepositQuantityByWallet
    );
  }

  // Trades //

  /**
   * @notice Settles a trade between two orders submitted and matched off-chain
   *
   * @param trade Trade execution parameters
   * @param buy Buy order
   * @param sell Sell order
   */
  function executeTrade(
    Trade memory trade,
    Order memory buy,
    Order memory sell
  ) public onlyDispatcherWhenExitFundHasNoPositions {
    Trading.executeTrade_delegatecall(
      trade,
      buy,
      sell,
      delegateKeyExpirationPeriodInMs,
      _domainSeparatorV4(),
      exitFundWallet,
      feeWallet,
      insuranceFundWallet,
      _balanceTracking,
      _baseAssetSymbolsWithOpenPositionsByWallet,
      _completedOrderHashes,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      _marketsByBaseAssetSymbol,
      _nonceInvalidationsByWallet,
      _partiallyFilledOrderQuantities,
      _walletExits
    );
  }

  // Liquidation //

  /**
   * @notice Liquidates a single position below the market's configured `minimumPositionSize` to the Insurance Fund
   * at the current index price
   */
  function liquidatePositionBelowMinimum(
    PositionBelowMinimumLiquidationArguments memory liquidationArguments
  ) public onlyDispatcherWhenExitFundHasNoPositions {
    PositionBelowMinimumLiquidation.liquidate_delegatecall(
      liquidationArguments,
      exitFundWallet,
      insuranceFundWallet,
      positionBelowMinimumLiquidationPriceToleranceMultiplier,
      _balanceTracking,
      _baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      _marketsByBaseAssetSymbol
    );
  }

  /**
   * @notice Liquidates a single position in a deactivated market at the previously set index price
   */
  function liquidatePositionInDeactivatedMarket(
    PositionInDeactivatedMarketLiquidationArguments memory liquidationArguments
  ) public onlyDispatcherWhenExitFundHasNoPositions {
    PositionInDeactivatedMarketLiquidation.liquidate_delegatecall(
      liquidationArguments,
      feeWallet,
      _balanceTracking,
      _baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      _marketsByBaseAssetSymbol
    );
  }

  /**
   * @notice Liquidates all positions held by a wallet below maintenance requirements to the Insurance Fund at each
   * position's bankruptcy price
   */
  function liquidateWalletInMaintenance(
    WalletLiquidationArguments memory liquidationArguments
  ) public onlyDispatcherWhenExitFundHasNoPositions {
    WalletInMaintenanceLiquidation.liquidate_delegatecall(
      liquidationArguments,
      exitFundPositionOpenedAtBlockTimestamp, // Will always be 0 per modifier
      exitFundWallet,
      insuranceFundWallet,
      LiquidationType.WalletInMaintenance,
      _balanceTracking,
      _baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      _marketsByBaseAssetSymbol,
      pendingDepositQuantityByWallet
    );
  }

  /**
   * @notice Liquidates all positions held by a wallet below maintenance requirements to the Exit Fund at each
   * position's bankruptcy price
   */
  function liquidateWalletInMaintenanceDuringSystemRecovery(
    WalletLiquidationArguments memory liquidationArguments
  ) public onlyDispatcherWhenExitFundHasOpenPositions {
    exitFundPositionOpenedAtBlockTimestamp = WalletInMaintenanceLiquidation.liquidate_delegatecall(
      liquidationArguments,
      exitFundPositionOpenedAtBlockTimestamp,
      exitFundWallet,
      insuranceFundWallet,
      LiquidationType.WalletInMaintenanceDuringSystemRecovery,
      _balanceTracking,
      _baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      _marketsByBaseAssetSymbol,
      pendingDepositQuantityByWallet
    );
  }

  /**
   * @notice Liquidates all positions of an exited wallet to the Insurance Fund at each position's exit price
   */
  function liquidateWalletExit(
    WalletLiquidationArguments memory liquidationArguments
  ) public onlyDispatcherWhenExitFundHasNoPositions {
    if (!_walletExits[liquidationArguments.liquidatingWallet].exists) {
      revert WalletMustBeExited();
    }

    WalletExitLiquidation.liquidate_delegatecall(
      liquidationArguments,
      exitFundWallet,
      insuranceFundWallet,
      _balanceTracking,
      _baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      _marketsByBaseAssetSymbol
    );
  }

  // Automatic Deleveraging (ADL) //

  /**
   * @notice Reduces a single position held by a wallet below maintenance requirements by deleveraging a counterparty
   * position at the bankruptcy price of the liquidating wallet
   */
  function deleverageInMaintenanceAcquisition(
    AcquisitionDeleverageArguments memory deleverageArguments
  ) public onlyDispatcherWhenExitFundHasNoPositions {
    WalletInMaintenanceAcquisitionDeleveraging.deleverage_delegatecall(
      deleverageArguments,
      exitFundWallet,
      insuranceFundWallet,
      _balanceTracking,
      _baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      _marketsByBaseAssetSymbol,
      pendingDepositQuantityByWallet
    );
  }

  /**
   * @notice Reduces a single position held by the Insurance Fund by deleveraging a counterparty position at the entry
   * price of the Insurance Fund
   */
  function deleverageInsuranceFundClosure(
    ClosureDeleverageArguments memory deleverageArguments
  ) public onlyDispatcherWhenExitFundHasNoPositions {
    ClosureDeleveraging.deleverage_delegatecall(
      deleverageArguments,
      DeleverageType.InsuranceFundClosure,
      exitFundPositionOpenedAtBlockTimestamp, // Will always be 0 per modifier
      exitFundWallet,
      insuranceFundWallet,
      _balanceTracking,
      _baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      _marketsByBaseAssetSymbol
    );
  }

  /**
   * @notice Reduces a single position held by an exited wallet by deleveraging a counterparty position at the exit
   * price of the liquidating wallet
   */
  function deleverageExitAcquisition(
    AcquisitionDeleverageArguments memory deleverageArguments
  ) public onlyDispatcherWhenExitFundHasNoPositions {
    if (!_walletExits[deleverageArguments.liquidatingWallet].exists) {
      revert WalletMustBeExited();
    }

    WalletExitAcquisitionDeleveraging.deleverage_delegatecall(
      deleverageArguments,
      exitFundWallet,
      insuranceFundWallet,
      _balanceTracking,
      _baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      _marketsByBaseAssetSymbol,
      _walletExits
    );
  }

  /**
   * @notice Reduces a single position held by the Exit Fund by deleveraging a counterparty position at the index
   * price or the Exit Fund's bankruptcy price if the Exit Fund account value is positive or negative, respectively
   */
  function deleverageExitFundClosure(
    ClosureDeleverageArguments memory deleverageArguments
  ) public onlyDispatcherWhenExitFundHasOpenPositions {
    exitFundPositionOpenedAtBlockTimestamp = ClosureDeleveraging.deleverage_delegatecall(
      deleverageArguments,
      DeleverageType.ExitFundClosure,
      exitFundPositionOpenedAtBlockTimestamp,
      exitFundWallet,
      insuranceFundWallet,
      _balanceTracking,
      _baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      _marketsByBaseAssetSymbol
    );
  }

  // Transfers //

  function transfer(Transfer memory transfer_) public onlyDispatcherWhenExitFundHasNoPositions {
    Transferring.transfer_delegatecall(
      transfer_,
      _domainSeparatorV4(),
      exitFundWallet,
      insuranceFundWallet,
      feeWallet,
      _balanceTracking,
      _baseAssetSymbolsWithOpenPositionsByWallet,
      _completedTransferHashes,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      _marketsByBaseAssetSymbol,
      _walletExits
    );
  }

  // Withdrawing //

  /**
   * @notice Settles a user withdrawal submitted off-chain. Calls restricted to currently
   * whitelisted Dispatcher wallet
   *
   * @param withdrawal A `Withdrawal` struct encoding the parameters of the withdrawal
   */
  function withdraw(Withdrawal memory withdrawal) public onlyDispatcherWhenExitFundHasNoPositions {
    Withdrawing.withdraw_delegatecall(
      withdrawal,
      _domainSeparatorV4(),
      custodian,
      exitFundPositionOpenedAtBlockTimestamp,
      exitFundWallet,
      feeWallet,
      quoteTokenAddress,
      _balanceTracking,
      _baseAssetSymbolsWithOpenPositionsByWallet,
      _completedWithdrawalHashes,
      _bridgeAdapters,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      _marketsByBaseAssetSymbol,
      _walletExits
    );
  }

  // Market management //

  /**
   * @notice Create a new market that will initially be deactivated. Funding multipliers will be backfilled with zero
   * values for the current day UTC. Note this may block publishing new funding multipliers for up to half the funding
   * period interval following market creation
   */
  function addMarket(Market memory newMarket) public onlyAdmin {
    MarketAdmin.addMarket_delegatecall(
      newMarket,
      oraclePriceAdapter,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      _marketBaseAssetSymbols,
      _marketsByBaseAssetSymbol
    );
  }

  /**
   * @notice Activate a market, which allows positions to be opened and funding payments made
   */
  function activateMarket(string memory baseAssetSymbol) public onlyDispatcherWhenExitFundHasNoPositions {
    MarketAdmin.activateMarket_delegatecall(baseAssetSymbol, _marketsByBaseAssetSymbol);
  }

  /**
   * @notice Deactivate a market
   */
  function deactivateMarket(string memory baseAssetSymbol) public onlyDispatcherWhenExitFundHasNoPositions {
    MarketAdmin.deactivateMarket_delegatecall(baseAssetSymbol, _marketsByBaseAssetSymbol);
  }

  /**
   * @notice Publish updated index prices for markets
   *
   * @dev Access must be `onlyDispatcher` rather than `onlyDispatcherWhenExitFundHasNoPositions` to facilitate EF
   * closure deleveraging during system recovery
   */
  function publishIndexPrices(IndexPricePayload[] calldata encodedIndexPrices) public onlyDispatcher {
    MarketAdmin.publishIndexPrices_delegatecall(encodedIndexPrices, _indexPriceAdapters, _marketsByBaseAssetSymbol);
  }

  /**
   * @notice Set overridable market parameters for a specific wallet or as new market defaults
   *
   * @param baseAssetSymbol The base asset symbol for the market
   * @param overridableFields New values for overridable fields
   * @param wallet The wallet to apply overrides to. If zero, overrides apply to entire market
   */
  function setMarketOverrides(
    string memory baseAssetSymbol,
    OverridableMarketFields memory overridableFields,
    address wallet
  ) public onlyGovernance {
    if (!_marketsByBaseAssetSymbol[baseAssetSymbol].exists) {
      revert UnknownBaseAssetSymbol();
    }

    if (wallet == address(0x0)) {
      _marketsByBaseAssetSymbol[baseAssetSymbol].overridableFields = overridableFields;
    } else {
      marketOverridesByBaseAssetSymbolAndWallet[baseAssetSymbol][wallet] = MarketOverrides({
        exists: true,
        overridableFields: overridableFields
      });
    }
  }

  /**
   * @notice Unset overridable market parameters for a specific wallet
   *
   * @param baseAssetSymbol The base asset symbol for the market
   * @param wallet The wallet to unset overrides for
   */
  function unsetMarketOverridesForWallet(string memory baseAssetSymbol, address wallet) public onlyAdminOrDispatcher {
    if (!_marketsByBaseAssetSymbol[baseAssetSymbol].exists) {
      revert UnknownBaseAssetSymbol();
    }
    if (wallet == address(0x0)) {
      revert InvalidWalletAddress();
    }
    if (!marketOverridesByBaseAssetSymbolAndWallet[baseAssetSymbol][wallet].exists) {
      revert WalletHasNoOverridesForMarket();
    }

    delete marketOverridesByBaseAssetSymbolAndWallet[baseAssetSymbol][wallet];

    emit MarketOverridesUnset(baseAssetSymbol, wallet);
  }

  /**
   * @notice Sends tokens mistakenly sent directly to the `Exchange` to the fee wallet (the absence of a `receive`
   * function rejects incoming native asset transfers)
   */
  function skim(address tokenAddress) public onlyAdmin {
    Withdrawing.skim_delegatecall(tokenAddress, feeWallet);
  }

  // Perps //

  /**
   * @notice Pushes fundingRate × indexPrice to fundingMultipliersByBaseAssetSymbol mapping for market. Uses timestamp
   * component of index price to determine if funding rate is too recent after previously publish funding rate, and to
   * backfill empty values if a funding period was missed
   */
  function publishFundingMultiplier(
    string memory baseAssetSymbol,
    int64 fundingRate
  ) public onlyDispatcherWhenExitFundHasNoPositions {
    Funding.publishFundingMultiplier_delegatecall(
      baseAssetSymbol,
      fundingRate,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      _marketsByBaseAssetSymbol
    );
  }

  /**
   * @notice Updates quote balance with historical funding payments for a market by walking funding multipliers
   * published since last position update up to max allowable by gas constraints
   */
  function applyOutstandingWalletFundingForMarket(address wallet, string memory baseAssetSymbol) public {
    Funding.applyOutstandingWalletFundingForMarket_delegatecall(
      baseAssetSymbol,
      wallet,
      _balanceTracking,
      _baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      _marketsByBaseAssetSymbol
    );
  }

  /**
   * @notice Calculate total outstanding funding payments
   */
  function loadOutstandingWalletFunding(address wallet) public view returns (int64) {
    return
      Funding.loadOutstandingWalletFunding_delegatecall(
        wallet,
        _balanceTracking,
        _baseAssetSymbolsWithOpenPositionsByWallet,
        fundingMultipliersByBaseAssetSymbol,
        lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
        _marketsByBaseAssetSymbol
      );
  }

  /**
   * @notice Calculate total account value for a wallet by summing its quote asset balance and each open position's
   * notional values as computed by latest published index price. Result may be negative. Since index prices are
   * published lazily, the result may be out of date for a market with little activity
   *
   * @param wallet The wallet address to calculate total account value for
   */
  function loadTotalAccountValueFromIndexPrices(address wallet) public view returns (int64) {
    return
      IndexPriceMargin.loadTotalAccountValueIncludingOutstandingWalletFunding_delegatecall(
        wallet,
        _balanceTracking,
        _baseAssetSymbolsWithOpenPositionsByWallet,
        fundingMultipliersByBaseAssetSymbol,
        lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
        _marketsByBaseAssetSymbol,
        pendingDepositQuantityByWallet
      );
  }

  /**
   * @notice Calculate total account value for a wallet by summing its quote asset balance and each open position's
   * notional values as computed by on-chain feed price. Result may be negative
   *
   * @param wallet The wallet address to calculate total account value for
   */
  function loadTotalAccountValueFromOraclePrices(address wallet) public view returns (int64) {
    return
      OraclePriceMargin.loadTotalAccountValueIncludingOutstandingWalletFunding_delegatecall(
        oraclePriceAdapter,
        wallet,
        _balanceTracking,
        _baseAssetSymbolsWithOpenPositionsByWallet,
        fundingMultipliersByBaseAssetSymbol,
        lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
        _marketsByBaseAssetSymbol,
        pendingDepositQuantityByWallet
      );
  }

  /**
   * @notice Calculate total initial margin requirement for a wallet by summing each open position's initial margin
   * requirement as computed by latest published index price. Since index prices are published lazily, the result may be
   * out of date for a market with little activity
   *
   * @param wallet The wallet address to calculate total initial margin requirement for
   */
  function loadTotalInitialMarginRequirementFromIndexPrices(address wallet) public view returns (uint64) {
    return
      IndexPriceMargin.loadTotalInitialMarginRequirement_delegatecall(
        wallet,
        _balanceTracking,
        _baseAssetSymbolsWithOpenPositionsByWallet,
        marketOverridesByBaseAssetSymbolAndWallet,
        _marketsByBaseAssetSymbol
      );
  }

  /**
   * @notice Calculate total initial margin requirement for a wallet by summing each open position's initial margin
   * requirement as computed by on-chain feed price
   *
   * @param wallet The wallet address to calculate total initial margin requirement for
   */
  function loadTotalInitialMarginRequirementFromOraclePrices(address wallet) public view returns (uint64) {
    return
      OraclePriceMargin.loadTotalInitialMarginRequirement_delegatecall(
        oraclePriceAdapter,
        wallet,
        _balanceTracking,
        _baseAssetSymbolsWithOpenPositionsByWallet,
        marketOverridesByBaseAssetSymbolAndWallet,
        _marketsByBaseAssetSymbol
      );
  }

  /**
   * @notice Calculate total maintenence margin requirement for a wallet by summing each open position's maintanence
   * margin requirement as computed by latest published index price. Since index prices are published lazily, the result
   * may be out of date for a market with little activity
   *
   * @param wallet The wallet address to calculate total maintanence margin requirement for
   */
  function loadTotalMaintenanceMarginRequirementFromIndexPrices(address wallet) public view returns (uint64) {
    return
      IndexPriceMargin.loadTotalMaintenanceMarginRequirement_delegatecall(
        wallet,
        _balanceTracking,
        _baseAssetSymbolsWithOpenPositionsByWallet,
        marketOverridesByBaseAssetSymbolAndWallet,
        _marketsByBaseAssetSymbol
      );
  }

  /**
   * @notice Calculate total maintenence margin requirement for a wallet by summing each open position's maintanence
   * margin requirement as computed by on-chain feed price
   *
   * @param wallet The wallet address to calculate total maintanence margin requirement for
   */
  function loadTotalMaintenanceMarginRequirementFromOraclePrices(address wallet) public view returns (uint64) {
    return
      OraclePriceMargin.loadTotalMaintenanceMarginRequirement_delegatecall(
        oraclePriceAdapter,
        wallet,
        _balanceTracking,
        _baseAssetSymbolsWithOpenPositionsByWallet,
        marketOverridesByBaseAssetSymbolAndWallet,
        _marketsByBaseAssetSymbol
      );
  }

  // Wallet exits //

  /**
   * @notice Flags a wallet as exited, immediately disabling deposits upon mining. After the Chain Propagation Period
   * passes trades and withdrawals are also disabled for the wallet, and quote asset may then be withdrawn via
   * `withdrawExit`
   *
   * @param wallet The wallet to exit. If the wallet is associated with a Managed Account, then the sender must be the
   * Managed Account Provider contract. Otherwise, the sender must be the same as the wallet to be flagged as exited
   */
  function exitWallet(address wallet) public {
    Withdrawing.exitWallet_delegatecall(
      chainPropagationPeriodInS,
      exitFundWallet,
      insuranceFundWallet,
      wallet,
      _balanceTracking,
      _walletExits
    );
  }

  /**
   * @notice Close all open positions and withdraw the net quote balance for an exited wallet. The Chain Propagation
   * Period must have already passed since calling `exitWallet`
   *
   * @param wallet Address of exited wallet
   */
  function withdrawExit(address wallet) public {
    uint256 exitFundPositionOpenedAtBlockTimestamp_ = Withdrawing.withdrawExit_delegatecall(
      wallet,
      custodian,
      exitFundWallet,
      oraclePriceAdapter,
      quoteTokenAddress,
      exitFundPositionOpenedAtBlockTimestamp,
      _balanceTracking,
      _baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      _marketsByBaseAssetSymbol,
      pendingDepositQuantityByWallet,
      _walletExits
    );

    exitFundPositionOpenedAtBlockTimestamp = exitFundPositionOpenedAtBlockTimestamp_;
  }

  /**
   * @notice Close all open positions and withdraw the net quote balance for an exited wallet during system recovery,
   * regardless of Chain Propagation Period elapsing
   *
   * @param wallet Address of exited wallet
   */
  function withdrawExitAdmin(address wallet) public onlyAdminOrDispatcher onlyWhenExitFundHasOpenPositions {
    uint256 exitFundPositionOpenedAtBlockTimestamp_ = Withdrawing.withdrawExitAdmin_delegatecall(
      wallet,
      custodian,
      exitFundWallet,
      oraclePriceAdapter,
      quoteTokenAddress,
      exitFundPositionOpenedAtBlockTimestamp,
      _balanceTracking,
      _baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      _marketsByBaseAssetSymbol,
      pendingDepositQuantityByWallet,
      _walletExits
    );
    exitFundPositionOpenedAtBlockTimestamp = exitFundPositionOpenedAtBlockTimestamp_;
  }

  /**
   * @notice Clears exited status of sending wallet. Upon mining immediately enables deposits, trades, and withdrawals
   * by sending wallet
   */
  function clearWalletExit() public {
    Withdrawing.clearWalletExit_delegatecall(
      _balanceTracking,
      _baseAssetSymbolsWithOpenPositionsByWallet,
      _walletExits
    );
  }

  // Invalidation //

  /**
   * @notice Invalidate all order nonces with a timestampInMs lower than the one provided
   *
   * @param nonce A Version 1 UUID. After calling and once the Chain Propagation Period has elapsed,
   * `executeTrade` will reject order nonces from this wallet with a timestampInMs component lower than the one
   * provided
   */
  function invalidateNonce(uint128 nonce) public {
    (uint64 timestampInMs, uint256 effectiveBlockTimestamp) = _nonceInvalidationsByWallet.invalidateNonce_delegatecall(
      nonce,
      chainPropagationPeriodInS
    );

    emit OrderNonceInvalidated(msg.sender, nonce, timestampInMs, effectiveBlockTimestamp);
  }

  function _onlyDispatcher() private view {
    if (msg.sender != dispatcherWallet) {
      revert SenderMustBeDispatcher();
    }
  }

  function _onlyWhenExitFundHasOpenPositions() private view {
    if (_baseAssetSymbolsWithOpenPositionsByWallet[exitFundWallet].length == 0) {
      revert ExitFundHasNoPositions();
    }
  }

  // Managed Accounts //

  /**
   * @notice Associate a wallet with a Managed Account Provider contract. The sender must be a
   * whitelisted Managed Account Provider and will be used as the contract to associate the wallet
   * with
   *
   * @param managerWallet The wallet which will be associated with the Managed Account
   */
  function associateManagerWalletWithManagedAccount(address managerWallet) public {
    ManagedAccounts.associateManagerWalletWithManagedAccount_delegatecall(
      managerWallet,
      _balanceTracking,
      _baseAssetSymbolsWithOpenPositionsByWallet,
      _managedAccountProviders,
      pendingDepositQuantityByWallet,
      _walletExits
    );
  }

  /**
   * @notice Deposit quote token to Managed Account
   *
   * @param quantityInAssetUnits The quantity to deposit. The sending wallet must first call the `approve` method on
   * the token contract for at least this quantity
   * @param depositorWallet The wallet which will be credited for the deposit
   * @param managedAccountProvider Address of Managed Account Provider contract
   * @param managedAccountProviderPayload ABI-encoded parameters to supply specific Managed Account Provider contract
   * @param managerWallet Address of wallet associated with Managed Account Provider that will be credited for the new
   * balance
   */
  function depositToManagedAccount(
    uint256 quantityInAssetUnits,
    address depositorWallet,
    IManagedAccountProvider managedAccountProvider,
    bytes calldata managedAccountProviderPayload,
    address managerWallet
  ) public {
    ManagedAccounts.depositToManagedAccount_delegatecall(
      quantityInAssetUnits,
      depositorWallet,
      managedAccountProvider,
      managedAccountProviderPayload,
      managerWallet,
      msg.sender,
      _bridgeAdapters,
      custodian,
      depositIndex,
      exitFundWallet,
      isDepositEnabled,
      quoteTokenAddress,
      _balanceTracking,
      _managedAccountProviders,
      pendingDepositQuantityByWallet,
      _walletExits
    );

    depositIndex++;
  }

  /**
   * @notice Apply a pending deposit to a manager wallet associated with a Managed Account
   *
   * @param depositIndex_ The unique index identifying the deposit
   * @param quantity The quantity to apply. Must be less than or equal to the total amount pending for the wallet
   * @param managerWallet The manager wallet for which to apply the pending deposit
   */
  function applyPendingDepositToManagedAccount(
    uint64 depositIndex_,
    uint64 quantity,
    address managerWallet
  ) public onlyDispatcher {
    ManagedAccounts.applyPendingDepositToManagedAccount_delegatecall(
      depositIndex_,
      quantity,
      managerWallet,
      _balanceTracking,
      pendingDepositQuantityByWallet,
      _walletExits
    );
  }

  /**
   * @notice Settles a user withdrawal submitted to a Managed Account provider. Calls restricted to
   * currently whitelisted Dispatcher wallet
   *
   * @param withdrawal A `WithdrawalFromManagedAccount` struct encoding the parameters of the withdrawal
   */
  function applyPendingWithdrawalFromManagedAccount(
    WithdrawalFromManagedAccount memory withdrawal
  ) public onlyDispatcher {
    ManagedAccounts.applyPendingWithdrawalFromManagedAccount_delegatecall(
      // External arguments
      withdrawal,
      // Exchange state values
      _domainSeparatorV4(),
      // Exchange state storage refs
      _balanceTracking,
      _baseAssetSymbolsWithOpenPositionsByWallet,
      _completedWithdrawalFromManagedAccountHashes,
      _bridgeAdapters,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      _managedAccountProviders,
      marketOverridesByBaseAssetSymbolAndWallet,
      _marketsByBaseAssetSymbol,
      _walletExits
    );
  }

  /**
   * @notice Cancels a user withdrawal submitted to a Managed Account provider. Calls restricted to
   * currently whitelisted Dispatcher wallet
   *
   * @param withdrawal A `WithdrawalFromManagedAccount` struct encoding the parameters of the withdrawal
   */
  function cancelPendingWithdrawalFromManagedAccount(
    WithdrawalFromManagedAccount memory withdrawal
  ) public onlyDispatcher {
    ManagedAccounts.cancelPendingWithdrawalFromManagedAccount_delegatecall(
      // External arguments
      withdrawal,
      // Exchange state values
      _domainSeparatorV4(),
      // Exchange state storage refs
      _balanceTracking,
      _baseAssetSymbolsWithOpenPositionsByWallet,
      _completedWithdrawalFromManagedAccountHashes,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      _managedAccountProviders,
      marketOverridesByBaseAssetSymbolAndWallet,
      _marketsByBaseAssetSymbol,
      _walletExits
    );
  }

  /**
   * @notice Withdraw quote token from an exited manager wallet
   *
   * @param depositorWallet The depositor wallet which will receive the withdrawn quantity
   * @param managerWallet The exited manager wallet that will be withdrawn from
   * @param quantity The quantity to withdraw
   */
  function withdrawExitFromManagedAccount(address depositorWallet, address managerWallet, uint64 quantity) public {
    uint256 exitFundPositionOpenedAtBlockTimestamp_ = ManagedAccounts.withdrawExitFromManagedAccount_delegatecall(
      depositorWallet,
      managerWallet,
      quantity,
      exitFundPositionOpenedAtBlockTimestamp,
      _balanceTracking,
      _baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      _managedAccountProviders,
      marketOverridesByBaseAssetSymbolAndWallet,
      _marketsByBaseAssetSymbol,
      pendingDepositQuantityByWallet,
      _walletExits
    );

    exitFundPositionOpenedAtBlockTimestamp = exitFundPositionOpenedAtBlockTimestamp_;
  }
}

File 2 of 54 : IERC5267.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC5267.sol)

pragma solidity ^0.8.20;

interface IERC5267 {
    /**
     * @dev MAY be emitted to signal that the domain could have changed.
     */
    event EIP712DomainChanged();

    /**
     * @dev returns the fields and values that describe the domain separator used by this contract for EIP-712
     * signature.
     */
    function eip712Domain()
        external
        view
        returns (
            bytes1 fields,
            string memory name,
            string memory version,
            uint256 chainId,
            address verifyingContract,
            bytes32 salt,
            uint256[] memory extensions
        );
}

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

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

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

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

    /**
     * @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` 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 value) external returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/ECDSA.sol)

pragma solidity ^0.8.20;

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSA {
    enum RecoverError {
        NoError,
        InvalidSignature,
        InvalidSignatureLength,
        InvalidSignatureS
    }

    /**
     * @dev The signature derives the `address(0)`.
     */
    error ECDSAInvalidSignature();

    /**
     * @dev The signature has an invalid length.
     */
    error ECDSAInvalidSignatureLength(uint256 length);

    /**
     * @dev The signature has an S value that is in the upper half order.
     */
    error ECDSAInvalidSignatureS(bytes32 s);

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not
     * return address(0) without also returning an error description. Errors are documented using an enum (error type)
     * and a bytes32 providing additional information about the error.
     *
     * If no error is returned, then the address can be used for verification purposes.
     *
     * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
     *
     * Documentation for signature generation:
     * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
     * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
     */
    function tryRecover(
        bytes32 hash,
        bytes memory signature
    ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
        if (signature.length == 65) {
            bytes32 r;
            bytes32 s;
            uint8 v;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            assembly ("memory-safe") {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
            return tryRecover(hash, v, r, s);
        } else {
            return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
        _throwError(error, errorArg);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
     *
     * See https://eips.ethereum.org/EIPS/eip-2098[ERC-2098 short signatures]
     */
    function tryRecover(
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
        unchecked {
            bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
            // We do not check for an overflow here since the shift operation results in 0 or 1.
            uint8 v = uint8((uint256(vs) >> 255) + 27);
            return tryRecover(hash, v, r, s);
        }
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
     */
    function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);
        _throwError(error, errorArg);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function tryRecover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            return (address(0), RecoverError.InvalidSignatureS, s);
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        if (signer == address(0)) {
            return (address(0), RecoverError.InvalidSignature, bytes32(0));
        }

        return (signer, RecoverError.NoError, bytes32(0));
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);
        _throwError(error, errorArg);
        return recovered;
    }

    /**
     * @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.
     */
    function _throwError(RecoverError error, bytes32 errorArg) private pure {
        if (error == RecoverError.NoError) {
            return; // no error: do nothing
        } else if (error == RecoverError.InvalidSignature) {
            revert ECDSAInvalidSignature();
        } else if (error == RecoverError.InvalidSignatureLength) {
            revert ECDSAInvalidSignatureLength(uint256(errorArg));
        } else if (error == RecoverError.InvalidSignatureS) {
            revert ECDSAInvalidSignatureS(errorArg);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/EIP712.sol)

pragma solidity ^0.8.20;

import {MessageHashUtils} from "./MessageHashUtils.sol";
import {ShortStrings, ShortString} from "../ShortStrings.sol";
import {IERC5267} from "../../interfaces/IERC5267.sol";

/**
 * @dev https://eips.ethereum.org/EIPS/eip-712[EIP-712] is a standard for hashing and signing of typed structured data.
 *
 * The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose
 * encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract
 * does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to
 * produce the hash of their typed data using a combination of `abi.encode` and `keccak256`.
 *
 * This contract implements the EIP-712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
 * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
 * ({_hashTypedDataV4}).
 *
 * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
 * the chain id to protect against replay attacks on an eventual fork of the chain.
 *
 * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
 * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
 *
 * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
 * separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the
 * separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
 *
 * @custom:oz-upgrades-unsafe-allow state-variable-immutable
 */
abstract contract EIP712 is IERC5267 {
    using ShortStrings for *;

    bytes32 private constant TYPE_HASH =
        keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");

    // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
    // invalidate the cached domain separator if the chain id changes.
    bytes32 private immutable _cachedDomainSeparator;
    uint256 private immutable _cachedChainId;
    address private immutable _cachedThis;

    bytes32 private immutable _hashedName;
    bytes32 private immutable _hashedVersion;

    ShortString private immutable _name;
    ShortString private immutable _version;
    string private _nameFallback;
    string private _versionFallback;

    /**
     * @dev Initializes the domain separator and parameter caches.
     *
     * The meaning of `name` and `version` is specified in
     * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP-712]:
     *
     * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
     * - `version`: the current major version of the signing domain.
     *
     * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
     * contract upgrade].
     */
    constructor(string memory name, string memory version) {
        _name = name.toShortStringWithFallback(_nameFallback);
        _version = version.toShortStringWithFallback(_versionFallback);
        _hashedName = keccak256(bytes(name));
        _hashedVersion = keccak256(bytes(version));

        _cachedChainId = block.chainid;
        _cachedDomainSeparator = _buildDomainSeparator();
        _cachedThis = address(this);
    }

    /**
     * @dev Returns the domain separator for the current chain.
     */
    function _domainSeparatorV4() internal view returns (bytes32) {
        if (address(this) == _cachedThis && block.chainid == _cachedChainId) {
            return _cachedDomainSeparator;
        } else {
            return _buildDomainSeparator();
        }
    }

    function _buildDomainSeparator() private view returns (bytes32) {
        return keccak256(abi.encode(TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this)));
    }

    /**
     * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
     * function returns the hash of the fully encoded EIP712 message for this domain.
     *
     * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
     *
     * ```solidity
     * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
     *     keccak256("Mail(address to,string contents)"),
     *     mailTo,
     *     keccak256(bytes(mailContents))
     * )));
     * address signer = ECDSA.recover(digest, signature);
     * ```
     */
    function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
        return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash);
    }

    /**
     * @dev See {IERC-5267}.
     */
    function eip712Domain()
        public
        view
        virtual
        returns (
            bytes1 fields,
            string memory name,
            string memory version,
            uint256 chainId,
            address verifyingContract,
            bytes32 salt,
            uint256[] memory extensions
        )
    {
        return (
            hex"0f", // 01111
            _EIP712Name(),
            _EIP712Version(),
            block.chainid,
            address(this),
            bytes32(0),
            new uint256[](0)
        );
    }

    /**
     * @dev The name parameter for the EIP712 domain.
     *
     * NOTE: By default this function reads _name which is an immutable value.
     * It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
     */
    // solhint-disable-next-line func-name-mixedcase
    function _EIP712Name() internal view returns (string memory) {
        return _name.toStringWithFallback(_nameFallback);
    }

    /**
     * @dev The version parameter for the EIP712 domain.
     *
     * NOTE: By default this function reads _version which is an immutable value.
     * It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
     */
    // solhint-disable-next-line func-name-mixedcase
    function _EIP712Version() internal view returns (string memory) {
        return _version.toStringWithFallback(_versionFallback);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/MessageHashUtils.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing.
 *
 * The library provides methods for generating a hash of a message that conforms to the
 * https://eips.ethereum.org/EIPS/eip-191[ERC-191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712]
 * specifications.
 */
library MessageHashUtils {
    /**
     * @dev Returns the keccak256 digest of an ERC-191 signed data with version
     * `0x45` (`personal_sign` messages).
     *
     * The digest is calculated by prefixing a bytes32 `messageHash` with
     * `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the
     * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method.
     *
     * NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with
     * keccak256, although any bytes32 value can be safely used because the final digest will
     * be re-hashed.
     *
     * See {ECDSA-recover}.
     */
    function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) {
        assembly ("memory-safe") {
            mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash
            mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix
            digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20)
        }
    }

    /**
     * @dev Returns the keccak256 digest of an ERC-191 signed data with version
     * `0x45` (`personal_sign` messages).
     *
     * The digest is calculated by prefixing an arbitrary `message` with
     * `"\x19Ethereum Signed Message:\n" + len(message)` and hashing the result. It corresponds with the
     * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method.
     *
     * See {ECDSA-recover}.
     */
    function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) {
        return
            keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message));
    }

    /**
     * @dev Returns the keccak256 digest of an ERC-191 signed data with version
     * `0x00` (data with intended validator).
     *
     * The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended
     * `validator` address. Then hashing the result.
     *
     * See {ECDSA-recover}.
     */
    function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(hex"19_00", validator, data));
    }

    /**
     * @dev Returns the keccak256 digest of an EIP-712 typed data (ERC-191 version `0x01`).
     *
     * The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with
     * `\x19\x01` and hashing the result. It corresponds to the hash signed by the
     * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712.
     *
     * See {ECDSA-recover}.
     */
    function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) {
        assembly ("memory-safe") {
            let ptr := mload(0x40)
            mstore(ptr, hex"19_01")
            mstore(add(ptr, 0x02), domainSeparator)
            mstore(add(ptr, 0x22), structHash)
            digest := keccak256(ptr, 0x42)
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/Math.sol)

pragma solidity ^0.8.20;

import {Panic} from "../Panic.sol";
import {SafeCast} from "./SafeCast.sol";

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Floor, // Toward negative infinity
        Ceil, // Toward positive infinity
        Trunc, // Toward zero
        Expand // Away from zero
    }

    /**
     * @dev Returns the addition of two unsigned integers, with an success flag (no overflow).
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an success flag (no overflow).
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an success flag (no overflow).
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a success flag (no division by zero).
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero).
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.
     *
     * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.
     * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute
     * one branch when needed, making this function more expensive.
     */
    function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {
        unchecked {
            // branchless ternary works because:
            // b ^ (a ^ b) == a
            // b ^ 0 == b
            return b ^ ((a ^ b) * SafeCast.toUint(condition));
        }
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return ternary(a > b, a, b);
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return ternary(a < b, a, b);
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds towards infinity instead
     * of rounding towards zero.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        if (b == 0) {
            // Guarantee the same behavior as in a regular Solidity division.
            Panic.panic(Panic.DIVISION_BY_ZERO);
        }

        // The following calculation ensures accurate ceiling division without overflow.
        // Since a is non-zero, (a - 1) / b will not overflow.
        // The largest possible result occurs when (a - 1) / b is type(uint256).max,
        // but the largest value we can obtain is type(uint256).max - 1, which happens
        // when a = type(uint256).max and b = 1.
        unchecked {
            return SafeCast.toUint(a > 0) * ((a - 1) / b + 1);
        }
    }

    /**
     * @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
     * denominator == 0.
     *
     * Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
     * Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 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²⁵⁶ + prod0.
            uint256 prod0 = x * y; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0.
            if (denominator <= prod1) {
                Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW));
            }

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

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator.
            // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.

            uint256 twos = denominator & (0 - denominator);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such
            // that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv ≡ 1 mod 2⁴.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the 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.
            inverse *= 2 - denominator * inverse; // inverse mod 2⁸
            inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶
            inverse *= 2 - denominator * inverse; // inverse mod 2³²
            inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴
            inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸
            inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2²⁵⁶. Since the preconditions guarantee that the outcome is
            // less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @dev Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0);
    }

    /**
     * @dev Calculate the modular multiplicative inverse of a number in Z/nZ.
     *
     * If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0.
     * If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible.
     *
     * If the input value is not inversible, 0 is returned.
     *
     * NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the
     * inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.
     */
    function invMod(uint256 a, uint256 n) internal pure returns (uint256) {
        unchecked {
            if (n == 0) return 0;

            // The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version)
            // Used to compute integers x and y such that: ax + ny = gcd(a, n).
            // When the gcd is 1, then the inverse of a modulo n exists and it's x.
            // ax + ny = 1
            // ax = 1 + (-y)n
            // ax ≡ 1 (mod n) # x is the inverse of a modulo n

            // If the remainder is 0 the gcd is n right away.
            uint256 remainder = a % n;
            uint256 gcd = n;

            // Therefore the initial coefficients are:
            // ax + ny = gcd(a, n) = n
            // 0a + 1n = n
            int256 x = 0;
            int256 y = 1;

            while (remainder != 0) {
                uint256 quotient = gcd / remainder;

                (gcd, remainder) = (
                    // The old remainder is the next gcd to try.
                    remainder,
                    // Compute the next remainder.
                    // Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd
                    // where gcd is at most n (capped to type(uint256).max)
                    gcd - remainder * quotient
                );

                (x, y) = (
                    // Increment the coefficient of a.
                    y,
                    // Decrement the coefficient of n.
                    // Can overflow, but the result is casted to uint256 so that the
                    // next value of y is "wrapped around" to a value between 0 and n - 1.
                    x - y * int256(quotient)
                );
            }

            if (gcd != 1) return 0; // No inverse exists.
            return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative.
        }
    }

    /**
     * @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`.
     *
     * From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is
     * prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that
     * `a**(p-2)` is the modular multiplicative inverse of a in Fp.
     *
     * NOTE: this function does NOT check that `p` is a prime greater than `2`.
     */
    function invModPrime(uint256 a, uint256 p) internal view returns (uint256) {
        unchecked {
            return Math.modExp(a, p - 2, p);
        }
    }

    /**
     * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m)
     *
     * Requirements:
     * - modulus can't be zero
     * - underlying staticcall to precompile must succeed
     *
     * IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make
     * sure the chain you're using it on supports the precompiled contract for modular exponentiation
     * at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise,
     * the underlying function will succeed given the lack of a revert, but the result may be incorrectly
     * interpreted as 0.
     */
    function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) {
        (bool success, uint256 result) = tryModExp(b, e, m);
        if (!success) {
            Panic.panic(Panic.DIVISION_BY_ZERO);
        }
        return result;
    }

    /**
     * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m).
     * It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying
     * to operate modulo 0 or if the underlying precompile reverted.
     *
     * IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain
     * you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in
     * https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack
     * of a revert, but the result may be incorrectly interpreted as 0.
     */
    function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) {
        if (m == 0) return (false, 0);
        assembly ("memory-safe") {
            let ptr := mload(0x40)
            // | Offset    | Content    | Content (Hex)                                                      |
            // |-----------|------------|--------------------------------------------------------------------|
            // | 0x00:0x1f | size of b  | 0x0000000000000000000000000000000000000000000000000000000000000020 |
            // | 0x20:0x3f | size of e  | 0x0000000000000000000000000000000000000000000000000000000000000020 |
            // | 0x40:0x5f | size of m  | 0x0000000000000000000000000000000000000000000000000000000000000020 |
            // | 0x60:0x7f | value of b | 0x<.............................................................b> |
            // | 0x80:0x9f | value of e | 0x<.............................................................e> |
            // | 0xa0:0xbf | value of m | 0x<.............................................................m> |
            mstore(ptr, 0x20)
            mstore(add(ptr, 0x20), 0x20)
            mstore(add(ptr, 0x40), 0x20)
            mstore(add(ptr, 0x60), b)
            mstore(add(ptr, 0x80), e)
            mstore(add(ptr, 0xa0), m)

            // Given the result < m, it's guaranteed to fit in 32 bytes,
            // so we can use the memory scratch space located at offset 0.
            success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20)
            result := mload(0x00)
        }
    }

    /**
     * @dev Variant of {modExp} that supports inputs of arbitrary length.
     */
    function modExp(bytes memory b, bytes memory e, bytes memory m) internal view returns (bytes memory) {
        (bool success, bytes memory result) = tryModExp(b, e, m);
        if (!success) {
            Panic.panic(Panic.DIVISION_BY_ZERO);
        }
        return result;
    }

    /**
     * @dev Variant of {tryModExp} that supports inputs of arbitrary length.
     */
    function tryModExp(
        bytes memory b,
        bytes memory e,
        bytes memory m
    ) internal view returns (bool success, bytes memory result) {
        if (_zeroBytes(m)) return (false, new bytes(0));

        uint256 mLen = m.length;

        // Encode call args in result and move the free memory pointer
        result = abi.encodePacked(b.length, e.length, mLen, b, e, m);

        assembly ("memory-safe") {
            let dataPtr := add(result, 0x20)
            // Write result on top of args to avoid allocating extra memory.
            success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen)
            // Overwrite the length.
            // result.length > returndatasize() is guaranteed because returndatasize() == m.length
            mstore(result, mLen)
            // Set the memory pointer after the returned data.
            mstore(0x40, add(dataPtr, mLen))
        }
    }

    /**
     * @dev Returns whether the provided byte array is zero.
     */
    function _zeroBytes(bytes memory byteArray) private pure returns (bool) {
        for (uint256 i = 0; i < byteArray.length; ++i) {
            if (byteArray[i] != 0) {
                return false;
            }
        }
        return true;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
     * towards zero.
     *
     * This method is based on Newton's method for computing square roots; the algorithm is restricted to only
     * using integer operations.
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        unchecked {
            // Take care of easy edge cases when a == 0 or a == 1
            if (a <= 1) {
                return a;
            }

            // In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a
            // sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between
            // the current value as `ε_n = | x_n - sqrt(a) |`.
            //
            // For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root
            // of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is
            // bigger than any uint256.
            //
            // By noticing that
            // `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)`
            // we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar
            // to the msb function.
            uint256 aa = a;
            uint256 xn = 1;

            if (aa >= (1 << 128)) {
                aa >>= 128;
                xn <<= 64;
            }
            if (aa >= (1 << 64)) {
                aa >>= 64;
                xn <<= 32;
            }
            if (aa >= (1 << 32)) {
                aa >>= 32;
                xn <<= 16;
            }
            if (aa >= (1 << 16)) {
                aa >>= 16;
                xn <<= 8;
            }
            if (aa >= (1 << 8)) {
                aa >>= 8;
                xn <<= 4;
            }
            if (aa >= (1 << 4)) {
                aa >>= 4;
                xn <<= 2;
            }
            if (aa >= (1 << 2)) {
                xn <<= 1;
            }

            // We now have x_n such that `x_n = 2**(e-1) ≤ sqrt(a) < 2**e = 2 * x_n`. This implies ε_n ≤ 2**(e-1).
            //
            // We can refine our estimation by noticing that the middle of that interval minimizes the error.
            // If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2).
            // This is going to be our x_0 (and ε_0)
            xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2)

            // From here, Newton's method give us:
            // x_{n+1} = (x_n + a / x_n) / 2
            //
            // One should note that:
            // x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a
            //              = ((x_n² + a) / (2 * x_n))² - a
            //              = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a
            //              = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²)
            //              = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²)
            //              = (x_n² - a)² / (2 * x_n)²
            //              = ((x_n² - a) / (2 * x_n))²
            //              ≥ 0
            // Which proves that for all n ≥ 1, sqrt(a) ≤ x_n
            //
            // This gives us the proof of quadratic convergence of the sequence:
            // ε_{n+1} = | x_{n+1} - sqrt(a) |
            //         = | (x_n + a / x_n) / 2 - sqrt(a) |
            //         = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) |
            //         = | (x_n - sqrt(a))² / (2 * x_n) |
            //         = | ε_n² / (2 * x_n) |
            //         = ε_n² / | (2 * x_n) |
            //
            // For the first iteration, we have a special case where x_0 is known:
            // ε_1 = ε_0² / | (2 * x_0) |
            //     ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2)))
            //     ≤ 2**(2*e-4) / (3 * 2**(e-1))
            //     ≤ 2**(e-3) / 3
            //     ≤ 2**(e-3-log2(3))
            //     ≤ 2**(e-4.5)
            //
            // For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n:
            // ε_{n+1} = ε_n² / | (2 * x_n) |
            //         ≤ (2**(e-k))² / (2 * 2**(e-1))
            //         ≤ 2**(2*e-2*k) / 2**e
            //         ≤ 2**(e-2*k)
            xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5)  -- special case, see above
            xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9)    -- general case with k = 4.5
            xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18)   -- general case with k = 9
            xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36)   -- general case with k = 18
            xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72)   -- general case with k = 36
            xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144)  -- general case with k = 72

            // Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision
            // ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either
            // sqrt(a) or sqrt(a) + 1.
            return xn - SafeCast.toUint(xn > a / xn);
        }
    }

    /**
     * @dev Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + SafeCast.toUint(unsignedRoundsUp(rounding) && result * result < a);
        }
    }

    /**
     * @dev Return the log in base 2 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        uint256 exp;
        unchecked {
            exp = 128 * SafeCast.toUint(value > (1 << 128) - 1);
            value >>= exp;
            result += exp;

            exp = 64 * SafeCast.toUint(value > (1 << 64) - 1);
            value >>= exp;
            result += exp;

            exp = 32 * SafeCast.toUint(value > (1 << 32) - 1);
            value >>= exp;
            result += exp;

            exp = 16 * SafeCast.toUint(value > (1 << 16) - 1);
            value >>= exp;
            result += exp;

            exp = 8 * SafeCast.toUint(value > (1 << 8) - 1);
            value >>= exp;
            result += exp;

            exp = 4 * SafeCast.toUint(value > (1 << 4) - 1);
            value >>= exp;
            result += exp;

            exp = 2 * SafeCast.toUint(value > (1 << 2) - 1);
            value >>= exp;
            result += exp;

            result += SafeCast.toUint(value > 1);
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << result < value);
        }
    }

    /**
     * @dev Return the log in base 10 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 10 ** result < value);
        }
    }

    /**
     * @dev Return the log in base 256 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        uint256 isGt;
        unchecked {
            isGt = SafeCast.toUint(value > (1 << 128) - 1);
            value >>= isGt * 128;
            result += isGt * 16;

            isGt = SafeCast.toUint(value > (1 << 64) - 1);
            value >>= isGt * 64;
            result += isGt * 8;

            isGt = SafeCast.toUint(value > (1 << 32) - 1);
            value >>= isGt * 32;
            result += isGt * 4;

            isGt = SafeCast.toUint(value > (1 << 16) - 1);
            value >>= isGt * 16;
            result += isGt * 2;

            result += SafeCast.toUint(value > (1 << 8) - 1);
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << (result << 3) < value);
        }
    }

    /**
     * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
     */
    function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
        return uint8(rounding) % 2 == 1;
    }
}

File 8 of 54 : SafeCast.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SafeCast.sol)
// This file was procedurally generated from scripts/generate/templates/SafeCast.js.

pragma solidity ^0.8.20;

/**
 * @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow
 * checks.
 *
 * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
 * easily result in undesired exploitation or bugs, since developers usually
 * assume that overflows raise errors. `SafeCast` restores this intuition by
 * reverting the transaction when such an operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeCast {
    /**
     * @dev Value doesn't fit in an uint of `bits` size.
     */
    error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);

    /**
     * @dev An int value doesn't fit in an uint of `bits` size.
     */
    error SafeCastOverflowedIntToUint(int256 value);

    /**
     * @dev Value doesn't fit in an int of `bits` size.
     */
    error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);

    /**
     * @dev An uint value doesn't fit in an int of `bits` size.
     */
    error SafeCastOverflowedUintToInt(uint256 value);

    /**
     * @dev Returns the downcasted uint248 from uint256, reverting on
     * overflow (when the input is greater than largest uint248).
     *
     * Counterpart to Solidity's `uint248` operator.
     *
     * Requirements:
     *
     * - input must fit into 248 bits
     */
    function toUint248(uint256 value) internal pure returns (uint248) {
        if (value > type(uint248).max) {
            revert SafeCastOverflowedUintDowncast(248, value);
        }
        return uint248(value);
    }

    /**
     * @dev Returns the downcasted uint240 from uint256, reverting on
     * overflow (when the input is greater than largest uint240).
     *
     * Counterpart to Solidity's `uint240` operator.
     *
     * Requirements:
     *
     * - input must fit into 240 bits
     */
    function toUint240(uint256 value) internal pure returns (uint240) {
        if (value > type(uint240).max) {
            revert SafeCastOverflowedUintDowncast(240, value);
        }
        return uint240(value);
    }

    /**
     * @dev Returns the downcasted uint232 from uint256, reverting on
     * overflow (when the input is greater than largest uint232).
     *
     * Counterpart to Solidity's `uint232` operator.
     *
     * Requirements:
     *
     * - input must fit into 232 bits
     */
    function toUint232(uint256 value) internal pure returns (uint232) {
        if (value > type(uint232).max) {
            revert SafeCastOverflowedUintDowncast(232, value);
        }
        return uint232(value);
    }

    /**
     * @dev Returns the downcasted uint224 from uint256, reverting on
     * overflow (when the input is greater than largest uint224).
     *
     * Counterpart to Solidity's `uint224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     */
    function toUint224(uint256 value) internal pure returns (uint224) {
        if (value > type(uint224).max) {
            revert SafeCastOverflowedUintDowncast(224, value);
        }
        return uint224(value);
    }

    /**
     * @dev Returns the downcasted uint216 from uint256, reverting on
     * overflow (when the input is greater than largest uint216).
     *
     * Counterpart to Solidity's `uint216` operator.
     *
     * Requirements:
     *
     * - input must fit into 216 bits
     */
    function toUint216(uint256 value) internal pure returns (uint216) {
        if (value > type(uint216).max) {
            revert SafeCastOverflowedUintDowncast(216, value);
        }
        return uint216(value);
    }

    /**
     * @dev Returns the downcasted uint208 from uint256, reverting on
     * overflow (when the input is greater than largest uint208).
     *
     * Counterpart to Solidity's `uint208` operator.
     *
     * Requirements:
     *
     * - input must fit into 208 bits
     */
    function toUint208(uint256 value) internal pure returns (uint208) {
        if (value > type(uint208).max) {
            revert SafeCastOverflowedUintDowncast(208, value);
        }
        return uint208(value);
    }

    /**
     * @dev Returns the downcasted uint200 from uint256, reverting on
     * overflow (when the input is greater than largest uint200).
     *
     * Counterpart to Solidity's `uint200` operator.
     *
     * Requirements:
     *
     * - input must fit into 200 bits
     */
    function toUint200(uint256 value) internal pure returns (uint200) {
        if (value > type(uint200).max) {
            revert SafeCastOverflowedUintDowncast(200, value);
        }
        return uint200(value);
    }

    /**
     * @dev Returns the downcasted uint192 from uint256, reverting on
     * overflow (when the input is greater than largest uint192).
     *
     * Counterpart to Solidity's `uint192` operator.
     *
     * Requirements:
     *
     * - input must fit into 192 bits
     */
    function toUint192(uint256 value) internal pure returns (uint192) {
        if (value > type(uint192).max) {
            revert SafeCastOverflowedUintDowncast(192, value);
        }
        return uint192(value);
    }

    /**
     * @dev Returns the downcasted uint184 from uint256, reverting on
     * overflow (when the input is greater than largest uint184).
     *
     * Counterpart to Solidity's `uint184` operator.
     *
     * Requirements:
     *
     * - input must fit into 184 bits
     */
    function toUint184(uint256 value) internal pure returns (uint184) {
        if (value > type(uint184).max) {
            revert SafeCastOverflowedUintDowncast(184, value);
        }
        return uint184(value);
    }

    /**
     * @dev Returns the downcasted uint176 from uint256, reverting on
     * overflow (when the input is greater than largest uint176).
     *
     * Counterpart to Solidity's `uint176` operator.
     *
     * Requirements:
     *
     * - input must fit into 176 bits
     */
    function toUint176(uint256 value) internal pure returns (uint176) {
        if (value > type(uint176).max) {
            revert SafeCastOverflowedUintDowncast(176, value);
        }
        return uint176(value);
    }

    /**
     * @dev Returns the downcasted uint168 from uint256, reverting on
     * overflow (when the input is greater than largest uint168).
     *
     * Counterpart to Solidity's `uint168` operator.
     *
     * Requirements:
     *
     * - input must fit into 168 bits
     */
    function toUint168(uint256 value) internal pure returns (uint168) {
        if (value > type(uint168).max) {
            revert SafeCastOverflowedUintDowncast(168, value);
        }
        return uint168(value);
    }

    /**
     * @dev Returns the downcasted uint160 from uint256, reverting on
     * overflow (when the input is greater than largest uint160).
     *
     * Counterpart to Solidity's `uint160` operator.
     *
     * Requirements:
     *
     * - input must fit into 160 bits
     */
    function toUint160(uint256 value) internal pure returns (uint160) {
        if (value > type(uint160).max) {
            revert SafeCastOverflowedUintDowncast(160, value);
        }
        return uint160(value);
    }

    /**
     * @dev Returns the downcasted uint152 from uint256, reverting on
     * overflow (when the input is greater than largest uint152).
     *
     * Counterpart to Solidity's `uint152` operator.
     *
     * Requirements:
     *
     * - input must fit into 152 bits
     */
    function toUint152(uint256 value) internal pure returns (uint152) {
        if (value > type(uint152).max) {
            revert SafeCastOverflowedUintDowncast(152, value);
        }
        return uint152(value);
    }

    /**
     * @dev Returns the downcasted uint144 from uint256, reverting on
     * overflow (when the input is greater than largest uint144).
     *
     * Counterpart to Solidity's `uint144` operator.
     *
     * Requirements:
     *
     * - input must fit into 144 bits
     */
    function toUint144(uint256 value) internal pure returns (uint144) {
        if (value > type(uint144).max) {
            revert SafeCastOverflowedUintDowncast(144, value);
        }
        return uint144(value);
    }

    /**
     * @dev Returns the downcasted uint136 from uint256, reverting on
     * overflow (when the input is greater than largest uint136).
     *
     * Counterpart to Solidity's `uint136` operator.
     *
     * Requirements:
     *
     * - input must fit into 136 bits
     */
    function toUint136(uint256 value) internal pure returns (uint136) {
        if (value > type(uint136).max) {
            revert SafeCastOverflowedUintDowncast(136, value);
        }
        return uint136(value);
    }

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

    /**
     * @dev Returns the downcasted uint120 from uint256, reverting on
     * overflow (when the input is greater than largest uint120).
     *
     * Counterpart to Solidity's `uint120` operator.
     *
     * Requirements:
     *
     * - input must fit into 120 bits
     */
    function toUint120(uint256 value) internal pure returns (uint120) {
        if (value > type(uint120).max) {
            revert SafeCastOverflowedUintDowncast(120, value);
        }
        return uint120(value);
    }

    /**
     * @dev Returns the downcasted uint112 from uint256, reverting on
     * overflow (when the input is greater than largest uint112).
     *
     * Counterpart to Solidity's `uint112` operator.
     *
     * Requirements:
     *
     * - input must fit into 112 bits
     */
    function toUint112(uint256 value) internal pure returns (uint112) {
        if (value > type(uint112).max) {
            revert SafeCastOverflowedUintDowncast(112, value);
        }
        return uint112(value);
    }

    /**
     * @dev Returns the downcasted uint104 from uint256, reverting on
     * overflow (when the input is greater than largest uint104).
     *
     * Counterpart to Solidity's `uint104` operator.
     *
     * Requirements:
     *
     * - input must fit into 104 bits
     */
    function toUint104(uint256 value) internal pure returns (uint104) {
        if (value > type(uint104).max) {
            revert SafeCastOverflowedUintDowncast(104, value);
        }
        return uint104(value);
    }

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

    /**
     * @dev Returns the downcasted uint88 from uint256, reverting on
     * overflow (when the input is greater than largest uint88).
     *
     * Counterpart to Solidity's `uint88` operator.
     *
     * Requirements:
     *
     * - input must fit into 88 bits
     */
    function toUint88(uint256 value) internal pure returns (uint88) {
        if (value > type(uint88).max) {
            revert SafeCastOverflowedUintDowncast(88, value);
        }
        return uint88(value);
    }

    /**
     * @dev Returns the downcasted uint80 from uint256, reverting on
     * overflow (when the input is greater than largest uint80).
     *
     * Counterpart to Solidity's `uint80` operator.
     *
     * Requirements:
     *
     * - input must fit into 80 bits
     */
    function toUint80(uint256 value) internal pure returns (uint80) {
        if (value > type(uint80).max) {
            revert SafeCastOverflowedUintDowncast(80, value);
        }
        return uint80(value);
    }

    /**
     * @dev Returns the downcasted uint72 from uint256, reverting on
     * overflow (when the input is greater than largest uint72).
     *
     * Counterpart to Solidity's `uint72` operator.
     *
     * Requirements:
     *
     * - input must fit into 72 bits
     */
    function toUint72(uint256 value) internal pure returns (uint72) {
        if (value > type(uint72).max) {
            revert SafeCastOverflowedUintDowncast(72, value);
        }
        return uint72(value);
    }

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

    /**
     * @dev Returns the downcasted uint56 from uint256, reverting on
     * overflow (when the input is greater than largest uint56).
     *
     * Counterpart to Solidity's `uint56` operator.
     *
     * Requirements:
     *
     * - input must fit into 56 bits
     */
    function toUint56(uint256 value) internal pure returns (uint56) {
        if (value > type(uint56).max) {
            revert SafeCastOverflowedUintDowncast(56, value);
        }
        return uint56(value);
    }

    /**
     * @dev Returns the downcasted uint48 from uint256, reverting on
     * overflow (when the input is greater than largest uint48).
     *
     * Counterpart to Solidity's `uint48` operator.
     *
     * Requirements:
     *
     * - input must fit into 48 bits
     */
    function toUint48(uint256 value) internal pure returns (uint48) {
        if (value > type(uint48).max) {
            revert SafeCastOverflowedUintDowncast(48, value);
        }
        return uint48(value);
    }

    /**
     * @dev Returns the downcasted uint40 from uint256, reverting on
     * overflow (when the input is greater than largest uint40).
     *
     * Counterpart to Solidity's `uint40` operator.
     *
     * Requirements:
     *
     * - input must fit into 40 bits
     */
    function toUint40(uint256 value) internal pure returns (uint40) {
        if (value > type(uint40).max) {
            revert SafeCastOverflowedUintDowncast(40, value);
        }
        return uint40(value);
    }

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

    /**
     * @dev Returns the downcasted uint24 from uint256, reverting on
     * overflow (when the input is greater than largest uint24).
     *
     * Counterpart to Solidity's `uint24` operator.
     *
     * Requirements:
     *
     * - input must fit into 24 bits
     */
    function toUint24(uint256 value) internal pure returns (uint24) {
        if (value > type(uint24).max) {
            revert SafeCastOverflowedUintDowncast(24, value);
        }
        return uint24(value);
    }

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

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

    /**
     * @dev Converts a signed int256 into an unsigned uint256.
     *
     * Requirements:
     *
     * - input must be greater than or equal to 0.
     */
    function toUint256(int256 value) internal pure returns (uint256) {
        if (value < 0) {
            revert SafeCastOverflowedIntToUint(value);
        }
        return uint256(value);
    }

    /**
     * @dev Returns the downcasted int248 from int256, reverting on
     * overflow (when the input is less than smallest int248 or
     * greater than largest int248).
     *
     * Counterpart to Solidity's `int248` operator.
     *
     * Requirements:
     *
     * - input must fit into 248 bits
     */
    function toInt248(int256 value) internal pure returns (int248 downcasted) {
        downcasted = int248(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(248, value);
        }
    }

    /**
     * @dev Returns the downcasted int240 from int256, reverting on
     * overflow (when the input is less than smallest int240 or
     * greater than largest int240).
     *
     * Counterpart to Solidity's `int240` operator.
     *
     * Requirements:
     *
     * - input must fit into 240 bits
     */
    function toInt240(int256 value) internal pure returns (int240 downcasted) {
        downcasted = int240(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(240, value);
        }
    }

    /**
     * @dev Returns the downcasted int232 from int256, reverting on
     * overflow (when the input is less than smallest int232 or
     * greater than largest int232).
     *
     * Counterpart to Solidity's `int232` operator.
     *
     * Requirements:
     *
     * - input must fit into 232 bits
     */
    function toInt232(int256 value) internal pure returns (int232 downcasted) {
        downcasted = int232(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(232, value);
        }
    }

    /**
     * @dev Returns the downcasted int224 from int256, reverting on
     * overflow (when the input is less than smallest int224 or
     * greater than largest int224).
     *
     * Counterpart to Solidity's `int224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     */
    function toInt224(int256 value) internal pure returns (int224 downcasted) {
        downcasted = int224(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(224, value);
        }
    }

    /**
     * @dev Returns the downcasted int216 from int256, reverting on
     * overflow (when the input is less than smallest int216 or
     * greater than largest int216).
     *
     * Counterpart to Solidity's `int216` operator.
     *
     * Requirements:
     *
     * - input must fit into 216 bits
     */
    function toInt216(int256 value) internal pure returns (int216 downcasted) {
        downcasted = int216(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(216, value);
        }
    }

    /**
     * @dev Returns the downcasted int208 from int256, reverting on
     * overflow (when the input is less than smallest int208 or
     * greater than largest int208).
     *
     * Counterpart to Solidity's `int208` operator.
     *
     * Requirements:
     *
     * - input must fit into 208 bits
     */
    function toInt208(int256 value) internal pure returns (int208 downcasted) {
        downcasted = int208(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(208, value);
        }
    }

    /**
     * @dev Returns the downcasted int200 from int256, reverting on
     * overflow (when the input is less than smallest int200 or
     * greater than largest int200).
     *
     * Counterpart to Solidity's `int200` operator.
     *
     * Requirements:
     *
     * - input must fit into 200 bits
     */
    function toInt200(int256 value) internal pure returns (int200 downcasted) {
        downcasted = int200(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(200, value);
        }
    }

    /**
     * @dev Returns the downcasted int192 from int256, reverting on
     * overflow (when the input is less than smallest int192 or
     * greater than largest int192).
     *
     * Counterpart to Solidity's `int192` operator.
     *
     * Requirements:
     *
     * - input must fit into 192 bits
     */
    function toInt192(int256 value) internal pure returns (int192 downcasted) {
        downcasted = int192(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(192, value);
        }
    }

    /**
     * @dev Returns the downcasted int184 from int256, reverting on
     * overflow (when the input is less than smallest int184 or
     * greater than largest int184).
     *
     * Counterpart to Solidity's `int184` operator.
     *
     * Requirements:
     *
     * - input must fit into 184 bits
     */
    function toInt184(int256 value) internal pure returns (int184 downcasted) {
        downcasted = int184(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(184, value);
        }
    }

    /**
     * @dev Returns the downcasted int176 from int256, reverting on
     * overflow (when the input is less than smallest int176 or
     * greater than largest int176).
     *
     * Counterpart to Solidity's `int176` operator.
     *
     * Requirements:
     *
     * - input must fit into 176 bits
     */
    function toInt176(int256 value) internal pure returns (int176 downcasted) {
        downcasted = int176(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(176, value);
        }
    }

    /**
     * @dev Returns the downcasted int168 from int256, reverting on
     * overflow (when the input is less than smallest int168 or
     * greater than largest int168).
     *
     * Counterpart to Solidity's `int168` operator.
     *
     * Requirements:
     *
     * - input must fit into 168 bits
     */
    function toInt168(int256 value) internal pure returns (int168 downcasted) {
        downcasted = int168(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(168, value);
        }
    }

    /**
     * @dev Returns the downcasted int160 from int256, reverting on
     * overflow (when the input is less than smallest int160 or
     * greater than largest int160).
     *
     * Counterpart to Solidity's `int160` operator.
     *
     * Requirements:
     *
     * - input must fit into 160 bits
     */
    function toInt160(int256 value) internal pure returns (int160 downcasted) {
        downcasted = int160(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(160, value);
        }
    }

    /**
     * @dev Returns the downcasted int152 from int256, reverting on
     * overflow (when the input is less than smallest int152 or
     * greater than largest int152).
     *
     * Counterpart to Solidity's `int152` operator.
     *
     * Requirements:
     *
     * - input must fit into 152 bits
     */
    function toInt152(int256 value) internal pure returns (int152 downcasted) {
        downcasted = int152(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(152, value);
        }
    }

    /**
     * @dev Returns the downcasted int144 from int256, reverting on
     * overflow (when the input is less than smallest int144 or
     * greater than largest int144).
     *
     * Counterpart to Solidity's `int144` operator.
     *
     * Requirements:
     *
     * - input must fit into 144 bits
     */
    function toInt144(int256 value) internal pure returns (int144 downcasted) {
        downcasted = int144(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(144, value);
        }
    }

    /**
     * @dev Returns the downcasted int136 from int256, reverting on
     * overflow (when the input is less than smallest int136 or
     * greater than largest int136).
     *
     * Counterpart to Solidity's `int136` operator.
     *
     * Requirements:
     *
     * - input must fit into 136 bits
     */
    function toInt136(int256 value) internal pure returns (int136 downcasted) {
        downcasted = int136(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(136, value);
        }
    }

    /**
     * @dev Returns the downcasted int128 from int256, reverting on
     * overflow (when the input is less than smallest int128 or
     * greater than largest int128).
     *
     * Counterpart to Solidity's `int128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     */
    function toInt128(int256 value) internal pure returns (int128 downcasted) {
        downcasted = int128(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(128, value);
        }
    }

    /**
     * @dev Returns the downcasted int120 from int256, reverting on
     * overflow (when the input is less than smallest int120 or
     * greater than largest int120).
     *
     * Counterpart to Solidity's `int120` operator.
     *
     * Requirements:
     *
     * - input must fit into 120 bits
     */
    function toInt120(int256 value) internal pure returns (int120 downcasted) {
        downcasted = int120(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(120, value);
        }
    }

    /**
     * @dev Returns the downcasted int112 from int256, reverting on
     * overflow (when the input is less than smallest int112 or
     * greater than largest int112).
     *
     * Counterpart to Solidity's `int112` operator.
     *
     * Requirements:
     *
     * - input must fit into 112 bits
     */
    function toInt112(int256 value) internal pure returns (int112 downcasted) {
        downcasted = int112(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(112, value);
        }
    }

    /**
     * @dev Returns the downcasted int104 from int256, reverting on
     * overflow (when the input is less than smallest int104 or
     * greater than largest int104).
     *
     * Counterpart to Solidity's `int104` operator.
     *
     * Requirements:
     *
     * - input must fit into 104 bits
     */
    function toInt104(int256 value) internal pure returns (int104 downcasted) {
        downcasted = int104(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(104, value);
        }
    }

    /**
     * @dev Returns the downcasted int96 from int256, reverting on
     * overflow (when the input is less than smallest int96 or
     * greater than largest int96).
     *
     * Counterpart to Solidity's `int96` operator.
     *
     * Requirements:
     *
     * - input must fit into 96 bits
     */
    function toInt96(int256 value) internal pure returns (int96 downcasted) {
        downcasted = int96(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(96, value);
        }
    }

    /**
     * @dev Returns the downcasted int88 from int256, reverting on
     * overflow (when the input is less than smallest int88 or
     * greater than largest int88).
     *
     * Counterpart to Solidity's `int88` operator.
     *
     * Requirements:
     *
     * - input must fit into 88 bits
     */
    function toInt88(int256 value) internal pure returns (int88 downcasted) {
        downcasted = int88(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(88, value);
        }
    }

    /**
     * @dev Returns the downcasted int80 from int256, reverting on
     * overflow (when the input is less than smallest int80 or
     * greater than largest int80).
     *
     * Counterpart to Solidity's `int80` operator.
     *
     * Requirements:
     *
     * - input must fit into 80 bits
     */
    function toInt80(int256 value) internal pure returns (int80 downcasted) {
        downcasted = int80(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(80, value);
        }
    }

    /**
     * @dev Returns the downcasted int72 from int256, reverting on
     * overflow (when the input is less than smallest int72 or
     * greater than largest int72).
     *
     * Counterpart to Solidity's `int72` operator.
     *
     * Requirements:
     *
     * - input must fit into 72 bits
     */
    function toInt72(int256 value) internal pure returns (int72 downcasted) {
        downcasted = int72(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(72, value);
        }
    }

    /**
     * @dev Returns the downcasted int64 from int256, reverting on
     * overflow (when the input is less than smallest int64 or
     * greater than largest int64).
     *
     * Counterpart to Solidity's `int64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     */
    function toInt64(int256 value) internal pure returns (int64 downcasted) {
        downcasted = int64(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(64, value);
        }
    }

    /**
     * @dev Returns the downcasted int56 from int256, reverting on
     * overflow (when the input is less than smallest int56 or
     * greater than largest int56).
     *
     * Counterpart to Solidity's `int56` operator.
     *
     * Requirements:
     *
     * - input must fit into 56 bits
     */
    function toInt56(int256 value) internal pure returns (int56 downcasted) {
        downcasted = int56(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(56, value);
        }
    }

    /**
     * @dev Returns the downcasted int48 from int256, reverting on
     * overflow (when the input is less than smallest int48 or
     * greater than largest int48).
     *
     * Counterpart to Solidity's `int48` operator.
     *
     * Requirements:
     *
     * - input must fit into 48 bits
     */
    function toInt48(int256 value) internal pure returns (int48 downcasted) {
        downcasted = int48(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(48, value);
        }
    }

    /**
     * @dev Returns the downcasted int40 from int256, reverting on
     * overflow (when the input is less than smallest int40 or
     * greater than largest int40).
     *
     * Counterpart to Solidity's `int40` operator.
     *
     * Requirements:
     *
     * - input must fit into 40 bits
     */
    function toInt40(int256 value) internal pure returns (int40 downcasted) {
        downcasted = int40(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(40, value);
        }
    }

    /**
     * @dev Returns the downcasted int32 from int256, reverting on
     * overflow (when the input is less than smallest int32 or
     * greater than largest int32).
     *
     * Counterpart to Solidity's `int32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     */
    function toInt32(int256 value) internal pure returns (int32 downcasted) {
        downcasted = int32(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(32, value);
        }
    }

    /**
     * @dev Returns the downcasted int24 from int256, reverting on
     * overflow (when the input is less than smallest int24 or
     * greater than largest int24).
     *
     * Counterpart to Solidity's `int24` operator.
     *
     * Requirements:
     *
     * - input must fit into 24 bits
     */
    function toInt24(int256 value) internal pure returns (int24 downcasted) {
        downcasted = int24(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(24, value);
        }
    }

    /**
     * @dev Returns the downcasted int16 from int256, reverting on
     * overflow (when the input is less than smallest int16 or
     * greater than largest int16).
     *
     * Counterpart to Solidity's `int16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     */
    function toInt16(int256 value) internal pure returns (int16 downcasted) {
        downcasted = int16(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(16, value);
        }
    }

    /**
     * @dev Returns the downcasted int8 from int256, reverting on
     * overflow (when the input is less than smallest int8 or
     * greater than largest int8).
     *
     * Counterpart to Solidity's `int8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits
     */
    function toInt8(int256 value) internal pure returns (int8 downcasted) {
        downcasted = int8(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(8, value);
        }
    }

    /**
     * @dev Converts an unsigned uint256 into a signed int256.
     *
     * Requirements:
     *
     * - input must be less than or equal to maxInt256.
     */
    function toInt256(uint256 value) internal pure returns (int256) {
        // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
        if (value > uint256(type(int256).max)) {
            revert SafeCastOverflowedUintToInt(value);
        }
        return int256(value);
    }

    /**
     * @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump.
     */
    function toUint(bool b) internal pure returns (uint256 u) {
        assembly ("memory-safe") {
            u := iszero(iszero(b))
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMath {
    /**
     * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.
     *
     * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.
     * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute
     * one branch when needed, making this function more expensive.
     */
    function ternary(bool condition, int256 a, int256 b) internal pure returns (int256) {
        unchecked {
            // branchless ternary works because:
            // b ^ (a ^ b) == a
            // b ^ 0 == b
            return b ^ ((a ^ b) * int256(SafeCast.toUint(condition)));
        }
    }

    /**
     * @dev Returns the largest of two signed numbers.
     */
    function max(int256 a, int256 b) internal pure returns (int256) {
        return ternary(a > b, a, b);
    }

    /**
     * @dev Returns the smallest of two signed numbers.
     */
    function min(int256 a, int256 b) internal pure returns (int256) {
        return ternary(a < b, a, b);
    }

    /**
     * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
    function average(int256 a, int256 b) internal pure returns (int256) {
        // Formula from the book "Hacker's Delight"
        int256 x = (a & b) + ((a ^ b) >> 1);
        return x + (int256(uint256(x) >> 255) & (a ^ b));
    }

    /**
     * @dev Returns the absolute unsigned value of a signed value.
     */
    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // Formula from the "Bit Twiddling Hacks" by Sean Eron Anderson.
            // Since `n` is a signed integer, the generated bytecode will use the SAR opcode to perform the right shift,
            // taking advantage of the most significant (or "sign" bit) in two's complement representation.
            // This opcode adds new most significant bits set to the value of the previous most significant bit. As a result,
            // the mask will either be `bytes32(0)` (if n is positive) or `~bytes32(0)` (if n is negative).
            int256 mask = n >> 255;

            // A `bytes32(0)` mask leaves the input unchanged, while a `~bytes32(0)` mask complements it.
            return uint256((n + mask) ^ mask);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Panic.sol)

pragma solidity ^0.8.20;

/**
 * @dev Helper library for emitting standardized panic codes.
 *
 * ```solidity
 * contract Example {
 *      using Panic for uint256;
 *
 *      // Use any of the declared internal constants
 *      function foo() { Panic.GENERIC.panic(); }
 *
 *      // Alternatively
 *      function foo() { Panic.panic(Panic.GENERIC); }
 * }
 * ```
 *
 * Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil].
 *
 * _Available since v5.1._
 */
// slither-disable-next-line unused-state
library Panic {
    /// @dev generic / unspecified error
    uint256 internal constant GENERIC = 0x00;
    /// @dev used by the assert() builtin
    uint256 internal constant ASSERT = 0x01;
    /// @dev arithmetic underflow or overflow
    uint256 internal constant UNDER_OVERFLOW = 0x11;
    /// @dev division or modulo by zero
    uint256 internal constant DIVISION_BY_ZERO = 0x12;
    /// @dev enum conversion error
    uint256 internal constant ENUM_CONVERSION_ERROR = 0x21;
    /// @dev invalid encoding in storage
    uint256 internal constant STORAGE_ENCODING_ERROR = 0x22;
    /// @dev empty array pop
    uint256 internal constant EMPTY_ARRAY_POP = 0x31;
    /// @dev array out of bounds access
    uint256 internal constant ARRAY_OUT_OF_BOUNDS = 0x32;
    /// @dev resource error (too large allocation or too large array)
    uint256 internal constant RESOURCE_ERROR = 0x41;
    /// @dev calling invalid internal function
    uint256 internal constant INVALID_INTERNAL_FUNCTION = 0x51;

    /// @dev Reverts with a panic code. Recommended to use with
    /// the internal constants with predefined codes.
    function panic(uint256 code) internal pure {
        assembly ("memory-safe") {
            mstore(0x00, 0x4e487b71)
            mstore(0x20, code)
            revert(0x1c, 0x24)
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ShortStrings.sol)

pragma solidity ^0.8.20;

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

// | string  | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA   |
// | length  | 0x                                                              BB |
type ShortString is bytes32;

/**
 * @dev This library provides functions to convert short memory strings
 * into a `ShortString` type that can be used as an immutable variable.
 *
 * Strings of arbitrary length can be optimized using this library if
 * they are short enough (up to 31 bytes) by packing them with their
 * length (1 byte) in a single EVM word (32 bytes). Additionally, a
 * fallback mechanism can be used for every other case.
 *
 * Usage example:
 *
 * ```solidity
 * contract Named {
 *     using ShortStrings for *;
 *
 *     ShortString private immutable _name;
 *     string private _nameFallback;
 *
 *     constructor(string memory contractName) {
 *         _name = contractName.toShortStringWithFallback(_nameFallback);
 *     }
 *
 *     function name() external view returns (string memory) {
 *         return _name.toStringWithFallback(_nameFallback);
 *     }
 * }
 * ```
 */
library ShortStrings {
    // Used as an identifier for strings longer than 31 bytes.
    bytes32 private constant FALLBACK_SENTINEL = 0x00000000000000000000000000000000000000000000000000000000000000FF;

    error StringTooLong(string str);
    error InvalidShortString();

    /**
     * @dev Encode a string of at most 31 chars into a `ShortString`.
     *
     * This will trigger a `StringTooLong` error is the input string is too long.
     */
    function toShortString(string memory str) internal pure returns (ShortString) {
        bytes memory bstr = bytes(str);
        if (bstr.length > 31) {
            revert StringTooLong(str);
        }
        return ShortString.wrap(bytes32(uint256(bytes32(bstr)) | bstr.length));
    }

    /**
     * @dev Decode a `ShortString` back to a "normal" string.
     */
    function toString(ShortString sstr) internal pure returns (string memory) {
        uint256 len = byteLength(sstr);
        // using `new string(len)` would work locally but is not memory safe.
        string memory str = new string(32);
        assembly ("memory-safe") {
            mstore(str, len)
            mstore(add(str, 0x20), sstr)
        }
        return str;
    }

    /**
     * @dev Return the length of a `ShortString`.
     */
    function byteLength(ShortString sstr) internal pure returns (uint256) {
        uint256 result = uint256(ShortString.unwrap(sstr)) & 0xFF;
        if (result > 31) {
            revert InvalidShortString();
        }
        return result;
    }

    /**
     * @dev Encode a string into a `ShortString`, or write it to storage if it is too long.
     */
    function toShortStringWithFallback(string memory value, string storage store) internal returns (ShortString) {
        if (bytes(value).length < 32) {
            return toShortString(value);
        } else {
            StorageSlot.getStringSlot(store).value = value;
            return ShortString.wrap(FALLBACK_SENTINEL);
        }
    }

    /**
     * @dev Decode a string that was encoded to `ShortString` or written to storage using {setWithFallback}.
     */
    function toStringWithFallback(ShortString value, string storage store) internal pure returns (string memory) {
        if (ShortString.unwrap(value) != FALLBACK_SENTINEL) {
            return toString(value);
        } else {
            return store;
        }
    }

    /**
     * @dev Return the length of a string that was encoded to `ShortString` or written to storage using
     * {setWithFallback}.
     *
     * WARNING: This will return the "byte length" of the string. This may not reflect the actual length in terms of
     * actual characters as the UTF-8 encoding of a single character can span over multiple bytes.
     */
    function byteLengthWithFallback(ShortString value, string storage store) internal view returns (uint256) {
        if (ShortString.unwrap(value) != FALLBACK_SENTINEL) {
            return byteLength(value);
        } else {
            return bytes(store).length;
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.

pragma solidity ^0.8.20;

/**
 * @dev Library for reading and writing primitive types to specific storage slots.
 *
 * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
 * This library helps with reading and writing to such slots without the need for inline assembly.
 *
 * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
 *
 * Example usage to set ERC-1967 implementation slot:
 * ```solidity
 * contract ERC1967 {
 *     // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
 *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
 *
 *     function _getImplementation() internal view returns (address) {
 *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
 *     }
 *
 *     function _setImplementation(address newImplementation) internal {
 *         require(newImplementation.code.length > 0);
 *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
 *     }
 * }
 * ```
 *
 * TIP: Consider using this library along with {SlotDerivation}.
 */
library StorageSlot {
    struct AddressSlot {
        address value;
    }

    struct BooleanSlot {
        bool value;
    }

    struct Bytes32Slot {
        bytes32 value;
    }

    struct Uint256Slot {
        uint256 value;
    }

    struct Int256Slot {
        int256 value;
    }

    struct StringSlot {
        string value;
    }

    struct BytesSlot {
        bytes value;
    }

    /**
     * @dev Returns an `AddressSlot` with member `value` located at `slot`.
     */
    function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `BooleanSlot` with member `value` located at `slot`.
     */
    function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Bytes32Slot` with member `value` located at `slot`.
     */
    function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Uint256Slot` with member `value` located at `slot`.
     */
    function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Int256Slot` with member `value` located at `slot`.
     */
    function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `StringSlot` with member `value` located at `slot`.
     */
    function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
     */
    function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
        assembly ("memory-safe") {
            r.slot := store.slot
        }
    }

    /**
     * @dev Returns a `BytesSlot` with member `value` located at `slot`.
     */
    function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
     */
    function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
        assembly ("memory-safe") {
            r.slot := store.slot
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (utils/Strings.sol)

pragma solidity ^0.8.20;

import {Math} from "./math/Math.sol";
import {SafeCast} from "./math/SafeCast.sol";
import {SignedMath} from "./math/SignedMath.sol";

/**
 * @dev String operations.
 */
library Strings {
    using SafeCast for *;

    bytes16 private constant HEX_DIGITS = "0123456789abcdef";
    uint8 private constant ADDRESS_LENGTH = 20;

    /**
     * @dev The `value` string doesn't fit in the specified `length`.
     */
    error StringsInsufficientHexLength(uint256 value, uint256 length);

    /**
     * @dev The string being parsed contains characters that are not in scope of the given base.
     */
    error StringsInvalidChar();

    /**
     * @dev The string being parsed is not a properly formatted address.
     */
    error StringsInvalidAddressFormat();

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            assembly ("memory-safe") {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                assembly ("memory-safe") {
                    mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toStringSigned(int256 value) internal pure returns (string memory) {
        return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        uint256 localValue = value;
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = HEX_DIGITS[localValue & 0xf];
            localValue >>= 4;
        }
        if (localValue != 0) {
            revert StringsInsufficientHexLength(value, length);
        }
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
     * representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its checksummed ASCII `string` hexadecimal
     * representation, according to EIP-55.
     */
    function toChecksumHexString(address addr) internal pure returns (string memory) {
        bytes memory buffer = bytes(toHexString(addr));

        // hash the hex part of buffer (skip length + 2 bytes, length 40)
        uint256 hashValue;
        assembly ("memory-safe") {
            hashValue := shr(96, keccak256(add(buffer, 0x22), 40))
        }

        for (uint256 i = 41; i > 1; --i) {
            // possible values for buffer[i] are 48 (0) to 57 (9) and 97 (a) to 102 (f)
            if (hashValue & 0xf > 7 && uint8(buffer[i]) > 96) {
                // case shift by xoring with 0x20
                buffer[i] ^= 0x20;
            }
            hashValue >>= 4;
        }
        return string(buffer);
    }

    /**
     * @dev Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
    }

    /**
     * @dev Parse a decimal string and returns the value as a `uint256`.
     *
     * Requirements:
     * - The string must be formatted as `[0-9]*`
     * - The result must fit into an `uint256` type
     */
    function parseUint(string memory input) internal pure returns (uint256) {
        return parseUint(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseUint} that parses a substring of `input` located between position `begin` (included) and
     * `end` (excluded).
     *
     * Requirements:
     * - The substring must be formatted as `[0-9]*`
     * - The result must fit into an `uint256` type
     */
    function parseUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {
        (bool success, uint256 value) = tryParseUint(input, begin, end);
        if (!success) revert StringsInvalidChar();
        return value;
    }

    /**
     * @dev Variant of {parseUint-string} that returns false if the parsing fails because of an invalid character.
     *
     * NOTE: This function will revert if the result does not fit in a `uint256`.
     */
    function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) {
        return _tryParseUintUncheckedBounds(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseUint-string-uint256-uint256} that returns false if the parsing fails because of an invalid
     * character.
     *
     * NOTE: This function will revert if the result does not fit in a `uint256`.
     */
    function tryParseUint(
        string memory input,
        uint256 begin,
        uint256 end
    ) internal pure returns (bool success, uint256 value) {
        if (end > bytes(input).length || begin > end) return (false, 0);
        return _tryParseUintUncheckedBounds(input, begin, end);
    }

    /**
     * @dev Implementation of {tryParseUint} that does not check bounds. Caller should make sure that
     * `begin <= end <= input.length`. Other inputs would result in undefined behavior.
     */
    function _tryParseUintUncheckedBounds(
        string memory input,
        uint256 begin,
        uint256 end
    ) private pure returns (bool success, uint256 value) {
        bytes memory buffer = bytes(input);

        uint256 result = 0;
        for (uint256 i = begin; i < end; ++i) {
            uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));
            if (chr > 9) return (false, 0);
            result *= 10;
            result += chr;
        }
        return (true, result);
    }

    /**
     * @dev Parse a decimal string and returns the value as a `int256`.
     *
     * Requirements:
     * - The string must be formatted as `[-+]?[0-9]*`
     * - The result must fit in an `int256` type.
     */
    function parseInt(string memory input) internal pure returns (int256) {
        return parseInt(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseInt-string} that parses a substring of `input` located between position `begin` (included) and
     * `end` (excluded).
     *
     * Requirements:
     * - The substring must be formatted as `[-+]?[0-9]*`
     * - The result must fit in an `int256` type.
     */
    function parseInt(string memory input, uint256 begin, uint256 end) internal pure returns (int256) {
        (bool success, int256 value) = tryParseInt(input, begin, end);
        if (!success) revert StringsInvalidChar();
        return value;
    }

    /**
     * @dev Variant of {parseInt-string} that returns false if the parsing fails because of an invalid character or if
     * the result does not fit in a `int256`.
     *
     * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
     */
    function tryParseInt(string memory input) internal pure returns (bool success, int256 value) {
        return _tryParseIntUncheckedBounds(input, 0, bytes(input).length);
    }

    uint256 private constant ABS_MIN_INT256 = 2 ** 255;

    /**
     * @dev Variant of {parseInt-string-uint256-uint256} that returns false if the parsing fails because of an invalid
     * character or if the result does not fit in a `int256`.
     *
     * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
     */
    function tryParseInt(
        string memory input,
        uint256 begin,
        uint256 end
    ) internal pure returns (bool success, int256 value) {
        if (end > bytes(input).length || begin > end) return (false, 0);
        return _tryParseIntUncheckedBounds(input, begin, end);
    }

    /**
     * @dev Implementation of {tryParseInt} that does not check bounds. Caller should make sure that
     * `begin <= end <= input.length`. Other inputs would result in undefined behavior.
     */
    function _tryParseIntUncheckedBounds(
        string memory input,
        uint256 begin,
        uint256 end
    ) private pure returns (bool success, int256 value) {
        bytes memory buffer = bytes(input);

        // Check presence of a negative sign.
        bytes1 sign = begin == end ? bytes1(0) : bytes1(_unsafeReadBytesOffset(buffer, begin)); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
        bool positiveSign = sign == bytes1("+");
        bool negativeSign = sign == bytes1("-");
        uint256 offset = (positiveSign || negativeSign).toUint();

        (bool absSuccess, uint256 absValue) = tryParseUint(input, begin + offset, end);

        if (absSuccess && absValue < ABS_MIN_INT256) {
            return (true, negativeSign ? -int256(absValue) : int256(absValue));
        } else if (absSuccess && negativeSign && absValue == ABS_MIN_INT256) {
            return (true, type(int256).min);
        } else return (false, 0);
    }

    /**
     * @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as a `uint256`.
     *
     * Requirements:
     * - The string must be formatted as `(0x)?[0-9a-fA-F]*`
     * - The result must fit in an `uint256` type.
     */
    function parseHexUint(string memory input) internal pure returns (uint256) {
        return parseHexUint(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseHexUint} that parses a substring of `input` located between position `begin` (included) and
     * `end` (excluded).
     *
     * Requirements:
     * - The substring must be formatted as `(0x)?[0-9a-fA-F]*`
     * - The result must fit in an `uint256` type.
     */
    function parseHexUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {
        (bool success, uint256 value) = tryParseHexUint(input, begin, end);
        if (!success) revert StringsInvalidChar();
        return value;
    }

    /**
     * @dev Variant of {parseHexUint-string} that returns false if the parsing fails because of an invalid character.
     *
     * NOTE: This function will revert if the result does not fit in a `uint256`.
     */
    function tryParseHexUint(string memory input) internal pure returns (bool success, uint256 value) {
        return _tryParseHexUintUncheckedBounds(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseHexUint-string-uint256-uint256} that returns false if the parsing fails because of an
     * invalid character.
     *
     * NOTE: This function will revert if the result does not fit in a `uint256`.
     */
    function tryParseHexUint(
        string memory input,
        uint256 begin,
        uint256 end
    ) internal pure returns (bool success, uint256 value) {
        if (end > bytes(input).length || begin > end) return (false, 0);
        return _tryParseHexUintUncheckedBounds(input, begin, end);
    }

    /**
     * @dev Implementation of {tryParseHexUint} that does not check bounds. Caller should make sure that
     * `begin <= end <= input.length`. Other inputs would result in undefined behavior.
     */
    function _tryParseHexUintUncheckedBounds(
        string memory input,
        uint256 begin,
        uint256 end
    ) private pure returns (bool success, uint256 value) {
        bytes memory buffer = bytes(input);

        // skip 0x prefix if present
        bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
        uint256 offset = hasPrefix.toUint() * 2;

        uint256 result = 0;
        for (uint256 i = begin + offset; i < end; ++i) {
            uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));
            if (chr > 15) return (false, 0);
            result *= 16;
            unchecked {
                // Multiplying by 16 is equivalent to a shift of 4 bits (with additional overflow check).
                // This guaratees that adding a value < 16 will not cause an overflow, hence the unchecked.
                result += chr;
            }
        }
        return (true, result);
    }

    /**
     * @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as an `address`.
     *
     * Requirements:
     * - The string must be formatted as `(0x)?[0-9a-fA-F]{40}`
     */
    function parseAddress(string memory input) internal pure returns (address) {
        return parseAddress(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseAddress} that parses a substring of `input` located between position `begin` (included) and
     * `end` (excluded).
     *
     * Requirements:
     * - The substring must be formatted as `(0x)?[0-9a-fA-F]{40}`
     */
    function parseAddress(string memory input, uint256 begin, uint256 end) internal pure returns (address) {
        (bool success, address value) = tryParseAddress(input, begin, end);
        if (!success) revert StringsInvalidAddressFormat();
        return value;
    }

    /**
     * @dev Variant of {parseAddress-string} that returns false if the parsing fails because the input is not a properly
     * formatted address. See {parseAddress} requirements.
     */
    function tryParseAddress(string memory input) internal pure returns (bool success, address value) {
        return tryParseAddress(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseAddress-string-uint256-uint256} that returns false if the parsing fails because input is not a properly
     * formatted address. See {parseAddress} requirements.
     */
    function tryParseAddress(
        string memory input,
        uint256 begin,
        uint256 end
    ) internal pure returns (bool success, address value) {
        if (end > bytes(input).length || begin > end) return (false, address(0));

        bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
        uint256 expectedLength = 40 + hasPrefix.toUint() * 2;

        // check that input is the correct length
        if (end - begin == expectedLength) {
            // length guarantees that this does not overflow, and value is at most type(uint160).max
            (bool s, uint256 v) = _tryParseHexUintUncheckedBounds(input, begin, end);
            return (s, address(uint160(v)));
        } else {
            return (false, address(0));
        }
    }

    function _tryParseChr(bytes1 chr) private pure returns (uint8) {
        uint8 value = uint8(chr);

        // Try to parse `chr`:
        // - Case 1: [0-9]
        // - Case 2: [a-f]
        // - Case 3: [A-F]
        // - otherwise not supported
        unchecked {
            if (value > 47 && value < 58) value -= 48;
            else if (value > 96 && value < 103) value -= 87;
            else if (value > 64 && value < 71) value -= 55;
            else return type(uint8).max;
        }

        return value;
    }

    /**
     * @dev Reads a bytes32 from a bytes array without bounds checking.
     *
     * NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the
     * assembly block as such would prevent some optimizations.
     */
    function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) {
        // This is not memory safe in the general case, but all calls to this private function are within bounds.
        assembly ("memory-safe") {
            value := mload(add(buffer, add(0x20, offset)))
        }
    }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

library Address {
  /**
   * @dev Returns true if `account` is a contract.
   *
   * [IMPORTANT]
   * ====
   * It is unsafe to assume that an address for which this function returns
   * false is an externally-owned account (EOA) and not a contract.
   *
   * Among others, `isContract` will return false for the following
   * types of addresses:
   *
   *  - an externally-owned account
   *  - a contract in construction
   *  - an address where a contract will be created
   *  - an address where a contract lived, but was destroyed
   * ====
   *
   * [IMPORTANT]
   * ====
   * You shouldn't rely on `isContract` to protect against flash loan attacks!
   *
   * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
   * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
   * constructor.
   * ====
   */
  function isContract(address account) internal view returns (bool) {
    // This method relies on extcodesize/address.code.length, which returns 0
    // for contracts in construction, since the code is only stored at the end
    // of the constructor execution.

    return account.code.length > 0;
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

/**
 * @notice Library helpers for converting asset quantities between asset units and pips
 */
library AssetUnitConversions {
  function pipsToAssetUnits(uint64 quantity, uint8 assetDecimals) internal pure returns (uint256) {
    require(assetDecimals <= 32, "Asset cannot have more than 32 decimals");

    // Exponents cannot be negative, so divide or multiply based on exponent signedness
    if (assetDecimals > 8) {
      return uint256(quantity) * (uint256(10) ** (assetDecimals - 8));
    }
    return uint256(quantity) / (uint256(10) ** (8 - assetDecimals));
  }

  function assetUnitsToPips(uint256 quantityInAssetUnits, uint8 assetDecimals) internal pure returns (uint64) {
    require(assetDecimals <= 32, "Asset cannot have more than 32 decimals");

    uint256 quantity;
    // Exponents cannot be negative, so divide or multiply based on exponent signedness
    if (assetDecimals > 8) {
      quantity = quantityInAssetUnits / (uint256(10) ** (assetDecimals - 8));
    } else {
      quantity = quantityInAssetUnits * (uint256(10) ** (8 - assetDecimals));
    }
    require(quantity <= type(uint64).max, "Pip quantity overflows uint64");

    return uint64(quantity);
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { BalanceTracking } from "./BalanceTracking.sol";
import { Constants } from "./Constants.sol";
import { Funding } from "./Funding.sol";
import { Math } from "./Math.sol";
import { String } from "./String.sol";
import { Balance, FundingMultiplierQuartet, Market } from "./Structs.sol";

library BalanceLoading {
  using BalanceTracking for BalanceTracking.Storage;

  // solhint-disable-next-line func-name-mixedcase
  function loadBalanceBySymbol_delegatecall(
    address wallet,
    string memory assetSymbol,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => Market) storage marketsByBaseAssetSymbol,
    mapping(address => uint64) storage pendingDepositQuantityByWallet
  ) public view returns (int64 balance) {
    balance = balanceTracking.loadBalanceFromMigrationSourceIfNeeded(wallet, assetSymbol);

    if (String.isEqual(assetSymbol, Constants.QUOTE_ASSET_SYMBOL)) {
      balance +=
        Funding.loadOutstandingWalletFunding(
          wallet,
          balanceTracking,
          baseAssetSymbolsWithOpenPositionsByWallet,
          fundingMultipliersByBaseAssetSymbol,
          lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
          marketsByBaseAssetSymbol
        ) +
        Math.toInt64(pendingDepositQuantityByWallet[wallet]);
    }
  }

  // solhint-disable-next-line func-name-mixedcase
  function loadBalanceStructBySymbol_delegatecall(
    address wallet,
    string memory assetSymbol,
    BalanceTracking.Storage storage balanceTracking
  ) public view returns (Balance memory) {
    return balanceTracking.loadBalanceStructFromMigrationSourceIfNeeded(wallet, assetSymbol);
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { Constants } from "./Constants.sol";
import { LiquidationValidations } from "./LiquidationValidations.sol";
import { MarketHelper } from "./MarketHelper.sol";
import { Math } from "./Math.sol";
import { OrderSide } from "./Enums.sol";
import { SortedStringSet } from "./SortedStringSet.sol";
import { IExchange, IManagedAccountProvider, IOraclePriceAdapter } from "./Interfaces.sol";
import {
  Balance,
  Market,
  MarketOverrides,
  Order,
  Trade,
  Transfer,
  Withdrawal,
  WithdrawalFromManagedAccountByQuantity,
  WithdrawalFromManagedAccountByShares
} from "./Structs.sol";

library BalanceTracking {
  using MarketHelper for Market;
  using SortedStringSet for string[];

  struct Storage {
    mapping(address => mapping(string => Balance)) balancesByWalletAssetPair;
    // Predecessor Exchange contract from which to lazily migrate balances
    IExchange migrationSource;
  }

  struct UpdatePositionForExitArguments {
    int64 exitAccountValue;
    address exitFundWallet;
    uint64 maintenanceMarginFraction;
    Market market;
    IOraclePriceAdapter oraclePriceAdapter;
    int256 totalAccountValueInDoublePips;
    uint256 totalMaintenanceMarginRequirementInTriplePips;
    address wallet;
  }

  // Depositing //

  function updateForDeposit(
    Storage storage self,
    address wallet,
    uint64 quantity
  ) internal returns (int64 newBalance, IManagedAccountProvider managedAccountProvider) {
    Balance storage balanceStruct = loadBalanceStructAndMigrateIfNeeded(self, wallet, Constants.QUOTE_ASSET_SYMBOL);
    balanceStruct.balance += Math.toInt64(quantity);

    return (balanceStruct.balance, balanceStruct.managedAccountProvider);
  }

  // Liquidation //

  function updatePositionsForDeleverage(
    Storage storage self,
    uint64 baseQuantity,
    address counterpartyWallet,
    address exitFundWallet,
    address liquidatingWallet,
    Market memory market,
    uint64 quoteQuantity,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet
  ) internal {
    _updatePositionsForDeleverageOrLiquidation(
      self,
      baseQuantity,
      counterpartyWallet,
      exitFundWallet,
      true,
      liquidatingWallet,
      market,
      quoteQuantity,
      baseAssetSymbolsWithOpenPositionsByWallet,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet
    );
  }

  function updatePositionsForLiquidation(
    Storage storage self,
    address counterpartyWallet,
    address exitFundWallet,
    address liquidatingWallet,
    Market memory market,
    int64 positionSize,
    uint64 quoteQuantity,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet
  ) internal {
    _updatePositionsForDeleverageOrLiquidation(
      self,
      Math.abs(positionSize),
      counterpartyWallet,
      exitFundWallet,
      false,
      liquidatingWallet,
      market,
      quoteQuantity,
      baseAssetSymbolsWithOpenPositionsByWallet,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet
    );
  }

  function updatePositionForDeactivatedMarketLiquidation(
    Storage storage self,
    string memory baseAssetSymbol,
    uint64 feeQuantity,
    address feeWallet,
    address liquidatingWallet,
    uint64 quoteQuantity,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet
  ) internal {
    Balance storage balanceStruct;

    // Zero out wallet position for market
    balanceStruct = loadBalanceStructAndMigrateIfNeeded(self, liquidatingWallet, baseAssetSymbol);
    bool isLiquidatingWalletPositionShort = balanceStruct.balance < 0;
    _resetPositionToZero(balanceStruct);

    _updateOpenPositionTrackingForWallet(
      liquidatingWallet,
      baseAssetSymbol,
      balanceStruct.balance,
      baseAssetSymbolsWithOpenPositionsByWallet
    );

    balanceStruct = loadBalanceStructAndMigrateIfNeeded(self, liquidatingWallet, Constants.QUOTE_ASSET_SYMBOL);
    if (isLiquidatingWalletPositionShort) {
      // Wallet gives quote including fee if short
      balanceStruct.balance -= Math.toInt64(quoteQuantity + feeQuantity);
    } else {
      // Wallet receives quote minus fee if long
      balanceStruct.balance += Math.toInt64(quoteQuantity - feeQuantity);
    }

    // Fee wallet receives fee
    balanceStruct = loadBalanceStructAndMigrateIfNeeded(self, feeWallet, Constants.QUOTE_ASSET_SYMBOL);
    balanceStruct.balance += Math.toInt64(feeQuantity);
  }

  function updateRemainingQuoteBalanceAfterWalletLiquidation(
    Storage storage self,
    address counterpartyWallet,
    address liquidatingWallet
  ) internal {
    Balance storage balanceStruct;

    // Liquidating wallet quote balance goes to zero
    balanceStruct = loadBalanceStructAndMigrateIfNeeded(self, liquidatingWallet, Constants.QUOTE_ASSET_SYMBOL);
    int64 quoteQuantity = balanceStruct.balance;
    balanceStruct.balance = 0;
    // Counterparty wallet takes any remaining quote from liquidating wallet
    if (quoteQuantity != 0) {
      balanceStruct = loadBalanceStructAndMigrateIfNeeded(self, counterpartyWallet, Constants.QUOTE_ASSET_SYMBOL);
      balanceStruct.balance += quoteQuantity;
    }
  }

  // Wallet exits //

  function updateExitFundWalletForExit(
    Storage storage self,
    address exitFundWallet
  ) internal returns (int64 walletQuoteQuantityToWithdraw) {
    Balance storage balanceStruct = loadBalanceStructAndMigrateIfNeeded(
      self,
      exitFundWallet,
      Constants.QUOTE_ASSET_SYMBOL
    );

    walletQuoteQuantityToWithdraw = balanceStruct.balance;
    balanceStruct.balance = 0;
  }

  /**
   * @return The signed change to the EF's quote balance as a result of closing the position. This will be positive for
   * a short position and negative for a long position. This function does not update the EF's quote balance itself;
   * that is left to the calling function so that it can perform a single update with the sum of each position's result
   */
  function updatePositionForExit(
    Storage storage self,
    UpdatePositionForExitArguments memory arguments,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol
  ) internal returns (int64) {
    Balance storage balanceStruct = loadBalanceStructAndMigrateIfNeeded(
      self,
      arguments.wallet,
      arguments.market.baseAssetSymbol
    );
    uint64 oraclePrice = arguments.oraclePriceAdapter.loadPriceForBaseAssetSymbol(arguments.market.baseAssetSymbol);
    int64 positionSize = balanceStruct.balance;
    // Calculate amount of quote to close position
    uint64 quoteQuantity = arguments.exitAccountValue <= 0
      ? LiquidationValidations.calculateQuoteQuantityAtBankruptcyPrice(
        // This exit path takes place entirely on-chain, so use on-chain oracle pricing rather than index pricing
        oraclePrice,
        arguments.maintenanceMarginFraction,
        positionSize,
        arguments.totalAccountValueInDoublePips,
        arguments.totalMaintenanceMarginRequirementInTriplePips
      )
      : LiquidationValidations.calculateQuoteQuantityAtExitPrice(balanceStruct.costBasis, oraclePrice, positionSize);

    // Zero out wallet position for market
    _resetPositionToZero(balanceStruct);
    _updateOpenPositionTrackingForWallet(
      arguments.wallet,
      arguments.market.baseAssetSymbol,
      balanceStruct.balance,
      baseAssetSymbolsWithOpenPositionsByWallet
    );

    // Exit Fund wallet takes on wallet's position
    balanceStruct = loadBalanceStructAndMigrateIfNeeded(
      self,
      arguments.exitFundWallet,
      arguments.market.baseAssetSymbol
    );

    if (positionSize < 0) {
      // Take on short position by subtracting base quantity
      _subtractFromPosition(
        arguments.market.baseAssetSymbol,
        Math.abs(positionSize),
        quoteQuantity,
        // EF can assume arbitrary position sizes
        Constants.MAX_MAXIMUM_POSITION_SIZE,
        balanceStruct,
        lastFundingRatePublishTimestampInMsByBaseAssetSymbol
      );
    } else {
      // Take on long position by adding base quantity
      _addToPosition(
        arguments.market.baseAssetSymbol,
        Math.abs(positionSize),
        quoteQuantity,
        // EF can assume arbitrary position sizes
        Constants.MAX_MAXIMUM_POSITION_SIZE,
        balanceStruct,
        lastFundingRatePublishTimestampInMsByBaseAssetSymbol
      );
    }
    // Update open position tracking for EF in case the position was opened or closed
    _updateOpenPositionTrackingForWallet(
      arguments.exitFundWallet,
      arguments.market.baseAssetSymbol,
      balanceStruct.balance,
      baseAssetSymbolsWithOpenPositionsByWallet
    );

    // Return the change to the EF's quote balance needed to acquire the position. For short positions, the EF
    // receives quote so returns a positive value. For long positions, the EF gives quote and returns a negative value
    return positionSize < 0 ? Math.toInt64(quoteQuantity) : -1 * Math.toInt64(quoteQuantity);

    // The Exit Fund quote balance is not updated here, but instead is updated a single time in the calling function
    // after summing the quote quantities needed to close each wallet position
  }

  // Trading //

  /**
   * @dev Updates buyer, seller, and fee wallet balances for both assets in trade pair according to
   * trade parameters
   */
  function updateForTrade(
    Storage storage self,
    Trade memory trade,
    Order memory buy,
    Order memory sell,
    address feeWallet,
    Market memory market,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet
  ) internal returns (bool wasBuyPositionReduced, bool wasSellPositionReduced) {
    Balance storage balanceStruct;

    (int64 buyFee, int64 sellFee) = trade.makerSide == OrderSide.Buy
      ? (trade.makerFeeQuantity, Math.toInt64(trade.takerFeeQuantity))
      : (Math.toInt64(trade.takerFeeQuantity), trade.makerFeeQuantity);

    // Seller gives base asset
    balanceStruct = loadBalanceStructAndMigrateIfNeeded(self, sell.wallet, trade.baseAssetSymbol);
    if (sell.isReduceOnly) {
      _validatePositionUpdatedTowardsZero(
        balanceStruct.balance,
        balanceStruct.balance - Math.toInt64(trade.baseQuantity)
      );
    }
    wasSellPositionReduced = _subtractFromPosition(
      market.baseAssetSymbol,
      trade.baseQuantity,
      trade.quoteQuantity,
      market
        .loadMarketWithOverridesForWallet(sell.wallet, marketOverridesByBaseAssetSymbolAndWallet)
        .overridableFields
        .maximumPositionSize,
      balanceStruct,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol
    );
    _updateOpenPositionTrackingForWallet(
      sell.wallet,
      trade.baseAssetSymbol,
      balanceStruct.balance,
      baseAssetSymbolsWithOpenPositionsByWallet
    );

    // Buyer receives base asset
    balanceStruct = loadBalanceStructAndMigrateIfNeeded(self, buy.wallet, trade.baseAssetSymbol);
    if (buy.isReduceOnly) {
      _validatePositionUpdatedTowardsZero(
        balanceStruct.balance,
        balanceStruct.balance + Math.toInt64(trade.baseQuantity)
      );
    }
    wasBuyPositionReduced = _addToPosition(
      market.baseAssetSymbol,
      trade.baseQuantity,
      trade.quoteQuantity,
      market
        .loadMarketWithOverridesForWallet(buy.wallet, marketOverridesByBaseAssetSymbolAndWallet)
        .overridableFields
        .maximumPositionSize,
      balanceStruct,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol
    );
    _updateOpenPositionTrackingForWallet(
      buy.wallet,
      trade.baseAssetSymbol,
      balanceStruct.balance,
      baseAssetSymbolsWithOpenPositionsByWallet
    );

    // Buyer gives quote asset including fees
    balanceStruct = loadBalanceStructAndMigrateIfNeeded(self, buy.wallet, Constants.QUOTE_ASSET_SYMBOL);
    balanceStruct.balance -= Math.toInt64(trade.quoteQuantity) + buyFee;

    // Seller receives quote asset minus fees
    balanceStruct = loadBalanceStructAndMigrateIfNeeded(self, sell.wallet, Constants.QUOTE_ASSET_SYMBOL);
    balanceStruct.balance += Math.toInt64(trade.quoteQuantity) - sellFee;

    // Fee wallet receives maker and taker fees
    balanceStruct = loadBalanceStructAndMigrateIfNeeded(self, feeWallet, Constants.QUOTE_ASSET_SYMBOL);
    balanceStruct.balance += buyFee + sellFee;
  }

  // Transferring //

  function updateForTransfer(
    Storage storage self,
    Transfer memory transfer,
    address feeWallet
  ) internal returns (int64 newDestinationWalletExchangeBalance, int64 newSourceWalletExchangeBalance) {
    Balance storage balanceStruct;

    // Remove quote amount from source wallet balance
    balanceStruct = loadBalanceStructAndMigrateIfNeeded(self, transfer.sourceWallet, Constants.QUOTE_ASSET_SYMBOL);
    // The calling function will subsequently validate this balance change by checking initial margin requirement
    balanceStruct.balance -= Math.toInt64(transfer.grossQuantity);
    newSourceWalletExchangeBalance = balanceStruct.balance;

    // Send quote amount minus gas fee (if any) to destination wallet balance
    balanceStruct = loadBalanceStructAndMigrateIfNeeded(self, transfer.destinationWallet, Constants.QUOTE_ASSET_SYMBOL);
    balanceStruct.balance += Math.toInt64(transfer.grossQuantity - transfer.gasFee);
    newDestinationWalletExchangeBalance = balanceStruct.balance;

    if (transfer.gasFee > 0) {
      balanceStruct = loadBalanceStructAndMigrateIfNeeded(self, feeWallet, Constants.QUOTE_ASSET_SYMBOL);

      balanceStruct.balance += Math.toInt64(transfer.gasFee);
    }
  }

  // Withdrawing //

  function updateForWithdrawal(
    Storage storage self,
    Withdrawal memory withdrawal,
    address feeWallet
  ) internal returns (int64 newExchangeBalance) {
    Balance storage balanceStruct;

    balanceStruct = loadBalanceStructAndMigrateIfNeeded(self, withdrawal.wallet, Constants.QUOTE_ASSET_SYMBOL);
    require(
      balanceStruct.managedAccountProvider == IManagedAccountProvider(address(0x0)),
      "Wallet is associated with MA"
    );

    // The calling function will subsequently validate this balance change by checking initial margin requirement
    balanceStruct.balance -= Math.toInt64(withdrawal.grossQuantity);
    newExchangeBalance = balanceStruct.balance;

    if (withdrawal.gasFee > 0) {
      balanceStruct = loadBalanceStructAndMigrateIfNeeded(self, feeWallet, Constants.QUOTE_ASSET_SYMBOL);

      balanceStruct.balance += Math.toInt64(withdrawal.gasFee);
    }
  }

  function updateForCancelWithdrawalFromManagedAccountByQuantity(
    Storage storage self,
    WithdrawalFromManagedAccountByQuantity memory withdrawal,
    address feeWallet
  ) internal returns (int64 newExchangeBalance) {
    return
      _updateForWithdrawalFromManagedAccount(
        self,
        feeWallet,
        withdrawal.gasFee,
        withdrawal.gasFee,
        withdrawal.managedAccountProvider,
        withdrawal.managerWallet
      );
  }

  function updateForWithdrawalFromManagedAccountByQuantity(
    Storage storage self,
    WithdrawalFromManagedAccountByQuantity memory withdrawal,
    address feeWallet
  ) internal returns (int64 newExchangeBalance) {
    return
      _updateForWithdrawalFromManagedAccount(
        self,
        feeWallet,
        withdrawal.gasFee,
        withdrawal.grossQuantity,
        withdrawal.managedAccountProvider,
        withdrawal.managerWallet
      );
  }

  function updateForCancelWithdrawalFromManagedAccountByShares(
    Storage storage self,
    WithdrawalFromManagedAccountByShares memory withdrawal,
    address feeWallet
  ) internal returns (int64 newExchangeBalance) {
    return
      _updateForWithdrawalFromManagedAccount(
        self,
        feeWallet,
        withdrawal.gasFee,
        withdrawal.gasFee,
        withdrawal.managedAccountProvider,
        withdrawal.managerWallet
      );
  }

  function updateForWithdrawalFromManagedAccountByShares(
    Storage storage self,
    WithdrawalFromManagedAccountByShares memory withdrawal,
    address feeWallet
  ) internal returns (int64 newExchangeBalance) {
    return
      _updateForWithdrawalFromManagedAccount(
        self,
        feeWallet,
        withdrawal.gasFee,
        withdrawal.grossQuantity,
        withdrawal.managedAccountProvider,
        withdrawal.managerWallet
      );
  }

  // Accessors //

  function loadBalanceFromMigrationSourceIfNeeded(
    Storage storage self,
    address wallet,
    string memory assetSymbol
  ) internal view returns (int64) {
    return loadBalanceStructFromMigrationSourceIfNeeded(self, wallet, assetSymbol).balance;
  }

  function loadBalanceStructFromMigrationSourceIfNeeded(
    Storage storage self,
    address wallet,
    string memory assetSymbol
  ) internal view returns (Balance memory) {
    Balance memory balanceStruct = self.balancesByWalletAssetPair[wallet][assetSymbol];

    if (!balanceStruct.isMigrated && address(self.migrationSource) != address(0x0)) {
      balanceStruct = self.migrationSource.loadBalanceStructBySymbol(wallet, assetSymbol);
    }

    return balanceStruct;
  }

  // Lazy updates //

  function loadBalanceAndMigrateIfNeeded(
    Storage storage self,
    address wallet,
    string memory assetSymbol
  ) internal returns (int64) {
    return loadBalanceStructAndMigrateIfNeeded(self, wallet, assetSymbol).balance;
  }

  function loadBalanceStructAndMigrateIfNeeded(
    Storage storage self,
    address wallet,
    string memory assetSymbol
  ) internal returns (Balance storage) {
    Balance storage balance = self.balancesByWalletAssetPair[wallet][assetSymbol];

    Balance memory migratedBalanceStruct;
    if (!balance.isMigrated && address(self.migrationSource) != address(0x0)) {
      migratedBalanceStruct = self.migrationSource.loadBalanceStructBySymbol(wallet, assetSymbol);

      balance.isMigrated = true;
      balance.balance = migratedBalanceStruct.balance;
      balance.lastUpdateTimestampInMs = migratedBalanceStruct.lastUpdateTimestampInMs;
      balance.costBasis = migratedBalanceStruct.costBasis;
    }

    return balance;
  }

  // Position updates //

  function _addToPosition(
    string memory baseAssetSymbol,
    uint64 baseQuantity,
    uint64 quoteQuantity,
    uint64 maximumPositionSize,
    Balance storage balanceStruct,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol
  ) private returns (bool wasPositionReduced) {
    int64 newBalance = balanceStruct.balance + Math.toInt64(baseQuantity);

    // Position closed
    if (newBalance == 0) {
      wasPositionReduced = balanceStruct.balance != 0;
      _resetPositionToZero(balanceStruct);
      return wasPositionReduced;
    }

    // Position opened (newBalance is non-zero per preceding guard)
    if (balanceStruct.balance == 0) {
      // Update newly-opened position with the latest published funding rate for that market so that no funding is
      // applied retroactively
      balanceStruct.lastUpdateTimestampInMs = lastFundingRatePublishTimestampInMsByBaseAssetSymbol[baseAssetSymbol];
    }

    wasPositionReduced = _validatePositionBelowMaximumOrReduced(balanceStruct.balance, newBalance, maximumPositionSize);

    if (balanceStruct.balance >= 0) {
      // Increase position
      balanceStruct.costBasis += Math.toInt64(quoteQuantity);
    } else if (newBalance > 0) {
      // Going from negative to positive. Only the portion of the quote qty that contributed to the new, positive
      // balance is its cost. Base quantity validated non-zero by calling function
      balanceStruct.costBasis = Math.multiplyPipsByFraction(
        Math.toInt64(quoteQuantity),
        newBalance,
        Math.toInt64(baseQuantity)
      );
    } else {
      // Reduce cost basis proportional to reduction of position
      balanceStruct.costBasis += Math.multiplyPipsByFraction(
        balanceStruct.costBasis,
        Math.toInt64(baseQuantity),
        balanceStruct.balance
      );
    }

    balanceStruct.balance = newBalance;
  }

  function _subtractFromPosition(
    string memory baseAssetSymbol,
    uint64 baseQuantity,
    uint64 quoteQuantity,
    uint64 maximumPositionSize,
    Balance storage balanceStruct,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol
  ) private returns (bool wasPositionReduced) {
    int64 newBalance = balanceStruct.balance - Math.toInt64(baseQuantity);

    // Position closed
    if (newBalance == 0) {
      wasPositionReduced = balanceStruct.balance != 0;
      _resetPositionToZero(balanceStruct);
      return wasPositionReduced;
    }

    // Position opened (newBalance is non-zero per preceding guard)
    if (balanceStruct.balance == 0) {
      // Update newly-opened position with the latest published funding rate for that market so that no funding is
      // applied retroactively
      balanceStruct.lastUpdateTimestampInMs = lastFundingRatePublishTimestampInMsByBaseAssetSymbol[baseAssetSymbol];
    }

    wasPositionReduced = _validatePositionBelowMaximumOrReduced(balanceStruct.balance, newBalance, maximumPositionSize);

    if (balanceStruct.balance <= 0) {
      // Increase position
      balanceStruct.costBasis -= Math.toInt64(quoteQuantity);
    } else if (newBalance < 0) {
      // Going from positive to negative. Only the portion of the quote qty that contributed to the new, positive balance
      // is its cost. Base quantity validated non-zero by calling function
      balanceStruct.costBasis = Math.multiplyPipsByFraction(
        Math.toInt64(quoteQuantity),
        newBalance,
        Math.toInt64(baseQuantity)
      );
    } else {
      // Reduce cost basis proportional to reduction of position
      balanceStruct.costBasis -= Math.multiplyPipsByFraction(
        balanceStruct.costBasis,
        Math.toInt64(baseQuantity),
        balanceStruct.balance
      );
    }

    balanceStruct.balance = newBalance;
  }

  function _resetPositionToZero(Balance storage balanceStruct) private {
    balanceStruct.balance = 0;
    balanceStruct.costBasis = 0;
    balanceStruct.lastUpdateTimestampInMs = 0;
  }

  function _updateCounterpartyPositionForDeleverageOrLiquidation(
    Storage storage self,
    uint64 baseQuantity,
    address counterpartyWallet,
    address exitFundWallet,
    bool isDeleverage,
    bool isLiquidatingWalletPositionShort,
    Market memory market,
    uint64 quoteQuantity,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet
  ) private {
    // Update counterparty wallet position by taking on liquidating wallet's position. During liquidation the IF or EF
    // position may validly increase by moving away from zero, but this is disallowed for the counterparty wallet
    // position during deleveraging
    Balance storage balanceStruct = loadBalanceStructAndMigrateIfNeeded(
      self,
      counterpartyWallet,
      market.baseAssetSymbol
    );
    // Counterparty wallet is EF for `WalletInMaintenanceDuringSystemRecovery` liquidations
    uint64 maximumPositionSize = counterpartyWallet == exitFundWallet
      ? Constants.MAX_MAXIMUM_POSITION_SIZE
      : market
        .loadMarketWithOverridesForWallet(counterpartyWallet, marketOverridesByBaseAssetSymbolAndWallet)
        .overridableFields
        .maximumPositionSize;

    if (isLiquidatingWalletPositionShort) {
      if (isDeleverage) {
        // Counterparty position must decrease during deleveraging
        _validatePositionUpdatedTowardsZero(balanceStruct.balance, balanceStruct.balance - Math.toInt64(baseQuantity));
      }

      // Take on short position by subtracting base quantity
      _subtractFromPosition(
        market.baseAssetSymbol,
        baseQuantity,
        quoteQuantity,
        maximumPositionSize,
        balanceStruct,
        lastFundingRatePublishTimestampInMsByBaseAssetSymbol
      );
    } else {
      if (isDeleverage) {
        // Counterparty position must decrease during deleveraging
        _validatePositionUpdatedTowardsZero(balanceStruct.balance, balanceStruct.balance + Math.toInt64(baseQuantity));
      }

      // Take on long position by adding base quantity
      _addToPosition(
        market.baseAssetSymbol,
        baseQuantity,
        quoteQuantity,
        maximumPositionSize,
        balanceStruct,
        lastFundingRatePublishTimestampInMsByBaseAssetSymbol
      );
    }

    // Update open position tracking in case it was just opened (if counterparty wallet is IF or EF only) or closed
    _updateOpenPositionTrackingForWallet(
      counterpartyWallet,
      market.baseAssetSymbol,
      balanceStruct.balance,
      baseAssetSymbolsWithOpenPositionsByWallet
    );

    // Update quote balance
    balanceStruct = loadBalanceStructAndMigrateIfNeeded(self, counterpartyWallet, Constants.QUOTE_ASSET_SYMBOL);
    if (isLiquidatingWalletPositionShort) {
      // Counterparty receives quote when taking on short position
      balanceStruct.balance += Math.toInt64(quoteQuantity);
    } else {
      // Counterparty gives quote when taking on long position
      balanceStruct.balance -= Math.toInt64(quoteQuantity);
    }
  }

  function _updateForWithdrawalFromManagedAccount(
    Storage storage self,
    address feeWallet,
    uint64 gasFee,
    uint64 grossQuantity,
    IManagedAccountProvider managedAccountProvider,
    address managerWallet
  ) private returns (int64 newExchangeBalance) {
    Balance storage balanceStruct;

    balanceStruct = loadBalanceStructAndMigrateIfNeeded(self, managerWallet, Constants.QUOTE_ASSET_SYMBOL);
    require(balanceStruct.managedAccountProvider == managedAccountProvider, "Manager wallet not associated with MA");

    // The calling function will subsequently validate this balance change by checking initial margin requirement
    balanceStruct.balance -= Math.toInt64(grossQuantity);
    newExchangeBalance = balanceStruct.balance;

    if (gasFee > 0) {
      balanceStruct = loadBalanceStructAndMigrateIfNeeded(self, feeWallet, Constants.QUOTE_ASSET_SYMBOL);

      balanceStruct.balance += Math.toInt64(gasFee);
    }
  }

  function _updateLiquidatingPositionForDeleverageOrLiquidation(
    Storage storage self,
    uint64 baseQuantity,
    address exitFundWallet,
    address liquidatingWallet,
    Market memory market,
    uint64 quoteQuantity,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet
  ) private returns (bool isLiquidatingWalletPositionShort) {
    // Update liquidating wallet position by decreasing it towards zero
    Balance storage balanceStruct = loadBalanceStructAndMigrateIfNeeded(
      self,
      liquidatingWallet,
      market.baseAssetSymbol
    );
    isLiquidatingWalletPositionShort = balanceStruct.balance < 0;
    uint64 maximumPositionSize = market
      .loadMarketWithOverridesForWallet(liquidatingWallet, marketOverridesByBaseAssetSymbolAndWallet)
      .overridableFields
      .maximumPositionSize;
    // Liquidating wallet is EF for ExitFundClosure deleverages
    if (liquidatingWallet == exitFundWallet) {
      maximumPositionSize = Constants.MAX_MAXIMUM_POSITION_SIZE;
    }

    if (isLiquidatingWalletPositionShort) {
      // Reduce negative short position by adding base quantity to it
      _validatePositionUpdatedTowardsZero(balanceStruct.balance, balanceStruct.balance + Math.toInt64(baseQuantity));

      // Reduce short position by adding base quantity
      _addToPosition(
        market.baseAssetSymbol,
        baseQuantity,
        quoteQuantity,
        maximumPositionSize,
        balanceStruct,
        lastFundingRatePublishTimestampInMsByBaseAssetSymbol
      );
    } else {
      // Reduce positive long position by subtracting base quantity from it
      _validatePositionUpdatedTowardsZero(balanceStruct.balance, balanceStruct.balance - Math.toInt64(baseQuantity));

      // Reduce long position by subtracting base quantity
      _subtractFromPosition(
        market.baseAssetSymbol,
        baseQuantity,
        quoteQuantity,
        maximumPositionSize,
        balanceStruct,
        lastFundingRatePublishTimestampInMsByBaseAssetSymbol
      );
    }

    // Update open position tracking in case it was just closed
    _updateOpenPositionTrackingForWallet(
      liquidatingWallet,
      market.baseAssetSymbol,
      balanceStruct.balance,
      baseAssetSymbolsWithOpenPositionsByWallet
    );

    // Update quote balance
    balanceStruct = loadBalanceStructAndMigrateIfNeeded(self, liquidatingWallet, Constants.QUOTE_ASSET_SYMBOL);
    if (isLiquidatingWalletPositionShort) {
      // Liquidating wallet gives quote if short
      balanceStruct.balance -= Math.toInt64(quoteQuantity);
    } else {
      // Liquidating wallet receives quote if long
      balanceStruct.balance += Math.toInt64(quoteQuantity);
    }
  }

  function _updateOpenPositionTrackingForWallet(
    address wallet,
    string memory assetSymbol,
    int64 balance,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet
  ) private {
    baseAssetSymbolsWithOpenPositionsByWallet[wallet] = balance == 0
      ? baseAssetSymbolsWithOpenPositionsByWallet[wallet].remove(assetSymbol)
      : baseAssetSymbolsWithOpenPositionsByWallet[wallet].insertSorted(assetSymbol);
  }

  function _updatePositionsForDeleverageOrLiquidation(
    Storage storage self,
    uint64 baseQuantity,
    address counterpartyWallet,
    address exitFundWallet,
    bool isDeleverage,
    address liquidatingWallet,
    Market memory market,
    uint64 quoteQuantity,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet
  ) private {
    bool isLiquidatingWalletPositionShort = _updateLiquidatingPositionForDeleverageOrLiquidation(
      self,
      baseQuantity,
      exitFundWallet,
      liquidatingWallet,
      market,
      quoteQuantity,
      baseAssetSymbolsWithOpenPositionsByWallet,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet
    );
    _updateCounterpartyPositionForDeleverageOrLiquidation(
      self,
      baseQuantity,
      counterpartyWallet,
      exitFundWallet,
      isDeleverage,
      isLiquidatingWalletPositionShort,
      market,
      quoteQuantity,
      baseAssetSymbolsWithOpenPositionsByWallet,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet
    );
  }

  function _validatePositionBelowMaximumOrReduced(
    int64 originalPositionSize,
    int64 newPositionSize,
    uint64 maximumPositionSize
  ) private pure returns (bool wasPositionReduced) {
    uint64 newPositionSizeUnsigned = Math.abs(newPositionSize);
    wasPositionReduced = newPositionSizeUnsigned < Math.abs(originalPositionSize);

    if (newPositionSizeUnsigned > maximumPositionSize) {
      require(wasPositionReduced, "Max position size exceeded");
    }
  }

  function _validatePositionUpdatedTowardsZero(int64 originalPositionSize, int64 newPositionSize) private pure {
    require(originalPositionSize != 0, "Position must be non-zero");

    bool isValidUpdate = originalPositionSize < 0
      ? newPositionSize > originalPositionSize && newPositionSize <= 0
      : newPositionSize < originalPositionSize && newPositionSize >= 0;
    require(isValidUpdate, "Position must move toward zero");
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { BalanceTracking } from "./BalanceTracking.sol";
import { DeleverageType } from "./Enums.sol";
import { ExchangeEvents } from "./ExchangeEvents.sol";
import { ExitFund } from "./ExitFund.sol";
import { Funding } from "./Funding.sol";
import { IndexPriceMargin } from "./IndexPriceMargin.sol";
import { LiquidationValidations } from "./LiquidationValidations.sol";
import { MarketHelper } from "./MarketHelper.sol";
import { Math } from "./Math.sol";
import { SortedStringSet } from "./SortedStringSet.sol";
import { Validations } from "./Validations.sol";
import { Balance, ClosureDeleverageArguments, FundingMultiplierQuartet, Market, MarketOverrides } from "./Structs.sol";

library ClosureDeleveraging {
  using BalanceTracking for BalanceTracking.Storage;
  using MarketHelper for Market;
  using SortedStringSet for string[];

  // solhint-disable-next-line func-name-mixedcase
  function deleverage_delegatecall(
    ClosureDeleverageArguments memory arguments,
    DeleverageType deleverageType,
    uint256 exitFundPositionOpenedAtBlockTimestamp,
    address exitFundWallet,
    address insuranceFundWallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) public returns (uint256) {
    require(arguments.liquidatingWallet != arguments.counterpartyWallet, "Cannot liquidate wallet against itself");
    require(arguments.counterpartyWallet != exitFundWallet, "Cannot deleverage EF");
    require(arguments.counterpartyWallet != insuranceFundWallet, "Cannot deleverage IF");
    if (deleverageType == DeleverageType.ExitFundClosure) {
      require(arguments.liquidatingWallet == exitFundWallet, "Liquidating wallet must be EF");
    } else {
      // DeleverageType.InsuranceFundClosure
      require(arguments.liquidatingWallet == insuranceFundWallet, "Liquidating wallet must be IF");
    }

    Funding.applyOutstandingWalletFunding(
      arguments.counterpartyWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );
    Funding.applyOutstandingWalletFunding(
      arguments.liquidatingWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );

    _validateArgumentsAndDeleverage(
      arguments,
      deleverageType,
      exitFundWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );

    // EF closure deleveraging can potentially change the `exitFundPositionOpenedAtBlockTimestamp` by setting it to
    // zero, whereas IF closure cannot
    if (deleverageType == DeleverageType.ExitFundClosure) {
      _emitDeleveragedExitFundClosure(arguments, exitFundWallet);

      return
        ExitFund.getExitFundPositionOpenedAtBlockTimestamp(
          exitFundPositionOpenedAtBlockTimestamp,
          exitFundWallet,
          balanceTracking,
          baseAssetSymbolsWithOpenPositionsByWallet
        );
    } else {
      _emitDeleveragedInsuranceFundClosure(arguments, insuranceFundWallet);

      // IF closure never changes `exitFundPositionOpenedAtBlockTimestamp`
      return exitFundPositionOpenedAtBlockTimestamp;
    }
  }

  function _emitDeleveragedExitFundClosure(
    ClosureDeleverageArguments memory arguments,
    address exitFundWallet
  ) private {
    emit ExchangeEvents.DeleveragedExitFundClosure(
      arguments.baseAssetSymbol,
      arguments.counterpartyWallet,
      exitFundWallet,
      arguments.liquidationBaseQuantity,
      arguments.liquidationQuoteQuantity
    );
  }

  function _emitDeleveragedInsuranceFundClosure(
    ClosureDeleverageArguments memory arguments,
    address insuranceFundWallet
  ) private {
    emit ExchangeEvents.DeleveragedInsuranceFundClosure(
      arguments.baseAssetSymbol,
      arguments.counterpartyWallet,
      insuranceFundWallet,
      arguments.liquidationBaseQuantity,
      arguments.liquidationQuoteQuantity
    );
  }

  function _validateArgumentsAndDeleverage(
    ClosureDeleverageArguments memory arguments,
    DeleverageType deleverageType,
    address exitFundWallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private {
    Market memory market = Validations.loadAndValidateActiveMarket(
      arguments.baseAssetSymbol,
      arguments.liquidatingWallet,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketsByBaseAssetSymbol
    );

    _validateQuoteQuantityAndDeleveragePosition(
      arguments,
      deleverageType,
      exitFundWallet,
      market,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );
  }

  function _validateQuoteQuantityForExitFundClosure(
    ClosureDeleverageArguments memory arguments,
    address exitFundWallet,
    Market memory market,
    int64 positionSize,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private view {
    (int256 totalAccountValueInDoublePips, uint256 totalMaintenanceMarginRequirementInTriplePips) = IndexPriceMargin
    // Use margin calculation specific to EF that accounts for its unlimited leverage
      .loadTotalAccountValueInDoublePipsAndMaintenanceMarginRequirementInTriplePipsForExitFund(
        exitFundWallet,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet,
        marketsByBaseAssetSymbol
      );

    // The provided liquidationBaseQuantity specifies how much of the position to liquidate, so we provide this
    // quantity as the position size to `LiquidationValidations.validateExitFundClosureQuoteQuantity` while observing
    // the same signedness
    LiquidationValidations.validateExitFundClosureQuoteQuantity(
      market.lastIndexPrice,
      Math.abs(positionSize) < market.overridableFields.minimumPositionSize,
      positionSize < 0
        ? (-1 * Math.toInt64(arguments.liquidationBaseQuantity))
        : Math.toInt64(arguments.liquidationBaseQuantity),
      arguments.liquidationQuoteQuantity,
      // Use market default values instead of wallet-specific overrides for the EF, since its margin fraction is zero
      market.overridableFields.maintenanceMarginFraction,
      totalAccountValueInDoublePips,
      totalMaintenanceMarginRequirementInTriplePips
    );
  }

  function _validateQuoteQuantityAndDeleveragePosition(
    ClosureDeleverageArguments memory arguments,
    DeleverageType deleverageType,
    address exitFundWallet,
    Market memory market,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private {
    Balance storage balanceStruct = balanceTracking.loadBalanceStructAndMigrateIfNeeded(
      arguments.liquidatingWallet,
      market.baseAssetSymbol
    );

    // Validate quote quantity
    if (deleverageType == DeleverageType.InsuranceFundClosure) {
      LiquidationValidations.validateInsuranceFundClosureQuoteQuantity(
        arguments.liquidationBaseQuantity,
        balanceStruct.costBasis,
        balanceStruct.balance,
        arguments.liquidationQuoteQuantity
      );
    } else {
      // DeleverageType.ExitFundClosure
      _validateQuoteQuantityForExitFundClosure(
        arguments,
        exitFundWallet,
        market,
        balanceStruct.balance,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet,
        marketsByBaseAssetSymbol
      );
    }

    balanceTracking.updatePositionsForDeleverage(
      arguments.liquidationBaseQuantity,
      arguments.counterpartyWallet,
      exitFundWallet,
      arguments.liquidatingWallet,
      market,
      arguments.liquidationQuoteQuantity,
      baseAssetSymbolsWithOpenPositionsByWallet,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet
    );

    // Validate that the counterparty wallet still meets its maintenance margin requirements
    IndexPriceMargin.validateMaintenanceMarginRequirement(
      arguments.counterpartyWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );
  }
}

File 19 of 54 : Constants.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

/**
 * @dev See GOVERNANCE.md for descriptions of fixed parameters and fees
 */

library Constants {
  uint64 public constant DEPOSIT_INDEX_NOT_SET = type(uint64).max;

  string public constant EIP_712_DOMAIN_NAME = "KatanaPerps";

  string public constant EIP_712_DOMAIN_VERSION = "1.0.0";

  // https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator
  bytes32 public constant EIP_712_TYPE_HASH_DOMAIN =
    keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");

  bytes32 public constant EIP_712_TYPE_HASH_DELEGATED_KEY_AUTHORIZATION =
    keccak256("DelegatedKeyAuthorization(uint128 nonce,address delegatedPublicKey,string message)");

  bytes32 public constant EIP_712_TYPE_HASH_ORDER =
    keccak256(
      "Order(uint128 nonce,address wallet,string marketSymbol,uint8 orderType,uint8 orderSide,string quantity,string limitPrice,string triggerPrice,uint8 triggerType,string callbackRate,uint128 conditionalOrderId,bool isReduceOnly,uint8 timeInForce,uint8 selfTradePrevention,bool isLiquidationAcquisitionOnly,address delegatedPublicKey,string clientOrderId)"
    );

  bytes32 public constant EIP_712_TYPE_HASH_TRANSFER =
    keccak256("Transfer(uint128 nonce,address sourceWallet,address destinationWallet,string quantity)");

  bytes32 public constant EIP_712_TYPE_HASH_WITHDRAWAL =
    keccak256(
      "Withdrawal(uint128 nonce,address wallet,string quantity,string maximumGasFee,address bridgeAdapter,bytes bridgeAdapterPayload)"
    );

  bytes32 public constant EIP_712_TYPE_HASH_WITHDRAWAL_FROM_MANAGED_ACCOUNT_BY_QUANTITY =
    keccak256(
      "WithdrawalFromManagedAccountByQuantity(uint128 nonce,address managerWallet,address depositorWallet,string quantity,string maxShares,string maximumGasFee,address managedAccountProvider,bytes managedAccountProviderPayload,address bridgeAdapter,bytes bridgeAdapterPayload)"
    );

  bytes32 public constant EIP_712_TYPE_HASH_WITHDRAWAL_FROM_MANAGED_ACCOUNT_BY_SHARES =
    keccak256(
      "WithdrawalFromManagedAccountByShares(uint128 nonce,address managerWallet,address depositorWallet,string shares,string minimumQuantity,string maximumGasFee,address managedAccountProvider,bytes managedAccountProviderPayload,address bridgeAdapter,bytes bridgeAdapterPayload)"
    );

  string public constant EMPTY_DECIMAL_STRING = "0.00000000";

  bytes32 public constant DELEGATED_KEY_AUTHORIZATION_MESSAGE_HASH =
    keccak256("Sign this free message to prove you control this wallet");

  // 1 week
  uint256 public constant EXIT_FUND_WITHDRAW_DELAY_IN_S = 7 * 24 * 60 * 60;

  // 1 day
  uint256 public constant FIELD_UPGRADE_DELAY_IN_S = 1 * 24 * 60 * 60;

  // 8 hours
  uint64 public constant FUNDING_PERIOD_IN_MS = 8 * 60 * 60 * 1000;

  // 1 day
  uint256 public constant MAX_CHAIN_PROPAGATION_PERIOD_IN_S = 1 * 24 * 60 * 60;

  // 1 year
  uint256 public constant MAX_DELEGATE_KEY_EXPIRATION_PERIOD_IN_MS = 365 * 24 * 60 * 60 * 1000;

  // 5%
  uint64 public constant MAX_FEE_MULTIPLIER = 5 * 10 ** 6;

  // 1 year - value must be evenly divisible by `FUNDING_PERIOD_IN_MS`
  uint64 public constant MAX_FUNDING_TIME_PERIOD_PER_UPDATE_IN_MS = 365 * 24 * 60 * 60 * 1000;

  // Max int64
  uint64 public constant MAX_MAXIMUM_POSITION_SIZE = uint64(type(int64).max);

  uint256 public constant MAX_NUMBER_OF_MARKETS = type(uint8).max;

  // Positions smaller than this threshold will skip quote quantity validation for Position Below Minimum liquidations
  // and skip non-negative total quote validation Wallet Exits
  uint64 public constant MINIMUM_QUOTE_QUANTITY_VALIDATION_THRESHOLD = 10000;

  // To convert integer pips to a fractional price shift decimal left by the pip precision of 8
  // decimals places
  uint64 public constant PIP_PRICE_MULTIPLIER = 10 ** 8;

  string public constant QUOTE_ASSET_SYMBOL = "USD";

  uint8 public constant QUOTE_TOKEN_DECIMALS = 6;
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

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

import { AssetUnitConversions } from "./AssetUnitConversions.sol";
import { BalanceTracking } from "./BalanceTracking.sol";
import { Constants } from "./Constants.sol";
import { ExchangeEvents } from "./ExchangeEvents.sol";
import { Balance, WalletExit } from "./Structs.sol";
import { ICustodian, IManagedAccountProvider } from "./Interfaces.sol";

library Depositing {
  using BalanceTracking for BalanceTracking.Storage;

  // solhint-disable-next-line func-name-mixedcase
  function deposit_delegatecall(
    // External arguments
    address depositorWallet,
    address sourceWallet,
    uint256 quantityInAssetUnits,
    // Exchange state values
    ICustodian custodian,
    uint64 depositIndex,
    address exitFundWallet,
    bool isDepositEnabled,
    address quoteTokenAddress,
    // Exchange state storage refs
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => uint64) storage pendingDepositQuantityByWallet,
    mapping(address => WalletExit) storage walletExits
  ) public {
    Balance storage balanceStruct = balanceTracking.loadBalanceStructAndMigrateIfNeeded(
      depositorWallet,
      Constants.QUOTE_ASSET_SYMBOL
    );
    require(
      balanceStruct.managedAccountProvider == IManagedAccountProvider(address(0x0)),
      "Wallet is associated with MA"
    );

    uint64 depositedQuantity = deposit(
      custodian,
      depositIndex,
      depositorWallet,
      exitFundWallet,
      isDepositEnabled,
      quantityInAssetUnits,
      quoteTokenAddress,
      sourceWallet,
      pendingDepositQuantityByWallet,
      walletExits
    );

    emit ExchangeEvents.Deposited(
      // The Exchange will update the stored deposit index after this function returns
      depositIndex + 1,
      sourceWallet,
      depositorWallet,
      depositedQuantity,
      false
    );
  }

  // solhint-disable-next-line func-name-mixedcase
  function applyPendingDepositsForWallet_delegatecall(
    uint64 quantity,
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => uint64) storage pendingDepositQuantityByWallet
  ) public {
    uint64 pendingDepositQuantity = pendingDepositQuantityByWallet[wallet];
    require(quantity <= pendingDepositQuantity, "Quantity to apply exceeds pending");

    pendingDepositQuantityByWallet[wallet] = pendingDepositQuantity - quantity;

    // Update balance with argument quantity
    (int64 newExchangeBalance, IManagedAccountProvider managedAccount) = balanceTracking.updateForDeposit(
      wallet,
      quantity
    );
    require(managedAccount == IManagedAccountProvider(address(0x0)), "Wallet is associated with MA");

    emit ExchangeEvents.PendingDepositApplied(wallet, quantity, newExchangeBalance, false);
  }

  function deposit(
    ICustodian custodian,
    uint64 depositIndex,
    address depositorWallet,
    address exitFundWallet,
    bool isDepositEnabled,
    uint256 quantityInAssetUnits,
    address quoteTokenAddress,
    address sourceWallet,
    mapping(address => uint64) storage pendingDepositQuantityByWallet,
    mapping(address => WalletExit) storage walletExits
  ) internal returns (uint64 depositedQuantity) {
    // Deposits are disabled until `setDepositIndex` is called successfully
    require(depositIndex != Constants.DEPOSIT_INDEX_NOT_SET && isDepositEnabled, "Deposits disabled");
    require(depositorWallet != exitFundWallet, "Cannot deposit to EF");

    // Calling exitWallet disables deposits immediately on mining, in contrast to withdrawals and trades which respect
    // the Chain Propagation Period given by `effectiveBlockTimestamp` via `_isWalletExitFinalized`
    require(!walletExits[sourceWallet].exists, "Source wallet exited");
    require(!walletExits[depositorWallet].exists, "Depositor wallet exited");

    uint64 quantity = AssetUnitConversions.assetUnitsToPips(quantityInAssetUnits, Constants.QUOTE_TOKEN_DECIMALS);

    require(quantity > 0, "Quantity is too low");
    require(quantity < uint64(type(int64).max), "Quantity is too large");

    // Convert from pips back into asset units to remove any fractional amount that is too small
    // to express in pips. The `Exchange` will call `transferFrom` without this fractional amount
    // and there will be no dust
    uint256 quantityInAssetUnitsWithoutFractionalPips = AssetUnitConversions.pipsToAssetUnits(
      quantity,
      Constants.QUOTE_TOKEN_DECIMALS
    );

    uint256 balanceBefore = IERC20(quoteTokenAddress).balanceOf(address(custodian));

    // Forward the funds to the `Custodian`
    IERC20(quoteTokenAddress).transferFrom(sourceWallet, address(custodian), quantityInAssetUnitsWithoutFractionalPips);

    uint256 balanceAfter = IERC20(quoteTokenAddress).balanceOf(address(custodian));

    // Support fee-on-transfer by only crediting actual token balance difference. If fee causes
    // transferred amount to have a fractional pip component, it will accumulate as dust in the
    // Custodian
    depositedQuantity = AssetUnitConversions.assetUnitsToPips(
      balanceAfter - balanceBefore,
      Constants.QUOTE_TOKEN_DECIMALS
    );

    // Increment pending deposit quantity by actual transferred quantity
    pendingDepositQuantityByWallet[depositorWallet] += depositedQuantity;
  }
}

File 21 of 54 : Enums.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

/**
 * @notice Enums definitions
 */

// Automatic Deleveraging (ADL) //

enum DeleverageType {
  ExitFundClosure,
  InsuranceFundClosure,
  // The following two values are unused but included for completeness
  WalletExitAcquisition,
  WalletInMaintenanceAcquisition
}

enum WalletExitAcquisitionDeleveragePriceStrategy {
  None,
  BankruptcyPrice,
  ExitPrice
}

// Liquidations //

enum LiquidationType {
  WalletExit,
  WalletInMaintenance,
  WalletInMaintenanceDuringSystemRecovery
}

// Order book //

enum OrderSelfTradePrevention {
  // Decrement and cancel
  dc,
  // Cancel oldest
  co,
  // Cancel newest
  cn,
  // Cancel both
  cb
}

enum OrderSide {
  Buy,
  Sell
}

enum OrderTimeInForce {
  // Good until canceled
  gtc,
  // Good until crossing (post-only)
  gtx,
  // Immediate or cancel
  ioc,
  // Fill or kill
  fok
}

enum OrderTriggerType {
  // Not a triggered order
  None,
  // Last trade price
  Last,
  // Index price
  Index
}

enum OrderType {
  Market,
  Limit,
  StopLossMarket,
  StopLossLimit,
  TakeProfitMarket,
  TakeProfitLimit,
  TrailingStop
}

// Managed Accounts //

enum ManagedAccountWithdrawalType {
  ByQuantity,
  ByShares
}

File 22 of 54 : ExchangeErrors.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

abstract contract ExchangeErrors {
  error ExitFundHasNoPositions();
  error ExitFundCannotHaveOpenPosition();
  error InvalidContractAddress();
  error InvalidWalletAddress();
  error NewInsuranceFundWalletCannotBeExited();
  error NewValueExceedsMaximum();
  error NewValueMustBeDifferentFromCurrent();
  error SenderMustBeAdminOrDispatcher();
  error SenderMustBeAdmin();
  error SenderMustBeDispatcher();
  error SenderMustBeGovernance();
  error SenderMustBeOwner();
  error UnknownBaseAssetSymbol();
  error ValueCanOnlyBetSetOnce();
  error WalletCannotBeExited();
  error WalletHasNoOverridesForMarket();
  error WalletMustBeExited();
}

File 23 of 54 : ExchangeEvents.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { OrderSide } from "./Enums.sol";

contract ExchangeEvents {
  /**
   * @notice Emitted when an admin changes the Chain Propagation Period tunable parameter with
   * `setChainPropagationPeriod`
   */
  event ChainPropagationPeriodChanged(uint256 previousValue, uint256 newValue);
  /**
   * @notice Emitted when an admin changes the Delegated Key Expiration Period tunable parameter with
   * `setDelegatedKeyExpirationPeriod`
   */
  event DelegateKeyExpirationPeriodChanged(uint256 previousValue, uint256 newValue);
  /**
   * @notice Emitted when the Dispatcher Wallet submits an exited wallet position deleverage with
   * `deleverageExitAcquisition`
   */
  event DeleveragedExitAcquisition(
    string baseAssetSymbol,
    address counterpartyWallet,
    address liquidatingWallet,
    uint64 liquidationBaseQuantity,
    uint64 liquidationQuoteQuantity
  );
  /**
   * @notice Emitted when the Dispatcher Wallet submits an Exit Fund closure deleverage with `deleverageExitFundClosure`
   */
  event DeleveragedExitFundClosure(
    string baseAssetSymbol,
    address counterpartyWallet,
    address exitFundWallet,
    uint64 liquidationBaseQuantity,
    uint64 liquidationQuoteQuantity
  );
  /**
   * @notice Emitted when the Dispatcher Wallet submits a wallet in maintenance deleverage with
   * `deleverageInMaintenanceAcquisition`
   */
  event DeleveragedInMaintenanceAcquisition(
    string baseAssetSymbol,
    address counterpartyWallet,
    address liquidatingWallet,
    uint64 liquidationBaseQuantity,
    uint64 liquidationQuoteQuantity
  );
  /**
   * @notice Emitted when the Dispatcher Wallet submits an Insurance Fund closure deleverage with
   * `deleverageInsuranceFundClosure`
   */
  event DeleveragedInsuranceFundClosure(
    string baseAssetSymbol,
    address counterpartyWallet,
    address insuranceFundWallet,
    uint64 liquidationBaseQuantity,
    uint64 liquidationQuoteQuantity
  );
  /**
   * @notice Emitted when a user deposits quote tokens with `deposit` or `depositToManagedAccount`
   */
  event Deposited(
    uint64 index,
    address sourceWallet,
    address depositorWallet,
    uint64 quantity,
    bool isAssociatedWithManagedAccount
  );
  /**
   * @notice Emitted when an admin disables deposits with `setDepositEnabled`
   */
  event DepositsDisabled();
  /**
   * @notice Emitted when an admin enables deposits with `setDepositEnabled`
   */
  event DepositsEnabled();
  /**
   * @notice Emitted when an admin changes the Dispatcher Wallet tunable parameter with `setDispatcher` or clears it
   * with `removeDispatcher`
   */
  event DispatcherChanged(address previousValue, address newValue);
  /**
   * @notice Emitted when an admin changes the Exit Fund Wallet tunable parameter with `setExitFundWallet`
   */
  event ExitFundWalletChanged(address previousValue, address newValue);
  /**
   * @notice Emitted when an admin changes the Fee Wallet tunable parameter with `setFeeWallet`
   */
  event FeeWalletChanged(address previousValue, address newValue);
  /**
   * @notice Emitted when the Dispatcher Wallet publishes a new funding rate with `publishFundingMutiplier`
   */
  event FundingRatePublished(string baseAssetSymbol, int64 fundingRate);
  /**
   * @notice Emitted when the Dispatcher Wallet publishes a new index price with `publishIndexPrices`
   */
  event IndexPricePublished(string baseAssetSymbol, uint64 timestampInMs, uint64 price);
  /**
   * @notice Emitted when the Dispatcher Wallet submits a position below minimum liquidation with
   * `liquidatePositionBelowMinimum`
   */
  event LiquidatedPositionBelowMinimum(
    string baseAssetSymbol,
    address liquidatingWallet,
    uint64 liquidationBaseQuantity,
    uint64 liquidationQuoteQuantity
  );
  /**
   * @notice Emitted when the Dispatcher Wallet submits a position in deactivated market liquidation with
   * `liquidatePositionInDeactivatedMarket`
   */
  event LiquidatedPositionInDeactivatedMarket(
    string baseAssetSymbol,
    address liquidatingWallet,
    uint64 liquidationBaseQuantity,
    uint64 liquidationQuoteQuantity
  );
  /**
   * @notice Emitted when the Dispatcher Wallet submits an exited wallet liquidation with `liquidateWalletExit`
   */
  event LiquidatedWalletExit(address liquidatingWallet);
  /**
   * @notice Emitted when the Dispatcher Wallet submits a wallet in maintenance liquidation with
   * `liquidateWalletInMaintenance`
   */
  event LiquidatedWalletInMaintenance(address liquidatingWallet);
  /**
   * @notice Emitted when the Dispatcher Wallet submits a wallet in maintenance liquidation during system recovery with
   * `liquidateWalletInMaintenanceDuringSystemRecovery`
   */
  event LiquidatedWalletInMaintenanceDuringSystemRecovery(address liquidatingWallet);
  /**
   * @notice Emitted when the Dispatcher Wallet submits a trade for execution with `executeTrade` and one of the orders
   * has the `isLiquidationAcquisitionOnly` asserted
   */
  event LiquidationAcquisitionExecuted(
    address buyWallet,
    address sellWallet,
    string baseAssetSymbol,
    string quoteAssetSymbol,
    uint64 baseQuantity,
    uint64 quoteQuantity,
    OrderSide makerSide,
    int64 makerFeeQuantity,
    uint64 takerFeeQuantity
  );
  /**
   * @notice Emitted when the Dispatcher Wallet activates a previously added market with `activateMarket`
   */
  event MarketActivated(string baseAssetSymbol);
  /**
   * @notice Emitted when admin adds a new market with `addMarket`
   */
  event MarketAdded(string baseAssetSymbol);
  /**
   * @notice Emitted when the Dispatcher Wallet deactivates a previously activated market with `deactivateMarket`
   */
  event MarketDeactivated(string baseAssetSymbol);
  /**
   * @notice Emitted when an admin or the Dispatcher Wallet unsets market overrides with `unsetMarketOverridesForWallet`
   */
  event MarketOverridesUnset(string baseAssetSymbol, address wallet);
  /**
   * @notice Emitted when a user invalidates an order nonce with `invalidateNonce`
   */
  event OrderNonceInvalidated(address wallet, uint128 nonce, uint128 timestampInMs, uint256 effectiveBlockTimestamp);
  /**
   * @notice Emitted when pending deposit quantity is applied via `applyPendingDepositsForWallet`
   */
  event PendingDepositApplied(
    address wallet,
    uint64 quantity,
    int64 newExchangeBalance,
    bool isAssociatedWithManagedAccount
  );
  /**
   * @notice Emitted when an admin changes the position below minimum liquidation price tolerance tunable parameter
   * with `setPositionBelowMinimumLiquidationPriceToleranceMultiplier`
   */
  event PositionBelowMinimumLiquidationPriceToleranceMultiplierChanged(uint256 previousValue, uint256 newValue);
  /**
   * @notice Emitted when an admin changes the quote token address with `setQuoteTokenAddress`
   */
  event QuoteTokenAddressChanged(address previousValue, address newValue);
  /**
   * @notice Emitted when the Dispatcher Wallet submits a trade for execution with `executeTrade`
   */
  event TradeExecuted(
    address buyWallet,
    address sellWallet,
    string baseAssetSymbol,
    string quoteAssetSymbol,
    uint64 baseQuantity,
    uint64 quoteQuantity,
    OrderSide makerSide,
    int64 makerFeeQuantity,
    uint64 takerFeeQuantity
  );
  /**
   * @notice Emitted when the Dispatcher Wallet submits a transfer with `transfer`
   */
  event Transferred(
    address destinationWallet,
    address sourceWallet,
    uint64 quantity,
    int64 newDestinationWalletExchangeBalance,
    int64 newSourceWalletExchangeBalance
  );
  /**
   * @notice Emitted when a user clears the exited status of a wallet previously exited with
   * `clearWalletExit`
   */
  event WalletExitCleared(address wallet);
  /**
   * @notice Emitted when a user invokes the Exit Wallet mechanism with `exitWallet`
   */
  event WalletExited(address wallet, uint256 effectiveBlockTimestamp, bool isAssociatedWithManagedAccount);
  /**
   * @notice Emitted when a user withdraws available quote token balance through the Exit Wallet mechanism with
   * `withdrawExitFromManagedAccount`
   */
  event WalletExitFromManagedAccountWithdrawn(address managerWallet, address depositorWallet, uint64 quantity);
  /**
   * @notice Emitted when a user withdraws available quote token balance through the Exit Wallet mechanism with
   * `withdrawExit`
   */
  event WalletExitWithdrawn(address wallet, uint64 quantity);
  /**
   * @notice Emitted when the Dispatcher Wallet cancels a withdrawal with `cancelPendingWithdrawalFromManagedAccount`
   */
  event WithdrawalFromManagedAccountCanceled(address wallet, uint64 quantity, int64 newExchangeBalance);
  /**
   * @notice Emitted when the Dispatcher Wallet submits a withdrawal with `withdraw` or `applyPendingWithdrawalFromManagedAccount`
   */
  event Withdrawn(address wallet, uint64 quantity, int64 newExchangeBalance, bool isAssociatedWithManagedAccount);
}

// SPDX-License-Identifier: MIT

import { BalanceTracking } from "./BalanceTracking.sol";
import { Constants } from "./Constants.sol";
import { ExchangeEvents } from "./ExchangeEvents.sol";

pragma solidity 0.8.25;

library ExitFund {
  using BalanceTracking for BalanceTracking.Storage;

  // solhint-disable-next-line func-name-mixedcase
  function validateExitFundWallet_delegatecall(
    address currentExitFundWallet,
    address newExitFundWallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet
  ) public {
    require(newExitFundWallet != address(0x0), "Invalid wallet address");
    require(newExitFundWallet != currentExitFundWallet, "Must be different from current Exit Fund");

    require(
      !ExitFund.doesWalletHaveOpenPositionsOrQuoteBalance(
        currentExitFundWallet,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet
      ),
      "Current Exit Fund cannot have open balance"
    );

    require(
      !ExitFund.doesWalletHaveOpenPositionsOrQuoteBalance(
        newExitFundWallet,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet
      ),
      "New Exit Fund cannot have open balance"
    );

    require(
      address(
        balanceTracking
          .loadBalanceStructAndMigrateIfNeeded(newExitFundWallet, Constants.QUOTE_ASSET_SYMBOL)
          .managedAccountProvider
      ) == address(0x0),
      "New Exit Fund cannot be associated with MA"
    );

    emit ExchangeEvents.ExitFundWalletChanged(currentExitFundWallet, newExitFundWallet);
  }

  // Returns the block timestamp at which an EF position initially opened; zero if EF has no open positions or quote
  // balance
  function getExitFundPositionOpenedAtBlockTimestamp(
    uint256 currentExitFundBalanceOpenedAtBlockTimestamp,
    address exitFundWallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet
  ) internal view returns (uint256) {
    bool isPositionOpen = baseAssetSymbolsWithOpenPositionsByWallet[exitFundWallet].length > 0;
    bool isQuoteOpen = balanceTracking.loadBalanceFromMigrationSourceIfNeeded(
      exitFundWallet,
      Constants.QUOTE_ASSET_SYMBOL
    ) > 0;

    // Position opened when none was before, return current block timestamp
    if (currentExitFundBalanceOpenedAtBlockTimestamp == 0 && isPositionOpen) {
      return block.timestamp;
    }

    // Position or quote was open before but both are now closed, reset block timestamp. Note that quote must be
    // drawn down to zero before resetting since EF quote withdrawals are not possible after reset
    if (currentExitFundBalanceOpenedAtBlockTimestamp > 0 && !(isPositionOpen || isQuoteOpen)) {
      return 0;
    }

    // No change in balance or quote opened block timestamp
    return currentExitFundBalanceOpenedAtBlockTimestamp;
  }

  function doesWalletHaveOpenPositionsOrQuoteBalance(
    address exitFundWallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet
  ) internal view returns (bool) {
    return
      baseAssetSymbolsWithOpenPositionsByWallet[exitFundWallet].length > 0 ||
      balanceTracking.loadBalanceFromMigrationSourceIfNeeded(exitFundWallet, Constants.QUOTE_ASSET_SYMBOL) > 0;
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { BalanceTracking } from "./BalanceTracking.sol";
import { Constants } from "./Constants.sol";
import { ExchangeEvents } from "./ExchangeEvents.sol";
import { FundingMultiplierQuartetHelper } from "./FundingMultiplierQuartetHelper.sol";
import { Math } from "./Math.sol";
import { SortedStringSet } from "./SortedStringSet.sol";
import { Time } from "./Time.sol";
import { Balance, FundingMultiplierQuartet, Market } from "./Structs.sol";

library Funding {
  using BalanceTracking for BalanceTracking.Storage;
  using FundingMultiplierQuartetHelper for FundingMultiplierQuartet[];
  using SortedStringSet for string[];

  // solhint-disable-next-line func-name-mixedcase
  function applyOutstandingWalletFundingForMarket_delegatecall(
    string memory baseAssetSymbol,
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) public {
    Market memory market = marketsByBaseAssetSymbol[baseAssetSymbol];
    require(market.exists, "Market not found");
    require(
      baseAssetSymbolsWithOpenPositionsByWallet[wallet].indexOf(market.baseAssetSymbol) != SortedStringSet.NOT_FOUND,
      "No open position in market"
    );

    Balance storage basePosition = balanceTracking.loadBalanceStructAndMigrateIfNeeded(wallet, market.baseAssetSymbol);
    (int64 funding, uint64 toTimestampInMs) = _loadWalletFundingForMarket(
      basePosition,
      true,
      market,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol
    );
    basePosition.lastUpdateTimestampInMs = toTimestampInMs;

    Balance storage quoteBalance = balanceTracking.loadBalanceStructAndMigrateIfNeeded(
      wallet,
      Constants.QUOTE_ASSET_SYMBOL
    );
    quoteBalance.balance += funding;
  }

  // solhint-disable-next-line func-name-mixedcase
  function loadOutstandingWalletFunding_delegatecall(
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) public view returns (int64 funding) {
    return
      loadOutstandingWalletFunding(
        wallet,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet,
        fundingMultipliersByBaseAssetSymbol,
        lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
        marketsByBaseAssetSymbol
      );
  }

  // solhint-disable-next-line func-name-mixedcase
  function publishFundingMultiplier_delegatecall(
    string memory baseAssetSymbol,
    int64 fundingRate,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) public {
    Market memory market = marketsByBaseAssetSymbol[baseAssetSymbol];
    require(market.exists && market.isActive, "No active market found");

    require(
      Math.abs(fundingRate) < market.overridableFields.maintenanceMarginFraction,
      "Funding rate exceeds maintenance margin fraction"
    );

    // The last publish timestamp will always be non-zero as set during market creation by `backfillFundingMultipliersForMarket`
    uint64 lastPublishTimestampInMs = lastFundingRatePublishTimestampInMsByBaseAssetSymbol[baseAssetSymbol];
    // Previous funding rate exists, next publish timestamp is exactly one period length from previous period start
    uint64 nextPublishTimestampInMs = lastPublishTimestampInMs + Constants.FUNDING_PERIOD_IN_MS;

    // Use the timestamp of the last index price for the market to determine if any funding periods were skipped
    if (market.lastIndexPriceTimestampInMs < nextPublishTimestampInMs) {
      // No missing periods, validate index price is not stale for next period
      require(
        nextPublishTimestampInMs - market.lastIndexPriceTimestampInMs < Constants.FUNDING_PERIOD_IN_MS / 2,
        "Index price too far before next period"
      );
    } else if (market.lastIndexPriceTimestampInMs > nextPublishTimestampInMs + Constants.FUNDING_PERIOD_IN_MS / 2) {
      // Backfill missing periods with a multiplier of 0 (no funding payments made)
      uint64 periodsToBackfill = Math.divideRoundNearest(
        market.lastIndexPriceTimestampInMs - nextPublishTimestampInMs,
        Constants.FUNDING_PERIOD_IN_MS
      );
      for (uint64 i = 0; i < periodsToBackfill; i++) {
        fundingMultipliersByBaseAssetSymbol[baseAssetSymbol].publishFundingMultiplier(0);
      }
      nextPublishTimestampInMs += periodsToBackfill * Constants.FUNDING_PERIOD_IN_MS;
    }

    int64 newFundingMultiplier = Math.multiplyPipsByFraction(
      Math.toInt64(market.lastIndexPrice),
      // The funding rate is positive when longs pay shorts, and negative when shorts pay longs. Flipping the sign
      // on the stored multiplier allows it to be directly multiplied by a wallet's position size to determine its
      // funding credit or debit
      -1 * fundingRate,
      Math.toInt64(Constants.PIP_PRICE_MULTIPLIER)
    );

    fundingMultipliersByBaseAssetSymbol[baseAssetSymbol].publishFundingMultiplier(newFundingMultiplier);

    lastFundingRatePublishTimestampInMsByBaseAssetSymbol[baseAssetSymbol] = nextPublishTimestampInMs;

    emit ExchangeEvents.FundingRatePublished(baseAssetSymbol, fundingRate);
  }

  function applyOutstandingWalletFunding(
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) internal {
    int64 funding;
    int64 marketFunding;
    uint64 lastFundingMultiplierTimestampInMs;

    string[] memory baseAssetSymbols = baseAssetSymbolsWithOpenPositionsByWallet[wallet];
    for (uint8 i = 0; i < baseAssetSymbols.length; i++) {
      Market memory market = marketsByBaseAssetSymbol[baseAssetSymbols[i]];
      Balance storage basePosition = balanceTracking.loadBalanceStructAndMigrateIfNeeded(
        wallet,
        market.baseAssetSymbol
      );

      (marketFunding, lastFundingMultiplierTimestampInMs) = _loadWalletFundingForMarket(
        basePosition,
        false,
        market,
        fundingMultipliersByBaseAssetSymbol,
        lastFundingRatePublishTimestampInMsByBaseAssetSymbol
      );
      funding += marketFunding;
      basePosition.lastUpdateTimestampInMs = lastFundingMultiplierTimestampInMs;
    }

    Balance storage quoteBalance = balanceTracking.loadBalanceStructAndMigrateIfNeeded(
      wallet,
      Constants.QUOTE_ASSET_SYMBOL
    );
    quoteBalance.balance += funding;
  }

  function backfillFundingMultipliersForMarket(
    Market memory market,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol
  ) internal {
    // Always backfill 1 period for midnight, and an additional period for every period boundary crossed since then
    uint64 periodsToBackfill = 1 + (Time.getMsSinceMidnight() / Constants.FUNDING_PERIOD_IN_MS);
    for (uint64 i = 0; i < periodsToBackfill; i++) {
      fundingMultipliersByBaseAssetSymbol[market.baseAssetSymbol].publishFundingMultiplier(0);
    }

    lastFundingRatePublishTimestampInMsByBaseAssetSymbol[market.baseAssetSymbol] =
      Time.getMidnightTodayInMs() +
      // Midnight today is always the first period to be backfilled
      ((periodsToBackfill - 1) * Constants.FUNDING_PERIOD_IN_MS);
  }

  function loadOutstandingWalletFunding(
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) internal view returns (int64 funding) {
    int64 marketFunding;

    string[] memory baseAssetSymbols = baseAssetSymbolsWithOpenPositionsByWallet[wallet];
    for (uint8 i = 0; i < baseAssetSymbols.length; i++) {
      Market memory market = marketsByBaseAssetSymbol[baseAssetSymbols[i]];
      Balance memory basePosition = balanceTracking.loadBalanceStructFromMigrationSourceIfNeeded(
        wallet,
        market.baseAssetSymbol
      );

      (marketFunding, ) = _loadWalletFundingForMarket(
        basePosition,
        false,
        market,
        fundingMultipliersByBaseAssetSymbol,
        lastFundingRatePublishTimestampInMsByBaseAssetSymbol
      );
      funding += marketFunding;
    }
  }

  function _loadWalletFundingForMarket(
    Balance memory basePosition,
    bool limitMaxTimePeriod,
    Market memory market,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol
  ) private view returns (int64, uint64) {
    // Load funding rates and index
    uint64 lastFundingRatePublishTimestampInMs = lastFundingRatePublishTimestampInMsByBaseAssetSymbol[
      market.baseAssetSymbol
    ];

    // Apply funding payments if new multipliers were published since the position was last updated
    if (basePosition.balance != 0 && basePosition.lastUpdateTimestampInMs < lastFundingRatePublishTimestampInMs) {
      // To calculate the number of multipliers to apply, start from the first funding multiplier following the last
      // position update and go up to the last published multiplier. Update timestamps are always period-aligned
      uint64 fromTimestampInMs = basePosition.lastUpdateTimestampInMs + Constants.FUNDING_PERIOD_IN_MS;

      uint64 toTimestampInMs;
      if (limitMaxTimePeriod) {
        // Limit number of multipliers applied if needed
        toTimestampInMs = Math.min(
          fromTimestampInMs + Constants.MAX_FUNDING_TIME_PERIOD_PER_UPDATE_IN_MS,
          lastFundingRatePublishTimestampInMs
        );
      } else {
        toTimestampInMs = lastFundingRatePublishTimestampInMs;
      }
      // There is no possibility of `fromTimestampInMs` being greater than `toTimestampInMs` - the maximum value
      // `fromTimestampInMs` can be initialized to is `lastFundingRatePublishTimestampInMs`, and `toTimestampInMs`
      // is either itself `lastFundingRatePublishTimestampInMs` OR `fromTimestampInMs` is at least
      // `MAX_FUNDING_TIME_PERIOD_PER_UPDATE_IN_MS` behind `lastFundingRatePublishTimestampInMs`

      // Load aggregate funding payment over specified from and to timestamps
      int64 funding = fundingMultipliersByBaseAssetSymbol[market.baseAssetSymbol].loadAggregatePayment(
        fromTimestampInMs,
        toTimestampInMs,
        lastFundingRatePublishTimestampInMs,
        basePosition.balance
      );

      return (funding, toTimestampInMs);
    }

    return (0, basePosition.lastUpdateTimestampInMs);
  }
}

// SPDX-License-Identifier: MIT

import { Constants } from "./Constants.sol";
import { FundingMultiplierQuartet } from "./Structs.sol";
import { Math } from "./Math.sol";

pragma solidity 0.8.25;

library FundingMultiplierQuartetHelper {
  // Avoid magic numbers
  uint64 private constant _QUARTET_SIZE = 4;

  int64 private constant _EMPTY = type(int64).min;

  /**
   * @dev Adds a new funding multiplier to an array of quartets
   */
  function publishFundingMultiplier(FundingMultiplierQuartet[] storage self, int64 newFundingMultiplier) internal {
    if (self.length > 0) {
      FundingMultiplierQuartet storage fundingMultiplierQuartet = self[self.length - 1];
      if (fundingMultiplierQuartet.fundingMultiplier3 != _EMPTY) {
        // Quartet is fully populated, add new entry
        self.push(FundingMultiplierQuartet(newFundingMultiplier, _EMPTY, _EMPTY, _EMPTY));
      } else if (fundingMultiplierQuartet.fundingMultiplier1 == _EMPTY) {
        fundingMultiplierQuartet.fundingMultiplier1 = newFundingMultiplier;
      } else if (fundingMultiplierQuartet.fundingMultiplier2 == _EMPTY) {
        fundingMultiplierQuartet.fundingMultiplier2 = newFundingMultiplier;
      } else {
        fundingMultiplierQuartet.fundingMultiplier3 = newFundingMultiplier;
      }
    } else {
      // First multiplier for market, add new entry
      self.push(FundingMultiplierQuartet(newFundingMultiplier, _EMPTY, _EMPTY, _EMPTY));
    }
  }

  /**
   * @dev Given a start and end timestamp, scans an array of funding multiplier quartets and calculates the aggregate
   * funding payment to apply
   *
   * @param self The array of funding multiplier quartets
   * @param fromTimestampInMs The publish timestamp of the first funding multiplier to apply
   * @param toTimestampInMs The publish timestamp of the last funding multiplier to apply
   * @param lastFundingRatePublishTimestampInMs The publish timestamp of the latest funding multiplier in the array
   * @param positionSize The position size against which all funding multipliers are multiplied
   */
  function loadAggregatePayment(
    FundingMultiplierQuartet[] storage self,
    uint64 fromTimestampInMs,
    uint64 toTimestampInMs,
    uint64 lastFundingRatePublishTimestampInMs,
    int64 positionSize
  ) internal view returns (int64) {
    (uint256 startIndex, uint64 startOffset) = _calculateIndexAndOffsetForTimestampInMs(
      self,
      fromTimestampInMs,
      lastFundingRatePublishTimestampInMs
    );
    (uint256 endIndex, uint64 endOffset) = _calculateIndexAndOffsetForTimestampInMs(
      self,
      toTimestampInMs,
      lastFundingRatePublishTimestampInMs
    );

    if (startIndex == endIndex) {
      return _calculateAggregatePaymentForQuartet(self[startIndex], startOffset, endOffset, positionSize);
    }

    int64 aggregatePayment = _calculateAggregatePaymentForQuartet(
      self[startIndex],
      startOffset,
      _QUARTET_SIZE - 1,
      positionSize
    );
    for (uint256 i = startIndex + 1; i < endIndex; i++) {
      aggregatePayment += _calculateAggregatePaymentForQuartet(self[i], 0, _QUARTET_SIZE - 1, positionSize);
    }
    aggregatePayment += _calculateAggregatePaymentForQuartet(self[endIndex], 0, endOffset, positionSize);

    return aggregatePayment;
  }

  /**
   * @dev Calculates the aggregate funding payment for one quartet
   */
  function _calculateAggregatePaymentForQuartet(
    FundingMultiplierQuartet memory fundingMultipliers,
    uint256 startOffset,
    uint256 endOffset,
    int64 positionSize
  ) private pure returns (int64 aggregatePayment) {
    if (startOffset == 0) {
      aggregatePayment += Math.multiplyPipsByFraction(
        positionSize,
        fundingMultipliers.fundingMultiplier0,
        Math.toInt64(Constants.PIP_PRICE_MULTIPLIER)
      );
    }
    if (startOffset <= 1 && endOffset >= 1) {
      aggregatePayment += Math.multiplyPipsByFraction(
        positionSize,
        fundingMultipliers.fundingMultiplier1,
        Math.toInt64(Constants.PIP_PRICE_MULTIPLIER)
      );
    }
    if (startOffset <= 2 && endOffset >= 2) {
      aggregatePayment += Math.multiplyPipsByFraction(
        positionSize,
        fundingMultipliers.fundingMultiplier2,
        Math.toInt64(Constants.PIP_PRICE_MULTIPLIER)
      );
    }
    if (startOffset <= 3 && endOffset == 3) {
      aggregatePayment += Math.multiplyPipsByFraction(
        positionSize,
        fundingMultipliers.fundingMultiplier3,
        Math.toInt64(Constants.PIP_PRICE_MULTIPLIER)
      );
    }
  }

  function _calculateIndexAndOffsetForTimestampInMs(
    FundingMultiplierQuartet[] storage self,
    uint64 targetTimestampInMs,
    uint64 lastTimestampInMs
  ) private view returns (uint256 index, uint64 offset) {
    // The last element may not be fully populated, but previous elements alway are
    uint64 totalNumberOfMultipliers = _calculateNumberOfMultipliersInQuartet(self[self.length - 1]) +
      // We can safely downcast since 2^63 multipliers would take several quadrillion years to exceed
      (uint64(self.length - 1) * _QUARTET_SIZE);
    // Calculate the timestamp of the very first multiplier
    uint64 firstTimestampInMs = lastTimestampInMs - ((totalNumberOfMultipliers - 1) * Constants.FUNDING_PERIOD_IN_MS);

    // Calculate the number of multipliers from the first published timestamp to the target timestamp, both inclusive
    uint64 numberOfMultipliersFromFirstToTargetTimestamp = 1 +
      ((targetTimestampInMs - firstTimestampInMs) / Constants.FUNDING_PERIOD_IN_MS);

    // Calculate index and offset of target timestamp
    index = Math.divideRoundUp(numberOfMultipliersFromFirstToTargetTimestamp, _QUARTET_SIZE) - 1;
    offset = numberOfMultipliersFromFirstToTargetTimestamp % _QUARTET_SIZE == 0
      ? 3
      : (numberOfMultipliersFromFirstToTargetTimestamp % _QUARTET_SIZE) - 1;
  }

  /**
   * @dev Calculates the number of multipliers packed in one quartet
   */
  function _calculateNumberOfMultipliersInQuartet(
    FundingMultiplierQuartet memory fundingMultipliers
  ) private pure returns (uint64 multiplierCount) {
    if (fundingMultipliers.fundingMultiplier3 != _EMPTY) {
      return 4;
    }
    if (fundingMultipliers.fundingMultiplier2 != _EMPTY) {
      return 3;
    }
    if (fundingMultipliers.fundingMultiplier1 != _EMPTY) {
      return 2;
    }

    // A quartet always includes at least one multiplier
    return 1;
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";

import { Constants } from "./Constants.sol";
import { String } from "./String.sol";
import {
  DelegatedKeyAuthorization,
  Order,
  Transfer,
  Withdrawal,
  WithdrawalFromManagedAccountByQuantity,
  WithdrawalFromManagedAccountByShares
} from "./Structs.sol";

/**
 * @notice Library helpers for building hashes and verifying wallet signatures
 */
library Hashing {
  function getSigner(
    bytes32 domainSeparator,
    bytes32 structHash,
    bytes memory signature
  ) internal pure returns (address) {
    return ECDSA.recover(MessageHashUtils.toTypedDataHash(domainSeparator, structHash), signature);
  }

  function isSignatureValid(
    bytes32 domainSeparator,
    bytes32 structHash,
    bytes memory signature,
    address signer
  ) internal pure returns (bool) {
    return getSigner(domainSeparator, structHash, signature) == signer;
  }

  function getDelegatedKeyAuthorizationHash(
    DelegatedKeyAuthorization memory delegatedKeyAuthorization
  ) internal pure returns (bytes32) {
    return
      keccak256(
        abi.encode(
          Constants.EIP_712_TYPE_HASH_DELEGATED_KEY_AUTHORIZATION,
          delegatedKeyAuthorization.nonce,
          delegatedKeyAuthorization.delegatedPublicKey,
          Constants.DELEGATED_KEY_AUTHORIZATION_MESSAGE_HASH
        )
      );
  }

  /**
   * @dev As a gas optimization, base and quote symbols are passed in separately and combined to
   * verify the wallet hash, since this is cheaper than splitting the market symbol into its two
   * constituent asset symbols
   */
  function getOrderHash(
    Order memory order,
    string memory baseSymbol,
    string memory quoteSymbol
  ) internal pure returns (bytes32) {
    // Placing all the fields in a single `abi.encode` call causes a `stack too deep` error
    return
      keccak256(
        abi.encodePacked(
          abi.encode(
            Constants.EIP_712_TYPE_HASH_ORDER,
            order.nonce,
            order.wallet,
            keccak256(abi.encodePacked(baseSymbol, "-", quoteSymbol)),
            uint8(order.orderType),
            uint8(order.side),
            keccak256(bytes(String.pipsToDecimalString(order.quantity))),
            keccak256(bytes(String.pipsToDecimalString(order.limitPrice)))
          ),
          abi.encode(
            keccak256(bytes(String.pipsToDecimalString(order.triggerPrice))),
            uint8(order.triggerType),
            keccak256(bytes(String.pipsToDecimalString(order.callbackRate))),
            order.conditionalOrderId,
            order.isReduceOnly,
            uint8(order.timeInForce),
            uint8(order.selfTradePrevention),
            order.isLiquidationAcquisitionOnly,
            order.delegatedKeyAuthorization.delegatedPublicKey,
            keccak256(bytes(order.clientOrderId))
          )
        )
      );
  }

  function getTransferHash(Transfer memory transfer) internal pure returns (bytes32) {
    return
      keccak256(
        abi.encode(
          Constants.EIP_712_TYPE_HASH_TRANSFER,
          transfer.nonce,
          transfer.sourceWallet,
          transfer.destinationWallet,
          keccak256(bytes(String.pipsToDecimalString(transfer.grossQuantity)))
        )
      );
  }

  function getWithdrawalHash(Withdrawal memory withdrawal) internal pure returns (bytes32) {
    return
      keccak256(
        abi.encode(
          Constants.EIP_712_TYPE_HASH_WITHDRAWAL,
          withdrawal.nonce,
          withdrawal.wallet,
          keccak256(bytes(String.pipsToDecimalString(withdrawal.grossQuantity))),
          keccak256(bytes(String.pipsToDecimalString(withdrawal.maximumGasFee))),
          withdrawal.bridgeAdapter,
          keccak256(withdrawal.bridgeAdapterPayload)
        )
      );
  }

  function getWithdrawalFromManagedAccountByQuantityHash(
    WithdrawalFromManagedAccountByQuantity memory withdrawal
  ) internal pure returns (bytes32) {
    return
      keccak256(
        abi.encode(
          Constants.EIP_712_TYPE_HASH_WITHDRAWAL_FROM_MANAGED_ACCOUNT_BY_QUANTITY,
          withdrawal.nonce,
          withdrawal.managerWallet,
          withdrawal.depositorWallet,
          keccak256(bytes(String.pipsToDecimalString(withdrawal.grossQuantity))),
          keccak256(bytes(String.pipsToDecimalString(withdrawal.maxShares))),
          keccak256(bytes(String.pipsToDecimalString(withdrawal.maximumGasFee))),
          withdrawal.managedAccountProvider,
          keccak256(withdrawal.managedAccountProviderPayload),
          withdrawal.bridgeAdapter,
          keccak256(withdrawal.bridgeAdapterPayload)
        )
      );
  }

  function getWithdrawalFromManagedAccountBySharesHash(
    WithdrawalFromManagedAccountByShares memory withdrawal
  ) internal pure returns (bytes32) {
    return
      keccak256(
        abi.encode(
          Constants.EIP_712_TYPE_HASH_WITHDRAWAL_FROM_MANAGED_ACCOUNT_BY_SHARES,
          withdrawal.nonce,
          withdrawal.managerWallet,
          withdrawal.depositorWallet,
          keccak256(bytes(String.pipsToDecimalString(withdrawal.shares))),
          keccak256(bytes(String.pipsToDecimalString(withdrawal.minimumQuantity))),
          keccak256(bytes(String.pipsToDecimalString(withdrawal.maximumGasFee))),
          withdrawal.managedAccountProvider,
          keccak256(withdrawal.managedAccountProviderPayload),
          withdrawal.bridgeAdapter,
          keccak256(withdrawal.bridgeAdapterPayload)
        )
      );
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { BalanceTracking } from "./BalanceTracking.sol";
import { Constants } from "./Constants.sol";
import { Funding } from "./Funding.sol";
import { LiquidationValidations } from "./LiquidationValidations.sol";
import { MarketHelper } from "./MarketHelper.sol";
import { Math } from "./Math.sol";
import { Balance, FundingMultiplierQuartet, Market, MarketOverrides } from "./Structs.sol";

library IndexPriceMargin {
  using BalanceTracking for BalanceTracking.Storage;
  using MarketHelper for Market;

  // solhint-disable-next-line func-name-mixedcase
  function loadTotalAccountValueIncludingOutstandingWalletFunding_delegatecall(
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => Market) storage marketsByBaseAssetSymbol,
    mapping(address => uint64) storage pendingDepositQuantityByWallet
  ) public view returns (int64) {
    int64 totalAccountValue = _loadTotalAccountValue(
      wallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketsByBaseAssetSymbol
    );

    return
      totalAccountValue +
      Funding.loadOutstandingWalletFunding(
        wallet,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet,
        fundingMultipliersByBaseAssetSymbol,
        lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
        marketsByBaseAssetSymbol
      ) +
      Math.toInt64(pendingDepositQuantityByWallet[wallet]);
  }

  // solhint-disable-next-line func-name-mixedcase
  function loadTotalInitialMarginRequirement_delegatecall(
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) public view returns (uint64 initialMarginRequirement) {
    return
      _loadTotalInitialMarginRequirement(
        wallet,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet,
        marketOverridesByBaseAssetSymbolAndWallet,
        marketsByBaseAssetSymbol
      );
  }

  // solhint-disable-next-line func-name-mixedcase
  function loadTotalMaintenanceMarginRequirement_delegatecall(
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) public view returns (uint64 maintenanceMarginRequirement) {
    return
      _loadTotalMaintenanceMarginRequirement(
        wallet,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet,
        marketOverridesByBaseAssetSymbolAndWallet,
        marketsByBaseAssetSymbol
      );
  }

  function loadTotalAccountValueInDoublePipsAndMaintenanceMarginRequirementInTriplePips(
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  )
    internal
    view
    returns (int256 totalAccountValueInDoublePips, uint256 totalMaintenanceMarginRequirementInTriplePips)
  {
    totalAccountValueInDoublePips = _loadTotalAccountValueInDoublePips(
      wallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketsByBaseAssetSymbol
    );
    totalMaintenanceMarginRequirementInTriplePips = _loadTotalMaintenanceMarginRequirementInTriplePips(
      wallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );
  }

  function loadTotalExitAccountValueAndAccountValueInDoublePipsAndMaintenanceMarginRequirementInTriplePips(
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  )
    internal
    view
    returns (
      int64 totalExitAccountValue,
      int256 totalAccountValueInDoublePips,
      uint256 totalMaintenanceMarginRequirementInTriplePips
    )
  {
    totalExitAccountValue = _loadTotalExitAccountValue(
      wallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketsByBaseAssetSymbol
    );
    totalAccountValueInDoublePips = _loadTotalAccountValueInDoublePips(
      wallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketsByBaseAssetSymbol
    );
    totalMaintenanceMarginRequirementInTriplePips = _loadTotalMaintenanceMarginRequirementInTriplePips(
      wallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );
  }

  // No wallet-specific overrides are observed for the EF
  function loadTotalAccountValueInDoublePipsAndMaintenanceMarginRequirementInTriplePipsForExitFund(
    address exitFundWallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  )
    internal
    view
    returns (int256 totalAccountValueInDoublePips, uint256 totalMaintenanceMarginRequirementInTriplePips)
  {
    totalAccountValueInDoublePips = _loadTotalAccountValueInDoublePips(
      exitFundWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketsByBaseAssetSymbol
    );

    Market memory market;
    int64 positionSize;
    string[] memory baseAssetSymbols = baseAssetSymbolsWithOpenPositionsByWallet[exitFundWallet];
    for (uint8 i = 0; i < baseAssetSymbols.length; i++) {
      market = marketsByBaseAssetSymbol[baseAssetSymbols[i]];
      positionSize = balanceTracking.loadBalanceFromMigrationSourceIfNeeded(exitFundWallet, market.baseAssetSymbol);

      totalMaintenanceMarginRequirementInTriplePips += _loadMarginRequirementInTriplePips(
        market.overridableFields.maintenanceMarginFraction,
        market.lastIndexPrice,
        positionSize
      );
    }
  }

  function validateInitialMarginRequirement(
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) internal view {
    int64 totalAccountValue = balanceTracking.loadBalanceFromMigrationSourceIfNeeded(
      wallet,
      Constants.QUOTE_ASSET_SYMBOL
    );
    uint64 totalInitialMarginRequirement;

    Market memory market;
    int64 positionSize;
    string[] memory baseAssetSymbols = baseAssetSymbolsWithOpenPositionsByWallet[wallet];
    for (uint8 i = 0; i < baseAssetSymbols.length; i++) {
      market = marketsByBaseAssetSymbol[baseAssetSymbols[i]];
      positionSize = balanceTracking.loadBalanceFromMigrationSourceIfNeeded(wallet, market.baseAssetSymbol);

      totalAccountValue += Math.multiplyPipsByFraction(
        positionSize,
        Math.toInt64(market.lastIndexPrice),
        Math.toInt64(Constants.PIP_PRICE_MULTIPLIER)
      );
      totalInitialMarginRequirement += _loadMarginRequirement(
        market.loadInitialMarginFractionForWallet(positionSize, wallet, marketOverridesByBaseAssetSymbolAndWallet),
        market.lastIndexPrice,
        positionSize
      );
    }

    require(totalAccountValue >= Math.toInt64(totalInitialMarginRequirement), "Initial margin requirement not met");
  }

  function validateMaintenanceMarginRequirement(
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) internal view {
    int64 totalAccountValue = balanceTracking.loadBalanceFromMigrationSourceIfNeeded(
      wallet,
      Constants.QUOTE_ASSET_SYMBOL
    );
    uint64 totalMaintenanceMarginRequirement;

    Market memory market;
    int64 positionSize;
    string[] memory baseAssetSymbols = baseAssetSymbolsWithOpenPositionsByWallet[wallet];
    for (uint8 i = 0; i < baseAssetSymbols.length; i++) {
      market = marketsByBaseAssetSymbol[baseAssetSymbols[i]];
      positionSize = balanceTracking.loadBalanceFromMigrationSourceIfNeeded(wallet, market.baseAssetSymbol);

      totalAccountValue += Math.multiplyPipsByFraction(
        positionSize,
        Math.toInt64(market.lastIndexPrice),
        Math.toInt64(Constants.PIP_PRICE_MULTIPLIER)
      );
      totalMaintenanceMarginRequirement += _loadMarginRequirement(
        market
          .loadMarketWithOverridesForWallet(wallet, marketOverridesByBaseAssetSymbolAndWallet)
          .overridableFields
          .maintenanceMarginFraction,
        market.lastIndexPrice,
        positionSize
      );
    }

    require(
      totalAccountValue >= Math.toInt64(totalMaintenanceMarginRequirement),
      "Maintenance margin requirement not met"
    );
  }

  function _loadMarginRequirement(
    uint64 marginFraction,
    uint64 lastIndexPrice,
    int64 positionSize
  ) private pure returns (uint64) {
    return
      Math.multiplyPipsByFraction(
        Math.multiplyPipsByFraction(Math.abs(positionSize), lastIndexPrice, Constants.PIP_PRICE_MULTIPLIER),
        marginFraction,
        Constants.PIP_PRICE_MULTIPLIER
      );
  }

  function _loadMarginRequirementInTriplePips(
    uint64 marginFraction,
    uint64 lastIndexPrice,
    int64 positionSize
  ) private pure returns (uint256) {
    return uint256(Math.abs(positionSize)) * lastIndexPrice * marginFraction;
  }

  function _loadTotalAccountValue(
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private view returns (int64 totalAccountValue) {
    totalAccountValue = balanceTracking.loadBalanceFromMigrationSourceIfNeeded(wallet, Constants.QUOTE_ASSET_SYMBOL);

    Market memory market;

    string[] memory baseAssetSymbols = baseAssetSymbolsWithOpenPositionsByWallet[wallet];
    for (uint8 i = 0; i < baseAssetSymbols.length; i++) {
      market = marketsByBaseAssetSymbol[baseAssetSymbols[i]];

      totalAccountValue += Math.multiplyPipsByFraction(
        balanceTracking.loadBalanceFromMigrationSourceIfNeeded(wallet, market.baseAssetSymbol),
        Math.toInt64(market.lastIndexPrice),
        Math.toInt64(Constants.PIP_PRICE_MULTIPLIER)
      );
    }
  }

  function _loadTotalAccountValueInDoublePips(
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private view returns (int256 totalAccountValueInDoublePips) {
    totalAccountValueInDoublePips =
      int256(balanceTracking.loadBalanceFromMigrationSourceIfNeeded(wallet, Constants.QUOTE_ASSET_SYMBOL)) *
      Math.toInt64(Constants.PIP_PRICE_MULTIPLIER);

    Market memory market;

    string[] memory baseAssetSymbols = baseAssetSymbolsWithOpenPositionsByWallet[wallet];
    for (uint8 i = 0; i < baseAssetSymbols.length; i++) {
      market = marketsByBaseAssetSymbol[baseAssetSymbols[i]];

      totalAccountValueInDoublePips +=
        int256(balanceTracking.loadBalanceFromMigrationSourceIfNeeded(wallet, market.baseAssetSymbol)) *
        Math.toInt64(market.lastIndexPrice);
    }
  }

  function _loadTotalExitAccountValue(
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private view returns (int64 exitAccountValue) {
    exitAccountValue = balanceTracking.loadBalanceFromMigrationSourceIfNeeded(wallet, Constants.QUOTE_ASSET_SYMBOL);

    Balance memory balanceStruct;
    Market memory market;
    uint64 quoteQuantityForPosition;

    string[] memory baseAssetSymbols = baseAssetSymbolsWithOpenPositionsByWallet[wallet];
    for (uint8 i = 0; i < baseAssetSymbols.length; i++) {
      balanceStruct = balanceTracking.loadBalanceStructFromMigrationSourceIfNeeded(wallet, baseAssetSymbols[i]);
      market = marketsByBaseAssetSymbol[baseAssetSymbols[i]];

      quoteQuantityForPosition = LiquidationValidations.calculateQuoteQuantityAtExitPrice(
        balanceStruct.costBasis,
        market.lastIndexPrice,
        balanceStruct.balance
      );

      if (balanceStruct.balance < 0) {
        // Short positions have negative value
        exitAccountValue -= Math.toInt64(quoteQuantityForPosition);
      } else {
        // Long positions have positive value
        exitAccountValue += Math.toInt64(quoteQuantityForPosition);
      }
    }
  }

  function _loadTotalInitialMarginRequirement(
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private view returns (uint64 initialMarginRequirement) {
    Market memory market;
    int64 positionSize;
    string[] memory baseAssetSymbols = baseAssetSymbolsWithOpenPositionsByWallet[wallet];
    for (uint8 i = 0; i < baseAssetSymbols.length; i++) {
      market = marketsByBaseAssetSymbol[baseAssetSymbols[i]];
      positionSize = balanceTracking.loadBalanceFromMigrationSourceIfNeeded(wallet, market.baseAssetSymbol);

      initialMarginRequirement += _loadMarginRequirement(
        market.loadInitialMarginFractionForWallet(
          balanceTracking.loadBalanceFromMigrationSourceIfNeeded(wallet, market.baseAssetSymbol),
          wallet,
          marketOverridesByBaseAssetSymbolAndWallet
        ),
        market.lastIndexPrice,
        positionSize
      );
    }
  }

  function _loadTotalMaintenanceMarginRequirement(
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private view returns (uint64 maintenanceMarginRequirement) {
    Market memory market;
    int64 positionSize;
    string[] memory baseAssetSymbols = baseAssetSymbolsWithOpenPositionsByWallet[wallet];
    for (uint8 i = 0; i < baseAssetSymbols.length; i++) {
      market = marketsByBaseAssetSymbol[baseAssetSymbols[i]];
      positionSize = balanceTracking.loadBalanceFromMigrationSourceIfNeeded(wallet, market.baseAssetSymbol);

      maintenanceMarginRequirement += _loadMarginRequirement(
        market
          .loadMarketWithOverridesForWallet(wallet, marketOverridesByBaseAssetSymbolAndWallet)
          .overridableFields
          .maintenanceMarginFraction,
        market.lastIndexPrice,
        positionSize
      );
    }
  }

  function _loadTotalMaintenanceMarginRequirementInTriplePips(
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private view returns (uint256 maintenanceMarginRequirementInTriplePip) {
    Market memory market;
    int64 positionSize;
    string[] memory baseAssetSymbols = baseAssetSymbolsWithOpenPositionsByWallet[wallet];
    for (uint8 i = 0; i < baseAssetSymbols.length; i++) {
      market = marketsByBaseAssetSymbol[baseAssetSymbols[i]];
      positionSize = balanceTracking.loadBalanceFromMigrationSourceIfNeeded(wallet, market.baseAssetSymbol);

      maintenanceMarginRequirementInTriplePip += _loadMarginRequirementInTriplePips(
        market
          .loadMarketWithOverridesForWallet(wallet, marketOverridesByBaseAssetSymbolAndWallet)
          .overridableFields
          .maintenanceMarginFraction,
        market.lastIndexPrice,
        positionSize
      );
    }
  }
}

File 29 of 54 : Interfaces.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import {
  Balance,
  IndexPrice,
  Market,
  OverridableMarketFields,
  WalletExit,
  WithdrawalFromManagedAccount,
  WithdrawalFromManagedAccountByQuantity,
  WithdrawalFromManagedAccountByShares
} from "./Structs.sol";

/*
 * @notice Interface to Asset Migrator contract used by Custodian to migrate funds from one ERC-20 contract to another
 */
interface IAssetMigrator {
  /**
   * @notice Migrate an asset quantity to a new address
   *
   * @param sourceAsset The address of the old asset that will be migrated from
   * @param quantityInAssetUnits The quantity of token to transfer in asset units
   */
  function migrate(address sourceAsset, uint256 quantityInAssetUnits) external;

  /**
   * @notice Load the address of the destination asset that will be migrated to
   *
   * @return destinationAsset The address of the new asset that will migrated to
   */
  function destinationAsset() external returns (address);
}

/**
 * @notice Interface to Bridge Adapter contracts used by Exchange for routing withdrawals
 */
interface IBridgeAdapter {
  /**
   * @notice Withdraw quote asset quantity to destination wallet using bridge-specific payload
   */
  function withdrawQuoteAsset(address destinationWallet, uint256 quantity, bytes memory payload) external;
}

/**
 * @notice Interface to Custodian contract. Used by Exchange and Governance contracts for internal
 * calls
 */
interface ICustodian {
  /**
   * @notice Migrate the entire balance of an asset to a new address using the currently whitelisted Asset Migrator
   *
   * @param sourceAsset The address of the asset the Custodian currently holds a balance in
   *
   * @return destinationAsset The address of the new asset that will migrated to
   */
  function migrateAsset(address sourceAsset) external returns (address destinationAsset);

  /**
   * @notice Withdraw any asset and amount to a target wallet
   *
   * @dev No balance checking performed
   *
   * @param wallet The wallet to which assets will be returned
   * @param asset The address of the asset to withdraw (ERC-20 contract)
   * @param quantityInAssetUnits The quantity in asset units to withdraw
   */
  function withdraw(address wallet, address asset, uint256 quantityInAssetUnits) external;

  /**
   * @notice Load address of the currently whitelisted Exchange contract
   *
   * @return The address of the currently whitelisted Exchange contract
   */
  function exchange() external view returns (address);

  /**
   * @notice Sets a new Asset Migrator contract address
   *
   * @param newAssetMigrator The address of the new whitelisted Asset Migrator contract or zero address to disable migration
   */
  function setAssetMigrator(address newAssetMigrator) external;

  /**
   * @notice Sets a new Exchange contract address
   *
   * @param newExchange The address of the new whitelisted Exchange contract
   */
  function setExchange(address newExchange) external;

  /**
   * @notice Load address of the currently whitelisted Asset Migrator contract
   *
   * @return The address of the currently whitelisted Asset Migrator contract
   */
  function assetMigrator() external view returns (address);

  /**
   * @notice Load address of the currently whitelisted Governance contract
   *
   * @return The address of the currently whitelisted Governance contract
   */
  function governance() external view returns (address);

  /**
   * @notice Sets a new Governance contract address
   *
   * @param newGovernance The address of the new whitelisted Governance contract
   */
  function setGovernance(address newGovernance) external;
}

/**
 * @notice Interface to Exchange contract
 */
interface IExchange {
  /**
   * @dev Returns the EIP-712 domain separator for the current chain
   */
  function domainSeparatorV4() external view returns (bytes32);

  /**
   * @notice Load a wallet's balance by asset symbol, in pips
   *
   * @param wallet The wallet address to load the balance for. Can be different from `msg.sender`
   * @param assetSymbol The asset symbol to load the wallet's balance for
   *
   * @return balance The quantity denominated in pips of asset at `assetSymbol` currently in an open position or
   * quote balance by `wallet` if base or quote respectively. Result may be negative
   */
  function loadBalanceBySymbol(address wallet, string calldata assetSymbol) external view returns (int64);

  /**
   * @notice Load a wallet's balance-tracking struct by asset symbol
   *
   * @param wallet The wallet address to load the balance for. Can be different from `msg.sender`
   * @param assetSymbol The asset symbol to load the wallet's balance for
   *
   * @return The internal `Balance` struct tracking the asset at `assetSymbol` currently in an open position for or
   * deposited by `wallet`
   */
  function loadBalanceStructBySymbol(
    address wallet,
    string calldata assetSymbol
  ) external view returns (Balance memory);

  /**
   * @notice Loads a list of all currently open positions for a wallet
   *
   * @param wallet The wallet address to load open positions for for. Can be different from `msg.sender`
   *
   * @return A list of base asset symbols corresponding to markets in which the wallet currently has an open position
   */
  function loadBaseAssetSymbolsWithOpenPositionsByWallet(address wallet) external view returns (string[] memory);

  /**
   * @notice Loads the total count of all Bridge Adapters currently whitelisted
   *
   * @return The total count of all Bridge Adapters currently whitelisted
   *
   */
  function loadBridgeAdaptersLength() external view returns (uint256);

  /**
   * @notice Loads the Bridge Adapter at the given index
   *
   * @param index The index at which to load
   *
   * @return The Bridge Adapter at the given index
   */
  function loadBridgeAdapter(uint8 index) external view returns (IBridgeAdapter);

  /**
   * @notice Loads the total count of all Index Price Adapters currently whitelisted
   *
   * @return The total count of all Index Price Adapters currently whitelisted
   *
   */
  function loadIndexPriceAdaptersLength() external view returns (uint256);

  /**
   * @notice Loads the Index Price Adapter at the given index
   *
   * @param index The index at which to load
   *
   * @return The Index Price Adapter at the given index
   */
  function loadIndexPriceAdapter(uint8 index) external view returns (IIndexPriceAdapter);

  /**
   * @notice Loads the total count of all Managed Account Providers currently whitelisted
   *
   * @return The total count of all Managed Account Providers currently whitelisted
   *
   */
  function loadManagedAccountProvidersLength() external view returns (uint256);

  /**
   * @notice Loads the Managed Account Provider at the given index
   *
   * @param index The index at which to load
   *
   * @return The Managed Account Provider at the given index
   */
  function loadManagedAccountProvider(uint8 index) external view returns (IManagedAccountProvider);

  /**
   * @notice Loads the total count of all markets added
   *
   * @return The total count of all markets added
   *
   */
  function loadMarketsLength() external view returns (uint256);

  /**
   * @notice Loads the Market at the given index by addition order
   *
   * @param index The index at which to load
   *
   * @return The Market at the given index by addition order
   */
  function loadMarket(uint8 index) external view returns (Market memory);

  /**
   * @notice Load the balance of quote asset the wallet can withdraw after exiting, in pips. Note that due to changing
   * prices the value returned is only an estimate and may not exactly match the value actually transferred after exit
   *
   * @param wallet The wallet address to load the exit quote balance for. Can be different from `msg.sender`
   *
   * @return balance The quantity denominated in pips of quote asset that can be withdrawn after exiting the wallet.
   * Result may be zero, in which case an exit withdrawal would not transfer out any quote but would still close all
   * positions and quote balance. The available quote for exit withdrawal can validly be negative for the EF wallet, in
   * which case this function will return 0 since no withdrawal is possible. For all other wallets, the exit quote
   * calculations are designed such that the result quantity to withdraw is never negative; however the return type is
   * still signed to provide visibility into unforeseen bugs or rounding errors
   */
  function loadQuoteQuantityAvailableForExitWithdrawal(address wallet) external view returns (int64);

  /**
   * @notice Calculate total account value for a wallet by summing its quote asset balance and each open position's
   * notional values as computed by latest published index price. Result may be negative. Since index prices are
   * published lazily, the result may be out of date for a market with little activity
   *
   * @param wallet The wallet address to calculate total account value for
   */
  function loadTotalAccountValueFromIndexPrices(address wallet) external view returns (int64);

  /*
   * @notice Loads the exit status for a wallet
   *
   * @param wallet The wallet to load exit status for
   *
   * @return The WalletExit struct corresponding to the wallet
   */
  function loadWalletExitStatus(address wallet) external view returns (WalletExit memory);

  /**
   * @notice Load the address of the Custodian contract
   *
   * @return The address of the Custodian contract
   */
  function custodian() external view returns (ICustodian);

  /**
   * @notice Load the number of deposits made to the contract, for use when upgrading to a new Exchange via Governance
   *
   * @return The number of deposits successfully made to the Exchange
   */
  function depositIndex() external view returns (uint64);

  /**
   * @notice Load the address of the currently whitelisted Dispatcher wallet
   *
   * @return The address of the Dispatcher wallet
   */
  function dispatcherWallet() external view returns (address);

  /**
   * @notice Load the address of the Exit Fund Wallet
   *
   * @return The address of the Exit Fund Wallet
   */
  function exitFundWallet() external view returns (address);

  /**
   * @notice Load the address of the Fee Wallet
   *
   * @return The address of the Fee Wallet
   */
  function feeWallet() external view returns (address);

  /**
   * @notice Load the address of the current Insurance Fund wallet
   *
   * @return The address of the Insurance Fund wallet
   */
  function insuranceFundWallet() external view returns (address);

  /**
   * @notice Load the address of the Oracle Price Adapter
   *
   * @return The address of the Oracle Price Adapter
   */
  function oraclePriceAdapter() external view returns (IOraclePriceAdapter);

  /**
   * @notice Load the address of the quote token address
   *
   * @return The address of the quote token ERC-20 contract
   */
  function quoteTokenAddress() external view returns (address);

  /**
   * @notice Associate a manager wallet with a Managed Account Provider contract. The sender must be a whitelisted Managed
   * Account and will be used as the contract to associate the wallet with
   *
   * @param managerWallet The wallet which will be associated with the Managed Account
   */
  function associateManagerWalletWithManagedAccount(address managerWallet) external;

  /**
   * @notice Deposit quote token
   *
   * @param quantityInAssetUnits The quantity to deposit. The sending wallet must first call the `approve` method on
   * the token contract for at least this quantity
   * @param depositorWallet The wallet which will be credited for the new balance. Defaults to sending wallet if zero
   */
  function deposit(uint256 quantityInAssetUnits, address depositorWallet) external;

  /**
   * @notice Deposit quote token to managed account
   *
   * @param quantityInAssetUnits The quantity to deposit. The sending wallet must first call the `approve` method on
   * the token contract for at least this quantity
   * @param depositorWallet The wallet which will be credited for the new shares resulting from the deposit
   * @param managedAccountProvider Address of ManagedAccount contract
   * @param managedAccountProviderPayload ABI-encoded parameters to supply specific Managed Account Provider contract
   * @param managerWallet Address of wallet associated with Managed Account that will be credited for the new balance
   */
  function depositToManagedAccount(
    uint256 quantityInAssetUnits,
    address depositorWallet,
    IManagedAccountProvider managedAccountProvider,
    bytes memory managedAccountProviderPayload,
    address managerWallet
  ) external;

  /**
   * @notice Flags a wallet as exited, immediately disabling deposits upon mining. After the Chain Propagation Period
   * passes trades and withdrawals are also disabled for the wallet, and quote asset may then be withdrawn via
   * `withdrawExit`
   *
   * @param wallet The wallet to exit. If the wallet is associated with a Managed Account, then the sender must be the
   * Managed Account Provider contract. Otherwise, the sender must be the same as the wallet to be flagged as exited
   */
  function exitWallet(address wallet) external;

  /**
   * @notice Sets bridge adapter contract addresses whitelisted for withdrawals
   *
   * @param newBridgeAdapters An array of bridge adapter contract addresses
   */
  function setBridgeAdapters(IBridgeAdapter[] memory newBridgeAdapters) external;

  /**
   * @notice Sets Index Price Adapter contract addresses
   *
   * @param newIndexPriceAdapters An array of contract addresses
   */
  function setIndexPriceAdapters(IIndexPriceAdapter[] memory newIndexPriceAdapters) external;

  /**
   * @notice Sets IF wallet address
   *
   * @param newInsuranceFundWallet The new IF wallet address
   */
  function setInsuranceFundWallet(address newInsuranceFundWallet) external;

  /**
   * @notice Sets whitelisted Managed Account Provider contract addresses
   *
   * @param newManagedAccountProviders An array of Managed Account Provider contract addresses
   */
  function setManagedAccountProviders(IManagedAccountProvider[] memory newManagedAccountProviders) external;

  /**
   * @notice Set overridable market parameters for a specific wallet or as new market defaults
   *
   * @param baseAssetSymbol The base asset symbol for the market
   * @param overridableFields New values for overridable fields
   * @param wallet The wallet to apply overrides to. If zero, overrides apply to entire market
   */
  function setMarketOverrides(
    string memory baseAssetSymbol,
    OverridableMarketFields memory overridableFields,
    address wallet
  ) external;

  /**
   * @notice Sets Oracle Price Adapter contract address
   *
   * @param newOraclePriceAdapter The new contract addresses
   */
  function setOraclePriceAdapter(IOraclePriceAdapter newOraclePriceAdapter) external;

  /**
   * @notice Withdraw quote token from an exited Managed Account wallet
   *
   * @param depositorWallet The depositor wallet which will receive the withdrawn quantity
   * @param managerWallet Address of wallet associated with Managed Account that will be withdrawn from
   * @param quantity The quantity to withdraw
   */
  function withdrawExitFromManagedAccount(address depositorWallet, address managerWallet, uint64 quantity) external;
}

/**
 * @notice Interface to Index Price Adapter
 */
interface IIndexPriceAdapter {
  /**
   * @notice Validate encoded payload and return `IndexPrice` struct
   */
  function validateIndexPricePayload(bytes calldata payload) external returns (IndexPrice memory);

  /**
   * @notice Sets adapter as active, indicating that it is now whitelisted by the Exchange
   */
  function setActive(IExchange exchange) external;
}

/**
 * @notice Interface to Managed Account Provider contract
 */
interface IManagedAccountProvider {
  // Events //

  event AddManagedAccountsDisabledAdmin();

  event AddManagedAccountsEnabledAdmin();

  event DepositsDisabledAdmin(bool isDepositEnabled, bool isApplyDepositEnabled);

  event DepositsEnabledAdmin();

  event DepositsDisabled(address indexed managerWallet);

  event DepositsEnabled(address indexed managerWallet);

  event DepositToManagedAccountReadyToApply(
    uint64 depositIndex,
    uint64 quantity,
    address sourceWallet,
    address depositorWallet,
    address managerWallet
  );

  /**
   * @notice Emitted when a manager wallet creates a new Managed Account via `addManagedAccount`
   */
  event ManagedAccountAdded(address managerWallet);
  /**
   * @notice Emitted when a manager wallet initiates upgrade of Managed Account configuration via
   * `initiateManagedAccountUpgrade`
   */
  event ManagedAccountUpgradeInitiated(address indexed managerWallet, uint256 blockTimestampThreshold);
  /**
   * @notice Emitted when a manager wallet cancels a previously initiated Managed Account
   * configuration upgrade via `cancelManagedAccountUpgrade`
   */
  event ManagedAccountUpgradeCanceled(address indexed managerWallet);
  /**
   * @notice Emitted when a manager wallet finalizes a Managed Account upgrade via `finalizeManagedAccountUpgrade`
   */
  event ManagedAccountUpgradeFinalized(address indexed managerWallet);
  /**
   * @notice Emitted when a vault manager wallet is liquidated  with `liquidateManagerWallet`
   */
  event ManagerWalletLiquidated(address managerWallet);
  /**
   * @notice Emitted when an enqueued withdrawal becomes ready to apply. May be emitted more than
   * once for the same withdrawal
   */
  event WithdrawalFromManagedAccountReadyToApply(
    WithdrawalFromManagedAccount withdrawal,
    uint64 totalShareSupply,
    uint256 numberOfWithdrawalsInQueue
  );

  error AddManagedAccountDisabled();

  error DepositDisabled(address managerWallet);

  error ApplyDepositDisabled(address managerWallet);

  // Admin controls //

  /**
   * @notice Enables or disables Managed Account creation across the provider
   *
   * @param isAddManagedAccountEnabled Enables Managed Account creation if true, disables if false
   */
  function setAddManagedAccountEnabledAdmin(bool isAddManagedAccountEnabled) external;

  /**
   * @notice Enables or disables Managed Account depositing across the provider
   *
   * @param isDepositEnabled Enables the `deposit` function if true, disables if false
   * @param isDepositEnabled Enables the `applyPendingDeposit` function if true, disables if false
   */
  function setDepositEnabledAdmin(bool isDepositEnabled, bool isApplyDepositEnabled) external;

  // MA creation //

  /**
   * @notice Create a new Managed Account including an initial deposit
   *
   * @param managerWallet The manager wallet for the new Managed Account
   * @param payload ABI-encoded provider-specific parameters for new Managed Account
   */
  function addManagedAccount(address managerWallet, bytes calldata payload) external;

  /**
   * @notice Returns true if function `addManagedAccount` is enabled for the provider, false if
   * function is disabled
   */
  function isAddManagedAccountEnabled() external view returns (bool);

  // MA upgrades //

  /**
   * @notice Initiate an upgrade of an existing Managed Account. Caller must be the manager wallet
   * associated with the Managed Account
   */
  function initiateManagedAccountUpgrade(bytes calldata payload) external;

  /**
   * @notice Cancel an in-flight upgrade of a Managed Account. Caller must be the manager wallet
   * associated with the Managed Account
   */
  function cancelManagedAccountUpgrade() external;

  /**
   * @notice Finalize an in-flight upgrade of a Managed Account. Caller must be the manager wallet
   * associated with the Managed Account
   */
  function finalizeManagedAccountUpgrade() external;

  // Interval ticks //

  /**
   * @notice Execute logic on Managed Accounts on regularly timed interval ticks
   */
  function intervalTick(bytes calldata payload) external;

  // Deposits //

  /**
   * @notice Enqueues a deposit. Called by Exchange contract via `depositToManagedAccount`
   */
  function deposit(
    uint64 depositIndex,
    uint64 quantity,
    address sourceWallet,
    address depositingWallet,
    address managerWallet,
    bytes calldata payload
  ) external;

  /**
   * @notice Apply the deposit that is currently at the front of the queue. Called by Exchange
   * contract via `applyPendingDepositToManagedAccount`
   */
  function applyPendingDeposit(uint64 depositIndex, address managerWallet, uint64 quantityInAssetUnits) external;

  /**
   * @notice Enables or disables depositing assets into a Managed Account. Caller must be the
   * manager wallet associated with the Managed Account
   *
   * @param isEnabled Enables deposit if true, disables if false
   */
  function setDepositEnabled(bool isEnabled) external;

  /**
   * @notice Returns true if function `deposit` is enabled for the Managed Account, false if
   * function is disabled
   */
  function isDepositEnabled(address managerWallet) external view returns (bool);

  /**
   * @notice Returns true if function `applyPendingDeposit` is enabled for the Managed Account,
   * false if function is disabled
   */
  function isApplyDepositEnabled(address managerWallet) external view returns (bool);

  // Withdrawals //

  /**
   * @notice Enqueues a user withdrawal specified in exact quote quantity
   *
   * @param withdrawal A `WithdrawalFromManagedAccountByQuantity` struct encoding the parameters
   * of the withdrawal
   */
  function withdrawByQuantity(WithdrawalFromManagedAccountByQuantity calldata withdrawal) external;

  /**
   * @notice Enqueues a user withdrawal specified in exact number of shares
   *
   * @param withdrawal A `WithdrawalFromManagedAccountByShares` struct encoding the parameters
   * of the withdrawal
   */
  function withdrawByShares(WithdrawalFromManagedAccountByShares calldata withdrawal) external;

  /**
   * @notice Cancel the withdrawal that is currently at the front of the queue
   */
  function cancelPendingWithdrawal(uint64 gasFee, address managerWallet, bytes32 withdrawalHash) external;

  /**
   * @notice Apply the withdrawal that is currently at the front of the queue and transfer the
   * corresponding amount of quote asset out of the contract
   */
  function applyPendingWithdrawal(
    uint64 gasFee,
    uint64 grossQuantity,
    address managerWallet,
    bytes32 withdrawalHash
  ) external;

  // Liquidation //

  /**
   * @notice Flag the manager wallet as liquidated by the Exchange contract and refund any pending
   * deposits
   */
  function liquidateManagerWallet(address managerWallet) external;

  // Exits //

  /**
   * @notice Flags the manager wallet as exited, immediately disabling deposits upon mining. After
   * the Chain Propagation Period passes trades and withdrawals are also disabled for the wallet,
   * and quote asset may then be withdrawn via `withdrawExit`
   */
  function exitWallet(address managerWallet) external;

  /**
   * @notice Close all open positions and withdraw the owed quote balance for an exited wallet. The
   * Chain Propagation Period must have already passed since calling `exitWallet`
   */
  function withdrawExit(address managerWallet, address depositorWallet) external;
}

/**
 * @notice Interface to Oracle Price Adapter
 */
interface IOraclePriceAdapter {
  /**
   * @notice Return latest price for base asset symbol in quote asset terms. Reverts if no price is available
   */
  function loadPriceForBaseAssetSymbol(string memory baseAssetSymbol) external view returns (uint64 price);

  /**
   * @notice Sets adapter as active, indicating that it is now whitelisted by the Exchange
   */
  function setActive(IExchange exchange) external;
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { Math as OpenZeppelinMath } from "@openzeppelin/contracts/utils/math/Math.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { SignedMath } from "@openzeppelin/contracts/utils/math/SignedMath.sol";

import { Constants } from "./Constants.sol";
import { Math } from "./Math.sol";

library LiquidationValidations {
  function calculateQuoteQuantityAtExitPrice(
    int64 costBasis,
    uint64 indexPrice,
    int64 positionSize
  ) internal pure returns (uint64 quoteQuantity) {
    // Calculate quote quantity at index price
    quoteQuantity = Math.multiplyPipsByFraction(Math.abs(positionSize), indexPrice, Constants.PIP_PRICE_MULTIPLIER);
    // Quote quantity is the worse of the index price or entry price. For long positions, quote is positive so at a
    // worse price quote is closer to zero (receive less); for short positions, quote is negative so at a worse price
    // is further from zero (give more)
    quoteQuantity = positionSize < 0
      ? Math.max(quoteQuantity, Math.abs(costBasis))
      : Math.min(quoteQuantity, Math.abs(costBasis));
  }

  /**
   * @dev Calculates quote quantity needed to close position at bankruptcy price
   */
  function calculateQuoteQuantityAtBankruptcyPrice(
    uint64 indexPrice,
    uint64 maintenanceMarginFraction,
    int64 positionSize,
    int256 totalAccountValueInDoublePips,
    uint256 totalMaintenanceMarginRequirementInTriplePips
  ) internal pure returns (uint64) {
    if (totalMaintenanceMarginRequirementInTriplePips == 0) {
      return 0;
    }

    int256 quoteQuantityInDoublePips = int256(positionSize) * Math.toInt64(indexPrice);

    uint256 quotePenaltyInDoublePipsUnsigned = OpenZeppelinMath.mulDiv(
      SignedMath.abs(quoteQuantityInDoublePips) * maintenanceMarginFraction,
      SignedMath.abs(totalAccountValueInDoublePips),
      totalMaintenanceMarginRequirementInTriplePips
    );

    int256 quotePenaltyInDoublePips = (totalAccountValueInDoublePips < 0 ? int256(1) : int256(-1)) *
      SafeCast.toInt256(quotePenaltyInDoublePipsUnsigned);

    int256 quoteQuantity = (quoteQuantityInDoublePips + quotePenaltyInDoublePips) /
      (Math.toInt64(Constants.PIP_PRICE_MULTIPLIER));

    // Under certain extreme pricing scenarios, liquidating a short position requires sending the liquidating wallet
    // quote as well as base. While this is correct per the above calculation it would necessitate signed handling
    // elsewhere and be counterintuitive to deleveraged positions, so instead coerce to zero. A similar result can
    // occur for long positions requiring taking both base and quote from the liquidating wallet, though in practice
    // such a wallet would meet the margin requirement and therefor not need liquidation; we include the latter check
    // here anyway for completeness
    if ((positionSize < 0 && quoteQuantity > 0) || (positionSize > 0 && quoteQuantity < 0)) {
      return 0;
    }

    return Math.abs(Math.toInt64(quoteQuantity));
  }

  function validateDeactivatedMarketLiquidationQuoteQuantity(
    uint64 indexPrice,
    int64 positionSize,
    uint64 quoteQuantity
  ) internal pure {
    uint64 expectedLiquidationQuoteQuantity = Math.multiplyPipsByFraction(
      Math.abs(positionSize),
      indexPrice,
      Constants.PIP_PRICE_MULTIPLIER
    );

    // Allow additional pip buffers for integer rounding
    require(
      expectedLiquidationQuoteQuantity - (expectedLiquidationQuoteQuantity == 0 ? 0 : 1) <= quoteQuantity &&
        expectedLiquidationQuoteQuantity + 1 >= quoteQuantity,
      "Invalid quote quantity"
    );
  }

  function validateExitFundClosureQuoteQuantity(
    uint64 indexPrice,
    bool isPositionBelowMinimum,
    int64 liquidationBaseQuantity,
    uint64 liquidationQuoteQuantity,
    uint64 maintenanceMarginFraction,
    int256 totalAccountValueInDoublePips,
    uint256 totalMaintenanceMarginRequirementInTriplePips
  ) internal pure {
    uint64 expectedLiquidationQuoteQuantity;
    if (totalAccountValueInDoublePips < 0) {
      // Use bankruptcy price for negative total account value
      expectedLiquidationQuoteQuantity = calculateQuoteQuantityAtBankruptcyPrice(
        indexPrice,
        maintenanceMarginFraction,
        liquidationBaseQuantity,
        totalAccountValueInDoublePips,
        totalMaintenanceMarginRequirementInTriplePips
      );
    } else {
      // Use index price for positive total account value
      expectedLiquidationQuoteQuantity = Math.multiplyPipsByFraction(
        Math.abs(liquidationBaseQuantity),
        indexPrice,
        Constants.PIP_PRICE_MULTIPLIER
      );
    }

    // Skip validation for positions with very low quote values to avoid false positives due to rounding error
    if (
      isPositionBelowMinimum &&
      expectedLiquidationQuoteQuantity < Constants.MINIMUM_QUOTE_QUANTITY_VALIDATION_THRESHOLD &&
      liquidationQuoteQuantity < Constants.MINIMUM_QUOTE_QUANTITY_VALIDATION_THRESHOLD
    ) {
      return;
    }

    // Allow additional pip buffers for integer rounding
    require(
      expectedLiquidationQuoteQuantity - (expectedLiquidationQuoteQuantity == 0 ? 0 : 1) <= liquidationQuoteQuantity &&
        expectedLiquidationQuoteQuantity + 1 >= liquidationQuoteQuantity,
      "Invalid quote quantity"
    );
  }

  function validateQuoteQuantityAtExitPrice(
    int64 costBasis,
    uint64 indexPrice,
    int64 liquidationBaseQuantity,
    uint64 liquidationQuoteQuantity
  ) internal pure {
    uint64 expectedLiquidationQuoteQuantity = calculateQuoteQuantityAtExitPrice(
      costBasis,
      indexPrice,
      liquidationBaseQuantity
    );

    // Allow additional pip buffers for integer rounding
    require(
      expectedLiquidationQuoteQuantity - (expectedLiquidationQuoteQuantity == 0 ? 0 : 1) <= liquidationQuoteQuantity &&
        expectedLiquidationQuoteQuantity + 1 >= liquidationQuoteQuantity,
      "Invalid quote quantity"
    );
  }

  function validateInsuranceFundClosureQuoteQuantity(
    uint64 baseQuantity,
    int64 costBasis,
    int64 positionSize,
    uint64 quoteQuantity
  ) internal pure {
    uint64 expectedLiquidationQuoteQuantity = Math.multiplyPipsByFraction(
      Math.abs(costBasis),
      baseQuantity,
      Math.abs(positionSize) // Position size validated non-zero by calling function
    );

    // Allow additional pip buffers for integer rounding
    require(
      expectedLiquidationQuoteQuantity - (expectedLiquidationQuoteQuantity == 0 ? 0 : 1) <= quoteQuantity &&
        expectedLiquidationQuoteQuantity + 1 >= quoteQuantity,
      "Invalid quote quantity"
    );
  }

  function validateQuoteQuantityAtBankruptcyPrice(
    uint64 indexPrice,
    int64 liquidationBaseQuantity,
    uint64 liquidationQuoteQuantity,
    uint64 maintenanceMarginFraction,
    int256 totalAccountValueInDoublePips,
    uint256 totalMaintenanceMarginRequirementInTriplePips
  ) internal pure {
    uint64 expectedLiquidationQuoteQuantity = calculateQuoteQuantityAtBankruptcyPrice(
      indexPrice,
      maintenanceMarginFraction,
      liquidationBaseQuantity,
      totalAccountValueInDoublePips,
      totalMaintenanceMarginRequirementInTriplePips
    );

    // Allow additional pip buffers for integer rounding
    require(
      expectedLiquidationQuoteQuantity - (expectedLiquidationQuoteQuantity == 0 ? 0 : 1) <= liquidationQuoteQuantity &&
        expectedLiquidationQuoteQuantity + 1 >= liquidationQuoteQuantity,
      "Invalid quote quantity"
    );
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { AssetUnitConversions } from "./AssetUnitConversions.sol";
import { BalanceTracking } from "./BalanceTracking.sol";
import { Constants } from "./Constants.sol";
import { Depositing } from "./Depositing.sol";
import { ExchangeEvents } from "./ExchangeEvents.sol";
import { ExitFund } from "./ExitFund.sol";
import { Funding } from "./Funding.sol";
import { Hashing } from "./Hashing.sol";
import { IndexPriceMargin } from "./IndexPriceMargin.sol";
import { ManagedAccountWithdrawalType } from "./Enums.sol";
import { Math } from "./Math.sol";
import { WalletExits } from "./WalletExits.sol";
import { Withdrawing } from "./Withdrawing.sol";
import {
  Balance,
  FundingMultiplierQuartet,
  Market,
  MarketOverrides,
  WalletExit,
  WithdrawalFromManagedAccount,
  WithdrawalFromManagedAccountByQuantity,
  WithdrawalFromManagedAccountByShares
} from "./Structs.sol";
import { IBridgeAdapter, ICustodian, IExchange, IManagedAccountProvider } from "./Interfaces.sol";

library ManagedAccounts {
  using BalanceTracking for BalanceTracking.Storage;

  // solhint-disable-next-line func-name-mixedcase
  function associateManagerWalletWithManagedAccount_delegatecall(
    // External argument
    address managerWallet,
    // Exchange state storage refs
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    IManagedAccountProvider[] memory managedAccountProviders,
    mapping(address => uint64) storage pendingDepositQuantityByWallet,
    mapping(address => WalletExit) storage walletExits
  ) public {
    require(!walletExits[managerWallet].exists, "Wallet exited");
    require(baseAssetSymbolsWithOpenPositionsByWallet[managerWallet].length == 0, "Wallet cannot have open positions");
    require(pendingDepositQuantityByWallet[managerWallet] == 0, "Wallet has pending deposits");

    _validateManagedAccountProviderIsWhitelisted(IManagedAccountProvider(msg.sender), managedAccountProviders);

    Balance storage balanceStruct = balanceTracking.loadBalanceStructAndMigrateIfNeeded(
      managerWallet,
      Constants.QUOTE_ASSET_SYMBOL
    );
    require(balanceStruct.balance == 0, "Wallet cannot have open balance");
    require(
      balanceStruct.managedAccountProvider == IManagedAccountProvider(address(0x0)),
      "Wallet already associated with MA"
    );

    balanceStruct.managedAccountProvider = IManagedAccountProvider(msg.sender);
  }

  // solhint-disable-next-line func-name-mixedcase
  function depositToManagedAccount_delegatecall(
    // External arguments
    uint256 quantityInAssetUnits,
    address depositorWallet,
    IManagedAccountProvider managedAccountProvider,
    bytes memory managedAccountProviderPayload,
    address managerWallet,
    address sourceWallet,
    // Exchange state values
    IBridgeAdapter[] memory bridgeAdapters,
    ICustodian custodian,
    uint64 depositIndex,
    address exitFundWallet,
    bool isDepositEnabled,
    address quoteTokenAddress,
    // Exchange state storage refs
    BalanceTracking.Storage storage balanceTracking,
    IManagedAccountProvider[] storage managedAccountProviders,
    mapping(address => uint64) storage pendingDepositQuantityByWallet,
    mapping(address => WalletExit) storage walletExits
  ) public {
    require(managerWallet != address(0x0), "Invalid manager wallet");
    require(depositorWallet != address(0x0), "Invalid depositor wallet");
    require(
      IManagedAccountProvider(msg.sender) == managedAccountProvider ||
        _isBridgeAdapterWhitelisted(IBridgeAdapter(msg.sender), bridgeAdapters),
      "Caller must be MA provider or bridge adapter"
    );
    _validateManagedAccountProviderIsWhitelisted(managedAccountProvider, managedAccountProviders);

    Balance storage balanceStruct = balanceTracking.loadBalanceStructAndMigrateIfNeeded(
      managerWallet,
      Constants.QUOTE_ASSET_SYMBOL
    );
    require(balanceStruct.managedAccountProvider == managedAccountProvider, "Manager wallet not associated with MA");

    balanceStruct = balanceTracking.loadBalanceStructAndMigrateIfNeeded(depositorWallet, Constants.QUOTE_ASSET_SYMBOL);
    if (balanceStruct.managedAccountProvider != IManagedAccountProvider(address(0x0))) {
      require(
        balanceStruct.managedAccountProvider == managedAccountProvider && managerWallet == depositorWallet,
        "Manager wallet can only deposit to its own MA"
      );
    }

    uint64 depositedQuantity = Depositing.deposit(
      custodian,
      depositIndex,
      managerWallet,
      exitFundWallet,
      isDepositEnabled,
      quantityInAssetUnits,
      quoteTokenAddress,
      sourceWallet,
      pendingDepositQuantityByWallet,
      walletExits
    );

    // The Exchange will update the stored deposit index after this function returns
    uint64 newDepositIndex = depositIndex + 1;

    managedAccountProvider.deposit(
      newDepositIndex,
      depositedQuantity,
      sourceWallet,
      depositorWallet,
      managerWallet,
      managedAccountProviderPayload
    );

    emit ExchangeEvents.Deposited(newDepositIndex, sourceWallet, managerWallet, depositedQuantity, true);
  }

  // solhint-disable-next-line func-name-mixedcase
  function applyPendingDepositToManagedAccount_delegatecall(
    uint64 depositIndex,
    uint64 quantity,
    address managerWallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => uint64) storage pendingDepositQuantityByWallet,
    mapping(address => WalletExit) storage walletExits
  ) public {
    require(!WalletExits.isWalletExitFinalized(managerWallet, walletExits), "Manager wallet is exited");

    uint64 pendingDepositQuantity = pendingDepositQuantityByWallet[managerWallet];
    require(quantity <= pendingDepositQuantity, "Quantity to apply exceeds pending");

    pendingDepositQuantityByWallet[managerWallet] = pendingDepositQuantity - quantity;

    // Update balance with argument quantity
    (int64 newExchangeBalance, IManagedAccountProvider managedAccountProvider) = balanceTracking.updateForDeposit(
      managerWallet,
      quantity
    );
    require(address(managedAccountProvider) != address(0x0), "Manager wallet not associated with MA");

    managedAccountProvider.applyPendingDeposit(depositIndex, managerWallet, quantity);

    emit ExchangeEvents.PendingDepositApplied(managerWallet, quantity, newExchangeBalance, true);
  }

  // solhint-disable-next-line func-name-mixedcase
  function applyPendingWithdrawalFromManagedAccount_delegatecall(
    // External arguments
    WithdrawalFromManagedAccount memory withdrawal,
    // Exchange state values
    bytes32 domainSeparator,
    // Exchange state storage refs
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(bytes32 => bool) storage completedWithdrawalFromManagedAccountHashes,
    IBridgeAdapter[] storage bridgeAdapters,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    IManagedAccountProvider[] memory managedAccountProviders,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol,
    mapping(address => WalletExit) storage walletExits
  ) public {
    if (withdrawal.withdrawalType == ManagedAccountWithdrawalType.ByQuantity) {
      return
        applyPendingWithdrawalFromManagedAccountByQuantity(
          withdrawal.withdrawalByQuantity,
          domainSeparator,
          balanceTracking,
          baseAssetSymbolsWithOpenPositionsByWallet,
          completedWithdrawalFromManagedAccountHashes,
          bridgeAdapters,
          fundingMultipliersByBaseAssetSymbol,
          lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
          managedAccountProviders,
          marketOverridesByBaseAssetSymbolAndWallet,
          marketsByBaseAssetSymbol,
          walletExits
        );
    }

    // If the withdrawalType value is not included in the enum then abi.decode will revert without a
    // reason string, so we can safely assume the final enum value below

    applyPendingWithdrawalFromManagedAccountByShares(
      withdrawal.withdrawalByShares,
      domainSeparator,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      completedWithdrawalFromManagedAccountHashes,
      bridgeAdapters,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      managedAccountProviders,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol,
      walletExits
    );
  }

  function applyPendingWithdrawalFromManagedAccountByQuantity(
    // External arguments
    WithdrawalFromManagedAccountByQuantity memory withdrawal,
    // Exchange state values
    bytes32 domainSeparator,
    // Exchange state storage refs
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(bytes32 => bool) storage completedWithdrawalFromManagedAccountHashes,
    IBridgeAdapter[] storage bridgeAdapters,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    IManagedAccountProvider[] memory managedAccountProviders,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol,
    mapping(address => WalletExit) storage walletExits
  ) private {
    // Validate preconditions
    require(!WalletExits.isWalletExitFinalized(withdrawal.depositorWallet, walletExits), "Depositor wallet is exited");
    require(!WalletExits.isWalletExitFinalized(withdrawal.managerWallet, walletExits), "Manager wallet is exited");
    require(withdrawal.depositorWallet != IExchange(address(this)).exitFundWallet(), "EF cannot withdraw from MA");
    require(
      withdrawal.gasFee <= withdrawal.maximumGasFee && withdrawal.maximumGasFee <= withdrawal.grossQuantity,
      "Excessive withdrawal fee"
    );
    // We do not perform any validation on maxShares here and instead leave it to the specific MA
    // provider contract

    _validateManagedAccountProviderIsWhitelisted(withdrawal.managedAccountProvider, managedAccountProviders);

    bytes32 withdrawalHash = _validateWithdrawalSignature(domainSeparator, withdrawal);
    require(!completedWithdrawalFromManagedAccountHashes[withdrawalHash], "Duplicate withdrawal");

    Funding.applyOutstandingWalletFunding(
      withdrawal.managerWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );

    // Update wallet balances
    int64 newExchangeBalance = balanceTracking.updateForWithdrawalFromManagedAccountByQuantity(
      withdrawal,
      IExchange(address(this)).feeWallet()
    );

    // Wallet must still maintain initial margin requirement after withdrawal
    IndexPriceMargin.validateInitialMarginRequirement(
      withdrawal.managerWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );

    _transferWithdrawnQuoteAsset(
      withdrawal.bridgeAdapter,
      IExchange(address(this)).custodian(),
      withdrawal.gasFee,
      withdrawal.grossQuantity,
      withdrawal.managedAccountProvider,
      IExchange(address(this)).quoteTokenAddress(),
      bridgeAdapters
    );
    withdrawal.managedAccountProvider.applyPendingWithdrawal(
      withdrawal.gasFee,
      withdrawal.grossQuantity,
      withdrawal.managerWallet,
      withdrawalHash
    );

    // Replay prevention
    completedWithdrawalFromManagedAccountHashes[withdrawalHash] = true;

    emit ExchangeEvents.Withdrawn(withdrawal.managerWallet, withdrawal.grossQuantity, newExchangeBalance, true);
  }

  function applyPendingWithdrawalFromManagedAccountByShares(
    // External arguments
    WithdrawalFromManagedAccountByShares memory withdrawal,
    // Exchange state values
    bytes32 domainSeparator,
    // Exchange state storage refs
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(bytes32 => bool) storage completedWithdrawalFromManagedAccountHashes,
    IBridgeAdapter[] storage bridgeAdapters,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    IManagedAccountProvider[] memory managedAccountProviders,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol,
    mapping(address => WalletExit) storage walletExits
  ) private {
    // Validate preconditions
    require(!WalletExits.isWalletExitFinalized(withdrawal.depositorWallet, walletExits), "Depositor wallet is exited");
    require(!WalletExits.isWalletExitFinalized(withdrawal.managerWallet, walletExits), "Manager wallet is exited");
    require(withdrawal.depositorWallet != IExchange(address(this)).exitFundWallet(), "EF cannot withdraw from MA");
    require(
      withdrawal.gasFee <= withdrawal.maximumGasFee && withdrawal.maximumGasFee <= withdrawal.grossQuantity,
      "Excessive withdrawal fee"
    );
    require(withdrawal.grossQuantity >= withdrawal.minimumQuantity, "Minimum quantity not met");

    _validateManagedAccountProviderIsWhitelisted(withdrawal.managedAccountProvider, managedAccountProviders);

    bytes32 withdrawalHash = _validateWithdrawalSignature(domainSeparator, withdrawal);
    require(!completedWithdrawalFromManagedAccountHashes[withdrawalHash], "Duplicate withdrawal");

    Funding.applyOutstandingWalletFunding(
      withdrawal.managerWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );

    // Update wallet balances
    int64 newExchangeBalance = balanceTracking.updateForWithdrawalFromManagedAccountByShares(
      withdrawal,
      IExchange(address(this)).feeWallet()
    );

    // Wallet must still maintain initial margin requirement after withdrawal
    IndexPriceMargin.validateInitialMarginRequirement(
      withdrawal.managerWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );

    _transferWithdrawnQuoteAsset(
      withdrawal.bridgeAdapter,
      IExchange(address(this)).custodian(),
      withdrawal.gasFee,
      withdrawal.grossQuantity,
      withdrawal.managedAccountProvider,
      IExchange(address(this)).quoteTokenAddress(),
      bridgeAdapters
    );
    withdrawal.managedAccountProvider.applyPendingWithdrawal(
      withdrawal.gasFee,
      withdrawal.grossQuantity,
      withdrawal.managerWallet,
      withdrawalHash
    );

    // Replay prevention
    completedWithdrawalFromManagedAccountHashes[withdrawalHash] = true;

    emit ExchangeEvents.Withdrawn(withdrawal.managerWallet, withdrawal.grossQuantity, newExchangeBalance, true);
  }

  // solhint-disable-next-line func-name-mixedcase
  function cancelPendingWithdrawalFromManagedAccount_delegatecall(
    // External arguments
    WithdrawalFromManagedAccount memory withdrawal,
    // Exchange state values
    bytes32 domainSeparator,
    // Exchange state storage refs
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(bytes32 => bool) storage completedWithdrawalFromManagedAccountHashes,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    IManagedAccountProvider[] memory managedAccountProviders,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol,
    mapping(address => WalletExit) storage walletExits
  ) public {
    if (withdrawal.withdrawalType == ManagedAccountWithdrawalType.ByQuantity) {
      return
        cancelPendingWithdrawalFromManagedAccountByQuantity(
          withdrawal.withdrawalByQuantity,
          domainSeparator,
          IExchange(address(this)).exitFundWallet(),
          IExchange(address(this)).feeWallet(),
          // Exchange state storage refs
          balanceTracking,
          baseAssetSymbolsWithOpenPositionsByWallet,
          completedWithdrawalFromManagedAccountHashes,
          fundingMultipliersByBaseAssetSymbol,
          lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
          managedAccountProviders,
          marketOverridesByBaseAssetSymbolAndWallet,
          marketsByBaseAssetSymbol,
          walletExits
        );
    }

    // If the withdrawalType value is not included in the enum then abi.decode will revert without
    // a reason string, so we can safely assume the final enum value below
    cancelPendingWithdrawalFromManagedAccountByShares(
      withdrawal.withdrawalByShares,
      domainSeparator,
      IExchange(address(this)).exitFundWallet(),
      IExchange(address(this)).feeWallet(),
      // Exchange state storage refs
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      completedWithdrawalFromManagedAccountHashes,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      managedAccountProviders,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol,
      walletExits
    );
  }

  function cancelPendingWithdrawalFromManagedAccountByQuantity(
    // External arguments
    WithdrawalFromManagedAccountByQuantity memory withdrawal,
    // Exchange state values
    bytes32 domainSeparator,
    address exitFundWallet,
    address feeWallet,
    // Exchange state storage refs
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(bytes32 => bool) storage completedWithdrawalFromManagedAccountHashes,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    IManagedAccountProvider[] memory managedAccountProviders,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol,
    mapping(address => WalletExit) storage walletExits
  ) private {
    // Validate preconditions
    require(!WalletExits.isWalletExitFinalized(withdrawal.depositorWallet, walletExits), "Depositor wallet is exited");
    require(!WalletExits.isWalletExitFinalized(withdrawal.managerWallet, walletExits), "Manager wallet is exited");
    require(withdrawal.depositorWallet != exitFundWallet, "EF cannot withdraw from MA");
    require(withdrawal.gasFee <= withdrawal.maximumGasFee, "Excessive withdrawal fee");

    _validateManagedAccountProviderIsWhitelisted(withdrawal.managedAccountProvider, managedAccountProviders);

    bytes32 withdrawalHash = _validateWithdrawalSignature(domainSeparator, withdrawal);
    require(!completedWithdrawalFromManagedAccountHashes[withdrawalHash], "Duplicate withdrawal");

    Funding.applyOutstandingWalletFunding(
      withdrawal.managerWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );

    // Update wallet balances
    int64 newExchangeBalance = balanceTracking.updateForCancelWithdrawalFromManagedAccountByQuantity(
      withdrawal,
      feeWallet
    );

    // Wallet must still maintain initial margin requirement after paying gas fee
    IndexPriceMargin.validateInitialMarginRequirement(
      withdrawal.managerWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );

    withdrawal.managedAccountProvider.cancelPendingWithdrawal(
      withdrawal.gasFee,
      withdrawal.managerWallet,
      withdrawalHash
    );

    // Replay prevention
    completedWithdrawalFromManagedAccountHashes[withdrawalHash] = true;

    emit ExchangeEvents.WithdrawalFromManagedAccountCanceled(
      withdrawal.managerWallet,
      withdrawal.grossQuantity,
      newExchangeBalance
    );
  }

  function cancelPendingWithdrawalFromManagedAccountByShares(
    // External arguments
    WithdrawalFromManagedAccountByShares memory withdrawal,
    // Exchange state values
    bytes32 domainSeparator,
    address exitFundWallet,
    address feeWallet,
    // Exchange state storage refs
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(bytes32 => bool) storage completedWithdrawalFromManagedAccountHashes,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    IManagedAccountProvider[] memory managedAccountProviders,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol,
    mapping(address => WalletExit) storage walletExits
  ) private {
    // Validate preconditions
    require(!WalletExits.isWalletExitFinalized(withdrawal.depositorWallet, walletExits), "Depositor wallet is exited");
    require(!WalletExits.isWalletExitFinalized(withdrawal.managerWallet, walletExits), "Manager wallet is exited");
    require(withdrawal.depositorWallet != exitFundWallet, "EF cannot withdraw from MA");
    require(withdrawal.gasFee <= withdrawal.maximumGasFee, "Excessive withdrawal fee");
    // The wallet specifies an exact number of shares but the gross quantity is variable based on
    // share price. Since no shares are withdrawn on cancellation, the gross quantity should
    // consist only of the gas fee
    require(withdrawal.grossQuantity == withdrawal.gasFee, "Quantity must equal fee");

    _validateManagedAccountProviderIsWhitelisted(withdrawal.managedAccountProvider, managedAccountProviders);

    bytes32 withdrawalHash = _validateWithdrawalSignature(domainSeparator, withdrawal);
    require(!completedWithdrawalFromManagedAccountHashes[withdrawalHash], "Duplicate withdrawal");

    Funding.applyOutstandingWalletFunding(
      withdrawal.managerWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );

    // Update wallet balances
    int64 newExchangeBalance = balanceTracking.updateForCancelWithdrawalFromManagedAccountByShares(
      withdrawal,
      feeWallet
    );

    // Wallet must still maintain initial margin requirement after paying gas fee
    IndexPriceMargin.validateInitialMarginRequirement(
      withdrawal.managerWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );

    withdrawal.managedAccountProvider.cancelPendingWithdrawal(
      withdrawal.gasFee,
      withdrawal.managerWallet,
      withdrawalHash
    );

    // Replay prevention
    completedWithdrawalFromManagedAccountHashes[withdrawalHash] = true;

    emit ExchangeEvents.WithdrawalFromManagedAccountCanceled(
      withdrawal.managerWallet,
      withdrawal.grossQuantity,
      newExchangeBalance
    );
  }

  // solhint-disable-next-line func-name-mixedcase
  function withdrawExitFromManagedAccount_delegatecall(
    // External arguments
    address depositorWallet,
    address managerWallet,
    uint64 quantity,
    // Exchange state values
    uint256 exitFundPositionOpenedAtBlockTimestamp,
    // Exchange state storage refs
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    IManagedAccountProvider[] storage managedAccountProviders,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol,
    mapping(address => uint64) storage pendingDepositQuantityByWallet,
    mapping(address => WalletExit) storage walletExits
  ) public returns (uint256 exitFundPositionOpenedAtBlockTimestamp_) {
    _validateManagedAccountProviderIsWhitelisted(IManagedAccountProvider(msg.sender), managedAccountProviders);

    Balance storage balanceStruct = balanceTracking.loadBalanceStructAndMigrateIfNeeded(
      managerWallet,
      Constants.QUOTE_ASSET_SYMBOL
    );
    require(
      balanceStruct.managedAccountProvider == IManagedAccountProvider(msg.sender),
      "Manager wallet not associated with MA"
    );

    Funding.applyOutstandingWalletFunding(
      managerWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );

    require(WalletExits.isWalletExitFinalized(managerWallet, walletExits), "Wallet exit not finalized");

    (Balance storage quoteBalanceStruct, uint64 walletQuoteQuantityAvailableToWithdraw) = Withdrawing
      .updatePositionsForWalletExit(
        managerWallet,
        IExchange(address(this)).exitFundWallet(),
        IExchange(address(this)).oraclePriceAdapter(),
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet,
        lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
        marketOverridesByBaseAssetSymbolAndWallet,
        marketsByBaseAssetSymbol,
        pendingDepositQuantityByWallet
      );
    require(walletQuoteQuantityAvailableToWithdraw >= quantity, "Quantity exceeds available quote balance");
    quoteBalanceStruct.balance -= Math.toInt64(quantity);

    IExchange(address(this)).custodian().withdraw(
      depositorWallet,
      IExchange(address(this)).quoteTokenAddress(),
      AssetUnitConversions.pipsToAssetUnits(quantity, Constants.QUOTE_TOKEN_DECIMALS)
    );

    emit ExchangeEvents.WalletExitFromManagedAccountWithdrawn(managerWallet, depositorWallet, quantity);

    return
      ExitFund.getExitFundPositionOpenedAtBlockTimestamp(
        exitFundPositionOpenedAtBlockTimestamp,
        IExchange(address(this)).exitFundWallet(),
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet
      );
  }

  function _isBridgeAdapterWhitelisted(
    IBridgeAdapter bridgeAdapter,
    IBridgeAdapter[] memory bridgeAdapters
  ) private pure returns (bool) {
    for (uint8 i = 0; i < bridgeAdapters.length; i++) {
      if (bridgeAdapter == bridgeAdapters[i]) {
        return true;
      }
    }

    return false;
  }

  function _transferWithdrawnQuoteAsset(
    address bridgeAdapter,
    ICustodian custodian,
    uint64 gasFee,
    uint64 grossQuantity,
    IManagedAccountProvider managedAccountProvider,
    address quoteTokenAddress,
    IBridgeAdapter[] storage bridgeAdapters
  ) private {
    if (bridgeAdapter != address(0x0)) {
      require(_isBridgeAdapterWhitelisted(IBridgeAdapter(bridgeAdapter), bridgeAdapters), "Invalid bridge adapter");
    }

    // Transfer funds from Custodian to MA contract
    uint256 netAssetQuantityInAssetUnits = AssetUnitConversions.pipsToAssetUnits(
      grossQuantity - gasFee,
      Constants.QUOTE_TOKEN_DECIMALS
    );
    if (netAssetQuantityInAssetUnits > 0) {
      custodian.withdraw(address(managedAccountProvider), quoteTokenAddress, netAssetQuantityInAssetUnits);
    }
  }

  function _validateManagedAccountProviderIsWhitelisted(
    IManagedAccountProvider managedAccountProvider,
    IManagedAccountProvider[] memory managedAccountProviders
  ) private pure {
    bool managedAccountIsWhitelisted = false;
    for (uint8 i = 0; i < managedAccountProviders.length; i++) {
      if (managedAccountProvider == managedAccountProviders[i]) {
        managedAccountIsWhitelisted = true;
        break;
      }
    }
    require(managedAccountIsWhitelisted, "Invalid Managed Account Provider contract");
  }

  function _validateWithdrawalSignature(
    bytes32 domainSeparator,
    WithdrawalFromManagedAccountByQuantity memory withdrawal
  ) private pure returns (bytes32 withdrawalHash) {
    withdrawalHash = Hashing.getWithdrawalFromManagedAccountByQuantityHash(withdrawal);

    require(
      Hashing.isSignatureValid(domainSeparator, withdrawalHash, withdrawal.walletSignature, withdrawal.depositorWallet),
      "Invalid wallet signature"
    );
  }

  function _validateWithdrawalSignature(
    bytes32 domainSeparator,
    WithdrawalFromManagedAccountByShares memory withdrawal
  ) private pure returns (bytes32 withdrawalHash) {
    withdrawalHash = Hashing.getWithdrawalFromManagedAccountBySharesHash(withdrawal);

    require(
      Hashing.isSignatureValid(domainSeparator, withdrawalHash, withdrawal.walletSignature, withdrawal.depositorWallet),
      "Invalid wallet signature"
    );
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { Constants } from "./Constants.sol";
import { ExchangeEvents } from "./ExchangeEvents.sol";
import { Funding } from "./Funding.sol";
import { MarketHelper } from "./MarketHelper.sol";
import { String } from "./String.sol";
import { Time } from "./Time.sol";
import { Validations } from "./Validations.sol";
import { IIndexPriceAdapter, IOraclePriceAdapter } from "./Interfaces.sol";
import { IndexPricePayload, FundingMultiplierQuartet, IndexPrice, Market } from "./Structs.sol";

library MarketAdmin {
  using MarketHelper for Market;

  // solhint-disable-next-line func-name-mixedcase
  function addMarket_delegatecall(
    Market memory newMarket,
    IOraclePriceAdapter oraclePriceAdapter,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    string[] storage marketBaseAssetSymbols,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) public {
    require(marketBaseAssetSymbols.length < Constants.MAX_NUMBER_OF_MARKETS, "Max number of markets reached");
    require(!marketsByBaseAssetSymbol[newMarket.baseAssetSymbol].exists, "Market already exists");
    require(
      !String.isEqual(newMarket.baseAssetSymbol, Constants.QUOTE_ASSET_SYMBOL),
      "Base asset symbol cannot be same as quote"
    );
    Validations.validateOverridableMarketFields(newMarket.overridableFields);

    // Populate non-overridable fields and commit new market to storage
    newMarket.exists = true;
    newMarket.isActive = false;
    newMarket.lastIndexPrice = oraclePriceAdapter.loadPriceForBaseAssetSymbol(newMarket.baseAssetSymbol);
    newMarket.lastIndexPriceTimestampInMs = uint64(block.timestamp * 1000);

    marketsByBaseAssetSymbol[newMarket.baseAssetSymbol] = newMarket;
    marketBaseAssetSymbols.push(newMarket.baseAssetSymbol);

    Funding.backfillFundingMultipliersForMarket(
      newMarket,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol
    );

    emit ExchangeEvents.MarketAdded(newMarket.baseAssetSymbol);
  }

  // solhint-disable-next-line func-name-mixedcase
  function activateMarket_delegatecall(
    string calldata baseAssetSymbol,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) public {
    Market storage market = marketsByBaseAssetSymbol[baseAssetSymbol];
    require(market.exists && !market.isActive, "No inactive market found");

    market.isActive = true;
    market.indexPriceAtDeactivation = 0;

    emit ExchangeEvents.MarketActivated(baseAssetSymbol);
  }

  // solhint-disable-next-line func-name-mixedcase
  function deactivateMarket_delegatecall(
    string calldata baseAssetSymbol,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) public {
    Market storage market = marketsByBaseAssetSymbol[baseAssetSymbol];
    require(market.exists && market.isActive, "No active market found");

    market.isActive = false;
    market.indexPriceAtDeactivation = market.lastIndexPrice;

    emit ExchangeEvents.MarketDeactivated(baseAssetSymbol);
  }

  // solhint-disable-next-line func-name-mixedcase
  function publishIndexPrices_delegatecall(
    IndexPricePayload[] memory encodedIndexPrices,
    IIndexPriceAdapter[] memory indexPriceAdapters,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) public {
    Market storage market;
    IndexPrice memory indexPrice;

    for (uint8 i = 0; i < encodedIndexPrices.length; i++) {
      bool indexPriceAdapterIsWhitelisted = false;
      for (uint8 j = 0; j < indexPriceAdapters.length; j++) {
        if (encodedIndexPrices[i].indexPriceAdapter == address(indexPriceAdapters[j])) {
          indexPriceAdapterIsWhitelisted = true;
          break;
        }
      }
      require(indexPriceAdapterIsWhitelisted, "Invalid index price adapter");

      indexPrice = IIndexPriceAdapter(encodedIndexPrices[i].indexPriceAdapter).validateIndexPricePayload(
        encodedIndexPrices[i].payload
      );
      require(indexPrice.timestampInMs < Time.getOneDayFromNowInMs(), "Index price timestamp too high");

      market = marketsByBaseAssetSymbol[indexPrice.baseAssetSymbol];
      require(market.exists && market.isActive, "Active market not found");
      require(market.lastIndexPriceTimestampInMs < indexPrice.timestampInMs, "Outdated index price");

      market.lastIndexPrice = indexPrice.price;
      market.lastIndexPriceTimestampInMs = indexPrice.timestampInMs;

      emit ExchangeEvents.IndexPricePublished(indexPrice.baseAssetSymbol, indexPrice.timestampInMs, indexPrice.price);
    }
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { Math } from "./Math.sol";
import { Market, MarketOverrides } from "./Structs.sol";

library MarketHelper {
  function loadInitialMarginFractionForWallet(
    Market memory market,
    int64 positionSize,
    address wallet,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet
  ) internal view returns (uint64) {
    Market memory marketWithOverrides = loadMarketWithOverridesForWallet(
      market,
      wallet,
      marketOverridesByBaseAssetSymbolAndWallet
    );

    uint64 absolutePositionSize = Math.abs(positionSize);
    if (absolutePositionSize <= marketWithOverrides.overridableFields.baselinePositionSize) {
      return marketWithOverrides.overridableFields.initialMarginFraction;
    }

    uint64 increments = Math.divideRoundUp(
      (absolutePositionSize - marketWithOverrides.overridableFields.baselinePositionSize),
      marketWithOverrides.overridableFields.incrementalPositionSize
    );
    return
      marketWithOverrides.overridableFields.initialMarginFraction +
      (increments * marketWithOverrides.overridableFields.incrementalInitialMarginFraction);
  }

  function loadMarketWithOverridesForWallet(
    Market memory market,
    address wallet,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet
  ) internal view returns (Market memory) {
    MarketOverrides memory marketOverrides = marketOverridesByBaseAssetSymbolAndWallet[market.baseAssetSymbol][wallet];
    if (marketOverrides.exists) {
      return
        Market({
          exists: market.exists,
          isActive: market.isActive,
          baseAssetSymbol: market.baseAssetSymbol,
          indexPriceAtDeactivation: market.indexPriceAtDeactivation,
          lastIndexPrice: market.lastIndexPrice,
          lastIndexPriceTimestampInMs: market.lastIndexPriceTimestampInMs,
          overridableFields: marketOverrides.overridableFields
        });
    }

    return market;
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

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

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

library Math {
  // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.0/contracts/utils/math/SignedMath.sol#L37
  function abs(int64 n) internal pure returns (uint64) {
    unchecked {
      // must be unchecked in order to support `n = type(int64).min`
      return uint64(n >= 0 ? n : -n);
    }
  }

  // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.0/contracts/utils/math/Math.sol#L45
  function divideRoundUp(uint64 a, uint64 b) internal pure returns (uint64) {
    // (a + b - 1) / b can overflow on addition, so we distribute
    return a == 0 ? 0 : (a - 1) / b + 1;
  }

  function divideRoundNearest(uint64 a, uint64 b) internal pure returns (uint64) {
    uint64 halfB = (b % 2 == 0) ? (b / 2) : (b / 2 + 1);
    if (a % b >= halfB) {
      // If remainder is greater than or equal to half of divisor, round up
      return (a / b) + 1;
    } else {
      // Otherwise round down (default division behavior)
      return a / b;
    }
  }

  function doublePipsToPips(int256 valueInDoublePips) internal pure returns (int64 valueInPips) {
    valueInPips = SafeCast.toInt64(valueInDoublePips / SafeCast.toInt256(Constants.PIP_PRICE_MULTIPLIER));
  }

  // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.0/contracts/utils/math/Math.sol#L19
  function max(uint64 a, uint64 b) internal pure returns (uint64) {
    return a >= b ? a : b;
  }

  // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.0/contracts/utils/math/Math.sol#L19
  function max(int64 a, int64 b) internal pure returns (int64) {
    return a >= b ? a : b;
  }

  // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.0/contracts/utils/math/Math.sol#L26
  function min(uint64 a, uint64 b) internal pure returns (uint64) {
    return a <= b ? a : b;
  }

  function multiplyPipsByFraction(
    uint64 multiplicand,
    uint64 fractionDividend,
    uint64 fractionDivisor
  ) internal pure returns (uint64) {
    uint256 result = (uint256(multiplicand) * fractionDividend) / fractionDivisor;

    require(result <= type(uint64).max, "Pip quantity overflows uint64");

    return uint64(result);
  }

  function multiplyPipsByFraction(
    int64 multiplicand,
    int64 fractionDividend,
    int64 fractionDivisor
  ) internal pure returns (int64) {
    int256 result = (int256(multiplicand) * fractionDividend) / fractionDivisor;

    require(result <= type(int64).max, "Pip quantity overflows int64");
    require(result >= type(int64).min, "Pip quantity underflows int64");

    return int64(result);
  }

  function toInt64(int256 value) internal pure returns (int64) {
    require(value <= type(int64).max, "Pip quantity overflows int64");
    require(value >= type(int64).min, "Pip quantity underflows int64");
    return int64(value);
  }

  function toInt64(uint64 value) internal pure returns (int64) {
    require(value <= uint64(type(int64).max), "Pip quantity overflows int64");
    return int64(value);
  }

  function triplePipsToPips(uint256 valueInTriplePips) internal pure returns (uint64 valueInPips) {
    valueInPips = SafeCast.toUint64(
      valueInTriplePips / Constants.PIP_PRICE_MULTIPLIER / Constants.PIP_PRICE_MULTIPLIER
    );
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { NonceInvalidation } from "./Structs.sol";
import { Time } from "./Time.sol";
import { UUID } from "./UUID.sol";

library NonceInvalidations {
  // solhint-disable-next-line func-name-mixedcase
  function invalidateNonce_delegatecall(
    mapping(address => NonceInvalidation[]) storage self,
    uint128 nonce,
    uint256 chainPropagationPeriodInS
  ) public returns (uint64 timestampInMs, uint256 effectiveBlockTimestamp) {
    timestampInMs = UUID.getTimestampInMsFromUuidV1(nonce);
    // Enforce a maximum skew for invalidating nonce timestamps in the future so the user doesn't
    // lock their wallet from trades indefinitely
    require(timestampInMs < Time.getOneDayFromNowInMs(), "Nonce timestamp too high");

    if (self[msg.sender].length > 0) {
      NonceInvalidation storage lastInvalidation = self[msg.sender][self[msg.sender].length - 1];
      require(lastInvalidation.timestampInMs < timestampInMs, "Nonce timestamp invalidated");
      require(lastInvalidation.effectiveBlockTimestamp <= block.timestamp, "Last invalidation not finalized");
    }

    // Changing the Chain Propagation Period will not affect the effectiveBlockTimestamp for this invalidation
    effectiveBlockTimestamp = block.timestamp + chainPropagationPeriodInS;
    self[msg.sender].push(NonceInvalidation(timestampInMs, effectiveBlockTimestamp));
  }

  function loadLastInvalidatedTimestamp(
    mapping(address => NonceInvalidation[]) storage self,
    address wallet
  ) internal view returns (uint64) {
    NonceInvalidation[] storage nonceInvalidations = self[wallet];
    if (nonceInvalidations.length > 0) {
      NonceInvalidation storage lastInvalidation = self[wallet][nonceInvalidations.length - 1];
      // If the latest invalidation has gone into effect, use its timestamp
      if (lastInvalidation.effectiveBlockTimestamp <= block.timestamp) {
        return lastInvalidation.timestampInMs;
      }
      // If the latest invalidation is still pending the Chain Propagation Period, then use the timestamp of the
      // invalidation preceding it
      if (nonceInvalidations.length > 1) {
        NonceInvalidation storage nextToLastInvalidation = self[wallet][nonceInvalidations.length - 2];
        // Validation in `invalidateNonce` prohibits invalidating a nonce while a previous nonce invalidation is still
        // pending, so no need to check the effective block timestamp here as it will always be in effect
        return nextToLastInvalidation.timestampInMs;
      }
    }

    return 0;
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { BalanceTracking } from "./BalanceTracking.sol";
import { Constants } from "./Constants.sol";
import { Funding } from "./Funding.sol";
import { IOraclePriceAdapter } from "./Interfaces.sol";
import { LiquidationValidations } from "./LiquidationValidations.sol";
import { MarketHelper } from "./MarketHelper.sol";
import { Math } from "./Math.sol";
import { Balance, FundingMultiplierQuartet, Market, MarketOverrides } from "./Structs.sol";

library OraclePriceMargin {
  using BalanceTracking for BalanceTracking.Storage;
  using MarketHelper for Market;

  struct LoadQuoteQuantityForPositionExitArguments {
    string baseAssetSymbol;
    int64 exitAccountValue;
    IOraclePriceAdapter oraclePriceAdapter;
    int256 totalAccountValueInDoublePips;
    uint256 totalMaintenanceMarginRequirementInTriplePips;
    address wallet;
  }

  // solhint-disable-next-line func-name-mixedcase
  function loadQuoteQuantityAvailableForExitWithdrawalIncludingOutstandingWalletFunding_delegatecall(
    address exitFundWallet,
    IOraclePriceAdapter oraclePriceAdapter,
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol,
    mapping(address => uint64) storage pendingDepositQuantityByWallet
  ) public view returns (int64) {
    int64 outstandingWalletFunding = Funding.loadOutstandingWalletFunding(
      wallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );

    return
      _loadQuoteQuantityAvailableForExitWithdrawal(
        exitFundWallet,
        oraclePriceAdapter,
        outstandingWalletFunding,
        wallet,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet,
        marketOverridesByBaseAssetSymbolAndWallet,
        marketsByBaseAssetSymbol
      ) + Math.toInt64(pendingDepositQuantityByWallet[wallet]);
  }

  // solhint-disable-next-line func-name-mixedcase
  function loadTotalAccountValueIncludingOutstandingWalletFunding_delegatecall(
    IOraclePriceAdapter oraclePriceAdapter,
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => Market) storage marketsByBaseAssetSymbol,
    mapping(address => uint64) storage pendingDepositQuantityByWallet
  ) public view returns (int64) {
    int64 outstandingWalletFunding = Funding.loadOutstandingWalletFunding(
      wallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );

    return
      _loadTotalAccountValue(
        oraclePriceAdapter,
        outstandingWalletFunding,
        wallet,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet,
        marketsByBaseAssetSymbol
      ) + Math.toInt64(pendingDepositQuantityByWallet[wallet]);
  }

  // solhint-disable-next-line func-name-mixedcase
  function loadTotalInitialMarginRequirement_delegatecall(
    IOraclePriceAdapter oraclePriceAdapter,
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) public view returns (uint64 initialMarginRequirement) {
    return
      _loadTotalInitialMarginRequirement(
        oraclePriceAdapter,
        wallet,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet,
        marketOverridesByBaseAssetSymbolAndWallet,
        marketsByBaseAssetSymbol
      );
  }

  // solhint-disable-next-line func-name-mixedcase
  function loadTotalMaintenanceMarginRequirement_delegatecall(
    IOraclePriceAdapter oraclePriceAdapter,
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) public view returns (uint64 maintenanceMarginRequirement) {
    return
      _loadTotalMaintenanceMarginRequirement(
        oraclePriceAdapter,
        wallet,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet,
        marketOverridesByBaseAssetSymbolAndWallet,
        marketsByBaseAssetSymbol
      );
  }

  function loadTotalExitAccountValueAndAccountValueInDoublePipsAndMaintenanceMarginRequirementInTriplePips(
    IOraclePriceAdapter oraclePriceAdapter,
    int64 outstandingWalletFunding,
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  )
    internal
    view
    returns (
      int64 totalExitAccountValue,
      int256 totalAccountValueInDoublePips,
      uint256 totalMaintenanceMarginRequirementInTriplePips
    )
  {
    totalExitAccountValue = _loadTotalExitAccountValue(
      oraclePriceAdapter,
      outstandingWalletFunding,
      wallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketsByBaseAssetSymbol
    );
    totalAccountValueInDoublePips = _loadTotalAccountValueInDoublePips(
      oraclePriceAdapter,
      outstandingWalletFunding,
      wallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketsByBaseAssetSymbol
    );
    totalMaintenanceMarginRequirementInTriplePips = _loadTotalMaintenanceMarginRequirementInTriplePips(
      oraclePriceAdapter,
      wallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );
  }

  function _loadMarginRequirement(
    uint64 marginFraction,
    Market memory market,
    IOraclePriceAdapter oraclePriceAdapter,
    address wallet,
    BalanceTracking.Storage storage balanceTracking
  ) private view returns (uint64) {
    return
      Math.abs(
        Math.multiplyPipsByFraction(
          Math.multiplyPipsByFraction(
            balanceTracking.loadBalanceFromMigrationSourceIfNeeded(wallet, market.baseAssetSymbol),
            Math.toInt64(oraclePriceAdapter.loadPriceForBaseAssetSymbol(market.baseAssetSymbol)),
            Math.toInt64(Constants.PIP_PRICE_MULTIPLIER)
          ),
          Math.toInt64(marginFraction),
          Math.toInt64(Constants.PIP_PRICE_MULTIPLIER)
        )
      );
  }

  function _loadMarginRequirementInTriplePips(
    uint64 marginFraction,
    Market memory market,
    IOraclePriceAdapter oraclePriceAdapter,
    address wallet,
    BalanceTracking.Storage storage balanceTracking
  ) private view returns (uint256) {
    return
      uint256(Math.abs(balanceTracking.loadBalanceFromMigrationSourceIfNeeded(wallet, market.baseAssetSymbol))) *
      oraclePriceAdapter.loadPriceForBaseAssetSymbol(market.baseAssetSymbol) *
      marginFraction;
  }

  function _loadQuoteQuantityAvailableForExitWithdrawal(
    address exitFundWallet,
    IOraclePriceAdapter oraclePriceAdapter,
    int64 outstandingWalletFunding,
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private view returns (int64 quoteQuantityAvailableForExitWithdrawal) {
    quoteQuantityAvailableForExitWithdrawal = balanceTracking.loadBalanceFromMigrationSourceIfNeeded(
      wallet,
      Constants.QUOTE_ASSET_SYMBOL
    );
    quoteQuantityAvailableForExitWithdrawal += outstandingWalletFunding;

    if (wallet == exitFundWallet) {
      // The EF wallet can withdraw any positive quote balance
      return Math.max(0, quoteQuantityAvailableForExitWithdrawal);
    }

    LoadQuoteQuantityForPositionExitArguments memory loadQuoteQuantityForPositionExitArguments;
    loadQuoteQuantityForPositionExitArguments.oraclePriceAdapter = oraclePriceAdapter;
    loadQuoteQuantityForPositionExitArguments.wallet = wallet;
    (
      loadQuoteQuantityForPositionExitArguments.exitAccountValue,
      loadQuoteQuantityForPositionExitArguments.totalAccountValueInDoublePips,
      loadQuoteQuantityForPositionExitArguments.totalMaintenanceMarginRequirementInTriplePips
    ) = loadTotalExitAccountValueAndAccountValueInDoublePipsAndMaintenanceMarginRequirementInTriplePips(
      oraclePriceAdapter,
      outstandingWalletFunding,
      wallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );

    string[] memory baseAssetSymbols = baseAssetSymbolsWithOpenPositionsByWallet[wallet];
    for (uint8 i = 0; i < baseAssetSymbols.length; i++) {
      loadQuoteQuantityForPositionExitArguments.baseAssetSymbol = baseAssetSymbols[i];
      quoteQuantityAvailableForExitWithdrawal += _loadQuoteQuantityForPositionExit(
        loadQuoteQuantityForPositionExitArguments,
        balanceTracking,
        marketOverridesByBaseAssetSymbolAndWallet,
        marketsByBaseAssetSymbol
      );
    }

    return quoteQuantityAvailableForExitWithdrawal;
  }

  function _loadQuoteQuantityForPositionExit(
    LoadQuoteQuantityForPositionExitArguments memory arguments,
    BalanceTracking.Storage storage balanceTracking,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private view returns (int64) {
    Balance memory balanceStruct = balanceTracking.loadBalanceStructFromMigrationSourceIfNeeded(
      arguments.wallet,
      arguments.baseAssetSymbol
    );
    Market memory market = marketsByBaseAssetSymbol[arguments.baseAssetSymbol];
    uint64 oraclePrice = arguments.oraclePriceAdapter.loadPriceForBaseAssetSymbol(market.baseAssetSymbol);

    uint64 quoteQuantityForPosition = arguments.exitAccountValue <= 0
      ? LiquidationValidations.calculateQuoteQuantityAtBankruptcyPrice(
        oraclePrice,
        market
          .loadMarketWithOverridesForWallet(arguments.wallet, marketOverridesByBaseAssetSymbolAndWallet)
          .overridableFields
          .maintenanceMarginFraction,
        balanceStruct.balance,
        arguments.totalAccountValueInDoublePips,
        arguments.totalMaintenanceMarginRequirementInTriplePips
      )
      : LiquidationValidations.calculateQuoteQuantityAtExitPrice(
        balanceStruct.costBasis,
        oraclePrice,
        balanceStruct.balance
      );

    // For short positions, the wallet gives quote to close the position so subtract. For long positions, the wallet
    // receives quote to close so add
    if (balanceStruct.balance < 0) {
      return -1 * Math.toInt64(quoteQuantityForPosition);
    }

    return Math.toInt64(quoteQuantityForPosition);
  }

  function _loadTotalAccountValue(
    IOraclePriceAdapter oraclePriceAdapter,
    int64 outstandingWalletFunding,
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private view returns (int64 totalAccountValue) {
    totalAccountValue =
      balanceTracking.loadBalanceFromMigrationSourceIfNeeded(wallet, Constants.QUOTE_ASSET_SYMBOL) +
      outstandingWalletFunding;

    string[] memory baseAssetSymbols = baseAssetSymbolsWithOpenPositionsByWallet[wallet];
    for (uint8 i = 0; i < baseAssetSymbols.length; i++) {
      Market memory market = marketsByBaseAssetSymbol[baseAssetSymbols[i]];

      totalAccountValue += Math.multiplyPipsByFraction(
        balanceTracking.loadBalanceFromMigrationSourceIfNeeded(wallet, market.baseAssetSymbol),
        Math.toInt64(oraclePriceAdapter.loadPriceForBaseAssetSymbol(market.baseAssetSymbol)),
        Math.toInt64(Constants.PIP_PRICE_MULTIPLIER)
      );
    }
  }

  function _loadTotalAccountValueInDoublePips(
    IOraclePriceAdapter oraclePriceAdapter,
    int64 outstandingWalletFunding,
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private view returns (int256 totalAccountValueInDoublePips) {
    totalAccountValueInDoublePips =
      int256(
        balanceTracking.loadBalanceFromMigrationSourceIfNeeded(wallet, Constants.QUOTE_ASSET_SYMBOL) +
          outstandingWalletFunding
      ) *
      Math.toInt64(Constants.PIP_PRICE_MULTIPLIER);

    string[] memory baseAssetSymbols = baseAssetSymbolsWithOpenPositionsByWallet[wallet];
    for (uint8 i = 0; i < baseAssetSymbols.length; i++) {
      Market memory market = marketsByBaseAssetSymbol[baseAssetSymbols[i]];

      totalAccountValueInDoublePips +=
        int256(balanceTracking.loadBalanceFromMigrationSourceIfNeeded(wallet, market.baseAssetSymbol)) *
        Math.toInt64(oraclePriceAdapter.loadPriceForBaseAssetSymbol(market.baseAssetSymbol));
    }
  }

  function _loadTotalExitAccountValue(
    IOraclePriceAdapter oraclePriceAdapter,
    int64 outstandingWalletFunding,
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private view returns (int64 exitAccountValue) {
    exitAccountValue =
      balanceTracking.loadBalanceFromMigrationSourceIfNeeded(wallet, Constants.QUOTE_ASSET_SYMBOL) +
      outstandingWalletFunding;

    Balance memory balanceStruct;
    Market memory market;
    uint64 quoteQuantityForPosition;

    string[] memory baseAssetSymbols = baseAssetSymbolsWithOpenPositionsByWallet[wallet];
    for (uint8 i = 0; i < baseAssetSymbols.length; i++) {
      balanceStruct = balanceTracking.loadBalanceStructFromMigrationSourceIfNeeded(wallet, baseAssetSymbols[i]);
      market = marketsByBaseAssetSymbol[baseAssetSymbols[i]];

      quoteQuantityForPosition = LiquidationValidations.calculateQuoteQuantityAtExitPrice(
        balanceStruct.costBasis,
        oraclePriceAdapter.loadPriceForBaseAssetSymbol(market.baseAssetSymbol),
        balanceStruct.balance
      );

      if (balanceStruct.balance < 0) {
        // Short positions have negative value
        exitAccountValue -= Math.toInt64(quoteQuantityForPosition);
      } else {
        // Long positions have positive value
        exitAccountValue += Math.toInt64(quoteQuantityForPosition);
      }
    }
  }

  function _loadTotalInitialMarginRequirement(
    IOraclePriceAdapter oraclePriceAdapter,
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private view returns (uint64 initialMarginRequirement) {
    string[] memory baseAssetSymbols = baseAssetSymbolsWithOpenPositionsByWallet[wallet];
    for (uint8 i = 0; i < baseAssetSymbols.length; i++) {
      Market memory market = marketsByBaseAssetSymbol[baseAssetSymbols[i]];

      initialMarginRequirement += _loadMarginRequirement(
        market.loadInitialMarginFractionForWallet(
          balanceTracking.loadBalanceFromMigrationSourceIfNeeded(wallet, market.baseAssetSymbol),
          wallet,
          marketOverridesByBaseAssetSymbolAndWallet
        ),
        market,
        oraclePriceAdapter,
        wallet,
        balanceTracking
      );
    }
  }

  function _loadTotalMaintenanceMarginRequirement(
    IOraclePriceAdapter oraclePriceAdapter,
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private view returns (uint64 maintenanceMarginRequirement) {
    string[] memory baseAssetSymbols = baseAssetSymbolsWithOpenPositionsByWallet[wallet];
    for (uint8 i = 0; i < baseAssetSymbols.length; i++) {
      Market memory market = marketsByBaseAssetSymbol[baseAssetSymbols[i]];

      maintenanceMarginRequirement += _loadMarginRequirement(
        market
          .loadMarketWithOverridesForWallet(wallet, marketOverridesByBaseAssetSymbolAndWallet)
          .overridableFields
          .maintenanceMarginFraction,
        market,
        oraclePriceAdapter,
        wallet,
        balanceTracking
      );
    }
  }

  function _loadTotalMaintenanceMarginRequirementInTriplePips(
    IOraclePriceAdapter oraclePriceAdapter,
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private view returns (uint256 maintenanceMarginRequirementInTriplePip) {
    string[] memory baseAssetSymbols = baseAssetSymbolsWithOpenPositionsByWallet[wallet];
    for (uint8 i = 0; i < baseAssetSymbols.length; i++) {
      Market memory market = marketsByBaseAssetSymbol[baseAssetSymbols[i]];

      maintenanceMarginRequirementInTriplePip += _loadMarginRequirementInTriplePips(
        market
          .loadMarketWithOverridesForWallet(wallet, marketOverridesByBaseAssetSymbolAndWallet)
          .overridableFields
          .maintenanceMarginFraction,
        market,
        oraclePriceAdapter,
        wallet,
        balanceTracking
      );
    }
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { BalanceTracking } from "./BalanceTracking.sol";
import { Constants } from "./Constants.sol";
import { ExchangeEvents } from "./ExchangeEvents.sol";
import { Funding } from "./Funding.sol";
import { IndexPriceMargin } from "./IndexPriceMargin.sol";
import { MarketHelper } from "./MarketHelper.sol";
import { Math } from "./Math.sol";
import { SortedStringSet } from "./SortedStringSet.sol";
import { Validations } from "./Validations.sol";
import {
  FundingMultiplierQuartet,
  Market,
  MarketOverrides,
  PositionBelowMinimumLiquidationArguments
} from "./Structs.sol";

library PositionBelowMinimumLiquidation {
  using BalanceTracking for BalanceTracking.Storage;
  using MarketHelper for Market;
  using SortedStringSet for string[];

  // solhint-disable-next-line func-name-mixedcase
  function liquidate_delegatecall(
    PositionBelowMinimumLiquidationArguments memory arguments,
    address exitFundWallet,
    address insuranceFundWallet,
    uint64 positionBelowMinimumLiquidationPriceToleranceMultiplier,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) public {
    require(arguments.liquidatingWallet != exitFundWallet, "Cannot liquidate EF");
    require(arguments.liquidatingWallet != insuranceFundWallet, "Cannot liquidate IF");

    Funding.applyOutstandingWalletFunding(
      arguments.liquidatingWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );
    Funding.applyOutstandingWalletFunding(
      insuranceFundWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );

    uint64 liquidationBaseQuantity = _validateAndLiquidatePositionBelowMinimum(
      arguments,
      exitFundWallet,
      insuranceFundWallet,
      positionBelowMinimumLiquidationPriceToleranceMultiplier,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );

    // Validate that the Insurance Fund still meets its initial margin requirements
    IndexPriceMargin.validateInitialMarginRequirement(
      insuranceFundWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );

    _emitLiquidatedPositionBelowMinimum(arguments, liquidationBaseQuantity);
  }

  function _emitLiquidatedPositionBelowMinimum(
    PositionBelowMinimumLiquidationArguments memory arguments,
    uint64 liquidationBaseQuantity
  ) private {
    emit ExchangeEvents.LiquidatedPositionBelowMinimum(
      arguments.baseAssetSymbol,
      arguments.liquidatingWallet,
      liquidationBaseQuantity,
      arguments.liquidationQuoteQuantity
    );
  }

  function _updateBalances(
    PositionBelowMinimumLiquidationArguments memory arguments,
    address exitFundWallet,
    address insuranceFundWallet,
    Market memory market,
    int64 positionSize,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet
  ) private {
    balanceTracking.updatePositionsForLiquidation(
      insuranceFundWallet,
      exitFundWallet,
      arguments.liquidatingWallet,
      market,
      positionSize,
      arguments.liquidationQuoteQuantity,
      baseAssetSymbolsWithOpenPositionsByWallet,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet
    );
  }

  function _validateAndLiquidatePositionBelowMinimum(
    PositionBelowMinimumLiquidationArguments memory arguments,
    address exitFundWallet,
    address insuranceFundWallet,
    uint64 positionBelowMinimumLiquidationPriceToleranceMultiplier,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private returns (uint64 liquidationBaseQuantity) {
    Market memory market = Validations.loadAndValidateActiveMarket(
      arguments.baseAssetSymbol,
      arguments.liquidatingWallet,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketsByBaseAssetSymbol
    );

    // Validate that position is under dust threshold
    int64 positionSize = balanceTracking.loadBalanceAndMigrateIfNeeded(
      arguments.liquidatingWallet,
      arguments.baseAssetSymbol
    );
    require(
      Math.abs(positionSize) <
        market
          .loadMarketWithOverridesForWallet(arguments.liquidatingWallet, marketOverridesByBaseAssetSymbolAndWallet)
          .overridableFields
          .minimumPositionSize,
      "Position size above minimum"
    );
    liquidationBaseQuantity = Math.abs(positionSize);

    // Validate quote quantity
    _validateQuoteQuantity(
      positionBelowMinimumLiquidationPriceToleranceMultiplier,
      arguments.liquidationQuoteQuantity,
      market.lastIndexPrice,
      positionSize
    );

    // Need to wrap `balanceTracking.updatePositionForLiquidation` with helper to avoid stack too deep error
    _updateBalances(
      arguments,
      exitFundWallet,
      insuranceFundWallet,
      market,
      positionSize,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet
    );
  }

  function _validateQuoteQuantity(
    uint64 positionBelowMinimumLiquidationPriceTolerance,
    uint64 liquidationQuoteQuantity,
    uint64 indexPrice,
    int64 positionSize
  ) private pure {
    uint64 expectedLiquidationQuoteQuantity = Math.multiplyPipsByFraction(
      Math.abs(positionSize),
      indexPrice,
      Constants.PIP_PRICE_MULTIPLIER
    );

    // Skip validation for positions with very low quote values to avoid false positives due to rounding error
    if (
      expectedLiquidationQuoteQuantity < Constants.MINIMUM_QUOTE_QUANTITY_VALIDATION_THRESHOLD &&
      liquidationQuoteQuantity < Constants.MINIMUM_QUOTE_QUANTITY_VALIDATION_THRESHOLD
    ) {
      return;
    }

    uint64 tolerance = Math.multiplyPipsByFraction(
      positionBelowMinimumLiquidationPriceTolerance,
      expectedLiquidationQuoteQuantity,
      Constants.PIP_PRICE_MULTIPLIER
    );

    require(
      expectedLiquidationQuoteQuantity - tolerance <= liquidationQuoteQuantity &&
        expectedLiquidationQuoteQuantity + tolerance >= liquidationQuoteQuantity,
      "Invalid quote quantity"
    );
  }
}

File 38 of 54 : PositionInDeactivatedMarketLiquidation.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { BalanceTracking } from "./BalanceTracking.sol";
import { ExchangeEvents } from "./ExchangeEvents.sol";
import { Funding } from "./Funding.sol";
import { LiquidationValidations } from "./LiquidationValidations.sol";
import { Math } from "./Math.sol";
import { SortedStringSet } from "./SortedStringSet.sol";
import { Validations } from "./Validations.sol";
import { FundingMultiplierQuartet, Market, PositionInDeactivatedMarketLiquidationArguments } from "./Structs.sol";

library PositionInDeactivatedMarketLiquidation {
  using BalanceTracking for BalanceTracking.Storage;
  using SortedStringSet for string[];

  // solhint-disable-next-line func-name-mixedcase
  function liquidate_delegatecall(
    PositionInDeactivatedMarketLiquidationArguments memory arguments,
    address feeWallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) public {
    Funding.applyOutstandingWalletFunding(
      arguments.liquidatingWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );

    Market memory market = Validations.loadAndValidateInactiveMarket(
      arguments.baseAssetSymbol,
      arguments.liquidatingWallet,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketsByBaseAssetSymbol
    );

    // Validate quote quantity
    int64 positionSize = balanceTracking.loadBalanceFromMigrationSourceIfNeeded(
      arguments.liquidatingWallet,
      market.baseAssetSymbol
    );
    LiquidationValidations.validateDeactivatedMarketLiquidationQuoteQuantity(
      market.indexPriceAtDeactivation,
      positionSize,
      arguments.liquidationQuoteQuantity
    );

    require(Validations.isFeeQuantityValid(arguments.feeQuantity, arguments.liquidationQuoteQuantity), "Excessive fee");

    balanceTracking.updatePositionForDeactivatedMarketLiquidation(
      market.baseAssetSymbol,
      arguments.feeQuantity,
      feeWallet,
      arguments.liquidatingWallet,
      arguments.liquidationQuoteQuantity,
      baseAssetSymbolsWithOpenPositionsByWallet
    );

    emit ExchangeEvents.LiquidatedPositionInDeactivatedMarket(
      arguments.baseAssetSymbol,
      arguments.liquidatingWallet,
      Math.abs(positionSize),
      arguments.liquidationQuoteQuantity
    );
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

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

library SortedStringSet {
  uint256 public constant NOT_FOUND = type(uint256).max;

  function indexOf(string[] memory array, string memory element) internal pure returns (uint256) {
    for (uint256 i = 0; i < array.length; i++) {
      if (String.isEqual(array[i], element)) {
        return i;
      }
    }

    return NOT_FOUND;
  }

  function insertSorted(string[] memory array, string memory element) internal pure returns (string[] memory result) {
    result = new string[](array.length + 1);

    bool indexFound = false;
    for (uint256 i = 0; i < result.length; i++) {
      if (!indexFound) {
        // Do not allow duplicates
        if (i < result.length - 1 && String.isEqual(array[i], element)) {
          return array;
        }
        if (i == result.length - 1 || _isLessThan(element, array[i])) {
          result[i] = element;
          indexFound = true;
        } else {
          result[i] = array[i];
        }
      } else {
        result[i] = array[i - 1];
      }
    }
  }

  function merge(string[] memory array1, string[] memory array2) internal pure returns (string[] memory result) {
    result = array1;

    for (uint256 i = 0; i < array2.length; i++) {
      result = insertSorted(result, array2[i]);
    }
  }

  function remove(string[] memory array, string memory element) internal pure returns (string[] memory result) {
    result = new string[](array.length - 1);

    bool indexFound = false;
    for (uint256 i = 0; i < array.length - 1; i++) {
      if (String.isEqual(array[i], element)) {
        indexFound = true;
      }

      if (indexFound) {
        result[i] = array[i + 1];
      } else {
        result[i] = array[i];
      }
    }

    require(indexFound || String.isEqual(array[array.length - 1], element), "Element to remove not found");
  }

  function _isLessThan(string memory a, string memory b) private pure returns (bool) {
    (bytes32 aHash, bytes32 bHash) = (keccak256(abi.encodePacked(a)), keccak256(abi.encodePacked(b)));

    return aHash < bHash;
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

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

library String {
  /**
   * @dev Returns true if the two strings are equal, false otherwise
   *
   * See https://docs.soliditylang.org/en/latest/types.html#bytes-and-string-as-arrays
   */
  function isEqual(string memory a, string memory b) internal pure returns (bool) {
    return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b));
  }

  /**
   * @dev Returns true if self starts with the provided prefix, false otherwise
   *
   * See https://docs.soliditylang.org/en/latest/types.html#bytes-and-string-as-arrays
   */
  function startsWith(string memory self, string memory prefix) internal pure returns (bool) {
    uint256 prefixLength = bytes(prefix).length;
    if (bytes(self).length < prefixLength) {
      return false;
    }

    bytes memory selfPrefix = new bytes(prefixLength);
    for (uint256 i = 0; i < prefixLength; i++) {
      selfPrefix[i] = bytes(self)[i];
    }

    return isEqual(string(selfPrefix), prefix);
  }

  /**
   * @dev Converts an integer pip quantity back into the fixed-precision decimal pip string
   * originally signed by the wallet. For example, 1234567890 becomes '12.34567890'
   */
  function pipsToDecimalString(uint64 pips) internal pure returns (string memory) {
    if (pips == 0) {
      return Constants.EMPTY_DECIMAL_STRING;
    }

    // Inspired by https://github.com/provable-things/ethereum-api/blob/831f4123816f7a3e57ebea171a3cdcf3b528e475/oraclizeAPI_0.5.sol#L1045-L1062
    uint256 copy = pips;
    uint256 length;
    while (copy != 0) {
      length++;
      copy /= 10;
    }
    if (length < 9) {
      length = 9; // a zero before the decimal point plus 8 decimals
    }
    length++; // for the decimal point

    bytes memory decimal = new bytes(length);
    for (uint256 i = length; i > 0; i--) {
      if (length - i == 8) {
        decimal[i - 1] = bytes1(uint8(46)); // period
      } else {
        decimal[i - 1] = bytes1(uint8(48 + (pips % 10)));
        pips /= 10;
      }
    }
    return string(decimal);
  }
}

File 41 of 54 : Structs.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import {
  ManagedAccountWithdrawalType,
  OrderSelfTradePrevention,
  OrderSide,
  OrderTimeInForce,
  OrderTriggerType,
  OrderType,
  WalletExitAcquisitionDeleveragePriceStrategy
} from "./Enums.sol";
import { IManagedAccountProvider } from "./Interfaces.sol";

// This file contains definitions for externally-facing structs used as argument or return types for Exchange functions

/**
 * @notice Argument type for `Exchange.deleverageInMaintenanceAcquisition` and `Exchange.deleverageExitAcquisition`
 */
struct AcquisitionDeleverageArguments {
  string baseAssetSymbol;
  address counterpartyWallet;
  address liquidatingWallet;
  // Base quantity to decrease position being liquidated
  uint64 liquidationBaseQuantity;
  // Quote quantity for the position being liquidated
  uint64 liquidationQuoteQuantity;
}

/**
 * @notice Struct for tracking wallet balances and funding updates
 */
struct Balance {
  bool isMigrated;
  int64 balance;
  // The cost basis and last funding update timestamp are only relevant for base asset positions
  int64 costBasis;
  uint64 lastUpdateTimestampInMs;
  // Only relevant for quote asset positions, will be zero address if wallet is not vault manager
  IManagedAccountProvider managedAccountProvider;
}

/**
 * @notice Argument type for `Exchange.deleverageInsuranceFundClosure` and `Exchange.deleverageExitFundClosure`
 */
struct ClosureDeleverageArguments {
  string baseAssetSymbol;
  address counterpartyWallet;
  // IF or EF depending on deleverageType
  address liquidatingWallet;
  // Base quantity to decrease position being liquidated
  uint64 liquidationBaseQuantity;
  // Quote quantity for the position being liquidated
  uint64 liquidationQuoteQuantity;
}

/**
 * @notice Field in `Order` struct for optionally authorizing a delegated key signing wallet
 */
struct DelegatedKeyAuthorization {
  // UUIDv1 unique to wallet
  uint128 nonce;
  // Public component of ECDSA signing key pair
  address delegatedPublicKey;
  // ECDSA signature of hash by issuing custody wallet private key
  bytes signature;
}

/**
 * @notice Struct for storing funding multipliers
 */
struct FundingMultiplierQuartet {
  int64 fundingMultiplier0;
  int64 fundingMultiplier1;
  int64 fundingMultiplier2;
  int64 fundingMultiplier3;
}

/**
 * @notice Index price data signed by index wallet
 */
struct IndexPrice {
  string baseAssetSymbol;
  // Milliseconds since epoch
  uint64 timestampInMs;
  // Price of base asset in quote terms
  uint64 price;
}

struct IndexPricePayload {
  // Address of IIndexPriceAdapter that will validate and decode payload
  address indexPriceAdapter;
  // Index price payload encoded in adapter-specific format
  bytes payload;
}

/**
 * @notice Argument type for `Exchange.addMarket` and `Exchange.setMarketOverrides`
 */
struct Market {
  // Flag to distinguish from empty struct
  bool exists;
  // Flag must be asserted to allow any actions other than deactivation liquidation
  bool isActive;
  // No need to specify quote asset, it is always `Constants.QUOTE_ASSET_SYMBOL`
  string baseAssetSymbol;
  // Set when deactivating a market to determine price for all position liquidations in that market
  uint64 indexPriceAtDeactivation;
  // The latest index price published for this market
  uint64 lastIndexPrice;
  // The timestamp of the latest index price published for this market
  uint64 lastIndexPriceTimestampInMs;
  // Fields that can be overriden per wallet
  OverridableMarketFields overridableFields;
}

/**
 * @notice Struct to track market overrides per wallet
 */
struct MarketOverrides {
  // Flag to distinguish from empty struct
  bool exists;
  // Market fields that can be overriden per wallet
  OverridableMarketFields overridableFields;
}

/**
 * @notice Struct capturing wallet order nonce invalidations created via `invalidateNonce`
 */
struct NonceInvalidation {
  uint64 timestampInMs;
  uint256 effectiveBlockTimestamp;
}

/**
 * @notice Argument type for `Exchange.liquidatePositionBelowMinimum`
 */
struct PositionBelowMinimumLiquidationArguments {
  string baseAssetSymbol;
  address liquidatingWallet;
  // Quote quantity for the position being liquidated
  uint64 liquidationQuoteQuantity;
}

/**
 * @notice Argument type for `Exchange.setMarketOverrides`
 */
struct OverridableMarketFields {
  // The margin fraction needed to open a position
  uint64 initialMarginFraction;
  // The margin fraction required to prevent liquidation
  uint64 maintenanceMarginFraction;
  // The increase of initialMarginFraction for each incrementalPositionSize above baselinePositionSize
  uint64 incrementalInitialMarginFraction;
  // The max position size in base token before increasing initialMarginFraction
  uint64 baselinePositionSize;
  // The step size (in base token) for increasing the initialMarginFraction by (incrementalInitialMarginFraction
  // per step)
  uint64 incrementalPositionSize;
  // The max position size in base token
  uint64 maximumPositionSize;
  // The min position size in base token
  uint64 minimumPositionSize;
}

/**
 * @notice Argument type for `Exchange.liquidatePositionInDeactivatedMarket`
 */
struct PositionInDeactivatedMarketLiquidationArguments {
  string baseAssetSymbol;
  uint64 feeQuantity;
  address liquidatingWallet;
  // Quote quantity for the position being liquidated
  uint64 liquidationQuoteQuantity;
}

/**
 * @notice Argument type for `Exchange.executeTrade` and `Hashing.getOrderHash`
 */
struct Order {
  // UUIDv1 unique to wallet
  uint128 nonce;
  // Custody wallet address that placed order and (if not using delegate wallet) signed hash
  address wallet;
  // Type of order
  OrderType orderType;
  // Order side wallet is on
  OrderSide side;
  // Order quantity in base asset terms
  uint64 quantity;
  // For limit orders, price in quote terms
  uint64 limitPrice;
  // For stop orders, stop loss or take profit price in quote terms
  uint64 triggerPrice;
  // Type of trigger condition
  OrderTriggerType triggerType;
  // Percentage of price movement in opposite direction before triggering trailing stop
  uint64 callbackRate;
  // Public ID of a separate order that must be filled before this stop order becomes active
  uint128 conditionalOrderId;
  // If true, order execution must move wallet position size towards zero
  bool isReduceOnly;
  // TIF option specified by wallet for order
  OrderTimeInForce timeInForce;
  // STP behavior specified by wallet for order
  OrderSelfTradePrevention selfTradePrevention;
  // Asserted when order can only reduce IF positions
  bool isLiquidationAcquisitionOnly;
  // Asserted when signed by delegated key instead of custody wallet
  bool isSignedByDelegatedKey;
  // If non-zero, an authorization for a delegated key signer authorized by the custody wallet
  DelegatedKeyAuthorization delegatedKeyAuthorization;
  // Optional custom client order ID
  string clientOrderId;
  // The ECDSA signature of the order hash as produced by `Hashing.getOrderHash`
  bytes walletSignature;
}

/**
 * @notice Argument type for `Exchange.executeTrade` specifying execution parameters for matching orders
 */
struct Trade {
  // Base asset symbol
  string baseAssetSymbol;
  // Amount of base asset executed
  uint64 baseQuantity;
  // Amount of quote asset executed
  uint64 quoteQuantity;
  // Fee paid by (or, if negative, rebated to) liquidity maker in quote (collateral) asset
  int64 makerFeeQuantity;
  // Fee paid by liquidity taker in quote (collateral) asset
  uint64 takerFeeQuantity;
  // Execution price of trade in quote terms
  uint64 price;
  // Which side of the order (buy or sell) the liquidity maker was on
  OrderSide makerSide;
}

struct Transfer {
  // UUIDv1 unique to wallet
  uint128 nonce;
  // Address of wallet giving funds
  address sourceWallet;
  // Address of wallet receiving funds
  address destinationWallet;
  // Transfer quantity
  uint64 grossQuantity;
  // Gas fee deducted from transfer quantity to cover dispatcher tx costs
  uint64 gasFee;
  // The ECDSA signature of the transfer hash as produced by `Hashing.getTransferHash`
  bytes walletSignature;
}

struct WalletExit {
  bool exists;
  // We can safely downcast here as 2^64 epoch seconds would take billions of years to exceed
  uint64 effectiveBlockTimestamp;
  WalletExitAcquisitionDeleveragePriceStrategy deleveragePriceStrategy;
}

/**
 * @notice Argument type for `Exchange.liquidateWalletInMaintenance`,
 * `Exchange.liquidateWalletInMaintenanceDuringSystemRecovery`, and `Exchange.liquidateWalletExit`
 */
struct WalletLiquidationArguments {
  // Insurance Fund or Exit Fund
  address counterpartyWallet;
  address liquidatingWallet;
  // Quote quantity for each liquidated positions
  uint64[] liquidationQuoteQuantities;
}

/**
 * @notice Argument type for `Exchange.withdraw` and `Hashing.getWithdrawalHash`
 */
struct Withdrawal {
  // UUIDv1 unique to wallet
  uint128 nonce;
  // Address of wallet to which funds will be returned
  address wallet;
  // Exact withdrawal quantity
  uint64 grossQuantity;
  // Maximum gas fee authorized by wallet
  uint64 maximumGasFee;
  // Address of target bridge adapter contract. If zero, quote goes to wallet on local chain
  address bridgeAdapter;
  // If bridge adapter contract is non-zero, ABI-encoded parameters to supply specific bridge protocol
  bytes bridgeAdapterPayload;
  // Actual gas fee deducted from withdrawn quantity to cover dispatcher tx costs
  uint64 gasFee;
  // The ECDSA signature of the withdrawal hash as produced by `Hashing.getWithdrawalHash`
  bytes walletSignature;
}

struct WithdrawalFromManagedAccount {
  ManagedAccountWithdrawalType withdrawalType;
  WithdrawalFromManagedAccountByQuantity withdrawalByQuantity;
  WithdrawalFromManagedAccountByShares withdrawalByShares;
}

struct WithdrawalFromManagedAccountByQuantity {
  // UUIDv1 unique to wallet
  uint128 nonce;
  // Address of wallet associated with Managed Account from which funds will be withdrawn
  address managerWallet;
  // Address of wallet to which funds will be returned
  address depositorWallet;
  // Exact withdrawal quantity
  uint64 grossQuantity;
  // Maximum number of shares to burn to create quote asset quantity
  uint64 maxShares;
  // Maximum gas fee authorized by wallet
  uint64 maximumGasFee;
  // Address of Managed Account Provider contract
  IManagedAccountProvider managedAccountProvider;
  // ABI-encoded parameters to supply specific Managed Account Provider contract
  bytes managedAccountProviderPayload;
  // Address of target bridge adapter contract. If zero, quote goes to wallet on local chain
  address bridgeAdapter;
  // If bridge adapter contract is non-zero, ABI-encoded parameters to supply specific bridge protocol
  bytes bridgeAdapterPayload;
  // Actual gas fee deducted from withdrawn quantity to cover dispatcher tx costs
  uint64 gasFee;
  // The ECDSA signature of the withdrawal hash as produced by `Hashing.getWithdrawalHash`
  bytes walletSignature;
}

struct WithdrawalFromManagedAccountByShares {
  // UUIDv1 unique to wallet
  uint128 nonce;
  // Address of wallet associated with Managed Account from which funds will be withdrawn
  address managerWallet;
  // Address of wallet to which funds will be returned
  address depositorWallet;
  // Exact number of shares to burn
  uint64 shares;
  // Minimum acceptable quote asset quantity created by burning shares
  uint64 minimumQuantity;
  // Maximum gas fee authorized by wallet
  uint64 maximumGasFee;
  // Address of Managed Account Provider contract
  IManagedAccountProvider managedAccountProvider;
  // ABI-encoded parameters to supply specific Managed Account Provider contract
  bytes managedAccountProviderPayload;
  // Address of target bridge adapter contract. If zero, quote goes to wallet on local chain
  address bridgeAdapter;
  // If bridge adapter contract is non-zero, ABI-encoded parameters to supply specific bridge protocol
  bytes bridgeAdapterPayload;
  // Actual gas fee deducted from withdrawn quantity to cover dispatcher tx costs
  uint64 gasFee;
  // Actual withdrawn quantity of quote asset
  uint64 grossQuantity;
  // The ECDSA signature of the withdrawal hash as produced by `Hashing.getWithdrawalHash`
  bytes walletSignature;
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

library Time {
  uint64 private constant _SECONDS_IN_ONE_DAY = (24 * 60 * 60); // 24 hours/day * 60 min/hour * 60 seconds/min
  uint64 private constant _MS_IN_ONE_SECOND = 1000;

  function getOneDayFromNowInMs() internal view returns (uint64) {
    return (uint64(block.timestamp) + _SECONDS_IN_ONE_DAY) * _MS_IN_ONE_SECOND;
  }

  // Epoch time starts at midnight UTC, which is the same time of day funding payments should start from
  function getMidnightTodayInMs() internal view returns (uint64) {
    uint64 blockTimestamp = uint64(block.timestamp);
    uint64 midnightTodayInSeconds = blockTimestamp - (blockTimestamp % _SECONDS_IN_ONE_DAY);

    return midnightTodayInSeconds * _MS_IN_ONE_SECOND;
  }

  function getMsSinceMidnight() internal view returns (uint64) {
    uint64 secondsSinceMidnight = uint64(block.timestamp) % _SECONDS_IN_ONE_DAY;

    return secondsSinceMidnight * _MS_IN_ONE_SECOND;
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { Constants } from "./Constants.sol";
import { Hashing } from "./Hashing.sol";
import { Math } from "./Math.sol";
import { NonceInvalidations } from "./NonceInvalidations.sol";
import { UUID } from "./UUID.sol";
import { Validations } from "./Validations.sol";
import { Market, Order, Trade, NonceInvalidation } from "./Structs.sol";
import { OrderSide, OrderTimeInForce, OrderTriggerType, OrderType } from "./Enums.sol";

library TradeValidations {
  using NonceInvalidations for mapping(address => NonceInvalidation[]);

  function validateTrade(
    Trade memory trade,
    Order memory buy,
    Order memory sell,
    uint64 delegateKeyExpirationPeriodInMs,
    bytes32 domainSeparator,
    address exitFundWallet,
    address insuranceFundWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol,
    mapping(address => NonceInvalidation[]) storage nonceInvalidationsByWallet
  ) internal view returns (bytes32 buyHash, bytes32 sellHash, Market memory market) {
    require(buy.wallet != sell.wallet, "Self-trading not allowed");

    // Order book trade validations
    market = _validateAssetPair(trade, marketsByBaseAssetSymbol);
    _validateExecutionConditions(buy, sell, trade, exitFundWallet, insuranceFundWallet);
    _validateNonces(buy, sell, delegateKeyExpirationPeriodInMs, nonceInvalidationsByWallet);
    (buyHash, sellHash) = _validateSignatures(domainSeparator, buy, sell, trade);
    _validateFees(trade);
  }

  function _calculateImpliedQuoteQuantity(uint64 baseQuantity, uint64 limitPrice) private pure returns (uint64) {
    return Math.multiplyPipsByFraction(baseQuantity, limitPrice, Constants.PIP_PRICE_MULTIPLIER);
  }

  function _isLimitOrderType(OrderType orderType) private pure returns (bool) {
    return
      orderType == OrderType.Limit || orderType == OrderType.StopLossLimit || orderType == OrderType.TakeProfitLimit;
  }

  function _validateAssetPair(
    Trade memory trade,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private view returns (Market memory market) {
    // The add market path already validates that the base asset symbol must be different from the quote
    market = marketsByBaseAssetSymbol[trade.baseAssetSymbol];
    require(market.exists && market.isActive, "No active market found");
  }

  function _validateFees(Trade memory trade) private pure {
    if (trade.makerFeeQuantity < 0) {
      require(Math.abs(trade.makerFeeQuantity) <= trade.takerFeeQuantity, "Excessive maker rebate");
    } else {
      require(
        Validations.isFeeQuantityValid(uint64(trade.makerFeeQuantity), trade.quoteQuantity),
        "Excessive maker fee"
      );
    }

    require(Validations.isFeeQuantityValid(trade.takerFeeQuantity, trade.quoteQuantity), "Excessive taker fee");
  }

  function _validateExecutionConditions(
    Order memory buy,
    Order memory sell,
    Trade memory trade,
    address exitFundWallet,
    address insuranceFundWallet
  ) private pure {
    require(trade.baseQuantity > 0, "Base quantity must be greater than zero");
    require(trade.quoteQuantity > 0, "Quote quantity must be greater than zero");

    _validateExecutionConditionsForOrder(buy, trade, true, exitFundWallet, insuranceFundWallet);
    _validateExecutionConditionsForOrder(sell, trade, false, exitFundWallet, insuranceFundWallet);

    bool isLiquidationAcquisitionTrade = buy.isLiquidationAcquisitionOnly || sell.isLiquidationAcquisitionOnly;
    if (isLiquidationAcquisitionTrade) {
      require(
        buy.isLiquidationAcquisitionOnly && sell.isLiquidationAcquisitionOnly,
        "Both orders must be liquidation acquisition"
      );
      require(sell.wallet == insuranceFundWallet || buy.wallet == insuranceFundWallet, "One order wallet must be IF");
    }
  }

  function _validateExecutionConditionsForOrder(
    Order memory order,
    Trade memory trade,
    bool isBuy,
    address exitFundWallet,
    address insuranceFundWallet
  ) private pure {
    _validateLimitPrice(order, trade, isBuy);
    _validateTimeInForce(order, trade, isBuy);
    _validateTriggerFields(order);

    require(order.wallet != exitFundWallet, "EF cannot trade");

    if (order.wallet == insuranceFundWallet) {
      require(order.isReduceOnly && order.isSignedByDelegatedKey, "IF order must be reduce only and signed by DK");
    }
  }

  function _validateLimitPrice(Order memory order, Trade memory trade, bool isBuy) private pure {
    if (_isLimitOrderType(order.orderType)) {
      require(order.limitPrice > 0, "Invalid limit price");

      uint64 impliedQuoteQuantity = _calculateImpliedQuoteQuantity(trade.baseQuantity, order.limitPrice);
      require(
        isBuy ? impliedQuoteQuantity >= trade.quoteQuantity : impliedQuoteQuantity <= trade.quoteQuantity,
        "Order limit price exceeded"
      );
    } else {
      require(order.limitPrice == 0, "Invalid limit price");
    }
  }

  function _validateNonces(
    Order memory buy,
    Order memory sell,
    uint64 delegateKeyExpirationPeriodInMs,
    mapping(address => NonceInvalidation[]) storage nonceInvalidationsByWallet
  ) private view {
    _validateNonceForOrder(buy, delegateKeyExpirationPeriodInMs, nonceInvalidationsByWallet);
    _validateNonceForOrder(sell, delegateKeyExpirationPeriodInMs, nonceInvalidationsByWallet);
  }

  function _validateNonceForOrder(
    Order memory order,
    uint64 delegateKeyExpirationPeriodInMs,
    mapping(address => NonceInvalidation[]) storage nonceInvalidationsByWallet
  ) private view {
    uint64 orderTimestampInMs = UUID.getTimestampInMsFromUuidV1(order.nonce);

    uint64 lastInvalidatedTimestamp = nonceInvalidationsByWallet.loadLastInvalidatedTimestamp(order.wallet);
    require(
      orderTimestampInMs > lastInvalidatedTimestamp,
      order.side == OrderSide.Buy ? "Buy order nonce timestamp invalidated" : "Sell order nonce timestamp invalidated"
    );

    if (order.isSignedByDelegatedKey) {
      uint64 issuedTimestampInMs = UUID.getTimestampInMsFromUuidV1(order.delegatedKeyAuthorization.nonce);
      require(
        issuedTimestampInMs > lastInvalidatedTimestamp,
        order.side == OrderSide.Buy ? "Buy order delegated key invalidated" : "Sell order delegated key invalidated"
      );
      require(
        issuedTimestampInMs + delegateKeyExpirationPeriodInMs > orderTimestampInMs,
        order.side == OrderSide.Buy ? "Buy order delegated key expired" : "Sell order delegated key expired"
      );
      require(
        orderTimestampInMs > issuedTimestampInMs,
        order.side == OrderSide.Buy ? "Buy order predates delegated key" : "Sell order predates delegated key"
      );
    }
  }

  function _validateSignatures(
    bytes32 domainSeparator,
    Order memory buy,
    Order memory sell,
    Trade memory trade
  ) private pure returns (bytes32, bytes32) {
    bytes32 buyOrderHash = _validateSignatureForOrder(trade.baseAssetSymbol, domainSeparator, buy, true);
    bytes32 sellOrderHash = _validateSignatureForOrder(trade.baseAssetSymbol, domainSeparator, sell, false);

    return (buyOrderHash, sellOrderHash);
  }

  function _validateSignatureForOrder(
    string memory baseAssetSymbol,
    bytes32 domainSeparator,
    Order memory order,
    bool isBuy
  ) private pure returns (bytes32 orderHash) {
    require(order.side == (isBuy ? OrderSide.Buy : OrderSide.Sell), "Order arguments do not match sides");

    orderHash = Hashing.getOrderHash(order, baseAssetSymbol, Constants.QUOTE_ASSET_SYMBOL);

    if (order.isSignedByDelegatedKey) {
      require(
        Hashing.isSignatureValid(
          domainSeparator,
          Hashing.getDelegatedKeyAuthorizationHash(order.delegatedKeyAuthorization),
          order.delegatedKeyAuthorization.signature,
          order.wallet
        ),
        order.side == OrderSide.Buy
          ? "Invalid DK authorization signature for buy order"
          : "Invalid DK authorization signature for sell order"
      );

      require(
        Hashing.isSignatureValid(
          domainSeparator,
          orderHash,
          order.walletSignature,
          order.delegatedKeyAuthorization.delegatedPublicKey
        ),
        order.side == OrderSide.Buy
          ? "Invalid wallet signature for buy order"
          : "Invalid wallet signature for sell order"
      );
    } else {
      require(
        Hashing.isSignatureValid(domainSeparator, orderHash, order.walletSignature, order.wallet),
        order.side == OrderSide.Buy
          ? "Invalid wallet signature for buy order"
          : "Invalid wallet signature for sell order"
      );
    }
  }

  function _validateTimeInForce(Order memory order, Trade memory trade, bool isBuy) private pure {
    bool isMakerOrder = trade.makerSide == (isBuy ? OrderSide.Buy : OrderSide.Sell);
    bool isTakerOrder = !isMakerOrder;

    if (order.timeInForce == OrderTimeInForce.gtx) {
      require(_isLimitOrderType(order.orderType) && isMakerOrder, "gtx order must be limit maker");
    } else if (order.timeInForce == OrderTimeInForce.ioc) {
      require(isTakerOrder, "ioc order must be taker");
    } else if (order.timeInForce == OrderTimeInForce.fok) {
      require(isTakerOrder, "fok order must be taker");
    }
  }

  function _validateTriggerFields(Order memory order) private pure {
    bool isTriggerPriceRequired = order.orderType == OrderType.StopLossMarket ||
      order.orderType == OrderType.StopLossLimit ||
      order.orderType == OrderType.TakeProfitMarket ||
      order.orderType == OrderType.TakeProfitLimit;
    if (isTriggerPriceRequired) {
      require(order.triggerPrice > 0, "Missing trigger price");
    } else if (order.orderType != OrderType.TrailingStop) {
      require(order.triggerPrice == 0, "Invalid trigger price");
    }

    require(
      order.orderType == OrderType.TrailingStop ? order.callbackRate > 0 : order.callbackRate == 0,
      "Invalid callback rate"
    );

    require(
      order.triggerPrice == 0 ? order.triggerType == OrderTriggerType.None : order.triggerType != OrderTriggerType.None,
      "Invalid trigger type"
    );
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { BalanceTracking } from "./BalanceTracking.sol";
import { Constants } from "./Constants.sol";
import { ExchangeEvents } from "./ExchangeEvents.sol";
import { Funding } from "./Funding.sol";
import { IndexPriceMargin } from "./IndexPriceMargin.sol";
import { TradeValidations } from "./TradeValidations.sol";
import { WalletExits } from "./WalletExits.sol";
import {
  FundingMultiplierQuartet,
  Market,
  MarketOverrides,
  Order,
  NonceInvalidation,
  Trade,
  WalletExit
} from "./Structs.sol";

library Trading {
  using BalanceTracking for BalanceTracking.Storage;

  // Placing arguments in calldata avoids a stack too deep error from the Yul optimizer
  // solhint-disable-next-line func-name-mixedcase
  function executeTrade_delegatecall(
    Trade memory trade,
    Order memory buy,
    Order memory sell,
    // Exchange state
    uint64 delegateKeyExpirationPeriodInMs,
    bytes32 domainSeparator,
    address exitFundWallet,
    address feeWallet,
    address insuranceFundWallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(bytes32 => bool) storage completedOrderHashes,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol,
    mapping(address => NonceInvalidation[]) storage nonceInvalidationsByWallet,
    mapping(bytes32 => uint64) storage partiallyFilledOrderQuantities,
    mapping(address => WalletExit) storage walletExits
  ) public {
    require(!WalletExits.isWalletExitFinalized(buy.wallet, walletExits), "Buy wallet exit finalized");
    require(!WalletExits.isWalletExitFinalized(sell.wallet, walletExits), "Sell wallet exit finalized");

    // Funding payments must be made prior to updating any position to ensure that the funding is calculated
    // against the position size at the time of each historic multiplier
    Funding.applyOutstandingWalletFunding(
      buy.wallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );
    Funding.applyOutstandingWalletFunding(
      sell.wallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );

    (bytes32 buyHash, bytes32 sellHash, Market memory market) = TradeValidations.validateTrade(
      trade,
      buy,
      sell,
      delegateKeyExpirationPeriodInMs,
      domainSeparator,
      exitFundWallet,
      insuranceFundWallet,
      marketsByBaseAssetSymbol,
      nonceInvalidationsByWallet
    );

    // Buy side
    _updateOrderFilledQuantity(buy, buyHash, trade.baseQuantity, completedOrderHashes, partiallyFilledOrderQuantities);
    // Sell side
    _updateOrderFilledQuantity(
      sell,
      sellHash,
      trade.baseQuantity,
      completedOrderHashes,
      partiallyFilledOrderQuantities
    );

    // Update balances
    (bool wasBuyPositionReduced, bool wasSellPositionReduced) = balanceTracking.updateForTrade(
      trade,
      buy,
      sell,
      feeWallet,
      market,
      baseAssetSymbolsWithOpenPositionsByWallet,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet
    );

    // Both wallets must still meet initial margin requirements
    _validateMarginRequirements(
      buy,
      sell,
      wasBuyPositionReduced,
      wasSellPositionReduced,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );

    // Either both or none of the orders will have `isLiquidationAcquisitionOnly` asserted as validated by
    // `TradeValidations.validateTrade`
    if (buy.isLiquidationAcquisitionOnly) {
      emit ExchangeEvents.LiquidationAcquisitionExecuted(
        buy.wallet,
        sell.wallet,
        trade.baseAssetSymbol,
        Constants.QUOTE_ASSET_SYMBOL,
        trade.baseQuantity,
        trade.quoteQuantity,
        trade.makerSide,
        trade.makerFeeQuantity,
        trade.takerFeeQuantity
      );
    } else {
      emit ExchangeEvents.TradeExecuted(
        buy.wallet,
        sell.wallet,
        trade.baseAssetSymbol,
        Constants.QUOTE_ASSET_SYMBOL,
        trade.baseQuantity,
        trade.quoteQuantity,
        trade.makerSide,
        trade.makerFeeQuantity,
        trade.takerFeeQuantity
      );
    }
  }

  // Update filled quantities tracking for order to prevent over- or double-filling orders
  function _updateOrderFilledQuantity(
    Order memory order,
    bytes32 orderHash,
    uint64 grossBaseQuantity,
    mapping(bytes32 => bool) storage completedOrderHashes,
    mapping(bytes32 => uint64) storage partiallyFilledOrderQuantities
  ) private {
    require(!completedOrderHashes[orderHash], "Order double filled");

    // Total quantity of above filled as a result of all trade executions, including this one
    uint64 newFilledQuantity;

    // Track partially filled quantities in base terms
    newFilledQuantity = grossBaseQuantity + partiallyFilledOrderQuantities[orderHash];

    uint64 quantity = order.quantity;
    require(newFilledQuantity <= quantity, "Order overfilled");
    if (newFilledQuantity < quantity) {
      // If the order was partially filled, track the new filled quantity
      partiallyFilledOrderQuantities[orderHash] = newFilledQuantity;
    } else {
      // If the order was completed, delete any partial fill tracking and instead track its completion
      // to prevent future double fills
      delete partiallyFilledOrderQuantities[orderHash];
      completedOrderHashes[orderHash] = true;
    }
  }

  function _validateMarginRequirements(
    Order memory buy,
    Order memory sell,
    bool wasBuyPositionReduced,
    bool wasSellPositionReduced,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private view {
    if (wasBuyPositionReduced) {
      IndexPriceMargin.validateMaintenanceMarginRequirement(
        buy.wallet,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet,
        marketOverridesByBaseAssetSymbolAndWallet,
        marketsByBaseAssetSymbol
      );
    } else {
      IndexPriceMargin.validateInitialMarginRequirement(
        buy.wallet,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet,
        marketOverridesByBaseAssetSymbolAndWallet,
        marketsByBaseAssetSymbol
      );
    }

    if (wasSellPositionReduced) {
      IndexPriceMargin.validateMaintenanceMarginRequirement(
        sell.wallet,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet,
        marketOverridesByBaseAssetSymbolAndWallet,
        marketsByBaseAssetSymbol
      );
    } else {
      IndexPriceMargin.validateInitialMarginRequirement(
        sell.wallet,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet,
        marketOverridesByBaseAssetSymbolAndWallet,
        marketsByBaseAssetSymbol
      );
    }
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { BalanceTracking } from "./BalanceTracking.sol";
import { ExchangeEvents } from "./ExchangeEvents.sol";
import { Funding } from "./Funding.sol";
import { Hashing } from "./Hashing.sol";
import { IndexPriceMargin } from "./IndexPriceMargin.sol";
import { Validations } from "./Validations.sol";
import { WalletExits } from "./WalletExits.sol";
import { FundingMultiplierQuartet, Market, MarketOverrides, Transfer, WalletExit } from "./Structs.sol";

library Transferring {
  using BalanceTracking for BalanceTracking.Storage;

  // solhint-disable-next-line func-name-mixedcase
  function transfer_delegatecall(
    // External arguments
    Transfer memory transfer,
    // Exchange state values
    bytes32 domainSeparator,
    address exitFundWallet,
    address insuranceFundWallet,
    address feeWallet,
    // Exchange state storage refs
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(bytes32 => bool) storage completedTransferHashes,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol,
    mapping(address => WalletExit) storage walletExits
  ) public {
    require(!WalletExits.isWalletExitFinalized(transfer.sourceWallet, walletExits), "Source wallet exited");
    require(!WalletExits.isWalletExitFinalized(transfer.destinationWallet, walletExits), "Destination wallet exited");
    require(transfer.sourceWallet != transfer.destinationWallet, "Cannot self-transfer");

    require(transfer.sourceWallet != exitFundWallet, "Cannot transfer from EF");
    require(transfer.sourceWallet != insuranceFundWallet, "Cannot transfer from IF");

    require(transfer.destinationWallet != address(0x0), "Invalid destination wallet");
    require(transfer.destinationWallet != exitFundWallet, "Cannot transfer to EF");

    require(Validations.isFeeQuantityValid(transfer.gasFee, transfer.grossQuantity), "Excessive transfer fee");
    bytes32 transferHash = _validateTransferSignature(transfer, domainSeparator);
    require(!completedTransferHashes[transferHash], "Duplicate transfer");

    Funding.applyOutstandingWalletFunding(
      transfer.sourceWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );
    Funding.applyOutstandingWalletFunding(
      transfer.destinationWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );

    // Update wallet balances
    (int64 newDestinationWalletExchangeBalance, int64 newSourceWalletExchangeBalance) = balanceTracking
      .updateForTransfer(transfer, feeWallet);

    // Wallet must still maintain initial margin requirement after withdrawal
    IndexPriceMargin.validateInitialMarginRequirement(
      transfer.sourceWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );

    // Replay prevention
    completedTransferHashes[transferHash] = true;

    emit ExchangeEvents.Transferred(
      transfer.destinationWallet,
      transfer.sourceWallet,
      transfer.grossQuantity,
      newDestinationWalletExchangeBalance,
      newSourceWalletExchangeBalance
    );
  }

  function _validateTransferSignature(
    Transfer memory transfer,
    bytes32 domainSeparator
  ) private pure returns (bytes32) {
    bytes32 transferHash = Hashing.getTransferHash(transfer);

    require(
      Hashing.isSignatureValid(domainSeparator, transferHash, transfer.walletSignature, transfer.sourceWallet),
      "Invalid wallet signature"
    );

    return transferHash;
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

/**
 * Library helper for extracting timestamp component of Version 1 UUIDs
 */
library UUID {
  /**
   * Extracts the timestamp component of a Version 1 UUID. Used to make time-based assertions
   * against a wallet-provided nonce
   */
  function getTimestampInMsFromUuidV1(uint128 uuid) internal pure returns (uint64 msSinceUnixEpoch) {
    // https://tools.ietf.org/html/rfc4122#section-4.1.2
    uint128 version = (uuid >> 76) & 0x0000000000000000000000000000000F;
    require(version == 1, "Must be v1 UUID");

    // Time components are in reverse order so shift+mask each to reassemble
    uint128 timeHigh = (uuid >> 16) & 0x00000000000000000FFF000000000000;
    uint128 timeMid = (uuid >> 48) & 0x00000000000000000000FFFF00000000;
    uint128 timeLow = (uuid >> 96) & 0x000000000000000000000000FFFFFFFF;
    uint128 nsSinceGregorianEpoch = (timeHigh | timeMid | timeLow);
    // Gregorian offset given in seconds by https://www.wolframalpha.com/input/?i=convert+1582-10-15+UTC+to+unix+time
    msSinceUnixEpoch = uint64(nsSinceGregorianEpoch / 10000) - 12219292800000;

    return msSinceUnixEpoch;
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { Constants } from "./Constants.sol";
import { Math } from "./Math.sol";
import { SortedStringSet } from "./SortedStringSet.sol";
import { Market, OverridableMarketFields } from "./Structs.sol";

library Validations {
  using SortedStringSet for string[];

  // 0.005
  uint64 private constant _MIN_INITIAL_MARGIN_FRACTION = 500000;
  // 0.003
  uint64 private constant _MIN_MAINTENANCE_MARGIN_FRACTION = 300000;
  // 0.001
  uint64 private constant _MIN_INCREMENTAL_INITIAL_MARGIN_FRACTION = 100000;
  // Max int64 - 1
  uint64 private constant _MAX_MINIMUM_POSITION_SIZE = uint64(type(int64).max - 1);

  function isFeeQuantityValid(uint64 fee, uint64 total) internal pure returns (bool) {
    uint64 feeMultiplier = Math.multiplyPipsByFraction(fee, Constants.PIP_PRICE_MULTIPLIER, total);

    return feeMultiplier <= Constants.MAX_FEE_MULTIPLIER;
  }

  function loadAndValidateActiveMarket(
    string memory baseAssetSymbol,
    address liquidatingWallet,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) internal view returns (Market memory market) {
    market = marketsByBaseAssetSymbol[baseAssetSymbol];
    require(market.exists && market.isActive, "No active market found");

    require(
      baseAssetSymbolsWithOpenPositionsByWallet[liquidatingWallet].indexOf(baseAssetSymbol) !=
        SortedStringSet.NOT_FOUND,
      "Open position not found for market"
    );
  }

  function loadAndValidateInactiveMarket(
    string memory baseAssetSymbol,
    address liquidatingWallet,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) internal view returns (Market memory market) {
    market = marketsByBaseAssetSymbol[baseAssetSymbol];
    require(market.exists && !market.isActive, "No inactive market found");

    require(
      baseAssetSymbolsWithOpenPositionsByWallet[liquidatingWallet].indexOf(baseAssetSymbol) !=
        SortedStringSet.NOT_FOUND,
      "Open position not found for market"
    );
  }

  // Validate reasonable limits on overridable market fields
  function validateOverridableMarketFields(OverridableMarketFields memory overridableFields) internal pure {
    require(
      overridableFields.initialMarginFraction >= _MIN_INITIAL_MARGIN_FRACTION,
      "Initial margin fraction below min"
    );
    require(
      overridableFields.maintenanceMarginFraction >= _MIN_MAINTENANCE_MARGIN_FRACTION,
      "Maintenance margin fraction below min"
    );
    require(
      overridableFields.incrementalInitialMarginFraction >= _MIN_INCREMENTAL_INITIAL_MARGIN_FRACTION,
      "Incremental initial margin fraction below min"
    );
    require(
      overridableFields.baselinePositionSize <= Constants.MAX_MAXIMUM_POSITION_SIZE,
      "Baseline position size exceeds max"
    );
    require(overridableFields.incrementalPositionSize > 0, "Incremental position size cannot be zero");
    require(
      overridableFields.maximumPositionSize <= Constants.MAX_MAXIMUM_POSITION_SIZE,
      "Maximum position size exceeds max"
    );
    require(overridableFields.minimumPositionSize <= _MAX_MINIMUM_POSITION_SIZE, "Minimum position size exceeds max");
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { BalanceTracking } from "./BalanceTracking.sol";
import { ExchangeEvents } from "./ExchangeEvents.sol";
import { Funding } from "./Funding.sol";
import { IndexPriceMargin } from "./IndexPriceMargin.sol";
import { LiquidationValidations } from "./LiquidationValidations.sol";
import { MarketHelper } from "./MarketHelper.sol";
import { Math } from "./Math.sol";
import { SortedStringSet } from "./SortedStringSet.sol";
import { Validations } from "./Validations.sol";
import {
  AcquisitionDeleverageArguments,
  Balance,
  FundingMultiplierQuartet,
  Market,
  MarketOverrides,
  WalletExit
} from "./Structs.sol";
import { WalletExitAcquisitionDeleveragePriceStrategy } from "./Enums.sol";

library WalletExitAcquisitionDeleveraging {
  using BalanceTracking for BalanceTracking.Storage;
  using MarketHelper for Market;
  using SortedStringSet for string[];

  struct ValidateDeleverageQuoteQuantityArguments {
    AcquisitionDeleverageArguments acquisitionDeleverageArguments;
    WalletExitAcquisitionDeleveragePriceStrategy deleveragePriceStrategy;
    address exitFundWallet;
    int256 liquidatingWalletTotalAccountValueInDoublePips;
    uint256 liquidatingWalletTotalMaintenanceMarginRequirementInTriplePips;
  }

  struct ValidateExitQuoteQuantityArguments {
    WalletExitAcquisitionDeleveragePriceStrategy deleveragePriceStrategy;
    uint64 indexPrice;
    int64 liquidationBaseQuantity;
    uint64 liquidationQuoteQuantity;
    uint64 maintenanceMarginFraction;
    int256 liquidatingWalletTotalAccountValueInDoublePips;
    uint256 liquidatingWalletTotalMaintenanceMarginRequirementInTriplePips;
  }

  // solhint-disable-next-line func-name-mixedcase
  function deleverage_delegatecall(
    AcquisitionDeleverageArguments memory arguments,
    address exitFundWallet,
    address insuranceFundWallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol,
    mapping(address => WalletExit) storage walletExits
  ) public {
    require(arguments.liquidatingWallet != arguments.counterpartyWallet, "Cannot liquidate wallet against itself");
    // The EF and IF cannot be exited, so no need validate that they are not set as liquidating wallet
    require(arguments.counterpartyWallet != exitFundWallet, "Cannot deleverage EF");
    require(arguments.counterpartyWallet != insuranceFundWallet, "Cannot deleverage IF");

    Funding.applyOutstandingWalletFunding(
      arguments.counterpartyWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );
    Funding.applyOutstandingWalletFunding(
      arguments.liquidatingWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );

    _validateArgumentsAndDeleverage(
      arguments,
      exitFundWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol,
      walletExits
    );

    emit ExchangeEvents.DeleveragedExitAcquisition(
      arguments.baseAssetSymbol,
      arguments.counterpartyWallet,
      arguments.liquidatingWallet,
      arguments.liquidationBaseQuantity,
      arguments.liquidationQuoteQuantity
    );
  }

  function _determineAndStoreDeleveragePriceStrategy(
    int64 exitAccountValue,
    address wallet,
    mapping(address => WalletExit) storage walletExits
  ) private returns (WalletExitAcquisitionDeleveragePriceStrategy) {
    WalletExit storage walletExit = walletExits[wallet];

    // Once we select bankruptcy price use it for the remainder of exit deleveraging
    if (walletExit.deleveragePriceStrategy == WalletExitAcquisitionDeleveragePriceStrategy.BankruptcyPrice) {
      return WalletExitAcquisitionDeleveragePriceStrategy.BankruptcyPrice;
    }

    // Wallets with a positive total account value should use the exit price (worse of entry price or current index
    // price) unless a change in index pricing between deleveraging the wallet positions moves its exit account value
    // negative, at which point the bankruptcy price will be used for the remainder of the positions
    walletExit.deleveragePriceStrategy = exitAccountValue <= 0
      ? WalletExitAcquisitionDeleveragePriceStrategy.BankruptcyPrice
      : WalletExitAcquisitionDeleveragePriceStrategy.ExitPrice;

    return walletExit.deleveragePriceStrategy;
  }

  function _updatePositionsForDeleverage(
    AcquisitionDeleverageArguments memory arguments,
    address exitFundWallet,
    Market memory market,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private {
    balanceTracking.updatePositionsForDeleverage(
      arguments.liquidationBaseQuantity,
      arguments.counterpartyWallet,
      exitFundWallet,
      arguments.liquidatingWallet,
      market,
      arguments.liquidationQuoteQuantity,
      baseAssetSymbolsWithOpenPositionsByWallet,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet
    );

    // Validate that the counterparty wallet still meets its maintenance margin requirements
    IndexPriceMargin.validateMaintenanceMarginRequirement(
      arguments.counterpartyWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );
  }

  function _validateArgumentsAndDeleverage(
    AcquisitionDeleverageArguments memory arguments,
    address exitFundWallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol,
    mapping(address => WalletExit) storage walletExits
  ) private {
    require(
      baseAssetSymbolsWithOpenPositionsByWallet[arguments.liquidatingWallet].indexOf(arguments.baseAssetSymbol) !=
        SortedStringSet.NOT_FOUND,
      "No open position in market"
    );

    (
      int64 exitAccountValue,
      int256 liquidatingWalletTotalAccountValueInDoublePips,
      uint256 liquidatingWalletTotalMaintenanceMarginRequirementInTriplePips
    ) = IndexPriceMargin
        .loadTotalExitAccountValueAndAccountValueInDoublePipsAndMaintenanceMarginRequirementInTriplePips(
          arguments.liquidatingWallet,
          balanceTracking,
          baseAssetSymbolsWithOpenPositionsByWallet,
          marketOverridesByBaseAssetSymbolAndWallet,
          marketsByBaseAssetSymbol
        );

    // Determine price strategy to use for subsequent IF acquisition simulation and actual position deleverage
    WalletExitAcquisitionDeleveragePriceStrategy deleveragePriceStrategy = _determineAndStoreDeleveragePriceStrategy(
      exitAccountValue,
      arguments.liquidatingWallet,
      walletExits
    );

    ValidateDeleverageQuoteQuantityArguments memory validateDeleverageQuoteQuantityArguments;
    validateDeleverageQuoteQuantityArguments.acquisitionDeleverageArguments = arguments;
    validateDeleverageQuoteQuantityArguments.deleveragePriceStrategy = deleveragePriceStrategy;
    validateDeleverageQuoteQuantityArguments.exitFundWallet = exitFundWallet;
    validateDeleverageQuoteQuantityArguments
      .liquidatingWalletTotalAccountValueInDoublePips = liquidatingWalletTotalAccountValueInDoublePips;
    validateDeleverageQuoteQuantityArguments
      .liquidatingWalletTotalMaintenanceMarginRequirementInTriplePips = liquidatingWalletTotalMaintenanceMarginRequirementInTriplePips;

    // Liquidate specified position by deleveraging a counterparty position
    _validateDeleverageQuoteQuantityAndUpdatePositions(
      validateDeleverageQuoteQuantityArguments,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );
  }

  function _validateDeleverageQuoteQuantity(
    ValidateDeleverageQuoteQuantityArguments memory arguments,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private returns (Market memory market) {
    market = Validations.loadAndValidateActiveMarket(
      arguments.acquisitionDeleverageArguments.baseAssetSymbol,
      arguments.acquisitionDeleverageArguments.liquidatingWallet,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketsByBaseAssetSymbol
    );

    Balance memory balanceStruct = balanceTracking.loadBalanceStructAndMigrateIfNeeded(
      arguments.acquisitionDeleverageArguments.liquidatingWallet,
      market.baseAssetSymbol
    );

    ValidateExitQuoteQuantityArguments memory validateExitQuoteQuantityArguments;
    validateExitQuoteQuantityArguments.deleveragePriceStrategy = arguments.deleveragePriceStrategy;
    validateExitQuoteQuantityArguments.indexPrice = market.lastIndexPrice;
    validateExitQuoteQuantityArguments.liquidationBaseQuantity = balanceStruct.balance < 0
      ? (-1 * Math.toInt64(arguments.acquisitionDeleverageArguments.liquidationBaseQuantity))
      : Math.toInt64(arguments.acquisitionDeleverageArguments.liquidationBaseQuantity);
    validateExitQuoteQuantityArguments.liquidationQuoteQuantity = arguments
      .acquisitionDeleverageArguments
      .liquidationQuoteQuantity;
    validateExitQuoteQuantityArguments.maintenanceMarginFraction = market
      .loadMarketWithOverridesForWallet(
        arguments.acquisitionDeleverageArguments.liquidatingWallet,
        marketOverridesByBaseAssetSymbolAndWallet
      )
      .overridableFields
      .maintenanceMarginFraction;
    validateExitQuoteQuantityArguments.liquidatingWalletTotalAccountValueInDoublePips = arguments
      .liquidatingWalletTotalAccountValueInDoublePips;
    validateExitQuoteQuantityArguments.liquidatingWalletTotalMaintenanceMarginRequirementInTriplePips = arguments
      .liquidatingWalletTotalMaintenanceMarginRequirementInTriplePips;
    _validateExitQuoteQuantity(balanceStruct, validateExitQuoteQuantityArguments);
  }

  function _validateDeleverageQuoteQuantityAndUpdatePositions(
    ValidateDeleverageQuoteQuantityArguments memory arguments,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private {
    Market memory market = _validateDeleverageQuoteQuantity(
      arguments,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );

    _updatePositionsForDeleverage(
      arguments.acquisitionDeleverageArguments,
      arguments.exitFundWallet,
      market,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );
  }

  function _validateExitQuoteQuantity(
    Balance memory balanceStruct,
    ValidateExitQuoteQuantityArguments memory arguments
  ) private pure {
    if (arguments.deleveragePriceStrategy == WalletExitAcquisitionDeleveragePriceStrategy.ExitPrice) {
      LiquidationValidations.validateQuoteQuantityAtExitPrice(
        // Calculate the cost basis of the base quantity being liquidated while observing signedness
        Math.multiplyPipsByFraction(
          balanceStruct.costBasis,
          arguments.liquidationBaseQuantity,
          // IF simulation explicitly validates non-zero position size, otherwise implicitly validated non-zero by
          // `Validations.loadAndValidateActiveMarket`
          Math.toInt64(Math.abs(balanceStruct.balance))
        ),
        arguments.indexPrice,
        arguments.liquidationBaseQuantity,
        arguments.liquidationQuoteQuantity
      );
    } else {
      LiquidationValidations.validateQuoteQuantityAtBankruptcyPrice(
        arguments.indexPrice,
        arguments.liquidationBaseQuantity,
        arguments.liquidationQuoteQuantity,
        arguments.maintenanceMarginFraction,
        arguments.liquidatingWalletTotalAccountValueInDoublePips,
        arguments.liquidatingWalletTotalMaintenanceMarginRequirementInTriplePips
      );
    }
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { BalanceTracking } from "./BalanceTracking.sol";
import { ExchangeEvents } from "./ExchangeEvents.sol";
import { Funding } from "./Funding.sol";
import { IndexPriceMargin } from "./IndexPriceMargin.sol";
import { LiquidationValidations } from "./LiquidationValidations.sol";
import { MarketHelper } from "./MarketHelper.sol";
import { SortedStringSet } from "./SortedStringSet.sol";
import { Balance, FundingMultiplierQuartet, Market, MarketOverrides, WalletLiquidationArguments } from "./Structs.sol";

library WalletExitLiquidation {
  using BalanceTracking for BalanceTracking.Storage;
  using MarketHelper for Market;
  using SortedStringSet for string[];

  struct ValidateExitQuoteQuantityArguments {
    Market market;
    int64 exitAccountValue;
    uint64 quoteQuantity;
    int256 totalAccountValueInDoublePips;
    uint256 totalMaintenanceMarginRequirementInTriplePips;
    address wallet;
  }

  // solhint-disable-next-line func-name-mixedcase
  function liquidate_delegatecall(
    WalletLiquidationArguments memory arguments,
    address exitFundWallet,
    address insuranceFundWallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) public {
    // The EF and IF cannot be exited, so no need validate that they are not set as liquidating wallet
    require(arguments.counterpartyWallet == insuranceFundWallet, "Must liquidate to IF");

    Funding.applyOutstandingWalletFunding(
      arguments.liquidatingWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );
    Funding.applyOutstandingWalletFunding(
      arguments.counterpartyWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );

    _validateQuoteQuantitiesAndLiquidatePositions(
      arguments,
      exitFundWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );

    // Validate that the Insurance Fund still meets its initial margin requirements
    IndexPriceMargin.validateInitialMarginRequirement(
      arguments.counterpartyWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );

    emit ExchangeEvents.LiquidatedWalletExit(arguments.liquidatingWallet);
  }

  // Wrap balance update to avoid stack too deep error
  function _updatePositionForLiquidation(
    WalletLiquidationArguments memory arguments,
    address exitFundWallet,
    uint8 index,
    Market memory market,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet
  ) private {
    Balance memory balanceStruct = balanceTracking.loadBalanceStructAndMigrateIfNeeded(
      arguments.liquidatingWallet,
      market.baseAssetSymbol
    );

    balanceTracking.updatePositionsForLiquidation(
      arguments.counterpartyWallet,
      exitFundWallet,
      arguments.liquidatingWallet,
      market,
      balanceStruct.balance,
      arguments.liquidationQuoteQuantities[index],
      baseAssetSymbolsWithOpenPositionsByWallet,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet
    );
  }

  function _validateQuoteQuantitiesAndLiquidatePositions(
    WalletLiquidationArguments memory arguments,
    address exitFundWallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private {
    ValidateExitQuoteQuantityArguments memory validateExitQuoteQuantityArguments;
    (
      validateExitQuoteQuantityArguments.exitAccountValue,
      validateExitQuoteQuantityArguments.totalAccountValueInDoublePips,
      validateExitQuoteQuantityArguments.totalMaintenanceMarginRequirementInTriplePips
    ) = IndexPriceMargin
      .loadTotalExitAccountValueAndAccountValueInDoublePipsAndMaintenanceMarginRequirementInTriplePips(
        arguments.liquidatingWallet,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet,
        marketOverridesByBaseAssetSymbolAndWallet,
        marketsByBaseAssetSymbol
      );
    validateExitQuoteQuantityArguments.wallet = arguments.liquidatingWallet;

    string[] memory baseAssetSymbols = baseAssetSymbolsWithOpenPositionsByWallet[arguments.liquidatingWallet];
    for (uint8 i = 0; i < baseAssetSymbols.length; i++) {
      validateExitQuoteQuantityArguments.market = marketsByBaseAssetSymbol[baseAssetSymbols[i]];
      require(validateExitQuoteQuantityArguments.market.isActive, "Cannot liquidate position in inactive market");

      validateExitQuoteQuantityArguments.quoteQuantity = arguments.liquidationQuoteQuantities[i];

      _validateLiquidationQuoteQuantity(
        validateExitQuoteQuantityArguments,
        balanceTracking,
        marketOverridesByBaseAssetSymbolAndWallet
      );

      _updatePositionForLiquidation(
        arguments,
        exitFundWallet,
        i,
        validateExitQuoteQuantityArguments.market,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet,
        lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
        marketOverridesByBaseAssetSymbolAndWallet
      );
    }

    if (validateExitQuoteQuantityArguments.exitAccountValue <= 0) {
      // When liquidating at the bankruptcy price, a small amount of quote asset may be left over due to rounding
      // errors - sweep this remaining amount into the counterparty wallet to fully zero out the liquidating wallet
      balanceTracking.updateRemainingQuoteBalanceAfterWalletLiquidation(
        arguments.counterpartyWallet,
        arguments.liquidatingWallet
      );
    }
  }

  function _validateLiquidationQuoteQuantity(
    ValidateExitQuoteQuantityArguments memory arguments,
    BalanceTracking.Storage storage balanceTracking,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet
  ) private {
    Balance memory balanceStruct = balanceTracking.loadBalanceStructAndMigrateIfNeeded(
      arguments.wallet,
      arguments.market.baseAssetSymbol
    );

    if (arguments.exitAccountValue <= 0) {
      LiquidationValidations.validateQuoteQuantityAtBankruptcyPrice(
        arguments.market.lastIndexPrice,
        balanceStruct.balance,
        arguments.quoteQuantity,
        arguments
          .market
          .loadMarketWithOverridesForWallet(arguments.wallet, marketOverridesByBaseAssetSymbolAndWallet)
          .overridableFields
          .maintenanceMarginFraction,
        arguments.totalAccountValueInDoublePips,
        arguments.totalMaintenanceMarginRequirementInTriplePips
      );
    } else {
      LiquidationValidations.validateQuoteQuantityAtExitPrice(
        balanceStruct.costBasis,
        arguments.market.lastIndexPrice,
        balanceStruct.balance,
        arguments.quoteQuantity
      );
    }
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { WalletExit } from "./Structs.sol";

library WalletExits {
  function isWalletExitFinalized(
    address wallet,
    mapping(address => WalletExit) storage walletExits
  ) internal view returns (bool) {
    WalletExit memory exit = walletExits[wallet];
    return exit.exists && exit.effectiveBlockTimestamp <= block.timestamp;
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { BalanceTracking } from "./BalanceTracking.sol";
import { ExchangeEvents } from "./ExchangeEvents.sol";
import { Funding } from "./Funding.sol";
import { IndexPriceMargin } from "./IndexPriceMargin.sol";
import { LiquidationValidations } from "./LiquidationValidations.sol";
import { MarketHelper } from "./MarketHelper.sol";
import { Math } from "./Math.sol";
import { SortedStringSet } from "./SortedStringSet.sol";
import { Validations } from "./Validations.sol";
import { WalletInMaintenanceLiquidation } from "./WalletInMaintenanceLiquidation.sol";
import {
  AcquisitionDeleverageArguments,
  Balance,
  FundingMultiplierQuartet,
  Market,
  MarketOverrides
} from "./Structs.sol";

library WalletInMaintenanceAcquisitionDeleveraging {
  using BalanceTracking for BalanceTracking.Storage;
  using MarketHelper for Market;
  using SortedStringSet for string[];

  // solhint-disable-next-line func-name-mixedcase
  function deleverage_delegatecall(
    AcquisitionDeleverageArguments memory arguments,
    address exitFundWallet,
    address insuranceFundWallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol,
    mapping(address => uint64) storage pendingDepositQuantityByWallet
  ) public {
    require(arguments.liquidatingWallet != arguments.counterpartyWallet, "Cannot liquidate wallet against itself");
    require(arguments.liquidatingWallet != exitFundWallet, "Cannot liquidate EF");
    require(arguments.liquidatingWallet != insuranceFundWallet, "Cannot liquidate IF");
    require(arguments.counterpartyWallet != exitFundWallet, "Cannot deleverage EF");
    require(arguments.counterpartyWallet != insuranceFundWallet, "Cannot deleverage IF");

    Funding.applyOutstandingWalletFunding(
      arguments.counterpartyWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );
    Funding.applyOutstandingWalletFunding(
      arguments.liquidatingWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );

    _validateArgumentsAndDeleverage(
      arguments,
      exitFundWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );

    emit ExchangeEvents.DeleveragedInMaintenanceAcquisition(
      arguments.baseAssetSymbol,
      arguments.counterpartyWallet,
      arguments.liquidatingWallet,
      arguments.liquidationBaseQuantity,
      arguments.liquidationQuoteQuantity
    );

    if (baseAssetSymbolsWithOpenPositionsByWallet[arguments.liquidatingWallet].length == 0) {
      WalletInMaintenanceLiquidation.liquidateManagerWalletAndRefundPendingDepositsIfNeeded(
        arguments.liquidatingWallet,
        balanceTracking,
        pendingDepositQuantityByWallet
      );
    }
  }

  function _validateArgumentsAndDeleverage(
    AcquisitionDeleverageArguments memory arguments,
    address exitFundWallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private {
    require(
      baseAssetSymbolsWithOpenPositionsByWallet[arguments.liquidatingWallet].indexOf(arguments.baseAssetSymbol) !=
        SortedStringSet.NOT_FOUND,
      "No open position in market"
    );

    // Validate that the liquidating account has fallen below margin requirements
    (
      int256 liquidatingWalletTotalAccountValueInDoublePips,
      uint256 liquidatingWalletTotalMaintenanceMarginRequirementInTriplePips
    ) = IndexPriceMargin.loadTotalAccountValueInDoublePipsAndMaintenanceMarginRequirementInTriplePips(
        arguments.liquidatingWallet,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet,
        marketOverridesByBaseAssetSymbolAndWallet,
        marketsByBaseAssetSymbol
      );
    require(
      Math.doublePipsToPips(liquidatingWalletTotalAccountValueInDoublePips) <
        Math.toInt64(Math.triplePipsToPips(liquidatingWalletTotalMaintenanceMarginRequirementInTriplePips)),
      "Maintenance margin requirement met"
    );

    // Liquidate specified position by deleveraging a counterparty position
    _validateDeleverageQuoteQuantityAndUpdatePositions(
      arguments,
      exitFundWallet,
      liquidatingWalletTotalAccountValueInDoublePips,
      liquidatingWalletTotalMaintenanceMarginRequirementInTriplePips,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );
  }

  function _updatePositionsForDeleverage(
    AcquisitionDeleverageArguments memory arguments,
    address exitFundWallet,
    Market memory market,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private {
    balanceTracking.updatePositionsForDeleverage(
      arguments.liquidationBaseQuantity,
      arguments.counterpartyWallet,
      exitFundWallet,
      arguments.liquidatingWallet,
      market,
      arguments.liquidationQuoteQuantity,
      baseAssetSymbolsWithOpenPositionsByWallet,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet
    );

    // Validate that the counterparty wallet still meets its maintenance margin requirements
    IndexPriceMargin.validateMaintenanceMarginRequirement(
      arguments.counterpartyWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );
  }

  function _validateDeleverageQuoteQuantity(
    AcquisitionDeleverageArguments memory arguments,
    int256 totalAccountValueInDoublePips,
    uint256 totalMaintenanceMarginRequirementInTriplePips,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private returns (Market memory market) {
    market = Validations.loadAndValidateActiveMarket(
      arguments.baseAssetSymbol,
      arguments.liquidatingWallet,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketsByBaseAssetSymbol
    );

    Balance memory balanceStruct = balanceTracking.loadBalanceStructAndMigrateIfNeeded(
      arguments.liquidatingWallet,
      market.baseAssetSymbol
    );

    LiquidationValidations.validateQuoteQuantityAtBankruptcyPrice(
      market.lastIndexPrice,
      balanceStruct.balance < 0
        ? (-1 * Math.toInt64(arguments.liquidationBaseQuantity))
        : Math.toInt64(arguments.liquidationBaseQuantity),
      arguments.liquidationQuoteQuantity,
      market
        .loadMarketWithOverridesForWallet(arguments.liquidatingWallet, marketOverridesByBaseAssetSymbolAndWallet)
        .overridableFields
        .maintenanceMarginFraction,
      totalAccountValueInDoublePips,
      totalMaintenanceMarginRequirementInTriplePips
    );
  }

  function _validateDeleverageQuoteQuantityAndUpdatePositions(
    AcquisitionDeleverageArguments memory arguments,
    address exitFundWallet,
    int256 totalAccountValueInDoublePips,
    uint256 totalMaintenanceMarginRequirementInTriplePips,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private {
    Market memory market = _validateDeleverageQuoteQuantity(
      arguments,
      totalAccountValueInDoublePips,
      totalMaintenanceMarginRequirementInTriplePips,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );

    _updatePositionsForDeleverage(
      arguments,
      exitFundWallet,
      market,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { AssetUnitConversions } from "../libraries/AssetUnitConversions.sol";
import { BalanceTracking } from "./BalanceTracking.sol";
import { Constants } from "../libraries/Constants.sol";
import { ExchangeEvents } from "./ExchangeEvents.sol";
import { ExitFund } from "./ExitFund.sol";
import { Funding } from "./Funding.sol";
import { IExchange } from "./Interfaces.sol";
import { IndexPriceMargin } from "./IndexPriceMargin.sol";
import { LiquidationType } from "./Enums.sol";
import { LiquidationValidations } from "./LiquidationValidations.sol";
import { MarketHelper } from "./MarketHelper.sol";
import { Math } from "./Math.sol";
import { SortedStringSet } from "./SortedStringSet.sol";
import { Balance, FundingMultiplierQuartet, Market, MarketOverrides, WalletLiquidationArguments } from "./Structs.sol";

library WalletInMaintenanceLiquidation {
  using BalanceTracking for BalanceTracking.Storage;
  using MarketHelper for Market;
  using SortedStringSet for string[];

  // Placing arguments in calldata avoids a stack too deep error from the Yul optimizer
  // solhint-disable-next-line func-name-mixedcase
  function liquidate_delegatecall(
    WalletLiquidationArguments calldata arguments,
    uint256 currentExitFundPositionOpenedAtBlockTimestamp,
    address exitFundWallet,
    address insuranceFundWallet,
    LiquidationType liquidationType,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol,
    mapping(address => uint64) storage pendingDepositQuantityByWallet
  ) public returns (uint256 resultingExitFundPositionOpenedAtBlockTimestamp) {
    require(arguments.liquidatingWallet != exitFundWallet, "Cannot liquidate EF");
    require(arguments.liquidatingWallet != insuranceFundWallet, "Cannot liquidate IF");
    if (liquidationType == LiquidationType.WalletInMaintenanceDuringSystemRecovery) {
      require(arguments.counterpartyWallet == exitFundWallet, "Must liquidate to EF");
    } else {
      // LiquidationType.WalletInMaintenance
      require(arguments.counterpartyWallet == insuranceFundWallet, "Must liquidate to IF");
    }

    Funding.applyOutstandingWalletFunding(
      arguments.liquidatingWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );
    Funding.applyOutstandingWalletFunding(
      arguments.counterpartyWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );

    _validateQuoteQuantitiesAndLiquidatePositions(
      arguments,
      exitFundWallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol
    );

    if (liquidationType == LiquidationType.WalletInMaintenanceDuringSystemRecovery) {
      resultingExitFundPositionOpenedAtBlockTimestamp = ExitFund.getExitFundPositionOpenedAtBlockTimestamp(
        currentExitFundPositionOpenedAtBlockTimestamp,
        exitFundWallet,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet
      );

      emit ExchangeEvents.LiquidatedWalletInMaintenanceDuringSystemRecovery(arguments.liquidatingWallet);
    } else {
      resultingExitFundPositionOpenedAtBlockTimestamp = currentExitFundPositionOpenedAtBlockTimestamp;

      // Validate that the Insurance Fund still meets its initial margin requirements
      IndexPriceMargin.validateInitialMarginRequirement(
        arguments.counterpartyWallet,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet,
        marketOverridesByBaseAssetSymbolAndWallet,
        marketsByBaseAssetSymbol
      );

      emit ExchangeEvents.LiquidatedWalletInMaintenance(arguments.liquidatingWallet);
    }

    liquidateManagerWalletAndRefundPendingDepositsIfNeeded(
      arguments.liquidatingWallet,
      balanceTracking,
      pendingDepositQuantityByWallet
    );
  }

  function liquidateManagerWalletAndRefundPendingDepositsIfNeeded(
    address liquidatingWallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => uint64) storage pendingDepositQuantityByWallet
  ) internal {
    // If the liquidating wallet is a manager wallet, then call the liquidate function on the
    // associated Managed Account provider contract
    Balance storage balanceStruct = BalanceTracking.loadBalanceStructAndMigrateIfNeeded(
      balanceTracking,
      liquidatingWallet,
      Constants.QUOTE_ASSET_SYMBOL
    );
    if (address(balanceStruct.managedAccountProvider) != address(0x0)) {
      // If there are unapplied deposits for the manager wallet, return them to the Managed Account
      // provider so that it can properly distribute the funds to depositors
      uint64 pendingDepositQuantity = pendingDepositQuantityByWallet[liquidatingWallet];
      if (pendingDepositQuantity > 0) {
        IExchange(address(this)).custodian().withdraw(
          address(balanceStruct.managedAccountProvider),
          IExchange(address(this)).quoteTokenAddress(),
          AssetUnitConversions.pipsToAssetUnits(pendingDepositQuantity, Constants.QUOTE_TOKEN_DECIMALS)
        );
        pendingDepositQuantityByWallet[liquidatingWallet] = 0;
      }

      // Call the liquidate function on the associated Managed Account provider contract
      balanceStruct.managedAccountProvider.liquidateManagerWallet(liquidatingWallet);
    }
  }

  // Wrap balance update to avoid stack too deep error
  function _updatePositionForLiquidation(
    WalletLiquidationArguments memory arguments,
    address exitFundWallet,
    uint8 index,
    Market memory market,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet
  ) private {
    Balance memory balanceStruct = balanceTracking.loadBalanceStructAndMigrateIfNeeded(
      arguments.liquidatingWallet,
      market.baseAssetSymbol
    );

    balanceTracking.updatePositionsForLiquidation(
      arguments.counterpartyWallet,
      exitFundWallet,
      arguments.liquidatingWallet,
      market,
      balanceStruct.balance,
      arguments.liquidationQuoteQuantities[index],
      baseAssetSymbolsWithOpenPositionsByWallet,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet
    );
  }

  function _validateQuoteQuantitiesAndLiquidatePositions(
    WalletLiquidationArguments memory arguments,
    address exitFundWallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol
  ) private {
    (int256 totalAccountValueInDoublePips, uint256 totalMaintenanceMarginRequirementInTriplePips) = IndexPriceMargin
      .loadTotalAccountValueInDoublePipsAndMaintenanceMarginRequirementInTriplePips(
        arguments.liquidatingWallet,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet,
        marketOverridesByBaseAssetSymbolAndWallet,
        marketsByBaseAssetSymbol
      );
    require(
      Math.doublePipsToPips(totalAccountValueInDoublePips) <
        Math.toInt64(Math.triplePipsToPips(totalMaintenanceMarginRequirementInTriplePips)),
      "Maintenance margin requirement met"
    );

    Market memory market;
    string[] memory baseAssetSymbols = baseAssetSymbolsWithOpenPositionsByWallet[arguments.liquidatingWallet];
    for (uint8 i = 0; i < baseAssetSymbols.length; i++) {
      market = marketsByBaseAssetSymbol[baseAssetSymbols[i]];
      require(market.isActive, "Cannot liquidate position in inactive market");

      _validateLiquidationQuoteQuantity(
        arguments,
        i,
        market,
        totalAccountValueInDoublePips,
        totalMaintenanceMarginRequirementInTriplePips,
        balanceTracking,
        marketOverridesByBaseAssetSymbolAndWallet
      );

      _updatePositionForLiquidation(
        arguments,
        exitFundWallet,
        i,
        marketsByBaseAssetSymbol[baseAssetSymbols[i]],
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet,
        lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
        marketOverridesByBaseAssetSymbolAndWallet
      );
    }

    // After closing all the positions for a wallet in maintenance, a small amount of quote asset may be left over
    // due to rounding errors - sweep this remaining amount into the counterparty wallet to fully zero out the
    // liquidating wallet
    balanceTracking.updateRemainingQuoteBalanceAfterWalletLiquidation(
      arguments.counterpartyWallet,
      arguments.liquidatingWallet
    );
  }

  function _validateLiquidationQuoteQuantity(
    WalletLiquidationArguments memory arguments,
    uint8 index,
    Market memory market,
    int256 totalAccountValueInDoublePips,
    uint256 totalMaintenanceMarginRequirementInTriplePips,
    BalanceTracking.Storage storage balanceTracking,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet
  ) private {
    LiquidationValidations.validateQuoteQuantityAtBankruptcyPrice(
      market.lastIndexPrice,
      balanceTracking.loadBalanceAndMigrateIfNeeded(arguments.liquidatingWallet, market.baseAssetSymbol),
      arguments.liquidationQuoteQuantities[index],
      market
        .loadMarketWithOverridesForWallet(arguments.liquidatingWallet, marketOverridesByBaseAssetSymbolAndWallet)
        .overridableFields
        .maintenanceMarginFraction,
      totalAccountValueInDoublePips,
      totalMaintenanceMarginRequirementInTriplePips
    );
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

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

import { Address } from "./Address.sol";
import { AssetUnitConversions } from "./AssetUnitConversions.sol";
import { BalanceTracking } from "./BalanceTracking.sol";
import { Constants } from "./Constants.sol";
import { ExchangeEvents } from "./ExchangeEvents.sol";
import { ExitFund } from "./ExitFund.sol";
import { Hashing } from "./Hashing.sol";
import { Funding } from "./Funding.sol";
import { IndexPriceMargin } from "./IndexPriceMargin.sol";
import { MarketHelper } from "./MarketHelper.sol";
import { Math } from "./Math.sol";
import { OraclePriceMargin } from "./OraclePriceMargin.sol";
import { WalletExitAcquisitionDeleveragePriceStrategy } from "./Enums.sol";
import { WalletExits } from "./WalletExits.sol";
import { IBridgeAdapter, ICustodian, IManagedAccountProvider, IOraclePriceAdapter } from "./Interfaces.sol";
import { Balance, FundingMultiplierQuartet, Market, MarketOverrides, WalletExit, Withdrawal } from "./Structs.sol";

library Withdrawing {
  using BalanceTracking for BalanceTracking.Storage;
  using MarketHelper for Market;

  // solhint-disable-next-line func-name-mixedcase
  function clearWalletExit_delegatecall(
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(address => WalletExit) storage walletExits
  ) public {
    require(WalletExits.isWalletExitFinalized(msg.sender, walletExits), "Wallet exit not finalized");
    require(
      baseAssetSymbolsWithOpenPositionsByWallet[msg.sender].length == 0 &&
        balanceTracking.loadBalanceFromMigrationSourceIfNeeded(msg.sender, Constants.QUOTE_ASSET_SYMBOL) == 0,
      "Must withdraw exit before clearing"
    );

    Balance storage balanceStruct = balanceTracking.loadBalanceStructAndMigrateIfNeeded(
      msg.sender,
      Constants.QUOTE_ASSET_SYMBOL
    );
    require(
      balanceStruct.managedAccountProvider == IManagedAccountProvider(address(0x0)),
      "Cannot clear exit for MA manager wallet"
    );

    delete walletExits[msg.sender];

    emit ExchangeEvents.WalletExitCleared(msg.sender);
  }

  // solhint-disable-next-line func-name-mixedcase
  function exitWallet_delegatecall(
    uint256 chainPropagationPeriodInS,
    address exitFundWallet,
    address insuranceFundWallet,
    address wallet,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => WalletExit) storage walletExits
  ) external {
    Balance storage balanceStruct = balanceTracking.loadBalanceStructAndMigrateIfNeeded(
      wallet,
      Constants.QUOTE_ASSET_SYMBOL
    );
    bool isAssociatedWithManagedAccount = balanceStruct.managedAccountProvider != IManagedAccountProvider(address(0x0));
    if (isAssociatedWithManagedAccount) {
      require(
        IManagedAccountProvider(msg.sender) == balanceStruct.managedAccountProvider,
        "Caller must be MA when exiting manager wallet"
      );
    } else {
      require(msg.sender == wallet, "Caller must be wallet to exit");
    }

    uint256 blockTimestampThreshold = exitWallet(
      chainPropagationPeriodInS,
      exitFundWallet,
      insuranceFundWallet,
      wallet,
      walletExits
    );

    emit ExchangeEvents.WalletExited(wallet, blockTimestampThreshold, isAssociatedWithManagedAccount);
  }

  // solhint-disable-next-line func-name-mixedcase
  function skim_delegatecall(address tokenAddress, address feeWallet) public {
    require(Address.isContract(tokenAddress), "Invalid token address");

    uint256 balance = IERC20(tokenAddress).balanceOf(address(this));

    // Ignore the return value of transfer
    IERC20(tokenAddress).transfer(feeWallet, balance);
  }

  // solhint-disable-next-line func-name-mixedcase
  function withdraw_delegatecall(
    // External arguments
    Withdrawal memory withdrawal,
    // Exchange state values
    bytes32 domainSeparator,
    ICustodian custodian,
    uint256 exitFundPositionOpenedAtBlockTimestamp,
    address exitFundWallet,
    address feeWallet,
    address quoteTokenAddress,
    // Exchange state storage refs
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(bytes32 => bool) storage completedWithdrawalHashes,
    IBridgeAdapter[] storage bridgeAdapters,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol,
    mapping(address => WalletExit) storage walletExits
  ) public {
    require(!WalletExits.isWalletExitFinalized(withdrawal.wallet, walletExits), "Wallet is exited");

    // Validate preconditions
    if (withdrawal.wallet == exitFundWallet) {
      _validateExitFundWithdrawDelayElapsed(exitFundPositionOpenedAtBlockTimestamp);
    }
    require(
      withdrawal.gasFee <= withdrawal.maximumGasFee && withdrawal.maximumGasFee <= withdrawal.grossQuantity,
      "Excessive withdrawal fee"
    );
    bytes32 withdrawalHash = _validateWithdrawalSignature(withdrawal, domainSeparator);
    require(!completedWithdrawalHashes[withdrawalHash], "Duplicate withdrawal");

    Funding.applyOutstandingWalletFunding(
      withdrawal.wallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );

    // Update wallet balances
    int64 newExchangeBalance = balanceTracking.updateForWithdrawal(withdrawal, feeWallet);

    // EF has no margin requirements but may not withdraw quote balance below zero
    if (withdrawal.wallet == exitFundWallet) {
      require(newExchangeBalance >= 0, "EF may not withdraw to a negative balance");
    } else {
      // Wallet must still maintain initial margin requirement after withdrawal
      IndexPriceMargin.validateInitialMarginRequirement(
        withdrawal.wallet,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet,
        marketOverridesByBaseAssetSymbolAndWallet,
        marketsByBaseAssetSymbol
      );
    }

    _transferWithdrawnQuoteAsset(withdrawal, custodian, quoteTokenAddress, bridgeAdapters);

    // Replay prevention
    completedWithdrawalHashes[withdrawalHash] = true;

    emit ExchangeEvents.Withdrawn(withdrawal.wallet, withdrawal.grossQuantity, newExchangeBalance, false);
  }

  // solhint-disable-next-line func-name-mixedcase
  function withdrawExit_delegatecall(
    // External arguments
    address wallet,
    // Exchange state values
    ICustodian custodian,
    address exitFundWallet,
    IOraclePriceAdapter oraclePriceAdapter,
    address quoteTokenAddress,
    uint256 exitFundPositionOpenedAtBlockTimestamp,
    // Exchange state storage refs
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol,
    mapping(address => uint64) storage pendingDepositQuantityByWallet,
    mapping(address => WalletExit) storage walletExits
  ) public returns (uint256 exitFundPositionOpenedAtBlockTimestamp_) {
    Funding.applyOutstandingWalletFunding(
      wallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );

    bool isExitFundWallet = wallet == exitFundWallet;

    uint64 walletQuoteQuantityToWithdraw;
    if (isExitFundWallet) {
      // Do not require prior exit for EF as it is already subject to a specific EF withdrawal delay
      _validateExitFundWithdrawDelayElapsed(exitFundPositionOpenedAtBlockTimestamp);

      // The EF wallet can withdraw its positive quote balance
      walletQuoteQuantityToWithdraw = validateExitQuoteQuantityAndCoerceIfNeeded(
        true,
        balanceTracking.updateExitFundWalletForExit(exitFundWallet)
      );
    } else {
      require(WalletExits.isWalletExitFinalized(wallet, walletExits), "Wallet exit not finalized");

      Balance storage quoteBalanceStruct;
      (quoteBalanceStruct, walletQuoteQuantityToWithdraw) = updatePositionsForWalletExit(
        wallet,
        exitFundWallet,
        oraclePriceAdapter,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet,
        lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
        marketOverridesByBaseAssetSymbolAndWallet,
        marketsByBaseAssetSymbol,
        pendingDepositQuantityByWallet
      );

      bool isAssociatedWithManagedAccount = quoteBalanceStruct.managedAccountProvider !=
        IManagedAccountProvider(address(0x0));
      require(!isAssociatedWithManagedAccount, "Cannot withdraw exit from MA manager wallet");

      // Zero out quote balance as entire amount will be withdrawn
      quoteBalanceStruct.balance = 0;
    }

    custodian.withdraw(
      wallet,
      quoteTokenAddress,
      AssetUnitConversions.pipsToAssetUnits(walletQuoteQuantityToWithdraw, Constants.QUOTE_TOKEN_DECIMALS)
    );

    // Quote quantity validated to be non-negative by `validateExitQuoteQuantityAndCoerceIfNeeded`
    emit ExchangeEvents.WalletExitWithdrawn(wallet, walletQuoteQuantityToWithdraw);

    return
      ExitFund.getExitFundPositionOpenedAtBlockTimestamp(
        exitFundPositionOpenedAtBlockTimestamp,
        exitFundWallet,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet
      );
  }

  // solhint-disable-next-line func-name-mixedcase
  function withdrawExitAdmin_delegatecall(
    // External arguments
    address wallet,
    // Exchange state values
    ICustodian custodian,
    address exitFundWallet,
    IOraclePriceAdapter oraclePriceAdapter,
    address quoteTokenAddress,
    uint256 exitFundPositionOpenedAtBlockTimestamp,
    // Exchange state storage refs
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => FundingMultiplierQuartet[]) storage fundingMultipliersByBaseAssetSymbol,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol,
    mapping(address => uint64) storage pendingDepositQuantityByWallet,
    mapping(address => WalletExit) storage walletExits
  ) public returns (uint256 exitFundPositionOpenedAtBlockTimestamp_) {
    require(walletExits[wallet].exists, "Wallet not exited");

    Funding.applyOutstandingWalletFunding(
      wallet,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      fundingMultipliersByBaseAssetSymbol,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketsByBaseAssetSymbol
    );

    // Quote quantity validated to be non-negative by `validateExitQuoteQuantityAndCoerceIfNeeded`
    (Balance storage quoteBalanceStruct, uint64 walletQuoteQuantityToWithdraw) = updatePositionsForWalletExit(
      wallet,
      exitFundWallet,
      oraclePriceAdapter,
      balanceTracking,
      baseAssetSymbolsWithOpenPositionsByWallet,
      lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
      marketOverridesByBaseAssetSymbolAndWallet,
      marketsByBaseAssetSymbol,
      pendingDepositQuantityByWallet
    );

    bool isAssociatedWithManagedAccount = quoteBalanceStruct.managedAccountProvider !=
      IManagedAccountProvider(address(0x0));
    require(!isAssociatedWithManagedAccount, "Cannot withdraw exit from MA manager wallet");

    // Zero out quote balance as entire amount will be withdrawn
    quoteBalanceStruct.balance = 0;

    custodian.withdraw(
      wallet,
      quoteTokenAddress,
      AssetUnitConversions.pipsToAssetUnits(walletQuoteQuantityToWithdraw, Constants.QUOTE_TOKEN_DECIMALS)
    );

    emit ExchangeEvents.WalletExitWithdrawn(wallet, walletQuoteQuantityToWithdraw);

    return
      ExitFund.getExitFundPositionOpenedAtBlockTimestamp(
        exitFundPositionOpenedAtBlockTimestamp,
        exitFundWallet,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet
      );
  }

  function validateExitQuoteQuantityAndCoerceIfNeeded(
    bool isExitFundWallet,
    int64 walletQuoteQuantityToWithdraw
  ) internal pure returns (uint64) {
    // Rounding errors can lead to a slightly negative result instead of zero - within the tolerance, coerce to zero
    // in these cases to allow wallet positions to be closed out
    if (
      !isExitFundWallet &&
      walletQuoteQuantityToWithdraw < 0 &&
      Math.abs(walletQuoteQuantityToWithdraw) <= Constants.MINIMUM_QUOTE_QUANTITY_VALIDATION_THRESHOLD
    ) {
      return 0;
    }

    // The available quote for exit withdrawal can validly be negative for the EF wallet. For all other wallets, the
    // exit quote calculations are designed such that the result quantity to withdraw is never negative; however we
    // still perform this check in case of unforeseen bugs or rounding errors. In either case we should revert on
    // negative. A zero available quantity would not transfer out any quote but would still close all positions and
    // quote balance, so we do not revert on zero
    require(walletQuoteQuantityToWithdraw >= 0, "Negative quote after exit");

    return uint64(walletQuoteQuantityToWithdraw);
  }

  function exitWallet(
    uint256 chainPropagationPeriodInS,
    address exitFundWallet,
    address insuranceFundWallet,
    address wallet,
    mapping(address => WalletExit) storage walletExits
  ) internal returns (uint256 blockTimestampThreshold) {
    require(!walletExits[wallet].exists, "Wallet already exited");
    require(wallet != exitFundWallet, "Cannot exit EF");
    require(wallet != insuranceFundWallet, "Cannot exit IF");

    blockTimestampThreshold = block.timestamp + chainPropagationPeriodInS;
    walletExits[wallet] = WalletExit(
      true,
      uint64(blockTimestampThreshold),
      WalletExitAcquisitionDeleveragePriceStrategy.None
    );
  }

  function updatePositionsForWalletExit(
    address wallet,
    address exitFundWallet,
    IOraclePriceAdapter oraclePriceAdapter,
    BalanceTracking.Storage storage balanceTracking,
    mapping(address => string[]) storage baseAssetSymbolsWithOpenPositionsByWallet,
    mapping(string => uint64) storage lastFundingRatePublishTimestampInMsByBaseAssetSymbol,
    mapping(string => mapping(address => MarketOverrides)) storage marketOverridesByBaseAssetSymbolAndWallet,
    mapping(string => Market) storage marketsByBaseAssetSymbol,
    mapping(address => uint64) storage pendingDepositQuantityByWallet
  ) internal returns (Balance storage quoteBalanceStruct, uint64 walletQuoteQuantityAvailableToWithdraw) {
    BalanceTracking.UpdatePositionForExitArguments memory updatePositionForExitArguments;
    updatePositionForExitArguments.exitFundWallet = exitFundWallet;
    updatePositionForExitArguments.oraclePriceAdapter = oraclePriceAdapter;
    (
      updatePositionForExitArguments.exitAccountValue,
      updatePositionForExitArguments.totalAccountValueInDoublePips,
      updatePositionForExitArguments.totalMaintenanceMarginRequirementInTriplePips
    ) = OraclePriceMargin
      .loadTotalExitAccountValueAndAccountValueInDoublePipsAndMaintenanceMarginRequirementInTriplePips(
        oraclePriceAdapter,
        0, // Outstanding funding payments already applied in withdrawExit_delegatecall
        wallet,
        balanceTracking,
        baseAssetSymbolsWithOpenPositionsByWallet,
        marketOverridesByBaseAssetSymbolAndWallet,
        marketsByBaseAssetSymbol
      );
    updatePositionForExitArguments.wallet = wallet;

    int64 exitFundQuoteQuantityChange;

    string[] memory baseAssetSymbols = baseAssetSymbolsWithOpenPositionsByWallet[wallet];
    for (uint8 i = 0; i < baseAssetSymbols.length; i++) {
      updatePositionForExitArguments.market = marketsByBaseAssetSymbol[baseAssetSymbols[i]];
      updatePositionForExitArguments.maintenanceMarginFraction = marketsByBaseAssetSymbol[baseAssetSymbols[i]]
        .loadMarketWithOverridesForWallet(wallet, marketOverridesByBaseAssetSymbolAndWallet)
        .overridableFields
        .maintenanceMarginFraction;

      // Sum EF quote quantity change needed to close each wallet position
      exitFundQuoteQuantityChange += balanceTracking.updatePositionForExit(
        updatePositionForExitArguments,
        baseAssetSymbolsWithOpenPositionsByWallet,
        lastFundingRatePublishTimestampInMsByBaseAssetSymbol
      );
    }

    // Update EF quote balance with total quote change calculated above in loop
    quoteBalanceStruct = balanceTracking.loadBalanceStructAndMigrateIfNeeded(
      exitFundWallet,
      Constants.QUOTE_ASSET_SYMBOL
    );
    quoteBalanceStruct.balance += exitFundQuoteQuantityChange;

    // Update exiting wallet's quote balance
    quoteBalanceStruct = balanceTracking.loadBalanceStructAndMigrateIfNeeded(wallet, Constants.QUOTE_ASSET_SYMBOL);

    walletQuoteQuantityAvailableToWithdraw = uint64(
      // Quote quantity validated to be non-negative by `validateExitQuoteQuantityAndCoerceIfNeeded`
      validateExitQuoteQuantityAndCoerceIfNeeded(
        wallet == exitFundWallet,
        // The wallet's change in quote quantity from position closure is inverse to that of the EF to acquire them.
        // Subtract the EF quote change from wallet's existing quote balance to obtain total quote available for withdrawal
        quoteBalanceStruct.balance - exitFundQuoteQuantityChange
      )
    );

    // Apply all pending deposits
    walletQuoteQuantityAvailableToWithdraw += pendingDepositQuantityByWallet[wallet];
    pendingDepositQuantityByWallet[wallet] = 0;

    quoteBalanceStruct.balance = Math.toInt64(walletQuoteQuantityAvailableToWithdraw);
  }

  function _transferWithdrawnQuoteAsset(
    Withdrawal memory withdrawal,
    ICustodian custodian,
    address quoteTokenAddress,
    IBridgeAdapter[] storage bridgeAdapters
  ) private {
    uint256 netAssetQuantityInAssetUnits = AssetUnitConversions.pipsToAssetUnits(
      withdrawal.grossQuantity - withdrawal.gasFee,
      Constants.QUOTE_TOKEN_DECIMALS
    );
    if (netAssetQuantityInAssetUnits == 0) {
      // If net quantity is zero there is nothing further to do, no tokens will be transferred
      return;
    }

    if (withdrawal.bridgeAdapter == address(0x0)) {
      // Transfer funds from Custodian to wallet
      custodian.withdraw(withdrawal.wallet, quoteTokenAddress, netAssetQuantityInAssetUnits);
    } else {
      // Validate bridge adapter is whitelisted
      bool bridgeAdapterIsWhitelisted = false;
      for (uint8 i = 0; i < bridgeAdapters.length; i++) {
        if (withdrawal.bridgeAdapter == address(bridgeAdapters[i])) {
          bridgeAdapterIsWhitelisted = true;
          break;
        }
      }
      require(bridgeAdapterIsWhitelisted, "Invalid bridge adapter");

      // Transfer funds from Custodian to bridge adapter contract
      custodian.withdraw(withdrawal.bridgeAdapter, quoteTokenAddress, netAssetQuantityInAssetUnits);

      // Bridge adapter callback after tokens are transferred
      IBridgeAdapter(withdrawal.bridgeAdapter).withdrawQuoteAsset(
        withdrawal.wallet,
        netAssetQuantityInAssetUnits,
        withdrawal.bridgeAdapterPayload
      );
    }
  }

  function _validateExitFundWithdrawDelayElapsed(uint256 exitFundPositionOpenedAtBlockTimestamp) private view {
    require(
      block.timestamp >= exitFundPositionOpenedAtBlockTimestamp + Constants.EXIT_FUND_WITHDRAW_DELAY_IN_S,
      "EF position opened too recently"
    );
  }

  function _validateWithdrawalSignature(
    Withdrawal memory withdrawal,
    bytes32 domainSeparator
  ) private pure returns (bytes32 withdrawalHash) {
    withdrawalHash = Hashing.getWithdrawalHash(withdrawal);

    require(
      Hashing.isSignatureValid(domainSeparator, withdrawalHash, withdrawal.walletSignature, withdrawal.wallet),
      "Invalid wallet signature"
    );
  }
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import { ExchangeErrors } from "./libraries/ExchangeErrors.sol";

/**
 * @notice Mixin that provide separate owner and admin roles for RBAC
 */
abstract contract Owned is ExchangeErrors {
  address public ownerWallet;
  address public adminWallet;

  modifier onlyOwner() {
    if (msg.sender != ownerWallet) {
      revert SenderMustBeOwner();
    }
    _;
  }
  modifier onlyAdmin() {
    if (msg.sender != adminWallet) {
      revert SenderMustBeAdmin();
    }
    _;
  }

  /**
   * @notice Sets both the owner and admin roles to the contract creator
   */
  constructor() {
    ownerWallet = msg.sender;
    adminWallet = msg.sender;
  }

  /**
   * @notice Sets a new whitelisted admin wallet
   *
   * @param newAdmin The new whitelisted admin wallet. Must be different from the current one
   */
  function setAdmin(address newAdmin) external onlyOwner {
    if (newAdmin == address(0x0)) {
      revert InvalidWalletAddress();
    }
    if (newAdmin == adminWallet) {
      revert NewValueMustBeDifferentFromCurrent();
    }

    adminWallet = newAdmin;
  }

  /**
   * @notice Sets a new owner wallet
   *
   * @param newOwner The new owner wallet. Must be different from the current one
   */
  function setOwner(address newOwner) external onlyOwner {
    if (newOwner == address(0x0)) {
      revert InvalidWalletAddress();
    }
    if (newOwner == ownerWallet) {
      revert NewValueMustBeDifferentFromCurrent();
    }

    ownerWallet = newOwner;
  }

  /**
   * @notice Clears the currently whitelisted admin wallet, effectively disabling any functions requiring
   * the admin role
   */
  function removeAdmin() external onlyOwner {
    adminWallet = address(0x0);
  }

  /**
   * @notice Permanently clears the owner wallet, effectively disabling any functions requiring the owner role
   */
  function removeOwner() external onlyOwner {
    ownerWallet = address(0x0);
  }
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 1
  },
  "viaIR": true,
  "evmVersion": "paris",
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {
    "contracts/Exchange_v1.sol": {
      "BalanceLoading": "0x6da84c3990e3f89f8f7045df2d7e731f9695805e",
      "ClosureDeleveraging": "0x57bf2dec1454ef43b45ba2c011df4fc773e2312a",
      "Depositing": "0x80d605b39e39df70a86e8d1977c8b8d12473d6e2",
      "ExitFund": "0xe2df7954fbd89f90a447f4a830205618d70bbe8c",
      "Funding": "0x682af734b0f04a9234e4d95974f88e03eb89dc95",
      "IndexPriceMargin": "0x47bb857393f40b08a62a52e81fc66b59c40f6c17",
      "ManagedAccounts": "0x9defe6fd18341097f02c3261430017eb5a1da1ad",
      "MarketAdmin": "0xb99826ba9304602032884f13439e1ba5dee62609",
      "NonceInvalidations": "0xb22dd85e1c87feaaf33192bf4dc099e5c53ee666",
      "OraclePriceMargin": "0x5d5661fbfbfa061fb51061f0d9759ff90606c4dd",
      "PositionBelowMinimumLiquidation": "0x4dbc24bcca09b43fdcd001569cfc7ad6bb4e2f86",
      "PositionInDeactivatedMarketLiquidation": "0x5d04d54916246bcebf425ea9af28e4163132b16f",
      "Trading": "0xcf58d39226e085d1db6e9cf1fd3209f31976581a",
      "Transferring": "0x3fc4c196e966d34db0b5def1401242dc164dfc6a",
      "WalletExitAcquisitionDeleveraging": "0x3e6623aecd7840d114cc814e342290daecfeb605",
      "WalletExitLiquidation": "0x426f7f2e61715343ecc6603628a033f5516c23f1",
      "WalletInMaintenanceAcquisitionDeleveraging": "0x1155250006ea43581fe695e165d68ccefd14a82d",
      "WalletInMaintenanceLiquidation": "0x8903624c18e665dd62ba9e0bb548103b6119df8c",
      "Withdrawing": "0xe8d88c09c626362483e09ea23f2633f2b15fbfae"
    }
  }
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"contract IExchange","name":"balanceMigrationSource","type":"address"},{"internalType":"address","name":"exitFundWallet_","type":"address"},{"internalType":"address","name":"feeWallet_","type":"address"},{"internalType":"contract IIndexPriceAdapter[]","name":"indexPriceAdapters","type":"address[]"},{"internalType":"address","name":"insuranceFundWallet_","type":"address"},{"internalType":"contract IOraclePriceAdapter","name":"oraclePriceAdapter_","type":"address"},{"internalType":"address","name":"quoteTokenAddress_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ExitFundCannotHaveOpenPosition","type":"error"},{"inputs":[],"name":"ExitFundHasNoPositions","type":"error"},{"inputs":[],"name":"InvalidContractAddress","type":"error"},{"inputs":[],"name":"InvalidShortString","type":"error"},{"inputs":[],"name":"InvalidWalletAddress","type":"error"},{"inputs":[],"name":"NewInsuranceFundWalletCannotBeExited","type":"error"},{"inputs":[],"name":"NewValueExceedsMaximum","type":"error"},{"inputs":[],"name":"NewValueMustBeDifferentFromCurrent","type":"error"},{"inputs":[],"name":"SenderMustBeAdmin","type":"error"},{"inputs":[],"name":"SenderMustBeAdminOrDispatcher","type":"error"},{"inputs":[],"name":"SenderMustBeDispatcher","type":"error"},{"inputs":[],"name":"SenderMustBeGovernance","type":"error"},{"inputs":[],"name":"SenderMustBeOwner","type":"error"},{"inputs":[{"internalType":"string","name":"str","type":"string"}],"name":"StringTooLong","type":"error"},{"inputs":[],"name":"UnknownBaseAssetSymbol","type":"error"},{"inputs":[],"name":"ValueCanOnlyBetSetOnce","type":"error"},{"inputs":[],"name":"WalletCannotBeExited","type":"error"},{"inputs":[],"name":"WalletHasNoOverridesForMarket","type":"error"},{"inputs":[],"name":"WalletMustBeExited","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"previousValue","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"ChainPropagationPeriodChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"previousValue","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"DelegateKeyExpirationPeriodChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"baseAssetSymbol","type":"string"},{"indexed":false,"internalType":"address","name":"counterpartyWallet","type":"address"},{"indexed":false,"internalType":"address","name":"liquidatingWallet","type":"address"},{"indexed":false,"internalType":"uint64","name":"liquidationBaseQuantity","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"liquidationQuoteQuantity","type":"uint64"}],"name":"DeleveragedExitAcquisition","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"baseAssetSymbol","type":"string"},{"indexed":false,"internalType":"address","name":"counterpartyWallet","type":"address"},{"indexed":false,"internalType":"address","name":"exitFundWallet","type":"address"},{"indexed":false,"internalType":"uint64","name":"liquidationBaseQuantity","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"liquidationQuoteQuantity","type":"uint64"}],"name":"DeleveragedExitFundClosure","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"baseAssetSymbol","type":"string"},{"indexed":false,"internalType":"address","name":"counterpartyWallet","type":"address"},{"indexed":false,"internalType":"address","name":"liquidatingWallet","type":"address"},{"indexed":false,"internalType":"uint64","name":"liquidationBaseQuantity","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"liquidationQuoteQuantity","type":"uint64"}],"name":"DeleveragedInMaintenanceAcquisition","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"baseAssetSymbol","type":"string"},{"indexed":false,"internalType":"address","name":"counterpartyWallet","type":"address"},{"indexed":false,"internalType":"address","name":"insuranceFundWallet","type":"address"},{"indexed":false,"internalType":"uint64","name":"liquidationBaseQuantity","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"liquidationQuoteQuantity","type":"uint64"}],"name":"DeleveragedInsuranceFundClosure","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"index","type":"uint64"},{"indexed":false,"internalType":"address","name":"sourceWallet","type":"address"},{"indexed":false,"internalType":"address","name":"depositorWallet","type":"address"},{"indexed":false,"internalType":"uint64","name":"quantity","type":"uint64"},{"indexed":false,"internalType":"bool","name":"isAssociatedWithManagedAccount","type":"bool"}],"name":"Deposited","type":"event"},{"anonymous":false,"inputs":[],"name":"DepositsDisabled","type":"event"},{"anonymous":false,"inputs":[],"name":"DepositsEnabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousValue","type":"address"},{"indexed":false,"internalType":"address","name":"newValue","type":"address"}],"name":"DispatcherChanged","type":"event"},{"anonymous":false,"inputs":[],"name":"EIP712DomainChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousValue","type":"address"},{"indexed":false,"internalType":"address","name":"newValue","type":"address"}],"name":"ExitFundWalletChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousValue","type":"address"},{"indexed":false,"internalType":"address","name":"newValue","type":"address"}],"name":"FeeWalletChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"baseAssetSymbol","type":"string"},{"indexed":false,"internalType":"int64","name":"fundingRate","type":"int64"}],"name":"FundingRatePublished","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"baseAssetSymbol","type":"string"},{"indexed":false,"internalType":"uint64","name":"timestampInMs","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"price","type":"uint64"}],"name":"IndexPricePublished","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"baseAssetSymbol","type":"string"},{"indexed":false,"internalType":"address","name":"liquidatingWallet","type":"address"},{"indexed":false,"internalType":"uint64","name":"liquidationBaseQuantity","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"liquidationQuoteQuantity","type":"uint64"}],"name":"LiquidatedPositionBelowMinimum","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"baseAssetSymbol","type":"string"},{"indexed":false,"internalType":"address","name":"liquidatingWallet","type":"address"},{"indexed":false,"internalType":"uint64","name":"liquidationBaseQuantity","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"liquidationQuoteQuantity","type":"uint64"}],"name":"LiquidatedPositionInDeactivatedMarket","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"liquidatingWallet","type":"address"}],"name":"LiquidatedWalletExit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"liquidatingWallet","type":"address"}],"name":"LiquidatedWalletInMaintenance","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"liquidatingWallet","type":"address"}],"name":"LiquidatedWalletInMaintenanceDuringSystemRecovery","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"buyWallet","type":"address"},{"indexed":false,"internalType":"address","name":"sellWallet","type":"address"},{"indexed":false,"internalType":"string","name":"baseAssetSymbol","type":"string"},{"indexed":false,"internalType":"string","name":"quoteAssetSymbol","type":"string"},{"indexed":false,"internalType":"uint64","name":"baseQuantity","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"quoteQuantity","type":"uint64"},{"indexed":false,"internalType":"enum OrderSide","name":"makerSide","type":"uint8"},{"indexed":false,"internalType":"int64","name":"makerFeeQuantity","type":"int64"},{"indexed":false,"internalType":"uint64","name":"takerFeeQuantity","type":"uint64"}],"name":"LiquidationAcquisitionExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"baseAssetSymbol","type":"string"}],"name":"MarketActivated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"baseAssetSymbol","type":"string"}],"name":"MarketAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"baseAssetSymbol","type":"string"}],"name":"MarketDeactivated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"baseAssetSymbol","type":"string"},{"indexed":false,"internalType":"address","name":"wallet","type":"address"}],"name":"MarketOverridesUnset","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"uint128","name":"nonce","type":"uint128"},{"indexed":false,"internalType":"uint128","name":"timestampInMs","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"effectiveBlockTimestamp","type":"uint256"}],"name":"OrderNonceInvalidated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"uint64","name":"quantity","type":"uint64"},{"indexed":false,"internalType":"int64","name":"newExchangeBalance","type":"int64"},{"indexed":false,"internalType":"bool","name":"isAssociatedWithManagedAccount","type":"bool"}],"name":"PendingDepositApplied","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"previousValue","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"PositionBelowMinimumLiquidationPriceToleranceMultiplierChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousValue","type":"address"},{"indexed":false,"internalType":"address","name":"newValue","type":"address"}],"name":"QuoteTokenAddressChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"buyWallet","type":"address"},{"indexed":false,"internalType":"address","name":"sellWallet","type":"address"},{"indexed":false,"internalType":"string","name":"baseAssetSymbol","type":"string"},{"indexed":false,"internalType":"string","name":"quoteAssetSymbol","type":"string"},{"indexed":false,"internalType":"uint64","name":"baseQuantity","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"quoteQuantity","type":"uint64"},{"indexed":false,"internalType":"enum OrderSide","name":"makerSide","type":"uint8"},{"indexed":false,"internalType":"int64","name":"makerFeeQuantity","type":"int64"},{"indexed":false,"internalType":"uint64","name":"takerFeeQuantity","type":"uint64"}],"name":"TradeExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"destinationWallet","type":"address"},{"indexed":false,"internalType":"address","name":"sourceWallet","type":"address"},{"indexed":false,"internalType":"uint64","name":"quantity","type":"uint64"},{"indexed":false,"internalType":"int64","name":"newDestinationWalletExchangeBalance","type":"int64"},{"indexed":false,"internalType":"int64","name":"newSourceWalletExchangeBalance","type":"int64"}],"name":"Transferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"}],"name":"WalletExitCleared","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"managerWallet","type":"address"},{"indexed":false,"internalType":"address","name":"depositorWallet","type":"address"},{"indexed":false,"internalType":"uint64","name":"quantity","type":"uint64"}],"name":"WalletExitFromManagedAccountWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"uint64","name":"quantity","type":"uint64"}],"name":"WalletExitWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"uint256","name":"effectiveBlockTimestamp","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isAssociatedWithManagedAccount","type":"bool"}],"name":"WalletExited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"uint64","name":"quantity","type":"uint64"},{"indexed":false,"internalType":"int64","name":"newExchangeBalance","type":"int64"}],"name":"WithdrawalFromManagedAccountCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"uint64","name":"quantity","type":"uint64"},{"indexed":false,"internalType":"int64","name":"newExchangeBalance","type":"int64"},{"indexed":false,"internalType":"bool","name":"isAssociatedWithManagedAccount","type":"bool"}],"name":"Withdrawn","type":"event"},{"inputs":[{"internalType":"string","name":"baseAssetSymbol","type":"string"}],"name":"activateMarket","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"bool","name":"exists","type":"bool"},{"internalType":"bool","name":"isActive","type":"bool"},{"internalType":"string","name":"baseAssetSymbol","type":"string"},{"internalType":"uint64","name":"indexPriceAtDeactivation","type":"uint64"},{"internalType":"uint64","name":"lastIndexPrice","type":"uint64"},{"internalType":"uint64","name":"lastIndexPriceTimestampInMs","type":"uint64"},{"components":[{"internalType":"uint64","name":"initialMarginFraction","type":"uint64"},{"internalType":"uint64","name":"maintenanceMarginFraction","type":"uint64"},{"internalType":"uint64","name":"incrementalInitialMarginFraction","type":"uint64"},{"internalType":"uint64","name":"baselinePositionSize","type":"uint64"},{"internalType":"uint64","name":"incrementalPositionSize","type":"uint64"},{"internalType":"uint64","name":"maximumPositionSize","type":"uint64"},{"internalType":"uint64","name":"minimumPositionSize","type":"uint64"}],"internalType":"struct OverridableMarketFields","name":"overridableFields","type":"tuple"}],"internalType":"struct Market","name":"newMarket","type":"tuple"}],"name":"addMarket","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"adminWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"},{"internalType":"string","name":"baseAssetSymbol","type":"string"}],"name":"applyOutstandingWalletFundingForMarket","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"depositIndex_","type":"uint64"},{"internalType":"uint64","name":"quantity","type":"uint64"},{"internalType":"address","name":"managerWallet","type":"address"}],"name":"applyPendingDepositToManagedAccount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"quantity","type":"uint64"},{"internalType":"address","name":"wallet","type":"address"}],"name":"applyPendingDepositsForWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"enum ManagedAccountWithdrawalType","name":"withdrawalType","type":"uint8"},{"components":[{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"managerWallet","type":"address"},{"internalType":"address","name":"depositorWallet","type":"address"},{"internalType":"uint64","name":"grossQuantity","type":"uint64"},{"internalType":"uint64","name":"maxShares","type":"uint64"},{"internalType":"uint64","name":"maximumGasFee","type":"uint64"},{"internalType":"contract IManagedAccountProvider","name":"managedAccountProvider","type":"address"},{"internalType":"bytes","name":"managedAccountProviderPayload","type":"bytes"},{"internalType":"address","name":"bridgeAdapter","type":"address"},{"internalType":"bytes","name":"bridgeAdapterPayload","type":"bytes"},{"internalType":"uint64","name":"gasFee","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct WithdrawalFromManagedAccountByQuantity","name":"withdrawalByQuantity","type":"tuple"},{"components":[{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"managerWallet","type":"address"},{"internalType":"address","name":"depositorWallet","type":"address"},{"internalType":"uint64","name":"shares","type":"uint64"},{"internalType":"uint64","name":"minimumQuantity","type":"uint64"},{"internalType":"uint64","name":"maximumGasFee","type":"uint64"},{"internalType":"contract IManagedAccountProvider","name":"managedAccountProvider","type":"address"},{"internalType":"bytes","name":"managedAccountProviderPayload","type":"bytes"},{"internalType":"address","name":"bridgeAdapter","type":"address"},{"internalType":"bytes","name":"bridgeAdapterPayload","type":"bytes"},{"internalType":"uint64","name":"gasFee","type":"uint64"},{"internalType":"uint64","name":"grossQuantity","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct WithdrawalFromManagedAccountByShares","name":"withdrawalByShares","type":"tuple"}],"internalType":"struct WithdrawalFromManagedAccount","name":"withdrawal","type":"tuple"}],"name":"applyPendingWithdrawalFromManagedAccount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"managerWallet","type":"address"}],"name":"associateManagerWalletWithManagedAccount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"enum ManagedAccountWithdrawalType","name":"withdrawalType","type":"uint8"},{"components":[{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"managerWallet","type":"address"},{"internalType":"address","name":"depositorWallet","type":"address"},{"internalType":"uint64","name":"grossQuantity","type":"uint64"},{"internalType":"uint64","name":"maxShares","type":"uint64"},{"internalType":"uint64","name":"maximumGasFee","type":"uint64"},{"internalType":"contract IManagedAccountProvider","name":"managedAccountProvider","type":"address"},{"internalType":"bytes","name":"managedAccountProviderPayload","type":"bytes"},{"internalType":"address","name":"bridgeAdapter","type":"address"},{"internalType":"bytes","name":"bridgeAdapterPayload","type":"bytes"},{"internalType":"uint64","name":"gasFee","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct WithdrawalFromManagedAccountByQuantity","name":"withdrawalByQuantity","type":"tuple"},{"components":[{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"managerWallet","type":"address"},{"internalType":"address","name":"depositorWallet","type":"address"},{"internalType":"uint64","name":"shares","type":"uint64"},{"internalType":"uint64","name":"minimumQuantity","type":"uint64"},{"internalType":"uint64","name":"maximumGasFee","type":"uint64"},{"internalType":"contract IManagedAccountProvider","name":"managedAccountProvider","type":"address"},{"internalType":"bytes","name":"managedAccountProviderPayload","type":"bytes"},{"internalType":"address","name":"bridgeAdapter","type":"address"},{"internalType":"bytes","name":"bridgeAdapterPayload","type":"bytes"},{"internalType":"uint64","name":"gasFee","type":"uint64"},{"internalType":"uint64","name":"grossQuantity","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct WithdrawalFromManagedAccountByShares","name":"withdrawalByShares","type":"tuple"}],"internalType":"struct WithdrawalFromManagedAccount","name":"withdrawal","type":"tuple"}],"name":"cancelPendingWithdrawalFromManagedAccount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"chainPropagationPeriodInS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"clearWalletExit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"custodian","outputs":[{"internalType":"contract ICustodian","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"baseAssetSymbol","type":"string"}],"name":"deactivateMarket","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"delegateKeyExpirationPeriodInMs","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"baseAssetSymbol","type":"string"},{"internalType":"address","name":"counterpartyWallet","type":"address"},{"internalType":"address","name":"liquidatingWallet","type":"address"},{"internalType":"uint64","name":"liquidationBaseQuantity","type":"uint64"},{"internalType":"uint64","name":"liquidationQuoteQuantity","type":"uint64"}],"internalType":"struct AcquisitionDeleverageArguments","name":"deleverageArguments","type":"tuple"}],"name":"deleverageExitAcquisition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"baseAssetSymbol","type":"string"},{"internalType":"address","name":"counterpartyWallet","type":"address"},{"internalType":"address","name":"liquidatingWallet","type":"address"},{"internalType":"uint64","name":"liquidationBaseQuantity","type":"uint64"},{"internalType":"uint64","name":"liquidationQuoteQuantity","type":"uint64"}],"internalType":"struct ClosureDeleverageArguments","name":"deleverageArguments","type":"tuple"}],"name":"deleverageExitFundClosure","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"baseAssetSymbol","type":"string"},{"internalType":"address","name":"counterpartyWallet","type":"address"},{"internalType":"address","name":"liquidatingWallet","type":"address"},{"internalType":"uint64","name":"liquidationBaseQuantity","type":"uint64"},{"internalType":"uint64","name":"liquidationQuoteQuantity","type":"uint64"}],"internalType":"struct AcquisitionDeleverageArguments","name":"deleverageArguments","type":"tuple"}],"name":"deleverageInMaintenanceAcquisition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"baseAssetSymbol","type":"string"},{"internalType":"address","name":"counterpartyWallet","type":"address"},{"internalType":"address","name":"liquidatingWallet","type":"address"},{"internalType":"uint64","name":"liquidationBaseQuantity","type":"uint64"},{"internalType":"uint64","name":"liquidationQuoteQuantity","type":"uint64"}],"internalType":"struct ClosureDeleverageArguments","name":"deleverageArguments","type":"tuple"}],"name":"deleverageInsuranceFundClosure","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"quantityInAssetUnits","type":"uint256"},{"internalType":"address","name":"depositorWallet","type":"address"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"depositIndex","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"quantityInAssetUnits","type":"uint256"},{"internalType":"address","name":"depositorWallet","type":"address"},{"internalType":"contract IManagedAccountProvider","name":"managedAccountProvider","type":"address"},{"internalType":"bytes","name":"managedAccountProviderPayload","type":"bytes"},{"internalType":"address","name":"managerWallet","type":"address"}],"name":"depositToManagedAccount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"dispatcherWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"domainSeparatorV4","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"eip712Domain","outputs":[{"internalType":"bytes1","name":"fields","type":"bytes1"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"address","name":"verifyingContract","type":"address"},{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"uint256[]","name":"extensions","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"baseAssetSymbol","type":"string"},{"internalType":"uint64","name":"baseQuantity","type":"uint64"},{"internalType":"uint64","name":"quoteQuantity","type":"uint64"},{"internalType":"int64","name":"makerFeeQuantity","type":"int64"},{"internalType":"uint64","name":"takerFeeQuantity","type":"uint64"},{"internalType":"uint64","name":"price","type":"uint64"},{"internalType":"enum OrderSide","name":"makerSide","type":"uint8"}],"internalType":"struct Trade","name":"trade","type":"tuple"},{"components":[{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"wallet","type":"address"},{"internalType":"enum OrderType","name":"orderType","type":"uint8"},{"internalType":"enum OrderSide","name":"side","type":"uint8"},{"internalType":"uint64","name":"quantity","type":"uint64"},{"internalType":"uint64","name":"limitPrice","type":"uint64"},{"internalType":"uint64","name":"triggerPrice","type":"uint64"},{"internalType":"enum OrderTriggerType","name":"triggerType","type":"uint8"},{"internalType":"uint64","name":"callbackRate","type":"uint64"},{"internalType":"uint128","name":"conditionalOrderId","type":"uint128"},{"internalType":"bool","name":"isReduceOnly","type":"bool"},{"internalType":"enum OrderTimeInForce","name":"timeInForce","type":"uint8"},{"internalType":"enum OrderSelfTradePrevention","name":"selfTradePrevention","type":"uint8"},{"internalType":"bool","name":"isLiquidationAcquisitionOnly","type":"bool"},{"internalType":"bool","name":"isSignedByDelegatedKey","type":"bool"},{"components":[{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"delegatedPublicKey","type":"address"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct DelegatedKeyAuthorization","name":"delegatedKeyAuthorization","type":"tuple"},{"internalType":"string","name":"clientOrderId","type":"string"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Order","name":"buy","type":"tuple"},{"components":[{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"wallet","type":"address"},{"internalType":"enum OrderType","name":"orderType","type":"uint8"},{"internalType":"enum OrderSide","name":"side","type":"uint8"},{"internalType":"uint64","name":"quantity","type":"uint64"},{"internalType":"uint64","name":"limitPrice","type":"uint64"},{"internalType":"uint64","name":"triggerPrice","type":"uint64"},{"internalType":"enum OrderTriggerType","name":"triggerType","type":"uint8"},{"internalType":"uint64","name":"callbackRate","type":"uint64"},{"internalType":"uint128","name":"conditionalOrderId","type":"uint128"},{"internalType":"bool","name":"isReduceOnly","type":"bool"},{"internalType":"enum OrderTimeInForce","name":"timeInForce","type":"uint8"},{"internalType":"enum OrderSelfTradePrevention","name":"selfTradePrevention","type":"uint8"},{"internalType":"bool","name":"isLiquidationAcquisitionOnly","type":"bool"},{"internalType":"bool","name":"isSignedByDelegatedKey","type":"bool"},{"components":[{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"delegatedPublicKey","type":"address"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct DelegatedKeyAuthorization","name":"delegatedKeyAuthorization","type":"tuple"},{"internalType":"string","name":"clientOrderId","type":"string"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Order","name":"sell","type":"tuple"}],"name":"executeTrade","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"exitFundPositionOpenedAtBlockTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"exitFundWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"exitWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"feeWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"fundingMultipliersByBaseAssetSymbol","outputs":[{"internalType":"int64","name":"fundingMultiplier0","type":"int64"},{"internalType":"int64","name":"fundingMultiplier1","type":"int64"},{"internalType":"int64","name":"fundingMultiplier2","type":"int64"},{"internalType":"int64","name":"fundingMultiplier3","type":"int64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"insuranceFundWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint128","name":"nonce","type":"uint128"}],"name":"invalidateNonce","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isDepositEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"lastFundingRatePublishTimestampInMsByBaseAssetSymbol","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"baseAssetSymbol","type":"string"},{"internalType":"address","name":"liquidatingWallet","type":"address"},{"internalType":"uint64","name":"liquidationQuoteQuantity","type":"uint64"}],"internalType":"struct PositionBelowMinimumLiquidationArguments","name":"liquidationArguments","type":"tuple"}],"name":"liquidatePositionBelowMinimum","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"baseAssetSymbol","type":"string"},{"internalType":"uint64","name":"feeQuantity","type":"uint64"},{"internalType":"address","name":"liquidatingWallet","type":"address"},{"internalType":"uint64","name":"liquidationQuoteQuantity","type":"uint64"}],"internalType":"struct PositionInDeactivatedMarketLiquidationArguments","name":"liquidationArguments","type":"tuple"}],"name":"liquidatePositionInDeactivatedMarket","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"counterpartyWallet","type":"address"},{"internalType":"address","name":"liquidatingWallet","type":"address"},{"internalType":"uint64[]","name":"liquidationQuoteQuantities","type":"uint64[]"}],"internalType":"struct WalletLiquidationArguments","name":"liquidationArguments","type":"tuple"}],"name":"liquidateWalletExit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"counterpartyWallet","type":"address"},{"internalType":"address","name":"liquidatingWallet","type":"address"},{"internalType":"uint64[]","name":"liquidationQuoteQuantities","type":"uint64[]"}],"internalType":"struct WalletLiquidationArguments","name":"liquidationArguments","type":"tuple"}],"name":"liquidateWalletInMaintenance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"counterpartyWallet","type":"address"},{"internalType":"address","name":"liquidatingWallet","type":"address"},{"internalType":"uint64[]","name":"liquidationQuoteQuantities","type":"uint64[]"}],"internalType":"struct WalletLiquidationArguments","name":"liquidationArguments","type":"tuple"}],"name":"liquidateWalletInMaintenanceDuringSystemRecovery","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"},{"internalType":"string","name":"assetSymbol","type":"string"}],"name":"loadBalanceBySymbol","outputs":[{"internalType":"int64","name":"","type":"int64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"},{"internalType":"string","name":"assetSymbol","type":"string"}],"name":"loadBalanceStructBySymbol","outputs":[{"components":[{"internalType":"bool","name":"isMigrated","type":"bool"},{"internalType":"int64","name":"balance","type":"int64"},{"internalType":"int64","name":"costBasis","type":"int64"},{"internalType":"uint64","name":"lastUpdateTimestampInMs","type":"uint64"},{"internalType":"contract IManagedAccountProvider","name":"managedAccountProvider","type":"address"}],"internalType":"struct Balance","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"loadBaseAssetSymbolsWithOpenPositionsByWallet","outputs":[{"internalType":"string[]","name":"","type":"string[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"index","type":"uint8"}],"name":"loadBridgeAdapter","outputs":[{"internalType":"contract IBridgeAdapter","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"loadBridgeAdaptersLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"index","type":"uint8"}],"name":"loadIndexPriceAdapter","outputs":[{"internalType":"contract IIndexPriceAdapter","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"loadIndexPriceAdaptersLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"loadLastNonceInvalidationForWallet","outputs":[{"components":[{"internalType":"uint64","name":"timestampInMs","type":"uint64"},{"internalType":"uint256","name":"effectiveBlockTimestamp","type":"uint256"}],"internalType":"struct NonceInvalidation","name":"nonceInvalidation","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"index","type":"uint8"}],"name":"loadManagedAccountProvider","outputs":[{"internalType":"contract IManagedAccountProvider","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"loadManagedAccountProvidersLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"index","type":"uint8"}],"name":"loadMarket","outputs":[{"components":[{"internalType":"bool","name":"exists","type":"bool"},{"internalType":"bool","name":"isActive","type":"bool"},{"internalType":"string","name":"baseAssetSymbol","type":"string"},{"internalType":"uint64","name":"indexPriceAtDeactivation","type":"uint64"},{"internalType":"uint64","name":"lastIndexPrice","type":"uint64"},{"internalType":"uint64","name":"lastIndexPriceTimestampInMs","type":"uint64"},{"components":[{"internalType":"uint64","name":"initialMarginFraction","type":"uint64"},{"internalType":"uint64","name":"maintenanceMarginFraction","type":"uint64"},{"internalType":"uint64","name":"incrementalInitialMarginFraction","type":"uint64"},{"internalType":"uint64","name":"baselinePositionSize","type":"uint64"},{"internalType":"uint64","name":"incrementalPositionSize","type":"uint64"},{"internalType":"uint64","name":"maximumPositionSize","type":"uint64"},{"internalType":"uint64","name":"minimumPositionSize","type":"uint64"}],"internalType":"struct OverridableMarketFields","name":"overridableFields","type":"tuple"}],"internalType":"struct Market","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"loadMarketsLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"loadOutstandingWalletFunding","outputs":[{"internalType":"int64","name":"","type":"int64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"loadQuoteQuantityAvailableForExitWithdrawal","outputs":[{"internalType":"int64","name":"","type":"int64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"loadTotalAccountValueFromIndexPrices","outputs":[{"internalType":"int64","name":"","type":"int64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"loadTotalAccountValueFromOraclePrices","outputs":[{"internalType":"int64","name":"","type":"int64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"loadTotalInitialMarginRequirementFromIndexPrices","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"loadTotalInitialMarginRequirementFromOraclePrices","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"loadTotalMaintenanceMarginRequirementFromIndexPrices","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"loadTotalMaintenanceMarginRequirementFromOraclePrices","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"loadWalletExitStatus","outputs":[{"components":[{"internalType":"bool","name":"exists","type":"bool"},{"internalType":"uint64","name":"effectiveBlockTimestamp","type":"uint64"},{"internalType":"enum WalletExitAcquisitionDeleveragePriceStrategy","name":"deleveragePriceStrategy","type":"uint8"}],"internalType":"struct WalletExit","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"marketOverridesByBaseAssetSymbolAndWallet","outputs":[{"internalType":"bool","name":"exists","type":"bool"},{"components":[{"internalType":"uint64","name":"initialMarginFraction","type":"uint64"},{"internalType":"uint64","name":"maintenanceMarginFraction","type":"uint64"},{"internalType":"uint64","name":"incrementalInitialMarginFraction","type":"uint64"},{"internalType":"uint64","name":"baselinePositionSize","type":"uint64"},{"internalType":"uint64","name":"incrementalPositionSize","type":"uint64"},{"internalType":"uint64","name":"maximumPositionSize","type":"uint64"},{"internalType":"uint64","name":"minimumPositionSize","type":"uint64"}],"internalType":"struct OverridableMarketFields","name":"overridableFields","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"migrateQuoteTokenAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"oraclePriceAdapter","outputs":[{"internalType":"contract IOraclePriceAdapter","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ownerWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"pendingDepositQuantityByWallet","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"positionBelowMinimumLiquidationPriceToleranceMultiplier","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"baseAssetSymbol","type":"string"},{"internalType":"int64","name":"fundingRate","type":"int64"}],"name":"publishFundingMultiplier","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"indexPriceAdapter","type":"address"},{"internalType":"bytes","name":"payload","type":"bytes"}],"internalType":"struct IndexPricePayload[]","name":"encodedIndexPrices","type":"tuple[]"}],"name":"publishIndexPrices","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"quoteTokenAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"removeAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"removeDispatcher","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"removeOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newAdmin","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IBridgeAdapter[]","name":"newBridgeAdapters","type":"address[]"}],"name":"setBridgeAdapters","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newChainPropagationPeriodInS","type":"uint256"}],"name":"setChainPropagationPeriod","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ICustodian","name":"newCustodian","type":"address"}],"name":"setCustodian","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"newDelegateKeyExpirationPeriodInMs","type":"uint64"}],"name":"setDelegatedKeyExpirationPeriod","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"isEnabled","type":"bool"}],"name":"setDepositEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"setDepositIndex","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newDispatcherWallet","type":"address"}],"name":"setDispatcher","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newExitFundWallet","type":"address"}],"name":"setExitFundWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newFeeWallet","type":"address"}],"name":"setFeeWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IIndexPriceAdapter[]","name":"newIndexPriceAdapters","type":"address[]"}],"name":"setIndexPriceAdapters","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newInsuranceFundWallet","type":"address"}],"name":"setInsuranceFundWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IManagedAccountProvider[]","name":"newManagedAccountProviders","type":"address[]"}],"name":"setManagedAccountProviders","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"baseAssetSymbol","type":"string"},{"components":[{"internalType":"uint64","name":"initialMarginFraction","type":"uint64"},{"internalType":"uint64","name":"maintenanceMarginFraction","type":"uint64"},{"internalType":"uint64","name":"incrementalInitialMarginFraction","type":"uint64"},{"internalType":"uint64","name":"baselinePositionSize","type":"uint64"},{"internalType":"uint64","name":"incrementalPositionSize","type":"uint64"},{"internalType":"uint64","name":"maximumPositionSize","type":"uint64"},{"internalType":"uint64","name":"minimumPositionSize","type":"uint64"}],"internalType":"struct OverridableMarketFields","name":"overridableFields","type":"tuple"},{"internalType":"address","name":"wallet","type":"address"}],"name":"setMarketOverrides","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IOraclePriceAdapter","name":"newOraclePriceAdapter","type":"address"}],"name":"setOraclePriceAdapter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"setOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"newPositionBelowMinimumLiquidationPriceToleranceMultiplier","type":"uint64"}],"name":"setPositionBelowMinimumLiquidationPriceToleranceMultiplier","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"skim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"sourceWallet","type":"address"},{"internalType":"address","name":"destinationWallet","type":"address"},{"internalType":"uint64","name":"grossQuantity","type":"uint64"},{"internalType":"uint64","name":"gasFee","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Transfer","name":"transfer_","type":"tuple"}],"name":"transfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"baseAssetSymbol","type":"string"},{"internalType":"address","name":"wallet","type":"address"}],"name":"unsetMarketOverridesForWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"wallet","type":"address"},{"internalType":"uint64","name":"grossQuantity","type":"uint64"},{"internalType":"uint64","name":"maximumGasFee","type":"uint64"},{"internalType":"address","name":"bridgeAdapter","type":"address"},{"internalType":"bytes","name":"bridgeAdapterPayload","type":"bytes"},{"internalType":"uint64","name":"gasFee","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Withdrawal","name":"withdrawal","type":"tuple"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"withdrawExit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"withdrawExitAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"depositorWallet","type":"address"},{"internalType":"address","name":"managerWallet","type":"address"},{"internalType":"uint64","name":"quantity","type":"uint64"}],"name":"withdrawExitFromManagedAccount","outputs":[],"stateMutability":"nonpayable","type":"function"}]

6101608060405234610743576000616af5803803809161001f8286610779565b843982019160e08184031261073f5780516001600160a01b038116919082900361073b5761004f6020820161079c565b9161005c6040830161079c565b60608301516001600160401b0381116107375783019386601f86011215610737578451946001600160401b038611610723578560051b97604051966100a460208b0189610779565b875260208701916020839a82010191821161071f57602001915b8183106106fb575050506100d46080850161079c565b60a0850151949093906001600160a01b03861686036106f75760c06100f9910161079c565b9060405161010681610748565b600b815260208101906a4b6174616e61506572707360a81b82526040519161012d83610748565b600583526020830191640312e302e360dc1b835261014a816107c7565b610120526101578461097a565b61014052519020918260e05251902080610100524660a0526040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a0815260c081019181831060018060401b038411176106e35782604052815190206080523060c0523360018060a01b031960025416176002553360018060a01b03196003541617600355821580156106d9575b15610695575050600580546001600160a01b031916919091179055803b1561065057601a80546001600160a01b0319166001600160a01b03928316179055601f541673e2df7954fbd89f90a447f4a830205618d70bbe8c3b1561064c5760405163383e73e760e21b81526004808201929092526001600160a01b0383166024820152604481019190915260066064820152868160848173e2df7954fbd89f90a447f4a830205618d70bbe8c5af4801561064157610626575b50601f80546001600160a01b0319166001600160a01b03928316179055600354163303610614576001600160a01b03168015610602576020546001600160a01b0381168281146105f057827f9f4f5dce3c4d197b5d7496cb96e25f0a89809167195964b0daa3ef5fed63c00a9360409360018060a01b0319161760205582519182526020820152a16001600160a01b038116156105b757602180546001600160a01b0319166001600160a01b0392909216919091179055825b825160ff8216101561041957600581901b611fe0168301602001516001600160a01b03163b156103c85760ff8091169081146103b457600101610376565b634e487b7160e01b84526011600452602484fd5b60405162461bcd60e51b815260206004820152602360248201527f496e76616c696420496e6465782050726963652041646170746572206164647260448201526265737360e81b6064820152608490fd5b50905190929091906001600160401b0383116105a3576801000000000000000083116105a357600e5483600e5580841061057b575b50600e825290600080516020616ad5833981519152905b83811061055e57846001600160a01b0381163b1561050d57601780546001600160a01b0319166001600160a01b0392909216919091179055600c8054600160a01b600160e01b031916600160a01b600160e01b03179055604051615fcd9081610b08823960805181615cc8015260a05181615d92015260c05181615c92015260e05181615d1701526101005181615d3d01526101205181612bd101526101405181612bfb0152f35b60405162461bcd60e51b8152602060048201526024808201527f496e76616c6964204f7261636c652050726963652041646170746572206164646044820152637265737360e01b6064820152608490fd5b82516001600160a01b031681830155602090920191600101610465565b600e835261059d90600080516020616ad58339815191529081019085016107b0565b3861044e565b634e487b7160e01b82526041600452602482fd5b60405162461bcd60e51b8152602060048201526011602482015270125b9d985b1a59081251881dd85b1b195d607a1b6044820152606490fd5b6040516376330f4960e11b8152600490fd5b60405163a5f90a1160e01b8152600490fd5b6040516358f509d960e11b8152600490fd5b9095906001600160401b0381116105a35760405294386102bd565b6040513d89823e3d90fd5b8680fd5b60405162461bcd60e51b815260206004820152601b60248201527f496e76616c69642071756f7465206173736574206164647265737300000000006044820152606490fd5b62461bcd60e51b8252602060c4820152601860e48201527f496e76616c6964206d6967726174696f6e20736f75726365000000000000000061010490910152606490fd5b50823b1515610205565b634e487b7160e01b8b52604160045260248bfd5b8780fd5b82516001600160a01b038116810361071b578152602092830192016100be565b8980fd5b8880fd5b634e487b7160e01b87526041600452602487fd5b8580fd5b8280fd5b5080fd5b600080fd5b604081019081106001600160401b0382111761076357604052565b634e487b7160e01b600052604160045260246000fd5b601f909101601f19168101906001600160401b0382119082101761076357604052565b51906001600160a01b038216820361074357565b8181106107bb575050565b600081556001016107b0565b80516020919082811015610861575090601f82511161080257808251920151908083106107f357501790565b82600019910360031b1b161790565b90604051809263305a27a960e01b82528060048301528251908160248401526000935b828510610848575050604492506000838284010152601f80199101168101030190fd5b8481018201518686016044015293810193859350610825565b6001600160401b038111610763576000928354926001938481811c91168015610970575b8382101461095c57601f8111610935575b5081601f84116001146108d2575092829391839286946108c7575b50501b916000199060031b1c191617905560ff90565b0151925038806108b1565b919083601f1981168780528488209488905b8883831061091b5750505010610902575b505050811b01905560ff90565b015160001960f88460031b161c191690553880806108f5565b8587015188559096019594850194879350908101906108e4565b61095690868052601f848820910160051c810190601f860160051c016107b0565b38610896565b634e487b7160e01b86526022600452602486fd5b90607f1690610885565b805160209081811015610a035750601f8251116109a457808251920151908083106107f357501790565b90604051809263305a27a960e01b82528060048301528251908160248401526000935b8285106109ea575050604492506000838284010152601f80199101168101030190fd5b84810182015186860160440152938101938593506109c7565b9192916001600160401b0381116107635760019182548381811c91168015610afd575b82821014610ae757601f8111610abe575b5080601f8311600114610a74575081929394600092610a69575b5050600019600383901b1c191690821b17905560ff90565b015190503880610a51565b90601f198316958460005282600020926000905b888210610aa7575050838596971061090257505050811b01905560ff90565b808785968294968601518155019501930190610a88565b610ae19084600052601f83600020910160051c810190601f850160051c016107b0565b38610a37565b634e487b7160e01b600052602260045260246000fd5b90607f1690610a2656fe608080604052600436101561001357600080fd5b600090813560e01c908162c0d66414614b5557508062e6ed7b14614b1b5780630227d10314614a1957806307b26b35146149385780630c187a72146148df5780630d1c46eb146148c157806310469abf146148595780631079364c1461483b57806313af4035146147eb57806315ed46e2146146e6578063162df1c9146146525780631768b2a2146145b757806319759e48146144935780631aa9e709146143b45780631c794f24146142c257806320e6a8e31461425657806321919d251461422d578063219896db14614100578063246f8b96146140cc5780632533a67e14614064578063262ca5421461401d5780632bfd5f5914613e1d5780632e9ac8df14613d1457806336b19cd714613ceb578063375b74c314613cc257806338dc0c5014613c995780633cfaad0214613bbc578063403f373114613b55578063498ae77614613aba5780634a284ef914613a51578063529e6e7e1461399d5780635456c157146139165780635656fc78146138f357806359702e171461378357806359ad6f50146136cd5780635b17d04b146136225780635f3462c4146135b0578063654a439c146134fa57806368ac20ea1461322d5780636dc0e8d9146131ed5780636e553f65146130e8578063704b6c021461309357806375bad745146130275780637698bf4c14612f6957806377affc1814612ece57806378e890ba14612eab5780637b89893914612e7e578063807cc2df14612e6057806381ae3aa114612ce657806383a5059014612cc857806384b0196e14612bbb57806384fa4d0214612b9d578063851216f814612a89578063863a01c8146129d85780638afda202146129ba5780638ccd0860146128f95780638d113b02146126985780638f51afe0146125bf5780638fde4c641461253e578063904cf00c1461246d578063909bdf5e1461244257806390d49b9d146123b85780639335dcb71461238f5780639388c24f1461236657806393916380146121d7578063985c4af5146121765780639895edbe14611f6e5780639a202d4714611f265780639e9aaaf814611e745780639fd9605914611d83578063a2ecaea314611cda578063a39c4f6114611c3e578063a6ec7a5a14611b4d578063a8028b8e14611a51578063ac9fc083146119b3578063b1cc952d146118db578063b72a334c146117e2578063ba22bd7614611745578063bad346201461171c578063bc25cf7714611680578063c662a0c21461144b578063c7744049146113af578063c9a90e9314611302578063d91bb791146112d9578063daa3629214611162578063db56627a146110c7578063dbba225d14610fc3578063dd7c4fe314610edb578063e0c56b1214610e23578063e916bbf114610b41578063eb3d16c614610816578063eee41690146107b4578063ef262be31461069c578063f25f4b5614610675578063f86a6fc4146105295763f9f196221461043f57600080fd5b3461051357602090816003193601126105135761045a614b7a565b60018060a01b0380601754169160405192631f820d8f60e01b84526004840152166024820152600460448201526006606482015260106084820152601160a4820152601560c4820152601960e4820152828161010481735d5661fbfbfa061fb51061f0d9759ff90606c4dd5af491821561051d5780926104e2575b50506040519060070b8152f35b9091508282813d8311610516575b6104fa8183614c7f565b81010312610513575061050c906155a8565b38806104d5565b80fd5b503d6104f0565b604051903d90823e3d90fd5b503461051357602080600319360112610671576004356001600160401b03811161066d5761055b903690600401614d64565b610563615c35565b601f546001600160a01b039081168085526006845260408520549092919061065b5783916105b891600d5490602154166040519586948594634576691b60e11b865261016060048701526101648601906155b6565b9260016024860152604485015260648401526084830152600460a4830152600660c4830152601060e483015260116101048301526013610124830152601561014483015203817357bf2dec1454ef43b45ba2c011df4fc773e2312a5af4801561065057610623578280f35b813d8311610649575b6106368183614c7f565b810103126106445738808280f35b600080fd5b503d61062c565b6040513d85823e3d90fd5b604051631895a5bf60e21b8152600490fd5b8280fd5b5080fd5b5034610513578060031936011261051357602080546040516001600160a01b039091168152f35b5034610513576020366003190112610513576004356001600160401b038111610671576106cd903690600401614d64565b6106d5615c35565b601f546001600160a01b03908116808452600660205260408420549192909161065b57602154849316731155250006ea43581fe695e165d68ccefd14a82d803b156107b05761074685936040519586948593849363ec9a531f60e01b855261014060048601526101448501906155b6565b91602484015260448301526004606483015260066084830152601060a4830152601160c4830152601360e48301526015610104830152601961012483015203915af480156107a5576107955750f35b61079e90614c00565b6105135780f35b6040513d84823e3d90fd5b8480fd5b503461051357602036600319011261051357600435906001600160401b0390818311610513575061080260206107ef81943690600401614ca2565b8160405193828580945193849201614e29565b810160118152030190205416604051908152f35b503461051357610120366003190112610513576001600160401b0360043581811161066d57610849903690600401614ca2565b60e0366023190112610644576040519161086283614c13565b60243581811681036106445783526044359181831683036106445760208085019384526064359383851685036106445760408601948552608435948486168603610644576060870195865260a435958587168703610644576080880196875260c4359386851685036106445760a0890194855260e4359587871687036106445760c08a01968752610104356001600160a01b038181169291839003610644578c60048583600c541660405192838092635aa6e67560e01b82525afa91821561051d5791610b14575b50163303610b02576040519160ff82519385818186019661094c81838a614e29565b81016015815203019020541615610af05780610a08575093610a059a8996946109c58861098d81976004976109fd9d9b604051938492839251928391614e29565b81016015815203019020946109bd8280600389019751169a60018060401b03199b8c895416178855511686615938565b511683615c0c565b5181546001600160c01b031660c09190911b6001600160c01b0319161790559851980180549092169716969096178655511684615938565b511690615c0c565b80f35b9350935081985060c09650610a059995506002945090610a4a9160405194610a2f86614c2e565b60018652828601978852604051938492839251928391614e29565b8101601381520301902090600052865260406000209051151560ff8019835416911617815560018101925192610a9986808651169860018060401b0319998a8554161784558601511682615938565b610aa98660408601511682615c0c565b606084015181546000196001881b0190811691871b90191617905560808301519101805490951690841617845560a0810151610ae790841685615938565b01511690615c0c565b6040516366cee12960e01b8152600490fd5b6040516308904b1160e01b8152600490fd5b610b349150853d8711610b3a575b610b2c8183614c7f565b810190615558565b3861092a565b503d610b22565b50346105135760031960203682011261067157600435906001600160401b03821161066d57610100908236030112610671576040519061010082016001600160401b03811183821017610e0d57604052610b9d8160040161501e565b8252610bab60248201614bbc565b6020830152610bbc60448201614d50565b6040830152610bcd60648201614d50565b6060830152610bde60848201614bbc565b608083015260a48101356001600160401b038111610e0957610c069060043691840101614ca2565b60a0830152610c1760c48201614d50565b60c083015260e4810135906001600160401b038211610e09576004610c3f9236920101614ca2565b60e0820152610c4c615c35565b601f546001600160a01b03168083526006602052604083205490919061065b578291610c76615c8f565b600c54600d54602054601a546001600160a01b039384169692949293908116911673e8d88c09c626362483e09ea23f2633f2b15fbfae3b15610e0557604080516345e295e160e01b8152610200600482015285516001600160801b031661020482015260208601516001600160a01b03908116610224830152918601516001600160401b03908116610244830152606087015116610264820152608086015190911661028482015260a08501516101006102a483015290978997899788979193909291610d799160e090610d4f906103048c0190614e4c565b60c08301516001600160401b03166102c48c015291015189820361020319016102e48b0152614e4c565b95602488015260448701526064860152608485015260a484015260c4830152600460e483015260066101048301526009610124830152600b6101448301526010610164830152601161018483015260136101a483015260156101c4830152601b6101e4830152038173e8d88c09c626362483e09ea23f2633f2b15fbfae5af480156107a5576107955750f35b8780fd5b8380fd5b634e487b7160e01b600052604160045260246000fd5b5034610513576020366003190112610513576004356001600160401b03811161067157610e54903690600401614ca2565b610e5c615c35565b601f546001600160a01b031682526006602052604082205461065b57819073b99826ba9304602032884f13439e1ba5dee62609803b15610ed757610ebf9183916040518080958194633ad2a03760e21b8352604060048401526044830190614e4c565b6015602483015203915af480156107a5576107955750f35b5050fd5b503461051357610f27906020610ef036614cf8565b60405163953af0f760e01b81526001600160a01b039092166004830152610100602483015290938491829190610104830190614e4c565b600460448301526006606483015260106084830152601160a4830152601560c4830152601960e48301520381736da84c3990e3f89f8f7045df2d7e731f9695805e5af490811561051d578091610f86575b6020826040519060070b8152f35b90506020823d602011610fbb575b81610fa160209383614c7f565b810103126105135750610fb56020916155a8565b38610f78565b3d9150610f94565b5034610513576020366003190112610513576004356001600160801b0381169081900361064457601c5460405190634652502b60e01b825260166004830152826024830152604482015260408160648173b22dd85e1c87feaaf33192bf4dc099e5c53ee6665af49081156106505783908492611071575b5091608091600080516020615f788339815191529360405192338452602084015260018060401b031660408301526060820152a180f35b9150506040813d6040116110bf575b8161108d60409383614c7f565b8101031261066d57600080516020615f78833981519152918160206110b3608094615515565b9101519250909261103a565b3d9150611080565b5034610513576020366003190112610513576004356001600160a01b038181169182900361066d576004602082600c541660405192838092635aa6e67560e01b82525afa908115611157578491611138575b50163303610b0257601780546001600160a01b03191691909117905580f35b611151915060203d602011610b3a57610b2c8183614c7f565b38611119565b6040513d86823e3d90fd5b50346105135761117136614cf8565b906111cf60a06040519361118485614c64565b8585528560208601528560408601528560806060968288820152015260405180938192630f40e4b960e11b8352600180861b0380971660048401528760248401526064830190614e4c565b600460448301520381736da84c3990e3f89f8f7045df2d7e731f9695805e5af493841561051d578094611242575b5050608060a09360405193815115158552602082015160070b6020860152604082015160070b604086015260018060401b038183015116908501520151166080820152f35b90935060a0843d60a0116112d1575b8161125e60a09383614c7f565b81010312610513576040519361127385614c64565b8051801515810361066d57855260809061128f602082016155a8565b60208701526112a0604082016155a8565b60408701526112b0858201615515565b8587015201519082821682036105135750836080918260a0960152936111fd565b3d9150611251565b5034610513578060031936011261051357601e546040516001600160a01b039091168152602090f35b503461051357602090816003193601126105135761133c82611322614b7a565b604051809381926336dac1a960e01b835260048301615529565b03817347bb857393f40b08a62a52e81fc66b59c40f6c175af491821561051d578092611377575b50506040516001600160401b039091168152f35b9091508282813d83116113a8575b61138f8183614c7f565b8101031261051357506113a190615515565b3880611363565b503d611385565b5034610513576020366003190112610513576004356001600160401b038111610671576113e0903690600401614ca2565b6113e8615c35565b601f546001600160a01b031682526006602052604082205461065b57819073b99826ba9304602032884f13439e1ba5dee62609803b15610ed757610ebf918391604051808095819463627f3f8360e01b8352604060048401526044830190614e4c565b50346105135760031960203682011261067157600435906001600160401b03821161066d5760c0908236030112610671576040519060c082016001600160401b03811183821017610e0d576040526114a58160040161501e565b82526114b360248201614bbc565b60208301526114c460448201614bbc565b60408301526114d560648201614d50565b60608301526114e660848201614d50565b608083015260a4810135906001600160401b038211610e0957600461150e9236920101614ca2565b60a082015261151b615c35565b601f546001600160a01b03168083526006602052604083205461065b578291611542615c8f565b6021546020546001600160a01b03908116949116929091733fc4c196e966d34db0b5def1401242dc164dfc6a3b1561167c576040805162e2278f60e51b81526101a0600482015283516001600160801b03166101a482015260208401516001600160a01b039081166101c4830152918401519091166101e482015260608301516001600160401b0390811661020483015260808401511661022482015260a09092015160c0610244840152919486948694859461160490610264870190614e4c565b936024860152604485015260648401526084830152600460a4830152600660c4830152600860e48301526010610104830152601161012483015260136101448301526015610164830152601b6101848301520381733fc4c196e966d34db0b5def1401242dc164dfc6a5af480156107a5576107955750f35b8580fd5b50346105135760203660031901126105135761169a614b7a565b6003546001600160a01b03908116330361170a57829173e8d88c09c626362483e09ea23f2633f2b15fbfae9160205416823b156117055760405163583d54a360e11b81529284928492839182916116f4916004840161558e565b03915af480156107a5576107955750f35b505050fd5b6040516358f509d960e11b8152600490fd5b5034610513578060031936011261051357601a546040516001600160a01b039091168152602090f35b50346105135760203660031901126105135761175f614b7a565b6003546001600160a01b03908116330361170a578082169182156117d057601e54918216908184146117be57600080516020615f58833981519152916117aa6040519283928361558e565b0390a16001600160a01b03191617601e5580f35b6040516376330f4960e11b8152600490fd5b60405163a5f90a1160e01b8152600490fd5b5034610513576020366003190112610513576117fc614b7a565b6003546001600160a01b0391908216331415806118cd575b6118bb57602090611823615c5b565b82600c54169061185b84601f541694806017541690601a5416600d5491604051978896879663175b99c160e21b885260048801615b92565b038173e8d88c09c626362483e09ea23f2633f2b15fbfae5af49081156107a5578291611889575b50600d5580f35b90506020813d6020116118b3575b816118a460209383614c7f565b81010312610644575138611882565b3d9150611897565b6040516325b232e360e21b8152600490fd5b5081601e5416331415611814565b5034610513576118ea36614ede565b6118f2615c35565b6118fa615c5b565b602061193a600d549260018060a01b039384601f541694602154166040519586948594634031316d60e01b8652610180600487015261018486019061571b565b9260248501526044840152606483015260026084830152600460a4830152600660c4830152601060e483015260116101048301526013610124830152601561014483015260196101648301520381738903624c18e665dd62ba9e0bb548103b6119df8c5af49081156107a55782916118895750600d5580f35b503461051357602036600319011261051357806119ce614b7a565b739defe6fd18341097f02c3261430017eb5a1da1ad90813b15610ed75760405163143f692d60e01b81526001600160a01b0390911660048083019190915260248201526006604482015260c0606482015290829082908180611a3260c48201615b1f565b60196084830152601b60a483015203915af480156107a5576107955750f35b503461051357611a6036614ede565b611a68615c35565b601f546001600160a01b03908116808452600660205260408420549192909161065b57826020820151168452601b60205260ff60408520541615611b3b5760215484931673426f7f2e61715343ecc6603628a033f5516c23f1803b156107b057611af4859360405195869485938493631dfa49c160e21b8552610120600486015261012485019061571b565b91602484015260448301526004606483015260066084830152601060a4830152601160c4830152601360e4830152601561010483015203915af480156107a5576107955750f35b604051631032047160e01b8152600490fd5b50346105135760208060031936011261067157906001600160a01b03611b71614b7a565b1681526006825260408120805491611b8883614ec7565b92611b966040519485614c7f565b8084528484018093835285832083915b838310611c0b5750505050604051928484019085855251809152604084019460408260051b8601019392955b828710611bdf5785850386f35b909192938280611bfb600193603f198a82030186528851614e4c565b9601920196019592919092611bd2565b600188819260409a99979a51611c2c81611c258189615685565b0382614c7f565b81520192019201919096939596611ba6565b5034610513576040366003190112610513576004356001600160401b03811161067157611c6f903690600401614ca2565b611c8b6020602435928160405193828580945193849201614e29565b81016010815203019020805482101561066d576080925060005260206000200154604051908060070b82528060401c60070b602083015280831c60070b604083015260c01d60070b6060820152f35b5034610513576020366003190112610513576040606091611cf9614b7a565b81838051611d0681614c49565b828152826020820152015260018060a01b03168152601b6020522060405190611d2e82614c49565b5460ff81161515825260018060401b0360ff6020840192828160081c16845260481c16916040840192611d60816154f5565b83526040519351151584525116602083015251611d7c816154f5565b6040820152f35b50346105135780600319360112610513576003546001600160a01b03908116330361170a57600c54906002600160401b031960a083901c6001600160401b031601611e62576005541680611dfc5750815b600160a01b600160e01b031990911660a09190911b600160a01b600160e01b031617600c5580f35b602060049160405192838092637b89893960e01b82525afa908115610650578391611e28575b50611dd4565b90506020813d602011611e5a575b81611e4360209383614c7f565b8101031261066d57611e5490615515565b38611e22565b3d9150611e36565b60405163a72b88cf60e01b8152600490fd5b503461051357604036600319011261051357611e8e614d3a565b611e96614b90565b60035490916001600160a01b0391821633141580611f18575b6118bb5783927380d605b39e39df70a86e8d1977c8b8d12473d6e2803b156107b0578492608491604051958694859363072d70d360e31b855260018060401b0316600485015216602483015260046044830152601960648301525af480156107a5576107955750f35b5081601e5416331415611eaf565b50346105135780600319360112610513576002546001600160a01b03163303611f5c57600380546001600160a01b031916905580f35b604051630d690a6360e21b8152600490fd5b50346105135760031960a03682011261067157611f89614b90565b90611f92614ba6565b6001600160401b03926064359084821161167c573660238301121561167c57816004013591858311612172573660248483010111612172576001600160a01b039460843586811694919085900361064457739defe6fd18341097f02c3261430017eb5a1da1ad95600c549188601f54169360ff600f5416958a601a5416978a3b1561216e57918b9a999897969593918b8f9d94968f976040519e8f9163fedcb20d60e01b835260043560048401521690602401521660448d015260648c0161020090526102048c01906024019161206892615b71565b9760848b01523360a48b01528988030160c48a0152600b549687815260209060200197600b8c5260208c20918c905b82821061214e5750505050811660e489015260a01c166101048701526101248601521515610144850152610164840152600461018484015260126101a484015260196101c4840152601b6101e4840152839183918290039082905af480156107a55761213a575b5050600c5490600160a01b600160e01b039061211f9060a084901c1661590a565b60a01b16600160a01b600160e01b03199190911617600c5580f35b61214390614c00565b6106715781386120fe565b835485168b528f9d50998a01998e96506001938401939190910190612097565b8d80fd5b8680fd5b503461051357602036600319011261051357612190614b7a565b600c54601f54601754601a54600d5460405163ab82d9b760e01b8152956020958795869561185b956001600160a01b03908116949181169392811692169060048801615b92565b5034610513576003199060203683018113610671576001600160401b0390600435828111610e095761220d903690600401614bd0565b929094612218615c35565b73b99826ba9304602032884f13439e1ba5dee6260995863b1561167c579291604051946379d7258160e01b865280606487016060600489015252608486019060848160051b88010195809389915b8383106122d85750505050505083830301602484015280600e54928381520191600e85528185209185905b8282106122b857601560448701528680878181808a03818e5af480156107a5576107955750f35b83546001600160a01b031685529384019360019384019390910190612291565b909192939496976083198a82030186528735603e19833603018112156123625782016001600160a01b0361230b82614bbc565b16825289810135601e198236030181121561235e570189813591019184821161235e57813603831361235e5761234f6001938c936040848187809701520191615b71565b9a990196019493019190612266565b8c80fd5b8b80fd5b50346105135780600319360112610513576017546040516001600160a01b039091168152602090f35b50346105135780600319360112610513576002546040516001600160a01b039091168152602090f35b5034610513576020366003190112610513576123d2614b7a565b6003546001600160a01b0391908216330361170a5781811680156117d057602054928316908181146117be577f9f4f5dce3c4d197b5d7496cb96e25f0a89809167195964b0daa3ef5fed63c00a9360018060a01b0319161760205561243c6040519283928361558e565b0390a180f35b5034610513578060031936011261051357601d546040805191901c6001600160401b03168152602090f35b5034610513578061247d36615205565b612485615c35565b739defe6fd18341097f02c3261430017eb5a1da1ad906124a3615c8f565b823b1561170557604051631962329160e01b81526101606004820152928492849283918291612515916124db90610164850190615961565b9060248401526004604484015260066064840152600a6084840152601060a4840152601160c48401526003198382030160e4840152615b1f565b60136101048301526015610124830152601b61014483015203915af480156107a5576107955750f35b50346105135760209081600319360112610513576125858261255e614b7a565b601754604051633c68d4d560e21b815293849283926001600160a01b031660048401615615565b0381735d5661fbfbfa061fb51061f0d9759ff90606c4dd5af491821561051d5780926113775750506040516001600160401b039091168152f35b503461051357806125cf36615205565b6125d7615c35565b739defe6fd18341097f02c3261430017eb5a1da1ad906125f5615c8f565b823b15611705576040516387d4e38160e01b8152610180600482015292849284928391829161266f9161262d90610184850190615961565b9060248401526004604484015260066064840152600a6084840152600b60a4840152601060c4840152601160e484015260031983820301610104840152615b1f565b60136101248301526015610144830152601b61016483015203915af480156107a5576107955750f35b503461051357600319602036820112610671576004356001600160401b03811161066d576101a08136039283011261066d57604051916126d783614c13565b6126e382600401614fb4565b83526126f160248301614fb4565b60208401526044820135906001600160401b0382116107b05761271c60e09260043691860101614ca2565b604085015261272d60648401614d50565b606085015261273e60848401614d50565b608085015261274f60a48401614d50565b60a085015260c3190112610644576127dd6101846040519261277084614c13565b61277c60c48201614d50565b845261278a60e48201614d50565b602085015261279c6101048201614d50565b60408501526127ae6101248201614d50565b60608501526127c06101448201614d50565b60808501526127d26101648201614d50565b60a085015201614d50565b60c0828101919091528201526003546001600160a01b0316330361170a576017548291906001600160a01b031673b99826ba9304602032884f13439e1ba5dee626093b15610ed7578290604051809381926336766ecf60e01b835260c060048401528151151560c48401526020820151151560e48401526128b260c061287560408501516101a0610104880152610264870190614e4c565b60608501516001600160401b039081166101248801526080860151811661014488015260a086015116610164870152930151610184850190614e71565b6024830152601060448301526011606483015260146084830152601560a4830152038173b99826ba9304602032884f13439e1ba5dee626095af480156107a5576107955750f35b50346105135780600319360112610513576003546001600160a01b03908116330361170a57808291601a5416602082600c5416602460405180968193634a3ab40b60e11b83528660048401525af1918215611157577f581fa472f6627aea226b2cc9510361c22cde232383b96578169b86cb17f4b27c938593612999575b50821660018060a01b0319601a541617601a5561243c6040519283928361558e565b6129b391935060203d602011610b3a57610b2c8183614c7f565b9138612977565b50346105135780600319360112610513576020600b54604051908152f35b5034610513576020366003190112610513576129f2614d3a565b6003546001600160a01b0316330361170a576001600160401b03624c4b4082821611612a7757601d8054600160401b600160801b03198116604085811b600160401b600160801b03169190911790925581517f6075d76e45ec51fde3ac15edcdb7880a5da054423736dbe73f2416d7df88696b949093849361243c93901c168361577f565b60405163b06ab8a960e01b8152600490fd5b5034610513576020366003190112610513576004356001600160401b03811161067157612aba903690600401614d64565b612ac2615c35565b601f546001600160a01b03908116808452600660205260408420549192909161065b57826040820151168452601b60205260ff60408520541615611b3b57602154849316733e6623aecd7840d114cc814e342290daecfeb605803b156107b057612b4e85936040519586948593849363deb153d160e01b855261014060048601526101448501906155b6565b91602484015260448301526004606483015260066084830152601060a4830152601160c4830152601360e48301526015610104830152601b61012483015203915af480156107a5576107955750f35b50346105135780600319360112610513576020601254604051908152f35b5034610513578060031936011261051357612bf57f0000000000000000000000000000000000000000000000000000000000000000615db8565b90612c1f7f0000000000000000000000000000000000000000000000000000000000000000615e92565b60405160208082019391929091906001600160401b03851184861017610e0d579284926020612c7e8896612c7098604052858552604051988998600f60f81b8a5260e0858b015260e08a0190614e4c565b9088820360408a0152614e4c565b924660608801523060808801528460a088015286840360c088015251928381520193925b828110612cb157505050500390f35b835185528695509381019392810192600101612ca2565b50346105135780600319360112610513576020600e54604051908152f35b503461051357600319602036820112610671576001600160401b039060043590828211610e0957608090823603011261066d57604051916080830183811082821117610e0d5760405281600401358181116107b057612d4b9060043691850101614ca2565b8352612d5960248301614d50565b9060208401918252612d806064612d7260448601614bbc565b946040870195865201614d50565b9160608501928352612d90615c35565b601f546001600160a01b0390811687526006602052604087205490939061065b578695735d04d54916246bcebf425ea9af28e4163132b16f928560205416843b15612e5c5785612e0a948a986040519a8b998a98899863e9381bb160e01b8a5260e060048b015251608060e48b01526101648a0190614e4c565b965116610104880152511661012486015251166101448401526024830152600460448301526006606483015260106084830152601160a4830152601560c483015203915af480156107a5576107955750f35b8880fd5b50346105135780600319360112610513576020601c54604051908152f35b5034610513578060031936011261051357600c5460405160a09190911c6001600160401b03168152602090f35b50346105135780600319360112610513576020612ec6615c8f565b604051908152f35b5034610513576020908160031936011261051357612eea614b7a565b6040516343ce14af60e11b81526001600160a01b039091166004808301919091526024820152600660448201526010606482015260116084820152601560a4820152601960c4820152828160e4817347bb857393f40b08a62a52e81fc66b59c40f6c175af491821561051d5780926104e25750506040519060070b8152f35b503461051357602036600319011261051357612f83614b7a565b6003546001600160a01b03908116330361170a57829073e2df7954fbd89f90a447f4a830205618d70bbe8c9281601f541690843b15610e095760848492604051948593849263383e73e760e21b84526004840152169687602483015260046044830152600660648301525af480156107a557613013575b5050601f80546001600160a01b03191691909117905580f35b61301c90614c00565b610671578138612ffa565b5034610513576020366003190112610513576004359060ff82168203610513575060125481101561307d576012600052600080516020615f3883398151915201546040516001600160a01b039091168152602090f35b634e487b7160e01b600052603260045260246000fd5b5034610513576020366003190112610513576130ad614b7a565b6002546001600160a01b03919082163303611f5c5781169081156117d05760035490811682146117be576001600160a01b0319161760035580f35b503461051357604036600319011261051357613102614b90565b6001600160a01b039082908083166131e7575033915b7380d605b39e39df70a86e8d1977c8b8d12473d6e2600c549360018060401b039483601f54169160ff600f54169385601a5416813b15610e0557879587958a610164966040519a8b998a986330a02d2d60e11b8a5216600489015233602489015260043560448901528116606488015260a01c16608486015260a4850152151560c484015260e483015260046101048301526019610124830152601b6101448301525af480156107a55761213a575050600c5490600160a01b600160e01b039061211f9060a084901c1661590a565b91613118565b5034610513576020366003190112610513576020906001600160a01b03613212614b7a565b168152601982526040600180821b0391205416604051908152f35b5034610513576060366003190112610513576004356001600160401b0381116106715760e06003198236030112610671576040519061326b82614c13565b60048101356001600160401b038111610e095761328e9060043691840101614ca2565b825261329c60248201614d50565b60208301526132ad60448201614d50565b60408301526064810135908160070b82036106445760c49160608401526132d660848201614d50565b60808401526132e760a48201614d50565b60a08401520135600281101561066d5760c08201526024356001600160401b03811161066d5761331b903690600401615032565b906044356001600160401b038111610e095761333b903690600401615032565b613343615c35565b601f546001600160a01b03168085526006602052604085205490929061065b57601d548594906001600160401b031661337a615c8f565b60205460215491966001600160a01b03928316929091169073cf58d39226e085d1db6e9cf1fd3209f31976581a3b15612e5c576134589761346a8a986040519a8b998a99631e83380f60e01b8b5261024060048c015260c06133eb8c61032485519160e06102448201520190614e4c565b60208401516001600160401b039081166102648f0152604085015181166102848f0152606085015160070b6102a48f0152608085015181166102c48f015260a0850151166102e48e015292015161344181615799565b6103048c01528a82036003190160248c01526157a3565b8881036003190160448a0152906157a3565b946064870152608486015260a485015260c484015260e48301526004610104830152600661012483015260076101448301526010610164830152601161018483015260136101a483015260156101c483015260166101e48301526018610204830152601b610224830152038173cf58d39226e085d1db6e9cf1fd3209f31976581a5af480156107a5576107955750f35b503461051357602036600319011261051357613514614b7a565b600c54604051635aa6e67560e01b81526001600160a01b0392916020908290600490829087165afa80156111575783918591613591575b50163303610b025716808252601b60205260ff60408320541661357f57602180546001600160a01b03191691909117905580f35b604051637fa3a69760e01b8152600490fd5b6135aa915060203d602011610b3a57610b2c8183614c7f565b3861354b565b50346105135760206135d96135c436614de8565b92908160405193828580945193849201614e29565b810160138152030190209060018060a01b0316600052602052610100604060002061362061360e600160ff8454169301614fc1565b60405192151583526020830190614e71565bf35b50346105135760203660031901126105135760043580151590818103610644576003546001600160a01b0316330361170a57156136995760ff600f54166117be577f8824dcb92309d9474923a7f382fc61d5bca1b2ef464bfdf9572d18d23ac75ffd8280a15b60ff8019600f5416911617600f5580f35b60ff600f5416156117be577f717a16489831f10040458886f0f4947e8d82dee5a3890d9e602f419208260f078280a1613688565b50346105135760209081600319360112610513576136e9614b7a565b60018060a01b0380601f54169181601754166040519363280b345360e11b8552600485015260248401521660448201526004606482015260066084820152601060a4820152601160c4820152601360e482015260156101048201526019610124820152828161014481735d5661fbfbfa061fb51061f0d9759ff90606c4dd5af491821561051d5780926104e25750506040519060070b8152f35b503461051357600319602036820112610671576001600160401b039060043590828211610e0957606090823603011261066d57604051916137c383614c49565b81600401358181116107b0576137df9060043691850101614ca2565b835261380060446137f260248501614bbc565b936020860194855201614d50565b9160408401928352613810615c35565b60018060a01b039081601f5416928387526006602052604087205461065b578695734dbc24bcca09b43fdcd001569cfc7ad6bb4e2f8692846021541683601d5460401c1690853b156138ef578997613892956040519a8b998a9889986355d9a13160e01b8a5261014060048b01525160606101448b01526101a48a0190614e4c565b965116610164880152511661018486015260248501526044840152606483015260046084830152600660a4830152601060c4830152601160e48301526013610104830152601561012483015203915af480156107a5576107955750f35b8980fd5b5034610513578060031936011261051357602060ff600f54166040519015158152f35b503461051357602036600319011261051357613930614d3a565b6003546001600160a01b0316330361170a576001600160401b03818116640757b12c008111612a775761243c7f2c372c30a44b54aac428d2a395067f758cd7461390beb2955065a96873d11e3593601d549260018060401b0319841617601d55604051938493168361577f565b5034610513576060366003190112610513576139b7614d3a565b6001600160401b039060243582811691908290036106445783926139d9614ba6565b6139e1615c35565b739defe6fd18341097f02c3261430017eb5a1da1ad92833b1561167c57859360c49260405196879586946381f2e8b360e01b8652166004850152602484015260018060a01b031660448301526004606483015260196084830152601b60a48301525af480156107a5576107955750f35b50346105135780600319360112610513578073e8d88c09c626362483e09ea23f2633f2b15fbfae803b15613ab757816064916040519283809263010c92c960e21b825260048083015260066024830152601b60448301525af480156107a5576107955750f35b50fd5b50346105135760203660031901126105135780613ad5614b7a565b601c54601f546021546001600160a01b039081169390929183169173e8d88c09c626362483e09ea23f2633f2b15fbfae803b1561217257869460c4936040519788968795631e37201960e01b875260048701526024860152604485015216606483015260046084830152601b60a48301525af480156107a5576107955750f35b5034610513576020366003190112610513576004356001600160a01b038181169182900361066d578060035416330361170a57600c54908116611e6257813b15613baa576001600160a01b03191617600c5580f35b60405163a710429d60e01b8152600490fd5b50346105135760208060031936011261067157613bd7614b7a565b90604051613be481614c2e565b8381528181018490526001600160a01b039092168352601681526040832080549081613c26575b6040805185516001600160401b031681528486015181860152f35b91925060001981019190808311613c8557821015613c715783839160409552209060011b016001835191613c5983614c2e565b8180861b038154168352015482820152903880613c0b565b634e487b7160e01b84526032600452602484fd5b634e487b7160e01b85526011600452602485fd5b5034610513578060031936011261051357601f546040516001600160a01b039091168152602090f35b5034610513578060031936011261051357600c546040516001600160a01b039091168152602090f35b50346105135780600319360112610513576003546040516001600160a01b039091168152602090f35b503461051357613d2336614ede565b613d2b615c35565b601f546001600160a01b03908116808452600660205260408420549092919061065b57602091613d8291600d5490602154166040519586948594634031316d60e01b8652610180600487015261018486019061571b565b9260248501526044840152606483015260016084830152600460a4830152600660c4830152601060e483015260116101048301526013610124830152601561014483015260196101648301520381738903624c18e665dd62ba9e0bb548103b6119df8c5af480156107a557613df5575080f35b602090813d8311613e16575b613e0b8183614c7f565b810103126106445780f35b503d613e01565b503461051357602090816003193601126105135760043560ff8116810361067157604051613e4a81614c13565b8281528284820152606060408201528260608201528260808201528260a082015260c060405191613e7a83614c13565b84835284868401528460408401528460608401528460808401528460a08401528482840152015260145481101561307d57829060146000528160002001604051928391818154613ec98161564b565b926001918083169081156140025750600114613fcb575b5050505060158152030190209060405191613efa83614c13565b613fc7815460ff81161515855260ff8486019160081c1615158152613fa6604051613f2c81611c258160018901615685565b604087019081526002850154908760018060401b0380948193613f726003606086019b8589168d528560a06080890198828c60401c168a52019960801c16895201614fc1565b9760c08d019889528b6040519d8e9d8e52511515908d015251151560408c0152516101a060608c01526101c08b0190614e4c565b9751166080890152511660a0870152511660c08501525160e0840190614e71565b0390f35b90918093945052848083205b848410613fed5750505050810138808080613ee0565b80548885015287955092019185908201613fd7565b60ff1916875250505050801515028201905038808080613ee0565b50346105135760209081600319360112610513576125858261403d614b7a565b6017546040516302fdb7bb60e61b815293849283926001600160a01b031660048401615615565b5034610513576020366003190112610513576004359060ff821682036105135750600b5481101561307d57600b6000527f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db901546040516001600160a01b039091168152602090f35b50346105135780600319360112610513576002546001600160a01b0381163303611f5c576001600160a01b03191660025580f35b50346105135761410f36614de8565b6003546001600160a01b03929083163314158061421f575b6118bb576040519281519260ff60209586818187019761414881838b614e29565b81016015815203019020541615610af057169283156117d05760405181818451614173818389614e29565b8101601381520301902084600052815260ff604060002054161561420d577fe6c444f78d147cbf9d43bd1148e37fd487c410f7970fe65736fc287ae1763c5a936141cc8261420395604051809381928851928391614e29565b8101601381520301902081600052825285600260406000208281558260018201550155604051938493604085526040850190614e4c565b918301520390a180f35b604051630def223560e21b8152600490fd5b5082601e5416331415614127565b5034610513578060031936011261051357601d546040516001600160401b039091168152602090f35b503461051357602036600319011261051357600354600435906001600160a01b0316330361170a57620151808111612a775760407f9a22227d6c0251a79ef8b846202ddcbe9d682ee5482e84abeec6dda096398a6f91601c549080601c5582519182526020820152a180f35b5034610513576020366003190112610513576004356001600160401b038111610671576142f3903690600401614d64565b6142fb615c35565b614303615c5b565b6020614343600d549260018060a01b039384601f541694602154166040519586948594634576691b60e11b865261016060048701526101648601906155b6565b9260006024860152604485015260648401526084830152600460a4830152600660c4830152601060e483015260116101048301526013610124830152601561014483015203817357bf2dec1454ef43b45ba2c011df4fc773e2312a5af49081156107a55782916118895750600d5580f35b5034610513576060366003190112610513576143ce614b7a565b6143d6614b90565b906044356001600160401b0381169081900361064457600d5491604051936308d726c160e41b855260018060a01b0380921660048601521660248401526044830152606482015260046084820152600660a4820152601060c4820152601160e48201526012610104820152601361012482015260156101448201526019610164820152601b6101848201526020816101a481739defe6fd18341097f02c3261430017eb5a1da1ad5af49081156107a55782916118895750600d5580f35b503461051357602080600319360112610671576001600160401b03600435818111610e09576144c6903690600401614bd0565b600c54604051635aa6e67560e01b815291936001600160a01b039392909186908290600490829088165afa80156145ac578491889161458f575b50163303610b0257831161457b57600160401b831161457b576012548360125580841061455d575b5092601285528085209385905b848210614540578680f35b8035918483168303610e0557836001920192818801550190614535565b61457590601260005284866000209182019101615577565b38614528565b634e487b7160e01b85526041600452602485fd5b6145a69150873d8911610b3a57610b2c8183614c7f565b38614500565b6040513d89823e3d90fd5b503461051357806145c736614cf8565b73682af734b0f04a9234e4d95974f88e03eb89dc9590813b1561170557839161460e91604051809581948293632bc98c7360e21b845260e0600485015260e4840190614e4c565b6001600160a01b039091166024830152600460448301526006606483015260106084830152601160a4830152601560c483015203915af480156107a5576107955750f35b503461051357602090816003193601126105135761466e614b7a565b6040516305afec4960e01b81526001600160a01b039091166004808301919091526024820152600660448201526010606482015260116084820152601560a4820152828160c48173682af734b0f04a9234e4d95974f88e03eb89dc955af491821561051d5780926104e25750506040519060070b8152f35b503461051357602080600319360112610671576001600160401b03600435818111610e0957614719903690600401614bd0565b600c54604051635aa6e67560e01b815291936001600160a01b039392909186908290600490829088165afa80156145ac57849188916147ce575b50163303610b0257831161457b57600160401b831161457b57600e5483600e558084106147b0575b5092600e85528085209385905b848210614793578680f35b8035918483168303610e0557836001920192818801550190614788565b6147c890600e60005284866000209182019101615577565b3861477b565b6147e59150873d8911610b3a57610b2c8183614c7f565b38614753565b503461051357602036600319011261051357614805614b7a565b6002546001600160a01b038082169233849003611f5c57169182156117d05782146117be576001600160a01b0319161760025580f35b50346105135780600319360112610513576020600d54604051908152f35b5034610513576020366003190112610513576004359060ff821682036105135750600e5481101561307d57600e6000527fbb7b4a454dc3493923482f07822329ed19e8244eff582cc204f8554c3620c3fd01546040516001600160a01b039091168152602090f35b50346105135780600319360112610513576020601454604051908152f35b50346105135780600319360112610513576003546001600160a01b03908116330361170a57600080516020615f588339815191526040601e54928151908416815260006020820152a16001600160a01b031916601e5580f35b5034610513576040366003190112610513576004356001600160401b03811161067157614969903690600401614ca2565b602435908160070b80920361066d57614980615c35565b601f546001600160a01b031683526006602052604083205461065b57829073682af734b0f04a9234e4d95974f88e03eb89dc95803b1561066d57604051634b19ff6960e11b815260a06004820152938492839182916149e39060a4840190614e4c565b90602483015260106044830152601160648301526015608483015203915af480156107a557614a10575080f35b610a0590614c00565b503461051357602080600319360112610671576001600160401b03600435818111610e0957614a4c903690600401614bd0565b600c54604051635aa6e67560e01b815291936001600160a01b039392909186908290600490829088165afa80156145ac5784918891614afe575b50163303610b0257831161457b57600160401b831161457b57600b5483600b55808410614ae3575b5092600b85528085209385905b848210614ac6578680f35b8035918483168303610e0557836001920192818801550190614abb565b600b8652848620614af8918101908501615577565b38614aae565b614b159150873d8911610b3a57610b2c8183614c7f565b38614a86565b503461051357602090816003193601126105135761133c82614b3b614b7a565b60405180938192633c9a30d360e11b835260048301615529565b9050346106715781600319360112610671576021546001600160a01b03168152602090f35b600435906001600160a01b038216820361064457565b602435906001600160a01b038216820361064457565b604435906001600160a01b038216820361064457565b35906001600160a01b038216820361064457565b9181601f84011215610644578235916001600160401b038311610644576020808501948460051b01011161064457565b6001600160401b038111610e0d57604052565b60e081019081106001600160401b03821117610e0d57604052565b604081019081106001600160401b03821117610e0d57604052565b606081019081106001600160401b03821117610e0d57604052565b60a081019081106001600160401b03821117610e0d57604052565b601f909101601f19168101906001600160401b03821190821017610e0d57604052565b81601f82011215610644578035906001600160401b038211610e0d5760405192614cd6601f8401601f191660200185614c7f565b8284526020838301011161064457816000926020809301838601378301015290565b906040600319830112610644576004356001600160a01b03811681036106445791602435906001600160401b03821161064457614d3791600401614ca2565b90565b600435906001600160401b038216820361064457565b35906001600160401b038216820361064457565b91909160a0818403126106445760405190614d7e82614c64565b90928391908135906001600160401b0382116106445782614da860809492614de394869401614ca2565b8552614db660208201614bbc565b6020860152614dc760408201614bbc565b6040860152614dd860608201614d50565b606086015201614d50565b910152565b604060031982011261064457600435906001600160401b03821161064457614e1291600401614ca2565b906024356001600160a01b03811681036106445790565b60005b838110614e3c5750506000910152565b8181015183820152602001614e2c565b90602091614e6581518092818552858086019101614e29565b601f01601f1916010190565b60c0908160018060401b03918281511685528260208201511660208601528260408201511660408601528260608201511660608601528260808201511660808601528260a08201511660a0860152015116910152565b6001600160401b038111610e0d5760051b60200190565b600319906020828201811361064457600435926001600160401b03908185116106445760609085850301126106445760405193614f1a85614c49565b614f2681600401614bbc565b8552614f3460248201614bbc565b6020860152604481013591821161064457019180602384011215610644576004830135614f6081614ec7565b93614f6e6040519586614c7f565b8185526024602086019260051b82010192831161064457602401905b828210614f9d5750505050604082015290565b838091614fa984614d50565b815201910190614f8a565b3590811515820361064457565b90604051614fce81614c13565b60c081936001815491818060401b03928381168652838160401c166020870152838160801c166040870152841c606086015201548181166080850152818160401c1660a085015260801c16910152565b35906001600160801b038216820361064457565b91906102409081848203126106445760408051909490926001600160401b039290840183811185821017610e0d578652839561506d8361501e565b855261507b60208401614bbc565b60208601528083013560078110156106445781860152606083013560028110156106445760608601526150b060808401614d50565b60808601526150c160a08401614d50565b60a08601526150d260c08401614d50565b60c086015260e083013560038110156106445760e08601526101006150f8818501614d50565b9086015261012061510a81850161501e565b9086015261014061511c818501614fb4565b90860152610160808401359060048210156106445786015261018080840135906004821015610644578601526101a0615156818501614fb4565b908601526101c0615168818501614fb4565b908601526101e080840135858111610644578401916060838503126106445780519261519384614c49565b61519c8161501e565b84526151aa60208201614bbc565b60208501528181013590878211610644576151c791869101614ca2565b908301528501526102008083013584811161064457826151e8918501614ca2565b90850152610220928383013590811161064457614de39201614ca2565b600319602082820112610644576004916001600160401b0383358181116106445760608482850301126106445760409283519561524187614c49565b828101356002811015610644578752602483013584811161064457830193610180948588828603011261064457865194868601868110848211176154e057885261528c84830161501e565b865261529a60248301614bbc565b60208701526152ab60448301614bbc565b888701526152bb60648301614d50565b60608701526152cc60848301614d50565b60808701526152dd60a48301614d50565b60a08701526152ee60c48301614bbc565b60c087015260e482013583811161064457858561530d92850101614ca2565b60e087015261010491615321838201614bbc565b9661010097888201526101249384830135948686116106445761534a898f978a90870101614ca2565b91610120928385015261014495615362878701614d50565b9461014095868201526101649687810135908b8211610644578d8d60209361538b930101614ca2565b996101609a8b840152015260448101359089821161064457019a6101a09e8f908d8d030112610644578d519e8f9081019081108a8211176154cb578f908f60c48f828f6153e19060c09661543d9652830161501e565b86526153ef60248301614bbc565b602087015261540060448301614bbc565b9086015261541060648201614d50565b606086015261542160848201614d50565b608086015261543260a48201614d50565b60a086015201614bbc565b91015260e48c0135898111610644578f926154608d8f61546c948f910101614ca2565b60e08501528d01614bbc565b91015289013593868511610644578c61549a926154908b8b6154a5998f0101614ca2565b9101528901614d50565b908b01528601614d50565b90880152610184840135908111610644576154c1930101614ca2565b9083015282015290565b60418b634e487b7160e01b6000525260246000fd5b604185634e487b7160e01b6000525260246000fd5b600311156154ff57565b634e487b7160e01b600052602160045260246000fd5b51906001600160401b038216820361064457565b6001600160a01b0390911681526004602082015260066040820152601360608201526015608082015260a00190565b9081602091031261064457516001600160a01b03811681036106445790565b818110615582575050565b60008155600101615577565b6001600160a01b0391821681529116602082015260400190565b51908160070b820361064457565b9060806155cc835160a0845260a0840190614e4c565b92602081015160018060a01b03809116602085015260408201511660408401528160608201519160018060401b03809316606086015201511691015290565b600411156154ff57565b6001600160a01b03918216815291166020820152600460408201526006606082015260136080820152601560a082015260c00190565b90600182811c9216801561567b575b602083101461566557565b634e487b7160e01b600052602260045260246000fd5b91607f169161565a565b8054600093926156948261564b565b918282526020936001916001811690816000146156fc57506001146156bb575b5050505050565b90939495506000929192528360002092846000945b8386106156e8575050505001019038808080806156b4565b8054858701830152940193859082016156d0565b60ff19168685015250505090151560051b0101915038808080806156b4565b6060820160206080604060018060a01b039485815116875283958482015116848801520151946060604082015285518094520193019160005b828110615762575050505090565b83516001600160401b031685529381019392810192600101615754565b6001600160401b0391821681529116602082015260400190565b600211156154ff57565b80516001600160801b0390811683526020808301516001600160a01b03908116918501919091526040830151919392916102409060078110156154ff57614d37956158f6936158e29360409384890152606087015161580181615799565b60608901526080870151600180861b0380911660808a01528060a08901511660a08a01528060c08901511660c08a015260e088015161583f816154f5565b60e08a0152610100908189015116908901526101208381890151169089015261014080880151151590890152610160808801519061587c8261560b565b89015261018080880151906158908261560b565b8901526101a0808801511515908901526101c0808801511515908901526101e0928184890151948a015283511690880152602082015116610260870152015160606102808601526102a0850190614e4c565b610200808401519085830390860152614e4c565b916102208092015191818403910152614e4c565b6001600160401b039081169081146159225760010190565b634e487b7160e01b600052601160045260246000fd5b8054600160401b600160801b03191660409290921b600160401b600160801b0316919091179055565b90815161596d81615799565b81526020820151916020820160609052600160801b6001900392838151166060840152602081015192600160a01b6001900380941660808201528360408301511660a0820152606082015194600160401b6001900380961660c08301528560808401511660e083015260a0830151958061010097168784015260c08401518661012091168185015260e08501519661018098899661014099888b8901526101e08801615a1891614e4c565b958282015196846101609816888a015285830151605f1991828b8203018c8c0152615a4291614e4c565b888d85015194896101a09616868d01520151918a8203016101c08b0152615a6891614e4c565b996040015197808b03906040015287511689528260208801511660208a01528260408801511660408a01528460608801511660608a01528460808801511660808a01528460a08801511660a08a01528260c08801511660c08a015260e0870151908060e08b01528901615ada91614e4c565b91818701511690880152818501519187820390880152615af991614e4c565b9581818501511690860152818301511690840152015192818303910152614d3791614e4c565b601280548083526000918252602092830192600080516020615f3883398151915292905b828210615b51575050505090565b83546001600160a01b031685529384019360019384019390910190615b43565b908060209392818452848401376000828201840152601f01601f1916010190565b6001600160a01b039182168152918116602083015291821660408201529181166060830152909116608082015260a0810191909152600460c0820152600660e082015260106101008201526011610120820152601361014082015260156101608201526019610180820152601b6101a08201526101c00190565b8054600160801b600160c01b03191660809290921b600160801b600160c01b0316919091179055565b601e546001600160a01b03163303615c4957565b6040516363b9419960e11b8152600490fd5b601f546001600160a01b031660009081526006602052604090205415615c7d57565b60405163351ef88160e01b8152600490fd5b307f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03161480615d8f575b15615cea577f000000000000000000000000000000000000000000000000000000000000000090565b60405160208101907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f82527f000000000000000000000000000000000000000000000000000000000000000060408201527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a082015260a0815260c0810181811060018060401b03821117610e0d5760405251902090565b507f00000000000000000000000000000000000000000000000000000000000000004614615cc1565b60ff8114615df65760ff811690601f8211615de45760405191615dda83614c2e565b8252602082015290565b604051632cd44ac360e21b8152600490fd5b5060405160008160005491615e0a8361564b565b808352602093600190818116908115615e725750600114615e34575b5050614d3792500382614c7f565b600080805285812095935091905b818310615e5a575050614d3793508201013880615e26565b85548784018501529485019486945091830191615e42565b915050614d3794925060ff191682840152151560051b8201013880615e26565b60ff8114615eb45760ff811690601f8211615de45760405191615dda83614c2e565b5060405160008160019160015492615ecb8461564b565b90818452602094600181169081600014615e725750600114615ef5575050614d3792500382614c7f565b90939150600160005281600020936000915b818310615f1f575050614d3793508201013880615e26565b85548784018501529485019486945091830191615f0756febb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec3444ddb86df2d5262dd7b44067db5962cc7875e9db409cb21c88adfe3c5760315e3910cf19671b43c88b1f02d4e94932d7ffaa89c7278bc5b8868fa7b7676210809ba2646970667358221220066b956ee34e230f1f8f8b27b40b7621c1a5a1ead6088611c8c4623782aab75864736f6c63430008190033bb7b4a454dc3493923482f07822329ed19e8244eff582cc204f8554c3620c3fd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d0239e8ff9fad24a3b55747ad2bbf32dddedab37000000000000000000000000f640d2502fbd9ca9f8a884b26a1e738e62d254cc00000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000431e9d1a03c5c1fc5370155002d239ab560e51fc000000000000000000000000a428c57e1625551c9bc605adc11810e29476cf26000000000000000000000000203a662b0bd271a6ed5a60edfbd04bfce608fd360000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a428c57e1625551c9bc605adc11810e29476cf260000000000000000000000004ad74c916450fcdd03c29e5f4b44aa4e1fc54de3

Deployed Bytecode

0x608080604052600436101561001357600080fd5b600090813560e01c908162c0d66414614b5557508062e6ed7b14614b1b5780630227d10314614a1957806307b26b35146149385780630c187a72146148df5780630d1c46eb146148c157806310469abf146148595780631079364c1461483b57806313af4035146147eb57806315ed46e2146146e6578063162df1c9146146525780631768b2a2146145b757806319759e48146144935780631aa9e709146143b45780631c794f24146142c257806320e6a8e31461425657806321919d251461422d578063219896db14614100578063246f8b96146140cc5780632533a67e14614064578063262ca5421461401d5780632bfd5f5914613e1d5780632e9ac8df14613d1457806336b19cd714613ceb578063375b74c314613cc257806338dc0c5014613c995780633cfaad0214613bbc578063403f373114613b55578063498ae77614613aba5780634a284ef914613a51578063529e6e7e1461399d5780635456c157146139165780635656fc78146138f357806359702e171461378357806359ad6f50146136cd5780635b17d04b146136225780635f3462c4146135b0578063654a439c146134fa57806368ac20ea1461322d5780636dc0e8d9146131ed5780636e553f65146130e8578063704b6c021461309357806375bad745146130275780637698bf4c14612f6957806377affc1814612ece57806378e890ba14612eab5780637b89893914612e7e578063807cc2df14612e6057806381ae3aa114612ce657806383a5059014612cc857806384b0196e14612bbb57806384fa4d0214612b9d578063851216f814612a89578063863a01c8146129d85780638afda202146129ba5780638ccd0860146128f95780638d113b02146126985780638f51afe0146125bf5780638fde4c641461253e578063904cf00c1461246d578063909bdf5e1461244257806390d49b9d146123b85780639335dcb71461238f5780639388c24f1461236657806393916380146121d7578063985c4af5146121765780639895edbe14611f6e5780639a202d4714611f265780639e9aaaf814611e745780639fd9605914611d83578063a2ecaea314611cda578063a39c4f6114611c3e578063a6ec7a5a14611b4d578063a8028b8e14611a51578063ac9fc083146119b3578063b1cc952d146118db578063b72a334c146117e2578063ba22bd7614611745578063bad346201461171c578063bc25cf7714611680578063c662a0c21461144b578063c7744049146113af578063c9a90e9314611302578063d91bb791146112d9578063daa3629214611162578063db56627a146110c7578063dbba225d14610fc3578063dd7c4fe314610edb578063e0c56b1214610e23578063e916bbf114610b41578063eb3d16c614610816578063eee41690146107b4578063ef262be31461069c578063f25f4b5614610675578063f86a6fc4146105295763f9f196221461043f57600080fd5b3461051357602090816003193601126105135761045a614b7a565b60018060a01b0380601754169160405192631f820d8f60e01b84526004840152166024820152600460448201526006606482015260106084820152601160a4820152601560c4820152601960e4820152828161010481735d5661fbfbfa061fb51061f0d9759ff90606c4dd5af491821561051d5780926104e2575b50506040519060070b8152f35b9091508282813d8311610516575b6104fa8183614c7f565b81010312610513575061050c906155a8565b38806104d5565b80fd5b503d6104f0565b604051903d90823e3d90fd5b503461051357602080600319360112610671576004356001600160401b03811161066d5761055b903690600401614d64565b610563615c35565b601f546001600160a01b039081168085526006845260408520549092919061065b5783916105b891600d5490602154166040519586948594634576691b60e11b865261016060048701526101648601906155b6565b9260016024860152604485015260648401526084830152600460a4830152600660c4830152601060e483015260116101048301526013610124830152601561014483015203817357bf2dec1454ef43b45ba2c011df4fc773e2312a5af4801561065057610623578280f35b813d8311610649575b6106368183614c7f565b810103126106445738808280f35b600080fd5b503d61062c565b6040513d85823e3d90fd5b604051631895a5bf60e21b8152600490fd5b8280fd5b5080fd5b5034610513578060031936011261051357602080546040516001600160a01b039091168152f35b5034610513576020366003190112610513576004356001600160401b038111610671576106cd903690600401614d64565b6106d5615c35565b601f546001600160a01b03908116808452600660205260408420549192909161065b57602154849316731155250006ea43581fe695e165d68ccefd14a82d803b156107b05761074685936040519586948593849363ec9a531f60e01b855261014060048601526101448501906155b6565b91602484015260448301526004606483015260066084830152601060a4830152601160c4830152601360e48301526015610104830152601961012483015203915af480156107a5576107955750f35b61079e90614c00565b6105135780f35b6040513d84823e3d90fd5b8480fd5b503461051357602036600319011261051357600435906001600160401b0390818311610513575061080260206107ef81943690600401614ca2565b8160405193828580945193849201614e29565b810160118152030190205416604051908152f35b503461051357610120366003190112610513576001600160401b0360043581811161066d57610849903690600401614ca2565b60e0366023190112610644576040519161086283614c13565b60243581811681036106445783526044359181831683036106445760208085019384526064359383851685036106445760408601948552608435948486168603610644576060870195865260a435958587168703610644576080880196875260c4359386851685036106445760a0890194855260e4359587871687036106445760c08a01968752610104356001600160a01b038181169291839003610644578c60048583600c541660405192838092635aa6e67560e01b82525afa91821561051d5791610b14575b50163303610b02576040519160ff82519385818186019661094c81838a614e29565b81016015815203019020541615610af05780610a08575093610a059a8996946109c58861098d81976004976109fd9d9b604051938492839251928391614e29565b81016015815203019020946109bd8280600389019751169a60018060401b03199b8c895416178855511686615938565b511683615c0c565b5181546001600160c01b031660c09190911b6001600160c01b0319161790559851980180549092169716969096178655511684615938565b511690615c0c565b80f35b9350935081985060c09650610a059995506002945090610a4a9160405194610a2f86614c2e565b60018652828601978852604051938492839251928391614e29565b8101601381520301902090600052865260406000209051151560ff8019835416911617815560018101925192610a9986808651169860018060401b0319998a8554161784558601511682615938565b610aa98660408601511682615c0c565b606084015181546000196001881b0190811691871b90191617905560808301519101805490951690841617845560a0810151610ae790841685615938565b01511690615c0c565b6040516366cee12960e01b8152600490fd5b6040516308904b1160e01b8152600490fd5b610b349150853d8711610b3a575b610b2c8183614c7f565b810190615558565b3861092a565b503d610b22565b50346105135760031960203682011261067157600435906001600160401b03821161066d57610100908236030112610671576040519061010082016001600160401b03811183821017610e0d57604052610b9d8160040161501e565b8252610bab60248201614bbc565b6020830152610bbc60448201614d50565b6040830152610bcd60648201614d50565b6060830152610bde60848201614bbc565b608083015260a48101356001600160401b038111610e0957610c069060043691840101614ca2565b60a0830152610c1760c48201614d50565b60c083015260e4810135906001600160401b038211610e09576004610c3f9236920101614ca2565b60e0820152610c4c615c35565b601f546001600160a01b03168083526006602052604083205490919061065b578291610c76615c8f565b600c54600d54602054601a546001600160a01b039384169692949293908116911673e8d88c09c626362483e09ea23f2633f2b15fbfae3b15610e0557604080516345e295e160e01b8152610200600482015285516001600160801b031661020482015260208601516001600160a01b03908116610224830152918601516001600160401b03908116610244830152606087015116610264820152608086015190911661028482015260a08501516101006102a483015290978997899788979193909291610d799160e090610d4f906103048c0190614e4c565b60c08301516001600160401b03166102c48c015291015189820361020319016102e48b0152614e4c565b95602488015260448701526064860152608485015260a484015260c4830152600460e483015260066101048301526009610124830152600b6101448301526010610164830152601161018483015260136101a483015260156101c4830152601b6101e4830152038173e8d88c09c626362483e09ea23f2633f2b15fbfae5af480156107a5576107955750f35b8780fd5b8380fd5b634e487b7160e01b600052604160045260246000fd5b5034610513576020366003190112610513576004356001600160401b03811161067157610e54903690600401614ca2565b610e5c615c35565b601f546001600160a01b031682526006602052604082205461065b57819073b99826ba9304602032884f13439e1ba5dee62609803b15610ed757610ebf9183916040518080958194633ad2a03760e21b8352604060048401526044830190614e4c565b6015602483015203915af480156107a5576107955750f35b5050fd5b503461051357610f27906020610ef036614cf8565b60405163953af0f760e01b81526001600160a01b039092166004830152610100602483015290938491829190610104830190614e4c565b600460448301526006606483015260106084830152601160a4830152601560c4830152601960e48301520381736da84c3990e3f89f8f7045df2d7e731f9695805e5af490811561051d578091610f86575b6020826040519060070b8152f35b90506020823d602011610fbb575b81610fa160209383614c7f565b810103126105135750610fb56020916155a8565b38610f78565b3d9150610f94565b5034610513576020366003190112610513576004356001600160801b0381169081900361064457601c5460405190634652502b60e01b825260166004830152826024830152604482015260408160648173b22dd85e1c87feaaf33192bf4dc099e5c53ee6665af49081156106505783908492611071575b5091608091600080516020615f788339815191529360405192338452602084015260018060401b031660408301526060820152a180f35b9150506040813d6040116110bf575b8161108d60409383614c7f565b8101031261066d57600080516020615f78833981519152918160206110b3608094615515565b9101519250909261103a565b3d9150611080565b5034610513576020366003190112610513576004356001600160a01b038181169182900361066d576004602082600c541660405192838092635aa6e67560e01b82525afa908115611157578491611138575b50163303610b0257601780546001600160a01b03191691909117905580f35b611151915060203d602011610b3a57610b2c8183614c7f565b38611119565b6040513d86823e3d90fd5b50346105135761117136614cf8565b906111cf60a06040519361118485614c64565b8585528560208601528560408601528560806060968288820152015260405180938192630f40e4b960e11b8352600180861b0380971660048401528760248401526064830190614e4c565b600460448301520381736da84c3990e3f89f8f7045df2d7e731f9695805e5af493841561051d578094611242575b5050608060a09360405193815115158552602082015160070b6020860152604082015160070b604086015260018060401b038183015116908501520151166080820152f35b90935060a0843d60a0116112d1575b8161125e60a09383614c7f565b81010312610513576040519361127385614c64565b8051801515810361066d57855260809061128f602082016155a8565b60208701526112a0604082016155a8565b60408701526112b0858201615515565b8587015201519082821682036105135750836080918260a0960152936111fd565b3d9150611251565b5034610513578060031936011261051357601e546040516001600160a01b039091168152602090f35b503461051357602090816003193601126105135761133c82611322614b7a565b604051809381926336dac1a960e01b835260048301615529565b03817347bb857393f40b08a62a52e81fc66b59c40f6c175af491821561051d578092611377575b50506040516001600160401b039091168152f35b9091508282813d83116113a8575b61138f8183614c7f565b8101031261051357506113a190615515565b3880611363565b503d611385565b5034610513576020366003190112610513576004356001600160401b038111610671576113e0903690600401614ca2565b6113e8615c35565b601f546001600160a01b031682526006602052604082205461065b57819073b99826ba9304602032884f13439e1ba5dee62609803b15610ed757610ebf918391604051808095819463627f3f8360e01b8352604060048401526044830190614e4c565b50346105135760031960203682011261067157600435906001600160401b03821161066d5760c0908236030112610671576040519060c082016001600160401b03811183821017610e0d576040526114a58160040161501e565b82526114b360248201614bbc565b60208301526114c460448201614bbc565b60408301526114d560648201614d50565b60608301526114e660848201614d50565b608083015260a4810135906001600160401b038211610e0957600461150e9236920101614ca2565b60a082015261151b615c35565b601f546001600160a01b03168083526006602052604083205461065b578291611542615c8f565b6021546020546001600160a01b03908116949116929091733fc4c196e966d34db0b5def1401242dc164dfc6a3b1561167c576040805162e2278f60e51b81526101a0600482015283516001600160801b03166101a482015260208401516001600160a01b039081166101c4830152918401519091166101e482015260608301516001600160401b0390811661020483015260808401511661022482015260a09092015160c0610244840152919486948694859461160490610264870190614e4c565b936024860152604485015260648401526084830152600460a4830152600660c4830152600860e48301526010610104830152601161012483015260136101448301526015610164830152601b6101848301520381733fc4c196e966d34db0b5def1401242dc164dfc6a5af480156107a5576107955750f35b8580fd5b50346105135760203660031901126105135761169a614b7a565b6003546001600160a01b03908116330361170a57829173e8d88c09c626362483e09ea23f2633f2b15fbfae9160205416823b156117055760405163583d54a360e11b81529284928492839182916116f4916004840161558e565b03915af480156107a5576107955750f35b505050fd5b6040516358f509d960e11b8152600490fd5b5034610513578060031936011261051357601a546040516001600160a01b039091168152602090f35b50346105135760203660031901126105135761175f614b7a565b6003546001600160a01b03908116330361170a578082169182156117d057601e54918216908184146117be57600080516020615f58833981519152916117aa6040519283928361558e565b0390a16001600160a01b03191617601e5580f35b6040516376330f4960e11b8152600490fd5b60405163a5f90a1160e01b8152600490fd5b5034610513576020366003190112610513576117fc614b7a565b6003546001600160a01b0391908216331415806118cd575b6118bb57602090611823615c5b565b82600c54169061185b84601f541694806017541690601a5416600d5491604051978896879663175b99c160e21b885260048801615b92565b038173e8d88c09c626362483e09ea23f2633f2b15fbfae5af49081156107a5578291611889575b50600d5580f35b90506020813d6020116118b3575b816118a460209383614c7f565b81010312610644575138611882565b3d9150611897565b6040516325b232e360e21b8152600490fd5b5081601e5416331415611814565b5034610513576118ea36614ede565b6118f2615c35565b6118fa615c5b565b602061193a600d549260018060a01b039384601f541694602154166040519586948594634031316d60e01b8652610180600487015261018486019061571b565b9260248501526044840152606483015260026084830152600460a4830152600660c4830152601060e483015260116101048301526013610124830152601561014483015260196101648301520381738903624c18e665dd62ba9e0bb548103b6119df8c5af49081156107a55782916118895750600d5580f35b503461051357602036600319011261051357806119ce614b7a565b739defe6fd18341097f02c3261430017eb5a1da1ad90813b15610ed75760405163143f692d60e01b81526001600160a01b0390911660048083019190915260248201526006604482015260c0606482015290829082908180611a3260c48201615b1f565b60196084830152601b60a483015203915af480156107a5576107955750f35b503461051357611a6036614ede565b611a68615c35565b601f546001600160a01b03908116808452600660205260408420549192909161065b57826020820151168452601b60205260ff60408520541615611b3b5760215484931673426f7f2e61715343ecc6603628a033f5516c23f1803b156107b057611af4859360405195869485938493631dfa49c160e21b8552610120600486015261012485019061571b565b91602484015260448301526004606483015260066084830152601060a4830152601160c4830152601360e4830152601561010483015203915af480156107a5576107955750f35b604051631032047160e01b8152600490fd5b50346105135760208060031936011261067157906001600160a01b03611b71614b7a565b1681526006825260408120805491611b8883614ec7565b92611b966040519485614c7f565b8084528484018093835285832083915b838310611c0b5750505050604051928484019085855251809152604084019460408260051b8601019392955b828710611bdf5785850386f35b909192938280611bfb600193603f198a82030186528851614e4c565b9601920196019592919092611bd2565b600188819260409a99979a51611c2c81611c258189615685565b0382614c7f565b81520192019201919096939596611ba6565b5034610513576040366003190112610513576004356001600160401b03811161067157611c6f903690600401614ca2565b611c8b6020602435928160405193828580945193849201614e29565b81016010815203019020805482101561066d576080925060005260206000200154604051908060070b82528060401c60070b602083015280831c60070b604083015260c01d60070b6060820152f35b5034610513576020366003190112610513576040606091611cf9614b7a565b81838051611d0681614c49565b828152826020820152015260018060a01b03168152601b6020522060405190611d2e82614c49565b5460ff81161515825260018060401b0360ff6020840192828160081c16845260481c16916040840192611d60816154f5565b83526040519351151584525116602083015251611d7c816154f5565b6040820152f35b50346105135780600319360112610513576003546001600160a01b03908116330361170a57600c54906002600160401b031960a083901c6001600160401b031601611e62576005541680611dfc5750815b600160a01b600160e01b031990911660a09190911b600160a01b600160e01b031617600c5580f35b602060049160405192838092637b89893960e01b82525afa908115610650578391611e28575b50611dd4565b90506020813d602011611e5a575b81611e4360209383614c7f565b8101031261066d57611e5490615515565b38611e22565b3d9150611e36565b60405163a72b88cf60e01b8152600490fd5b503461051357604036600319011261051357611e8e614d3a565b611e96614b90565b60035490916001600160a01b0391821633141580611f18575b6118bb5783927380d605b39e39df70a86e8d1977c8b8d12473d6e2803b156107b0578492608491604051958694859363072d70d360e31b855260018060401b0316600485015216602483015260046044830152601960648301525af480156107a5576107955750f35b5081601e5416331415611eaf565b50346105135780600319360112610513576002546001600160a01b03163303611f5c57600380546001600160a01b031916905580f35b604051630d690a6360e21b8152600490fd5b50346105135760031960a03682011261067157611f89614b90565b90611f92614ba6565b6001600160401b03926064359084821161167c573660238301121561167c57816004013591858311612172573660248483010111612172576001600160a01b039460843586811694919085900361064457739defe6fd18341097f02c3261430017eb5a1da1ad95600c549188601f54169360ff600f5416958a601a5416978a3b1561216e57918b9a999897969593918b8f9d94968f976040519e8f9163fedcb20d60e01b835260043560048401521690602401521660448d015260648c0161020090526102048c01906024019161206892615b71565b9760848b01523360a48b01528988030160c48a0152600b549687815260209060200197600b8c5260208c20918c905b82821061214e5750505050811660e489015260a01c166101048701526101248601521515610144850152610164840152600461018484015260126101a484015260196101c4840152601b6101e4840152839183918290039082905af480156107a55761213a575b5050600c5490600160a01b600160e01b039061211f9060a084901c1661590a565b60a01b16600160a01b600160e01b03199190911617600c5580f35b61214390614c00565b6106715781386120fe565b835485168b528f9d50998a01998e96506001938401939190910190612097565b8d80fd5b8680fd5b503461051357602036600319011261051357612190614b7a565b600c54601f54601754601a54600d5460405163ab82d9b760e01b8152956020958795869561185b956001600160a01b03908116949181169392811692169060048801615b92565b5034610513576003199060203683018113610671576001600160401b0390600435828111610e095761220d903690600401614bd0565b929094612218615c35565b73b99826ba9304602032884f13439e1ba5dee6260995863b1561167c579291604051946379d7258160e01b865280606487016060600489015252608486019060848160051b88010195809389915b8383106122d85750505050505083830301602484015280600e54928381520191600e85528185209185905b8282106122b857601560448701528680878181808a03818e5af480156107a5576107955750f35b83546001600160a01b031685529384019360019384019390910190612291565b909192939496976083198a82030186528735603e19833603018112156123625782016001600160a01b0361230b82614bbc565b16825289810135601e198236030181121561235e570189813591019184821161235e57813603831361235e5761234f6001938c936040848187809701520191615b71565b9a990196019493019190612266565b8c80fd5b8b80fd5b50346105135780600319360112610513576017546040516001600160a01b039091168152602090f35b50346105135780600319360112610513576002546040516001600160a01b039091168152602090f35b5034610513576020366003190112610513576123d2614b7a565b6003546001600160a01b0391908216330361170a5781811680156117d057602054928316908181146117be577f9f4f5dce3c4d197b5d7496cb96e25f0a89809167195964b0daa3ef5fed63c00a9360018060a01b0319161760205561243c6040519283928361558e565b0390a180f35b5034610513578060031936011261051357601d546040805191901c6001600160401b03168152602090f35b5034610513578061247d36615205565b612485615c35565b739defe6fd18341097f02c3261430017eb5a1da1ad906124a3615c8f565b823b1561170557604051631962329160e01b81526101606004820152928492849283918291612515916124db90610164850190615961565b9060248401526004604484015260066064840152600a6084840152601060a4840152601160c48401526003198382030160e4840152615b1f565b60136101048301526015610124830152601b61014483015203915af480156107a5576107955750f35b50346105135760209081600319360112610513576125858261255e614b7a565b601754604051633c68d4d560e21b815293849283926001600160a01b031660048401615615565b0381735d5661fbfbfa061fb51061f0d9759ff90606c4dd5af491821561051d5780926113775750506040516001600160401b039091168152f35b503461051357806125cf36615205565b6125d7615c35565b739defe6fd18341097f02c3261430017eb5a1da1ad906125f5615c8f565b823b15611705576040516387d4e38160e01b8152610180600482015292849284928391829161266f9161262d90610184850190615961565b9060248401526004604484015260066064840152600a6084840152600b60a4840152601060c4840152601160e484015260031983820301610104840152615b1f565b60136101248301526015610144830152601b61016483015203915af480156107a5576107955750f35b503461051357600319602036820112610671576004356001600160401b03811161066d576101a08136039283011261066d57604051916126d783614c13565b6126e382600401614fb4565b83526126f160248301614fb4565b60208401526044820135906001600160401b0382116107b05761271c60e09260043691860101614ca2565b604085015261272d60648401614d50565b606085015261273e60848401614d50565b608085015261274f60a48401614d50565b60a085015260c3190112610644576127dd6101846040519261277084614c13565b61277c60c48201614d50565b845261278a60e48201614d50565b602085015261279c6101048201614d50565b60408501526127ae6101248201614d50565b60608501526127c06101448201614d50565b60808501526127d26101648201614d50565b60a085015201614d50565b60c0828101919091528201526003546001600160a01b0316330361170a576017548291906001600160a01b031673b99826ba9304602032884f13439e1ba5dee626093b15610ed7578290604051809381926336766ecf60e01b835260c060048401528151151560c48401526020820151151560e48401526128b260c061287560408501516101a0610104880152610264870190614e4c565b60608501516001600160401b039081166101248801526080860151811661014488015260a086015116610164870152930151610184850190614e71565b6024830152601060448301526011606483015260146084830152601560a4830152038173b99826ba9304602032884f13439e1ba5dee626095af480156107a5576107955750f35b50346105135780600319360112610513576003546001600160a01b03908116330361170a57808291601a5416602082600c5416602460405180968193634a3ab40b60e11b83528660048401525af1918215611157577f581fa472f6627aea226b2cc9510361c22cde232383b96578169b86cb17f4b27c938593612999575b50821660018060a01b0319601a541617601a5561243c6040519283928361558e565b6129b391935060203d602011610b3a57610b2c8183614c7f565b9138612977565b50346105135780600319360112610513576020600b54604051908152f35b5034610513576020366003190112610513576129f2614d3a565b6003546001600160a01b0316330361170a576001600160401b03624c4b4082821611612a7757601d8054600160401b600160801b03198116604085811b600160401b600160801b03169190911790925581517f6075d76e45ec51fde3ac15edcdb7880a5da054423736dbe73f2416d7df88696b949093849361243c93901c168361577f565b60405163b06ab8a960e01b8152600490fd5b5034610513576020366003190112610513576004356001600160401b03811161067157612aba903690600401614d64565b612ac2615c35565b601f546001600160a01b03908116808452600660205260408420549192909161065b57826040820151168452601b60205260ff60408520541615611b3b57602154849316733e6623aecd7840d114cc814e342290daecfeb605803b156107b057612b4e85936040519586948593849363deb153d160e01b855261014060048601526101448501906155b6565b91602484015260448301526004606483015260066084830152601060a4830152601160c4830152601360e48301526015610104830152601b61012483015203915af480156107a5576107955750f35b50346105135780600319360112610513576020601254604051908152f35b5034610513578060031936011261051357612bf57f4b6174616e61506572707300000000000000000000000000000000000000000b615db8565b90612c1f7f312e302e30000000000000000000000000000000000000000000000000000005615e92565b60405160208082019391929091906001600160401b03851184861017610e0d579284926020612c7e8896612c7098604052858552604051988998600f60f81b8a5260e0858b015260e08a0190614e4c565b9088820360408a0152614e4c565b924660608801523060808801528460a088015286840360c088015251928381520193925b828110612cb157505050500390f35b835185528695509381019392810192600101612ca2565b50346105135780600319360112610513576020600e54604051908152f35b503461051357600319602036820112610671576001600160401b039060043590828211610e0957608090823603011261066d57604051916080830183811082821117610e0d5760405281600401358181116107b057612d4b9060043691850101614ca2565b8352612d5960248301614d50565b9060208401918252612d806064612d7260448601614bbc565b946040870195865201614d50565b9160608501928352612d90615c35565b601f546001600160a01b0390811687526006602052604087205490939061065b578695735d04d54916246bcebf425ea9af28e4163132b16f928560205416843b15612e5c5785612e0a948a986040519a8b998a98899863e9381bb160e01b8a5260e060048b015251608060e48b01526101648a0190614e4c565b965116610104880152511661012486015251166101448401526024830152600460448301526006606483015260106084830152601160a4830152601560c483015203915af480156107a5576107955750f35b8880fd5b50346105135780600319360112610513576020601c54604051908152f35b5034610513578060031936011261051357600c5460405160a09190911c6001600160401b03168152602090f35b50346105135780600319360112610513576020612ec6615c8f565b604051908152f35b5034610513576020908160031936011261051357612eea614b7a565b6040516343ce14af60e11b81526001600160a01b039091166004808301919091526024820152600660448201526010606482015260116084820152601560a4820152601960c4820152828160e4817347bb857393f40b08a62a52e81fc66b59c40f6c175af491821561051d5780926104e25750506040519060070b8152f35b503461051357602036600319011261051357612f83614b7a565b6003546001600160a01b03908116330361170a57829073e2df7954fbd89f90a447f4a830205618d70bbe8c9281601f541690843b15610e095760848492604051948593849263383e73e760e21b84526004840152169687602483015260046044830152600660648301525af480156107a557613013575b5050601f80546001600160a01b03191691909117905580f35b61301c90614c00565b610671578138612ffa565b5034610513576020366003190112610513576004359060ff82168203610513575060125481101561307d576012600052600080516020615f3883398151915201546040516001600160a01b039091168152602090f35b634e487b7160e01b600052603260045260246000fd5b5034610513576020366003190112610513576130ad614b7a565b6002546001600160a01b03919082163303611f5c5781169081156117d05760035490811682146117be576001600160a01b0319161760035580f35b503461051357604036600319011261051357613102614b90565b6001600160a01b039082908083166131e7575033915b7380d605b39e39df70a86e8d1977c8b8d12473d6e2600c549360018060401b039483601f54169160ff600f54169385601a5416813b15610e0557879587958a610164966040519a8b998a986330a02d2d60e11b8a5216600489015233602489015260043560448901528116606488015260a01c16608486015260a4850152151560c484015260e483015260046101048301526019610124830152601b6101448301525af480156107a55761213a575050600c5490600160a01b600160e01b039061211f9060a084901c1661590a565b91613118565b5034610513576020366003190112610513576020906001600160a01b03613212614b7a565b168152601982526040600180821b0391205416604051908152f35b5034610513576060366003190112610513576004356001600160401b0381116106715760e06003198236030112610671576040519061326b82614c13565b60048101356001600160401b038111610e095761328e9060043691840101614ca2565b825261329c60248201614d50565b60208301526132ad60448201614d50565b60408301526064810135908160070b82036106445760c49160608401526132d660848201614d50565b60808401526132e760a48201614d50565b60a08401520135600281101561066d5760c08201526024356001600160401b03811161066d5761331b903690600401615032565b906044356001600160401b038111610e095761333b903690600401615032565b613343615c35565b601f546001600160a01b03168085526006602052604085205490929061065b57601d548594906001600160401b031661337a615c8f565b60205460215491966001600160a01b03928316929091169073cf58d39226e085d1db6e9cf1fd3209f31976581a3b15612e5c576134589761346a8a986040519a8b998a99631e83380f60e01b8b5261024060048c015260c06133eb8c61032485519160e06102448201520190614e4c565b60208401516001600160401b039081166102648f0152604085015181166102848f0152606085015160070b6102a48f0152608085015181166102c48f015260a0850151166102e48e015292015161344181615799565b6103048c01528a82036003190160248c01526157a3565b8881036003190160448a0152906157a3565b946064870152608486015260a485015260c484015260e48301526004610104830152600661012483015260076101448301526010610164830152601161018483015260136101a483015260156101c483015260166101e48301526018610204830152601b610224830152038173cf58d39226e085d1db6e9cf1fd3209f31976581a5af480156107a5576107955750f35b503461051357602036600319011261051357613514614b7a565b600c54604051635aa6e67560e01b81526001600160a01b0392916020908290600490829087165afa80156111575783918591613591575b50163303610b025716808252601b60205260ff60408320541661357f57602180546001600160a01b03191691909117905580f35b604051637fa3a69760e01b8152600490fd5b6135aa915060203d602011610b3a57610b2c8183614c7f565b3861354b565b50346105135760206135d96135c436614de8565b92908160405193828580945193849201614e29565b810160138152030190209060018060a01b0316600052602052610100604060002061362061360e600160ff8454169301614fc1565b60405192151583526020830190614e71565bf35b50346105135760203660031901126105135760043580151590818103610644576003546001600160a01b0316330361170a57156136995760ff600f54166117be577f8824dcb92309d9474923a7f382fc61d5bca1b2ef464bfdf9572d18d23ac75ffd8280a15b60ff8019600f5416911617600f5580f35b60ff600f5416156117be577f717a16489831f10040458886f0f4947e8d82dee5a3890d9e602f419208260f078280a1613688565b50346105135760209081600319360112610513576136e9614b7a565b60018060a01b0380601f54169181601754166040519363280b345360e11b8552600485015260248401521660448201526004606482015260066084820152601060a4820152601160c4820152601360e482015260156101048201526019610124820152828161014481735d5661fbfbfa061fb51061f0d9759ff90606c4dd5af491821561051d5780926104e25750506040519060070b8152f35b503461051357600319602036820112610671576001600160401b039060043590828211610e0957606090823603011261066d57604051916137c383614c49565b81600401358181116107b0576137df9060043691850101614ca2565b835261380060446137f260248501614bbc565b936020860194855201614d50565b9160408401928352613810615c35565b60018060a01b039081601f5416928387526006602052604087205461065b578695734dbc24bcca09b43fdcd001569cfc7ad6bb4e2f8692846021541683601d5460401c1690853b156138ef578997613892956040519a8b998a9889986355d9a13160e01b8a5261014060048b01525160606101448b01526101a48a0190614e4c565b965116610164880152511661018486015260248501526044840152606483015260046084830152600660a4830152601060c4830152601160e48301526013610104830152601561012483015203915af480156107a5576107955750f35b8980fd5b5034610513578060031936011261051357602060ff600f54166040519015158152f35b503461051357602036600319011261051357613930614d3a565b6003546001600160a01b0316330361170a576001600160401b03818116640757b12c008111612a775761243c7f2c372c30a44b54aac428d2a395067f758cd7461390beb2955065a96873d11e3593601d549260018060401b0319841617601d55604051938493168361577f565b5034610513576060366003190112610513576139b7614d3a565b6001600160401b039060243582811691908290036106445783926139d9614ba6565b6139e1615c35565b739defe6fd18341097f02c3261430017eb5a1da1ad92833b1561167c57859360c49260405196879586946381f2e8b360e01b8652166004850152602484015260018060a01b031660448301526004606483015260196084830152601b60a48301525af480156107a5576107955750f35b50346105135780600319360112610513578073e8d88c09c626362483e09ea23f2633f2b15fbfae803b15613ab757816064916040519283809263010c92c960e21b825260048083015260066024830152601b60448301525af480156107a5576107955750f35b50fd5b50346105135760203660031901126105135780613ad5614b7a565b601c54601f546021546001600160a01b039081169390929183169173e8d88c09c626362483e09ea23f2633f2b15fbfae803b1561217257869460c4936040519788968795631e37201960e01b875260048701526024860152604485015216606483015260046084830152601b60a48301525af480156107a5576107955750f35b5034610513576020366003190112610513576004356001600160a01b038181169182900361066d578060035416330361170a57600c54908116611e6257813b15613baa576001600160a01b03191617600c5580f35b60405163a710429d60e01b8152600490fd5b50346105135760208060031936011261067157613bd7614b7a565b90604051613be481614c2e565b8381528181018490526001600160a01b039092168352601681526040832080549081613c26575b6040805185516001600160401b031681528486015181860152f35b91925060001981019190808311613c8557821015613c715783839160409552209060011b016001835191613c5983614c2e565b8180861b038154168352015482820152903880613c0b565b634e487b7160e01b84526032600452602484fd5b634e487b7160e01b85526011600452602485fd5b5034610513578060031936011261051357601f546040516001600160a01b039091168152602090f35b5034610513578060031936011261051357600c546040516001600160a01b039091168152602090f35b50346105135780600319360112610513576003546040516001600160a01b039091168152602090f35b503461051357613d2336614ede565b613d2b615c35565b601f546001600160a01b03908116808452600660205260408420549092919061065b57602091613d8291600d5490602154166040519586948594634031316d60e01b8652610180600487015261018486019061571b565b9260248501526044840152606483015260016084830152600460a4830152600660c4830152601060e483015260116101048301526013610124830152601561014483015260196101648301520381738903624c18e665dd62ba9e0bb548103b6119df8c5af480156107a557613df5575080f35b602090813d8311613e16575b613e0b8183614c7f565b810103126106445780f35b503d613e01565b503461051357602090816003193601126105135760043560ff8116810361067157604051613e4a81614c13565b8281528284820152606060408201528260608201528260808201528260a082015260c060405191613e7a83614c13565b84835284868401528460408401528460608401528460808401528460a08401528482840152015260145481101561307d57829060146000528160002001604051928391818154613ec98161564b565b926001918083169081156140025750600114613fcb575b5050505060158152030190209060405191613efa83614c13565b613fc7815460ff81161515855260ff8486019160081c1615158152613fa6604051613f2c81611c258160018901615685565b604087019081526002850154908760018060401b0380948193613f726003606086019b8589168d528560a06080890198828c60401c168a52019960801c16895201614fc1565b9760c08d019889528b6040519d8e9d8e52511515908d015251151560408c0152516101a060608c01526101c08b0190614e4c565b9751166080890152511660a0870152511660c08501525160e0840190614e71565b0390f35b90918093945052848083205b848410613fed5750505050810138808080613ee0565b80548885015287955092019185908201613fd7565b60ff1916875250505050801515028201905038808080613ee0565b50346105135760209081600319360112610513576125858261403d614b7a565b6017546040516302fdb7bb60e61b815293849283926001600160a01b031660048401615615565b5034610513576020366003190112610513576004359060ff821682036105135750600b5481101561307d57600b6000527f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db901546040516001600160a01b039091168152602090f35b50346105135780600319360112610513576002546001600160a01b0381163303611f5c576001600160a01b03191660025580f35b50346105135761410f36614de8565b6003546001600160a01b03929083163314158061421f575b6118bb576040519281519260ff60209586818187019761414881838b614e29565b81016015815203019020541615610af057169283156117d05760405181818451614173818389614e29565b8101601381520301902084600052815260ff604060002054161561420d577fe6c444f78d147cbf9d43bd1148e37fd487c410f7970fe65736fc287ae1763c5a936141cc8261420395604051809381928851928391614e29565b8101601381520301902081600052825285600260406000208281558260018201550155604051938493604085526040850190614e4c565b918301520390a180f35b604051630def223560e21b8152600490fd5b5082601e5416331415614127565b5034610513578060031936011261051357601d546040516001600160401b039091168152602090f35b503461051357602036600319011261051357600354600435906001600160a01b0316330361170a57620151808111612a775760407f9a22227d6c0251a79ef8b846202ddcbe9d682ee5482e84abeec6dda096398a6f91601c549080601c5582519182526020820152a180f35b5034610513576020366003190112610513576004356001600160401b038111610671576142f3903690600401614d64565b6142fb615c35565b614303615c5b565b6020614343600d549260018060a01b039384601f541694602154166040519586948594634576691b60e11b865261016060048701526101648601906155b6565b9260006024860152604485015260648401526084830152600460a4830152600660c4830152601060e483015260116101048301526013610124830152601561014483015203817357bf2dec1454ef43b45ba2c011df4fc773e2312a5af49081156107a55782916118895750600d5580f35b5034610513576060366003190112610513576143ce614b7a565b6143d6614b90565b906044356001600160401b0381169081900361064457600d5491604051936308d726c160e41b855260018060a01b0380921660048601521660248401526044830152606482015260046084820152600660a4820152601060c4820152601160e48201526012610104820152601361012482015260156101448201526019610164820152601b6101848201526020816101a481739defe6fd18341097f02c3261430017eb5a1da1ad5af49081156107a55782916118895750600d5580f35b503461051357602080600319360112610671576001600160401b03600435818111610e09576144c6903690600401614bd0565b600c54604051635aa6e67560e01b815291936001600160a01b039392909186908290600490829088165afa80156145ac578491889161458f575b50163303610b0257831161457b57600160401b831161457b576012548360125580841061455d575b5092601285528085209385905b848210614540578680f35b8035918483168303610e0557836001920192818801550190614535565b61457590601260005284866000209182019101615577565b38614528565b634e487b7160e01b85526041600452602485fd5b6145a69150873d8911610b3a57610b2c8183614c7f565b38614500565b6040513d89823e3d90fd5b503461051357806145c736614cf8565b73682af734b0f04a9234e4d95974f88e03eb89dc9590813b1561170557839161460e91604051809581948293632bc98c7360e21b845260e0600485015260e4840190614e4c565b6001600160a01b039091166024830152600460448301526006606483015260106084830152601160a4830152601560c483015203915af480156107a5576107955750f35b503461051357602090816003193601126105135761466e614b7a565b6040516305afec4960e01b81526001600160a01b039091166004808301919091526024820152600660448201526010606482015260116084820152601560a4820152828160c48173682af734b0f04a9234e4d95974f88e03eb89dc955af491821561051d5780926104e25750506040519060070b8152f35b503461051357602080600319360112610671576001600160401b03600435818111610e0957614719903690600401614bd0565b600c54604051635aa6e67560e01b815291936001600160a01b039392909186908290600490829088165afa80156145ac57849188916147ce575b50163303610b0257831161457b57600160401b831161457b57600e5483600e558084106147b0575b5092600e85528085209385905b848210614793578680f35b8035918483168303610e0557836001920192818801550190614788565b6147c890600e60005284866000209182019101615577565b3861477b565b6147e59150873d8911610b3a57610b2c8183614c7f565b38614753565b503461051357602036600319011261051357614805614b7a565b6002546001600160a01b038082169233849003611f5c57169182156117d05782146117be576001600160a01b0319161760025580f35b50346105135780600319360112610513576020600d54604051908152f35b5034610513576020366003190112610513576004359060ff821682036105135750600e5481101561307d57600e6000527fbb7b4a454dc3493923482f07822329ed19e8244eff582cc204f8554c3620c3fd01546040516001600160a01b039091168152602090f35b50346105135780600319360112610513576020601454604051908152f35b50346105135780600319360112610513576003546001600160a01b03908116330361170a57600080516020615f588339815191526040601e54928151908416815260006020820152a16001600160a01b031916601e5580f35b5034610513576040366003190112610513576004356001600160401b03811161067157614969903690600401614ca2565b602435908160070b80920361066d57614980615c35565b601f546001600160a01b031683526006602052604083205461065b57829073682af734b0f04a9234e4d95974f88e03eb89dc95803b1561066d57604051634b19ff6960e11b815260a06004820152938492839182916149e39060a4840190614e4c565b90602483015260106044830152601160648301526015608483015203915af480156107a557614a10575080f35b610a0590614c00565b503461051357602080600319360112610671576001600160401b03600435818111610e0957614a4c903690600401614bd0565b600c54604051635aa6e67560e01b815291936001600160a01b039392909186908290600490829088165afa80156145ac5784918891614afe575b50163303610b0257831161457b57600160401b831161457b57600b5483600b55808410614ae3575b5092600b85528085209385905b848210614ac6578680f35b8035918483168303610e0557836001920192818801550190614abb565b600b8652848620614af8918101908501615577565b38614aae565b614b159150873d8911610b3a57610b2c8183614c7f565b38614a86565b503461051357602090816003193601126105135761133c82614b3b614b7a565b60405180938192633c9a30d360e11b835260048301615529565b9050346106715781600319360112610671576021546001600160a01b03168152602090f35b600435906001600160a01b038216820361064457565b602435906001600160a01b038216820361064457565b604435906001600160a01b038216820361064457565b35906001600160a01b038216820361064457565b9181601f84011215610644578235916001600160401b038311610644576020808501948460051b01011161064457565b6001600160401b038111610e0d57604052565b60e081019081106001600160401b03821117610e0d57604052565b604081019081106001600160401b03821117610e0d57604052565b606081019081106001600160401b03821117610e0d57604052565b60a081019081106001600160401b03821117610e0d57604052565b601f909101601f19168101906001600160401b03821190821017610e0d57604052565b81601f82011215610644578035906001600160401b038211610e0d5760405192614cd6601f8401601f191660200185614c7f565b8284526020838301011161064457816000926020809301838601378301015290565b906040600319830112610644576004356001600160a01b03811681036106445791602435906001600160401b03821161064457614d3791600401614ca2565b90565b600435906001600160401b038216820361064457565b35906001600160401b038216820361064457565b91909160a0818403126106445760405190614d7e82614c64565b90928391908135906001600160401b0382116106445782614da860809492614de394869401614ca2565b8552614db660208201614bbc565b6020860152614dc760408201614bbc565b6040860152614dd860608201614d50565b606086015201614d50565b910152565b604060031982011261064457600435906001600160401b03821161064457614e1291600401614ca2565b906024356001600160a01b03811681036106445790565b60005b838110614e3c5750506000910152565b8181015183820152602001614e2c565b90602091614e6581518092818552858086019101614e29565b601f01601f1916010190565b60c0908160018060401b03918281511685528260208201511660208601528260408201511660408601528260608201511660608601528260808201511660808601528260a08201511660a0860152015116910152565b6001600160401b038111610e0d5760051b60200190565b600319906020828201811361064457600435926001600160401b03908185116106445760609085850301126106445760405193614f1a85614c49565b614f2681600401614bbc565b8552614f3460248201614bbc565b6020860152604481013591821161064457019180602384011215610644576004830135614f6081614ec7565b93614f6e6040519586614c7f565b8185526024602086019260051b82010192831161064457602401905b828210614f9d5750505050604082015290565b838091614fa984614d50565b815201910190614f8a565b3590811515820361064457565b90604051614fce81614c13565b60c081936001815491818060401b03928381168652838160401c166020870152838160801c166040870152841c606086015201548181166080850152818160401c1660a085015260801c16910152565b35906001600160801b038216820361064457565b91906102409081848203126106445760408051909490926001600160401b039290840183811185821017610e0d578652839561506d8361501e565b855261507b60208401614bbc565b60208601528083013560078110156106445781860152606083013560028110156106445760608601526150b060808401614d50565b60808601526150c160a08401614d50565b60a08601526150d260c08401614d50565b60c086015260e083013560038110156106445760e08601526101006150f8818501614d50565b9086015261012061510a81850161501e565b9086015261014061511c818501614fb4565b90860152610160808401359060048210156106445786015261018080840135906004821015610644578601526101a0615156818501614fb4565b908601526101c0615168818501614fb4565b908601526101e080840135858111610644578401916060838503126106445780519261519384614c49565b61519c8161501e565b84526151aa60208201614bbc565b60208501528181013590878211610644576151c791869101614ca2565b908301528501526102008083013584811161064457826151e8918501614ca2565b90850152610220928383013590811161064457614de39201614ca2565b600319602082820112610644576004916001600160401b0383358181116106445760608482850301126106445760409283519561524187614c49565b828101356002811015610644578752602483013584811161064457830193610180948588828603011261064457865194868601868110848211176154e057885261528c84830161501e565b865261529a60248301614bbc565b60208701526152ab60448301614bbc565b888701526152bb60648301614d50565b60608701526152cc60848301614d50565b60808701526152dd60a48301614d50565b60a08701526152ee60c48301614bbc565b60c087015260e482013583811161064457858561530d92850101614ca2565b60e087015261010491615321838201614bbc565b9661010097888201526101249384830135948686116106445761534a898f978a90870101614ca2565b91610120928385015261014495615362878701614d50565b9461014095868201526101649687810135908b8211610644578d8d60209361538b930101614ca2565b996101609a8b840152015260448101359089821161064457019a6101a09e8f908d8d030112610644578d519e8f9081019081108a8211176154cb578f908f60c48f828f6153e19060c09661543d9652830161501e565b86526153ef60248301614bbc565b602087015261540060448301614bbc565b9086015261541060648201614d50565b606086015261542160848201614d50565b608086015261543260a48201614d50565b60a086015201614bbc565b91015260e48c0135898111610644578f926154608d8f61546c948f910101614ca2565b60e08501528d01614bbc565b91015289013593868511610644578c61549a926154908b8b6154a5998f0101614ca2565b9101528901614d50565b908b01528601614d50565b90880152610184840135908111610644576154c1930101614ca2565b9083015282015290565b60418b634e487b7160e01b6000525260246000fd5b604185634e487b7160e01b6000525260246000fd5b600311156154ff57565b634e487b7160e01b600052602160045260246000fd5b51906001600160401b038216820361064457565b6001600160a01b0390911681526004602082015260066040820152601360608201526015608082015260a00190565b9081602091031261064457516001600160a01b03811681036106445790565b818110615582575050565b60008155600101615577565b6001600160a01b0391821681529116602082015260400190565b51908160070b820361064457565b9060806155cc835160a0845260a0840190614e4c565b92602081015160018060a01b03809116602085015260408201511660408401528160608201519160018060401b03809316606086015201511691015290565b600411156154ff57565b6001600160a01b03918216815291166020820152600460408201526006606082015260136080820152601560a082015260c00190565b90600182811c9216801561567b575b602083101461566557565b634e487b7160e01b600052602260045260246000fd5b91607f169161565a565b8054600093926156948261564b565b918282526020936001916001811690816000146156fc57506001146156bb575b5050505050565b90939495506000929192528360002092846000945b8386106156e8575050505001019038808080806156b4565b8054858701830152940193859082016156d0565b60ff19168685015250505090151560051b0101915038808080806156b4565b6060820160206080604060018060a01b039485815116875283958482015116848801520151946060604082015285518094520193019160005b828110615762575050505090565b83516001600160401b031685529381019392810192600101615754565b6001600160401b0391821681529116602082015260400190565b600211156154ff57565b80516001600160801b0390811683526020808301516001600160a01b03908116918501919091526040830151919392916102409060078110156154ff57614d37956158f6936158e29360409384890152606087015161580181615799565b60608901526080870151600180861b0380911660808a01528060a08901511660a08a01528060c08901511660c08a015260e088015161583f816154f5565b60e08a0152610100908189015116908901526101208381890151169089015261014080880151151590890152610160808801519061587c8261560b565b89015261018080880151906158908261560b565b8901526101a0808801511515908901526101c0808801511515908901526101e0928184890151948a015283511690880152602082015116610260870152015160606102808601526102a0850190614e4c565b610200808401519085830390860152614e4c565b916102208092015191818403910152614e4c565b6001600160401b039081169081146159225760010190565b634e487b7160e01b600052601160045260246000fd5b8054600160401b600160801b03191660409290921b600160401b600160801b0316919091179055565b90815161596d81615799565b81526020820151916020820160609052600160801b6001900392838151166060840152602081015192600160a01b6001900380941660808201528360408301511660a0820152606082015194600160401b6001900380961660c08301528560808401511660e083015260a0830151958061010097168784015260c08401518661012091168185015260e08501519661018098899661014099888b8901526101e08801615a1891614e4c565b958282015196846101609816888a015285830151605f1991828b8203018c8c0152615a4291614e4c565b888d85015194896101a09616868d01520151918a8203016101c08b0152615a6891614e4c565b996040015197808b03906040015287511689528260208801511660208a01528260408801511660408a01528460608801511660608a01528460808801511660808a01528460a08801511660a08a01528260c08801511660c08a015260e0870151908060e08b01528901615ada91614e4c565b91818701511690880152818501519187820390880152615af991614e4c565b9581818501511690860152818301511690840152015192818303910152614d3791614e4c565b601280548083526000918252602092830192600080516020615f3883398151915292905b828210615b51575050505090565b83546001600160a01b031685529384019360019384019390910190615b43565b908060209392818452848401376000828201840152601f01601f1916010190565b6001600160a01b039182168152918116602083015291821660408201529181166060830152909116608082015260a0810191909152600460c0820152600660e082015260106101008201526011610120820152601361014082015260156101608201526019610180820152601b6101a08201526101c00190565b8054600160801b600160c01b03191660809290921b600160801b600160c01b0316919091179055565b601e546001600160a01b03163303615c4957565b6040516363b9419960e11b8152600490fd5b601f546001600160a01b031660009081526006602052604090205415615c7d57565b60405163351ef88160e01b8152600490fd5b307f000000000000000000000000835ba5b1b202773a94daaa07168b26b22584637a6001600160a01b03161480615d8f575b15615cea577f56da844407fa4bebcc9233ed8e34738d777f57b3e5ed29f601d392e0d3b901fa90565b60405160208101907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f82527f809a6c1ac068a0ca6daad975d36fc978b90a61c2f866814d4bd6426bd38bb86860408201527f06c015bd22b4c69690933c1058878ebdfef31f9aaae40bbe86d8a09fe1b2972c60608201524660808201523060a082015260a0815260c0810181811060018060401b03821117610e0d5760405251902090565b507f00000000000000000000000000000000000000000000000000000000000b67d24614615cc1565b60ff8114615df65760ff811690601f8211615de45760405191615dda83614c2e565b8252602082015290565b604051632cd44ac360e21b8152600490fd5b5060405160008160005491615e0a8361564b565b808352602093600190818116908115615e725750600114615e34575b5050614d3792500382614c7f565b600080805285812095935091905b818310615e5a575050614d3793508201013880615e26565b85548784018501529485019486945091830191615e42565b915050614d3794925060ff191682840152151560051b8201013880615e26565b60ff8114615eb45760ff811690601f8211615de45760405191615dda83614c2e565b5060405160008160019160015492615ecb8461564b565b90818452602094600181169081600014615e725750600114615ef5575050614d3792500382614c7f565b90939150600160005281600020936000915b818310615f1f575050614d3793508201013880615e26565b85548784018501529485019486945091830191615f0756febb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec3444ddb86df2d5262dd7b44067db5962cc7875e9db409cb21c88adfe3c5760315e3910cf19671b43c88b1f02d4e94932d7ffaa89c7278bc5b8868fa7b7676210809ba2646970667358221220066b956ee34e230f1f8f8b27b40b7621c1a5a1ead6088611c8c4623782aab75864736f6c63430008190033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d0239e8ff9fad24a3b55747ad2bbf32dddedab37000000000000000000000000f640d2502fbd9ca9f8a884b26a1e738e62d254cc00000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000431e9d1a03c5c1fc5370155002d239ab560e51fc000000000000000000000000a428c57e1625551c9bc605adc11810e29476cf26000000000000000000000000203a662b0bd271a6ed5a60edfbd04bfce608fd360000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a428c57e1625551c9bc605adc11810e29476cf260000000000000000000000004ad74c916450fcdd03c29e5f4b44aa4e1fc54de3

-----Decoded View---------------
Arg [0] : balanceMigrationSource (address): 0x0000000000000000000000000000000000000000
Arg [1] : exitFundWallet_ (address): 0xd0239E8FF9faD24A3b55747aD2BBF32DDdeDAb37
Arg [2] : feeWallet_ (address): 0xf640d2502FBd9Ca9F8a884b26a1e738E62d254Cc
Arg [3] : indexPriceAdapters (address[]): 0xA428C57E1625551C9bc605ADc11810e29476cf26,0x4aD74C916450Fcdd03c29e5f4B44aa4e1Fc54de3
Arg [4] : insuranceFundWallet_ (address): 0x431e9d1a03C5c1fC5370155002D239aB560E51FC
Arg [5] : oraclePriceAdapter_ (address): 0xA428C57E1625551C9bc605ADc11810e29476cf26
Arg [6] : quoteTokenAddress_ (address): 0x203A662b0BD271A6ed5a60EdFbd04bFce608FD36

-----Encoded View---------------
10 Constructor Arguments found :
Arg [0] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [1] : 000000000000000000000000d0239e8ff9fad24a3b55747ad2bbf32dddedab37
Arg [2] : 000000000000000000000000f640d2502fbd9ca9f8a884b26a1e738e62d254cc
Arg [3] : 00000000000000000000000000000000000000000000000000000000000000e0
Arg [4] : 000000000000000000000000431e9d1a03c5c1fc5370155002d239ab560e51fc
Arg [5] : 000000000000000000000000a428c57e1625551c9bc605adc11810e29476cf26
Arg [6] : 000000000000000000000000203a662b0bd271a6ed5a60edfbd04bfce608fd36
Arg [7] : 0000000000000000000000000000000000000000000000000000000000000002
Arg [8] : 000000000000000000000000a428c57e1625551c9bc605adc11810e29476cf26
Arg [9] : 0000000000000000000000004ad74c916450fcdd03c29e5f4b44aa4e1fc54de3


Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

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
Loading...
Loading
[ 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.