Overview
ETH Balance
ETH Value
$0.00Latest 25 from a total of 489,780 transactions
| Transaction Hash |
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
| Execute Trade | 29531688 | 26 secs ago | IN | 0 ETH | 0.00002982 | ||||
| Execute Trade | 29531688 | 26 secs ago | IN | 0 ETH | 0.00003385 | ||||
| Publish Index Pr... | 29531688 | 26 secs ago | IN | 0 ETH | 0.00003278 | ||||
| Execute Trade | 29531624 | 1 min ago | IN | 0 ETH | 0.0000297 | ||||
| Execute Trade | 29531624 | 1 min ago | IN | 0 ETH | 0.00003363 | ||||
| Execute Trade | 29531624 | 1 min ago | IN | 0 ETH | 0.00002951 | ||||
| Execute Trade | 29531623 | 1 min ago | IN | 0 ETH | 0.00003354 | ||||
| Publish Index Pr... | 29531623 | 1 min ago | IN | 0 ETH | 0.0000329 | ||||
| Execute Trade | 29531610 | 1 min ago | IN | 0 ETH | 0.00001093 | ||||
| Execute Trade | 29531610 | 1 min ago | IN | 0 ETH | 0.00001224 | ||||
| Publish Index Pr... | 29531610 | 1 min ago | IN | 0 ETH | 0.00001192 | ||||
| Execute Trade | 29531606 | 1 min ago | IN | 0 ETH | 0.00001102 | ||||
| Execute Trade | 29531606 | 1 min ago | IN | 0 ETH | 0.00001231 | ||||
| Publish Index Pr... | 29531606 | 1 min ago | IN | 0 ETH | 0.00001188 | ||||
| Execute Trade | 29531599 | 1 min ago | IN | 0 ETH | 0.00001086 | ||||
| Execute Trade | 29531599 | 1 min ago | IN | 0 ETH | 0.00001221 | ||||
| Publish Index Pr... | 29531599 | 1 min ago | IN | 0 ETH | 0.000012 | ||||
| Execute Trade | 29531582 | 2 mins ago | IN | 0 ETH | 0.00000733 | ||||
| Publish Index Pr... | 29531582 | 2 mins ago | IN | 0 ETH | 0.00000433 | ||||
| Execute Trade | 29531553 | 2 mins ago | IN | 0 ETH | 0.00002033 | ||||
| Publish Index Pr... | 29531553 | 2 mins ago | IN | 0 ETH | 0.00002239 | ||||
| Execute Trade | 29531553 | 2 mins ago | IN | 0 ETH | 0.00002302 | ||||
| Publish Index Pr... | 29531553 | 2 mins ago | IN | 0 ETH | 0.00002263 | ||||
| Execute Trade | 29531498 | 3 mins ago | IN | 0 ETH | 0.00002017 | ||||
| Execute Trade | 29531497 | 3 mins ago | IN | 0 ETH | 0.00002277 |
View more zero value Internal Transactions in Advanced View mode
Cross-Chain Transactions
Contract Source Code (Solidity Standard Json-Input format)
// 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_;
}
}// 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;
}
}// 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
);
}
}// 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;
}
}// 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
}// 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();
}// 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
);
}
}
}// 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"
);
}
}// 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);
}
}// 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);
}
}{
"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
- No Contract Security Audit Submitted- Submit Audit Here
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"}]Contract Creation Code
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
Net Worth in USD
Net Worth in ETH
Multichain Portfolio | 32 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
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.