Source Code
Overview
S Balance
0 S
More Info
ContractCreator
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
Loading...
Loading
Contract Name:
WarmPath
Compiler Version
v0.8.19+commit.7dd6d404
Optimization Enabled:
Yes with 1000000 runs
Other Settings:
default evmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; import '../libraries/Directives.sol'; import '../libraries/Encoding.sol'; import '../libraries/TokenFlow.sol'; import '../libraries/PriceGrid.sol'; import '../libraries/ProtocolCmd.sol'; import '../mixins/MarketSequencer.sol'; import '../mixins/SettleLayer.sol'; import '../mixins/PoolRegistry.sol'; import '../mixins/MarketSequencer.sol'; import '../mixins/ProtocolAccount.sol'; import '../CrocEvents.sol'; /* @title Warm path callpath sidecar. * @notice Defines a proxy sidecar contract that's used to move code outside the * main contract to avoid Ethereum's contract code size limit. Contains top- * level logic for the core liquidity provider actions: * * Mint ambient liquidity * * Mint concentrated range liquidity * * Burn ambient liquidity * * Burn concentrated range liquidity * These methods are exposed as atomic single-action calls. Useful for traders * who only need to execute a single action, and want to get the lowest gas fee * possible. Compound calls are available in LongPath, but the overhead with * parsing a longer OrderDirective makes the gas cost higher. * * @dev This exists as a standalone contract but will only ever contain proxy code, * not state. As such it should never be called directly or externally, and should * only be invoked with DELEGATECALL so that it operates on the contract state * within the primary CrocSwap contract. */ contract WarmPath is MarketSequencer, SettleLayer, ProtocolAccount { using SafeCast for uint128; using TokenFlow for TokenFlow.PairSeq; using CurveMath for CurveMath.CurveState; using Chaining for Chaining.PairFlow; /* @notice Consolidated method for all atomic liquidity provider actions. * @dev We consolidate multiple call types into a single method to reduce the * contract size in the main contract by paring down methods. * * @param code The command code corresponding to the actual method being called. */ function userCmd (bytes calldata input) public payable returns (int128 baseFlow, int128 quoteFlow) { (uint8 code, address base, address quote, uint256 poolIdx, int24 bidTick, int24 askTick, uint128 liq, uint128 limitLower, uint128 limitHigher, uint8 reserveFlags, address lpConduit) = abi.decode(input, (uint8,address,address,uint256,int24,int24, uint128,uint128,uint128,uint8,address)); if (lpConduit == address(0)) { lpConduit = lockHolder_; } (baseFlow, quoteFlow) = commitLP(code, base, quote, poolIdx, bidTick, askTick, liq, limitLower, limitHigher, lpConduit); settleFlows(base, quote, baseFlow, quoteFlow, reserveFlags); emit CrocEvents.CrocWarmCmd(input, baseFlow, quoteFlow); } function commitLP (uint8 code, address base, address quote, uint256 poolIdx, int24 bidTick, int24 askTick, uint128 liq, uint128 limitLower, uint128 limitHigher, address lpConduit) private returns (int128, int128) { if (code == UserCmd.MINT_RANGE_LIQ_LP) { return mintConcentratedLiq(base, quote, poolIdx, bidTick, askTick, liq, lpConduit, limitLower, limitHigher); } else if (code == UserCmd.MINT_RANGE_BASE_LP) { return mintConcentratedQty(base, quote, poolIdx, bidTick, askTick, true, liq, lpConduit, limitLower, limitHigher); } else if (code == UserCmd.MINT_RANGE_QUOTE_LP) { return mintConcentratedQty(base, quote, poolIdx, bidTick, askTick, false, liq, lpConduit, limitLower, limitHigher); } else if (code == UserCmd.BURN_RANGE_LIQ_LP) { return burnConcentratedLiq(base, quote, poolIdx, bidTick, askTick, liq, lpConduit, limitLower, limitHigher); } else if (code == UserCmd.BURN_RANGE_BASE_LP) { return burnConcentratedQty(base, quote, poolIdx, bidTick, askTick, true, liq, lpConduit, limitLower, limitHigher); } else if (code == UserCmd.BURN_RANGE_QUOTE_LP) { return burnConcentratedQty(base, quote, poolIdx, bidTick, askTick, false, liq, lpConduit, limitLower, limitHigher); } else if (code == UserCmd.MINT_AMBIENT_LIQ_LP) { return mintAmbientLiq(base, quote, poolIdx, liq, lpConduit, limitLower, limitHigher); } else if (code == UserCmd.MINT_AMBIENT_BASE_LP) { return mintAmbientQty(base, quote, poolIdx, true, liq, lpConduit, limitLower, limitHigher); } else if (code == UserCmd.MINT_AMBIENT_QUOTE_LP) { return mintAmbientQty(base, quote, poolIdx, false, liq, lpConduit, limitLower, limitHigher); } else if (code == UserCmd.BURN_AMBIENT_LIQ_LP) { return burnAmbientLiq(base, quote, poolIdx, liq, lpConduit, limitLower, limitHigher); } else if (code == UserCmd.BURN_AMBIENT_BASE_LP) { return burnAmbientQty(base, quote, poolIdx, true, liq, lpConduit, limitLower, limitHigher); } else if (code == UserCmd.BURN_AMBIENT_QUOTE_LP) { return burnAmbientQty(base, quote, poolIdx, false, liq, lpConduit, limitLower, limitHigher); } else if (code == UserCmd.HARVEST_LP) { return harvest(base, quote, poolIdx, bidTick, askTick, lpConduit, limitLower, limitHigher); } else { revert("Invalid command"); } } /* @notice Mints liquidity as a concentrated liquidity range order. * @param base The base-side token in the pair. * @param quote The quote-side token in the par. * @param poolIdx The index of the pool type being minted on. * @param bidTick The price tick index of the lower boundary of the range order. * @param askTick The price tick index of the upper boundary of the range order. * @param liq The total amount of liquidity being minted. Represented as sqrt(X*Y) * for the equivalent constant-product AMM. * @param lpConduit The address of the LP conduit to deposit the minted position at * (direct owned liquidity if 0) * @param limitLower Exists to make sure the user is happy with the price the * liquidity is minted at. Transaction fails if the curve price * at call time is below this value. * @param limitUpper Transaction fails if the curve price at call time is above this * threshold. */ function mintConcentratedLiq (address base, address quote, uint256 poolIdx, int24 bidTick, int24 askTick, uint128 liq, address lpConduit, uint128 limitLower, uint128 limitHigher) internal returns (int128, int128) { PoolSpecs.PoolCursor memory pool = queryPool(base, quote, poolIdx); verifyPermitMint(pool, base, quote, bidTick, askTick, liq); return mintOverPool(bidTick, askTick, liq, pool, limitLower, limitHigher, lpConduit); } /* @notice Burns liquidity as a concentrated liquidity range order. * @param base The base-side token in the pair. * @param quote The quote-side token in the par. * @param poolIdx The index of the pool type being burned on. * @param bidTick The price tick index of the lower boundary of the range order. * @param askTick The price tick index of the upper boundary of the range order. * @param liq The total amount of liquidity being burned. Represented as sqrt(X*Y) * for the equivalent constant-product AMM. * @param lpConduit The address of the LP conduit to deposit the minted position at * (direct owned liquidity if 0) * @param limitLower Exists to make sure the user is happy with the price the * liquidity is burned at. Transaction fails if the curve price * at call time is below this value. * @param limitUpper Transaction fails if the curve price at call time is above this * threshold. */ function burnConcentratedLiq (address base, address quote, uint256 poolIdx, int24 bidTick, int24 askTick, uint128 liq, address lpConduit, uint128 limitLower, uint128 limitHigher) internal returns (int128, int128) { PoolSpecs.PoolCursor memory pool = queryPool(base, quote, poolIdx); verifyPermitBurn(pool, base, quote, bidTick, askTick, liq); return burnOverPool(bidTick, askTick, liq, pool, limitLower, limitHigher, lpConduit); } /* @notice Harvests the rewards for a concentrated liquidity position. * @param base The base-side token in the pair. * @param quote The quote-side token in the par. * @param poolIdx The index of the pool type being burned on. * @param bidTick The price tick index of the lower boundary of the range order. * @param askTick The price tick index of the upper boundary of the range order. * @param lpConduit The address of the LP conduit to deposit the minted position at * (direct owned liquidity if 0) * @param limitLower Exists to make sure the user is happy with the price the * liquidity is burned at. Transaction fails if the curve price * at call time is below this value. * @param limitUpper Transaction fails if the curve price at call time is above this * threshold. */ function harvest (address base, address quote, uint256 poolIdx, int24 bidTick, int24 askTick, address lpConduit, uint128 limitLower, uint128 limitHigher) internal returns (int128, int128) { PoolSpecs.PoolCursor memory pool = queryPool(base, quote, poolIdx); // On permissioned pools harvests are treated like a special case burn // with 0 liquidity. Note that unlike a true 0 burn, ambient liquidity will still // be returned, so oracles should handle 0 as special case if that's an issue. verifyPermitBurn(pool, base, quote, bidTick, askTick, 0); return harvestOverPool(bidTick, askTick, pool, limitLower, limitHigher, lpConduit); } /* @notice Mints ambient liquidity that's active at every price. * @param base The base-side token in the pair. * @param quote The quote-side token in the par. * @param poolIdx The index of the pool type being minted on. * @param liq The total amount of liquidity being minted. Represented as sqrt(X*Y) * for the equivalent constant-product AMM. @ @param lpConduit The address of the LP conduit to deposit the minted position at * (direct owned liquidity if 0) * @param limitLower Exists to make sure the user is happy with the price the * liquidity is minted at. Transaction fails if the curve price * at call time is below this value. * @param limitUpper Transaction fails if the curve price at call time is above this * threshold. */ function mintAmbientLiq (address base, address quote, uint256 poolIdx, uint128 liq, address lpConduit, uint128 limitLower, uint128 limitHigher) internal returns (int128, int128) { PoolSpecs.PoolCursor memory pool = queryPool(base, quote, poolIdx); verifyPermitMint(pool, base, quote, 0, 0, liq); return mintOverPool(liq, pool, limitLower, limitHigher, lpConduit); } function mintAmbientQty (address base, address quote, uint256 poolIdx, bool inBase, uint128 qty, address lpConduit, uint128 limitLower, uint128 limitHigher) internal returns (int128, int128) { bytes32 poolKey = PoolSpecs.encodeKey(base, quote, poolIdx); CurveMath.CurveState memory curve = snapCurve(poolKey); uint128 liq = Chaining.sizeAmbientLiq(qty, true, curve.priceRoot_, inBase); (int128 baseFlow, int128 quoteFlow) = mintAmbientLiq(base, quote, poolIdx, liq, lpConduit, limitLower, limitHigher); return Chaining.pinFlow(baseFlow, quoteFlow, qty, inBase); } function mintConcentratedQty (address base, address quote, uint256 poolIdx, int24 bidTick, int24 askTick, bool inBase, uint128 qty, address lpConduit, uint128 limitLower, uint128 limitHigher) internal returns (int128, int128) { uint128 liq = sizeAddLiq(base, quote, poolIdx, qty, bidTick, askTick, inBase); (int128 baseFlow, int128 quoteFlow) = mintConcentratedLiq(base, quote, poolIdx, bidTick, askTick, liq, lpConduit, limitLower, limitHigher); return Chaining.pinFlow(baseFlow, quoteFlow, qty, inBase); } function sizeAddLiq (address base, address quote, uint256 poolIdx, uint128 qty, int24 bidTick, int24 askTick, bool inBase) internal view returns (uint128) { bytes32 poolKey = PoolSpecs.encodeKey(base, quote, poolIdx); CurveMath.CurveState memory curve = snapCurve(poolKey); return Chaining.sizeConcLiq(qty, true, curve.priceRoot_, bidTick, askTick, inBase); } /* @notice Burns ambient liquidity that's active at every price. * @param base The base-side token in the pair. * @param quote The quote-side token in the par. * @param poolIdx The index of the pool type being burned on. * @param liq The total amount of liquidity being burned. Represented as sqrt(X*Y) * for the equivalent constant-product AMM. * @param limitLower Exists to make sure the user is happy with the price the * liquidity is burned at. Transaction fails if the curve price * at call time is below this value. * @param limitUpper Transaction fails if the curve price at call time is above this * threshold. */ function burnAmbientLiq (address base, address quote, uint256 poolIdx, uint128 liq, address lpConduit, uint128 limitLower, uint128 limitHigher) internal returns (int128, int128) { PoolSpecs.PoolCursor memory pool = queryPool(base, quote, poolIdx); verifyPermitBurn(pool, base, quote, 0, 0, liq); return burnOverPool(liq, pool, limitLower, limitHigher, lpConduit); } function burnAmbientQty (address base, address quote, uint256 poolIdx, bool inBase, uint128 qty, address lpConduit, uint128 limitLower, uint128 limitHigher) internal returns (int128, int128) { bytes32 poolKey = PoolSpecs.encodeKey(base, quote, poolIdx); CurveMath.CurveState memory curve = snapCurve(poolKey); uint128 liq = Chaining.sizeAmbientLiq(qty, false, curve.priceRoot_, inBase); return burnAmbientLiq(base, quote, poolIdx, liq, lpConduit, limitLower, limitHigher); } function burnConcentratedQty (address base, address quote, uint256 poolIdx, int24 bidTick, int24 askTick, bool inBase, uint128 qty, address lpConduit, uint128 limitLower, uint128 limitHigher) internal returns (int128, int128) { bytes32 poolKey = PoolSpecs.encodeKey(base, quote, poolIdx); CurveMath.CurveState memory curve = snapCurve(poolKey); uint128 liq = Chaining.sizeConcLiq(qty, false, curve.priceRoot_, bidTick, askTick, inBase); return burnConcentratedLiq(base, quote, poolIdx, bidTick, askTick, liq, lpConduit, limitLower, limitHigher); } /* @notice Used at upgrade time to verify that the contract is a valid Croc sidecar proxy and used * in the correct slot. */ function acceptCrocProxyRole (address, uint16 slot) public pure returns (bool) { return slot == CrocSlots.LP_PROXY_IDX; } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; library CrocEvents { /* @notice Emitted when governance authority for CrocSwapDex is transfered. * @param The authority being transfered to. */ event AuthorityTransfer (address indexed authority); /* @notice Indicates a new pool liquidity initialization value is set. * @param liq The pool initialization value. */ event SetNewPoolLiq (uint128 liq); /* @notice Emitted when a new protocol take rate is set. * @param takeRate The take rate represents in units of 1/256. */ event SetTakeRate (uint8 takeRate); /* @notice Emitted when a new protocol relayer take rate is set. * @param takeRate The relayer take rate represents in units of 1/256. */ event SetRelayerTakeRate (uint8 takeRate); /* @notice Emitted when a new template is disabled, halting new creation of that pool type. * @param poolIdx The pool type index being disabled. */ event DisablePoolTemplate (uint256 indexed poolIdx); /* @notice Emitted when a new template is written or overwrriten. * @param poolIdx The pool type index being disabled. * @param feeRate The swap fee rate for the pool (represented in units of 0.0001%) * @param tickSize The minimum tick size for range orders in the pool. * @param jitThresh The JIT liquiidty TTL time in the pool (represented in 10s of seconds) * @param knockout The knockout liquidity paramter bits (see KnockoutLiq library for more detail) * @param oracleFlags The permissioned pool oracle flags if this is setup as a permissioned pool. */ event SetPoolTemplate (uint256 indexed poolIdx, uint16 feeRate, uint16 tickSize, uint8 jitThresh, uint8 knockout, uint8 oracleFlags); /* @notice Emitted when a previously created pool with a pre-existing protocol take rate is re- * sychronized to the current dex-wide protocol take rate setting. * @param base The base token of the pool. * @param quote The quote token of the pool. * @param poolIdx The pool type index of the pool. * @param takeRate The newly set protocol take rate of the pool. */ event ResyncTakeRate (address indexed base, address indexed quote, uint256 indexed poolIdx, uint8 takeRate); /* @notice Emitted when new minimum thresholds are set for off-grid price improvement liquidity * thresholds. * @param token The token the thresholds apply to. * @param unitTickCollateral The size of commited collateral required to mint positions off-grid * @param awayTickTol The maximum distance away an off-grid range can be minted from the current * price tick. */ event PriceImproveThresh (address indexed token, uint128 unitTickCollateral, uint16 awayTickTol); /* @notice Emitted when protocol governance sets a new teasury vault address * @param treasury The address the treasury vault is set to * @param startTime The earliest time that the vault will be eligible to collect protocol fees. */ event TreasurySet (address indexed treasury, uint64 indexed startTime); /* @notice Emitted when accumulated protocol fees are collected by the treasury. * @param token The token of the fees being collected. * @param recv The vault the collected fees are being paid to. */ event ProtocolDividend (address indexed token, address indexed recv); /* @notice Called when any proxy sidecar contract is upgraded. * @param proxy The address of the new proxy smart contract. * @param proxyIdx The proxy sidecar index slot the upgrade is applied to. */ event UpgradeProxy (address indexed proxy, uint16 proxyIdx); /* @notice Called whenever the hot path open is toggled. * @param If true indicates the hot-path is open and users can directly call the swap() function * If false, the hot path is closed and users must call the proxy contract to swap. */ event HotPathOpen (bool); /* @notice Called whenever emergency safe mode is toggled * @param If true indicates emergency safe mode is turned on * If false indicates emergency safe mode is turned off */ event SafeMode (bool); event CrocSwap (address indexed base, address indexed quote, uint256 poolIdx, bool isBuy, bool inBaseQty, uint128 qty, uint16 tip, uint128 limitPrice, uint128 minOut, uint8 reserveFlags, int128 baseFlow, int128 quoteFlow); event CrocHotCmd (bytes input, int128 baseFlow, int128 quoteFlow); event CrocColdCmd (bytes input); event CrocColdProtocolCmd (bytes input); event CrocWarmCmd (bytes input, int128 baseFlow, int128 quoteFlow); event CrocKnockoutCmd (bytes input, int128 baseFlow, int128 quoteFlow); event CrocMicroMintAmbient(bytes input, bytes output); event CrocMicroMintRange(bytes input, bytes output); event CrocMicroBurnAmbient(bytes input, bytes output); event CrocMicroBurnRange(bytes input, bytes output); event CrocMicroSwap(bytes input, bytes output); }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; /* @title Croc conditional oracle interface * @notice Defines a generalized interface for checking an arbitrary condition. Used in * an off-chain relayer context. User can gate specific order on a runtime * condition by calling to the oracle. */ interface ICrocNonceOracle { /* @notice Oracle function that tests a condition. * * @param user The address of the underlying call. * @param nonceSalt The salt of the nonce being reset on this call. Implementations * can either ignore, or use it to check call-specific conditions. * @param nonce The new nonce value that will be set for the user at the salt, if the * oracle returns true. Presumably this nonce will open a secondary order * executes some desired action. * @param args Arbitrary args supplied to oracle check call. * * @return True if the condition is met. If false, CrocSwap will revert the * transaction, and the nonce will not be reset. */ function checkCrocNonceSet (address user, bytes32 nonceSalt, uint32 nonce, bytes calldata args) external returns (bool); } interface ICrocCondOracle { function checkCrocCond (address user, bytes calldata args) external returns (bool); }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; import '../libraries/Directives.sol'; /* @title LP conduit interface * @notice Standard interface for contracts that accept and manage LP positions on behalf * of end users. Typical example would be an ERC20 tracker for LP tokens. */ interface ICrocLpConduit { /* @notice Called anytime a user mints liquidity against the conduit instance. To * utilize the user would call a mint operation on the dex with the address * of the LP conduit they want to use. This method will be called to notify * conduit contract (e.g. to perform tracking), and the LP position will be * held in the name of the conduit. * * @param sender The address of the user that owns the newly minted position. * @param poolHash The hash (see PoolRegistry.sol) of the AMM pool the liquidity is * minted on. * @param lowerTick The tick index of the lower range (0 if ambient liquidity) * @param upperTick The tick index of the upper range (0 if ambient liquidity) * @param liq The amount of liquidity being minted. If ambient liquidity this * is denominated as ambient seeds. If concentrated this is flat * sqrt(X*Y) liquidity of the liquidity minted. * @param mileage The accumulated fee mileage (see PositionRegistrar.sol) of the * concentrated liquidity at mint time. If ambient, this is zero. * * @return Return false if the conduit implementation does not accept the liquidity * deposit. Reverts the transaction. */ function depositCrocLiq (address sender, bytes32 poolHash, int24 lowerTick, int24 upperTick, uint128 liq, uint72 mileage) external returns (bool); function withdrawCrocLiq (address sender, bytes32 poolHash, int24 lowerTick, int24 upperTick, uint128 liq, uint72 mileage) external returns (bool); }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; import '../libraries/Directives.sol'; /* @notice Standard interface for a permit oracle to be used by a permissioned pool. * * @dev For pools under their control permit oracles have the ability to approve or deny * pool initialization, swaps, mints and burns for all liquidity types (ambient, * concentrated and knockout). * * Note that permit oracles do *not* have the ability to restrict claims or recovers * on post-knockout liquidity. An order is eligible to be claimed/recovered only after * its liquidity has been knocked out of the curve, and is no longer active. Since a * no longer active order does not affect the liquidity or state of the curve, permit * oracles have no economic reason to restrict knockout claims/recovers. */ interface ICrocPermitOracle { /* @notice Verifies whether a given user is permissioned to perform an arbitrary * action on the pool. * * @param user The address of the caller to the contract. * @param sender The value of msg.sender for the caller of the action. Will either * be same as user, the calling router, or the off-chain relayer. * @param base The base-side token in the pair. * @param quote The quote-side token in the pair. * @param ambient The ambient liquidity directive for the pool action (possibly zero) * @param swap The swap directive for the pool (possibly zero) * @param concs The concentrated liquidity directives for the pool (possibly empty) * @param poolFee The effective pool fee set for the swap (either the base fee or the * base fee plus user tip). * * @returns discount Either returns 0, indicating the action is not approved at all. * Or returns the discount (in units of 0.0001%) that should be applied * to the pool's pre-existing swap fee on this call. Be aware that this value * is defined in terms of N-1 (because 0 is already used to indicate failure). * Hence return value of 1 indicates a discount of 0, return value of 2 * indicates discount of 0.0001%, return value of 3 is 0.0002%, and so on */ function checkApprovedForCrocPool (address user, address sender, address base, address quote, Directives.AmbientDirective calldata ambient, Directives.SwapDirective calldata swap, Directives.ConcentratedDirective[] calldata concs, uint16 poolFee) external returns (uint16 discount); /* @notice Verifies whether a given user is permissioned to perform a swap on the pool * * @param user The address of the caller to the contract. * @param sender The value of msg.sender for the caller of the action. Will either * be same as user, the calling router, or the off-chain relayer. * @param base The base-side token in the pair. * @param quote The quote-side token in the pair. * @param isBuy If true, the swapper is paying base and receiving quote * @param inBaseQty If true, the qty is denominated in the base token side. * @param qty The full qty on the swap request (could possibly be lower if user * hits limit price. * @param poolFee The effective pool fee set for the swap (either the base fee or the * base fee plus user tip). * @returns discount Either returns 0, indicating the action is not approved at all. * Or returns the discount (in units of 0.0001%) that should be applied * to the pool's pre-existing swap fee on this call. Be aware that this value * is defined in terms of N-1 (because 0 is already used to indicate failure). * Hence return value of 1 indicates a discount of 0, return value of 2 * indicates discount of 0.0001%, return value of 3 is 0.0002%, and so on */ function checkApprovedForCrocSwap (address user, address sender, address base, address quote, bool isBuy, bool inBaseQty, uint128 qty, uint16 poolFee) external returns (uint16 discount); /* @notice Verifies whether a given user is permissioned to mint liquidity * on the pool. * * @param user The address of the caller to the contract. * @param sender The value of msg.sender for the caller of the action. Will either * be same as user, the calling router, or the off-chain relayer. * @param base The base-side token in the pair. * @param quote The quote-side token in the pair. * @param bidTick The tick index of the lower side of the range (0 if ambient) * @param askTick The tick index of the upper side of the range (0 if ambient) * @param liq The total amount of liquidity being minted. Denominated as * sqrt(X*Y) * * @returns Returns true if action is permitted. If false, CrocSwap will revert * the transaction. */ function checkApprovedForCrocMint (address user, address sender, address base, address quote, int24 bidTick, int24 askTick, uint128 liq) external returns (bool); /* @notice Verifies whether a given user is permissioned to burn liquidity * on the pool. * * @param user The address of the caller to the contract. * @param sender The value of msg.sender for the caller of the action. Will either * be same as user, the calling router, or the off-chain relayer. * @param base The base-side token in the pair. * @param quote The quote-side token in the pair. * @param bidTick The tick index of the lower side of the range (0 if ambient) * @param askTick The tick index of the upper side of the range (0 if ambient) * @param liq The total amount of liquidity being minted. Denominated as * sqrt(X*Y) * * @returns Returns true if action is permitted. If false, CrocSwap will revert * the transaction. */ function checkApprovedForCrocBurn (address user, address sender, address base, address quote, int24 bidTick, int24 askTick, uint128 liq) external returns (bool); /* @notice Verifies whether a given user is permissioned to initialize a pool * attached to this oracle. * * @param user The address of the caller to the contract. * @param sender The value of msg.sender for the caller of the action. Will either * be same as user, the calling router, or the off-chain relayer. * @param base The base-side token in the pair. * @param quote The quote-side token in the pair. * @param poolIdx The Croc-specific pool type index the pool is being created on. * * @returns Returns true if action is permitted. If false, CrocSwap will revert * the transaction, and pool will not be initialized. */ function checkApprovedForCrocInit (address user, address sender, address base, address quote, uint256 poolIdx) external returns (bool); /* @notice Just used to validate the contract address at pool creation time. */ function acceptsPermitOracle() external returns (bool); }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity >=0.5.0; /// @title Minimal ERC20 interface for Uniswap /// @notice Contains a subset of the full ERC20 interface that is used in Uniswap V3 interface IERC20Minimal { /// @notice Returns the balance of a token /// @param account The account for which to look up the number of tokens it has, i.e. its balance /// @return The number of tokens held by the account function balanceOf(address account) external view returns (uint256); /// @notice Transfers the amount of token from the `msg.sender` to the recipient /// @param recipient The account that will receive the amount transferred /// @param amount The number of tokens to send from the sender to the recipient /// @return Returns true for a successful transfer, false for an unsuccessful transfer function transfer(address recipient, uint256 amount) external returns (bool); /// @notice Returns the current allowance given to a spender by an owner /// @param owner The account of the token owner /// @param spender The account of the token spender /// @return The current allowance granted by `owner` to `spender` function allowance(address owner, address spender) external view returns (uint256); /// @notice Sets the allowance of a spender from the `msg.sender` to the value `amount` /// @param spender The account which will be allowed to spend a given amount of the owners tokens /// @param amount The amount of tokens allowed to be used by `spender` /// @return Returns true for a successful approval, false for unsuccessful function approve(address spender, uint256 amount) external returns (bool); /// @notice Transfers `amount` tokens from `sender` to `recipient` up to the allowance given to the `msg.sender` /// @param sender The account from which the transfer will be initiated /// @param recipient The recipient of the transfer /// @param amount The amount of the transfer /// @return Returns true for a successful transfer, false for unsuccessful function transferFrom( address sender, address recipient, uint256 amount ) external returns (bool); /// @notice Event emitted when tokens are transferred from one address to another, either via `#transfer` or `#transferFrom`. /// @param from The account from which the tokens were sent, i.e. the balance decreased /// @param to The account to which the tokens were sent, i.e. the balance increased /// @param value The amount of tokens that were transferred event Transfer(address indexed from, address indexed to, uint256 value); /// @notice Event emitted when the approval amount for the spender of a given owner's tokens changes. /// @param owner The account that approved spending of its tokens /// @param spender The account for which the spending allowance was modified /// @param value The new allowance from the owner to the spender event Approval(address indexed owner, address indexed spender, uint256 value); } interface IERC20Permit is IERC20Minimal { function permit( address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external; }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; import "./BitMath.sol"; /* @title Tick bitmap library * * @notice Tick bitmaps are used for the tracking of tick initialization * state over a 256-bit interval. Tick indices are 24-bit integer, so * this library provides for 3-layers of recursive 256-bit bitmaps. Each * layer covers the first (lobby), middle (mezzanine) or last (terminus) * 8-bits in the 24-bit index. * * @dev Note that the bitmap library works with the full set of possible int24 * values. Whereas other parts of the protocol set a MIN_TICK and MAX_TICK * that are well within the type bounds of int24. It's the responsibility of * calling code to assure that ticks being set are within the MIN_TICK and * MAX_TICK, and this library does *not* provide those checks. */ library Bitmaps { /* @notice Transforms the bitmap so the first or last N bits are set to zero. * @param bitmap - The original 256-bit bitmap object. * @param shift - The number N of slots in the bitmap to mask to zero. * @param right - If true mask the N bits from right to left. Otherwise from * left to right. * @return The bitmap with N bits (on the right or left side) masked. */ function truncateBitmap (uint256 bitmap, uint16 shift, bool right) pure internal returns (uint256) { return right ? (bitmap >> shift) << shift: (bitmap << shift) >> shift; } /* @notice - Determine the index of the first set bit in the bitmap starting * after N bits from the right or the left. * @param bitmap - The 256-bit bitmap object. * @param shift - Exclude the first shift N bits from the index result. * @param right - If true find the first set bit starting from the right * (least significant bit as EVM is big endian). Otherwise from the lefft. * @return idx - The index of the matching set bit. Index position is always * left indexed starting at zero regardless of the @right parameter. * @return spills - If no matching set bit is found, this return value is set to * true. */ function bitAfterTrunc (uint256 bitmap, uint16 shift, bool right) pure internal returns (uint8 idx, bool spills) { bitmap = truncateBitmap(bitmap, shift, right); spills = (bitmap == 0); if (!spills) { idx = right ? BitMath.leastSignificantBit(bitmap) : BitMath.mostSignificantBit(bitmap); } } /* @notice Returns true if the bitmap's Nth bit slot is set. * @param bitmap - The 256 bit bitmap object. * @param pos - The bitmap index to check. Value is left indexed starting at zero. * @return True if the bit is set. */ function isBitSet (uint256 bitmap, uint8 pos) pure internal returns (bool) { (uint idx, bool spill) = bitAfterTrunc(bitmap, pos, true); return !spill && idx == pos; } /* @notice Converts a signed integer bitmap index to an unsigned integer. */ function castBitmapIndex (int8 x) internal pure returns (uint8) { unchecked { return x >= 0 ? uint8(x) + 128 : // max(int8(x)) + 128 <= 255, so this never overflows uint8(uint16(int16(x) + 128)); // min(int8(x)) + 128 >= 0 (and less than 255) } } /* @notice Converts an unsigned integer bitmap index to a signed integer. */ function uncastBitmapIndex (uint8 x) internal pure returns (int8) { unchecked { return x < 128 ? int8(int16(uint16(x)) - 128) : // max(uint8) - 128 <= 127, so never overflows int8 int8(x - 128); // min(uint8) - 128 >= -128, so never underflows int8 } } /* @notice Extracts the 8-bit tick lobby index from the full 24-bit tick index. */ function lobbyKey (int24 tick) internal pure returns (int8) { return int8(tick >> 16); // 24-bit int shifted by 16 bits will always fit in 8 bits } /* @notice Extracts the 16-bit tick root from the full 24-bit tick * index. */ function mezzKey (int24 tick) internal pure returns (int16) { return int16(tick >> 8); // 24-bit int shifted by 8 bits will always fit in 16 bits } /* @notice Extracts the 8-bit lobby bits (the last 8-bits) from the full 24-bit tick * index. Result can be used to index on a lobby bitmap. */ function lobbyBit (int24 tick) internal pure returns (uint8) { return castBitmapIndex(lobbyKey(tick)); } /* @notice Extracts the 8-bit mezznine bits (the middle 8-bits) from the full 24-bit * tick index. Result can be used to index on a mezzanine bitmap. */ function mezzBit (int24 tick) internal pure returns (uint8) { return uint8(uint16(mezzKey(tick) % 256)); // Modulo 256 will always <= 255, and fit in uint8 } /* @notice Extracts the 8-bit terminus bits (the last 8-bits) from the full 24-bit * tick index. Result can be used to index on a terminus bitmap. */ function termBit (int24 tick) internal pure returns (uint8) { return uint8(uint24(tick % 256)); // Modulo 256 will always <= 255, and fit in uint8 } /* @notice Determines the next shift bump from a starting terminus value. Note for * upper the barrier is always to the right. For lower it's on the tick. This is * because bumps always occur at the start of the tick. * * @param tick - The full 24-bit tick index. * @param isUpper - If true, shift and index from left-to-right. Otherwise right-to- * left. * @return - Returns the bumped terminus bit indexed directionally based on param * isUpper. Can be 256, if the terminus bit occurs at the last slot. */ function termBump (int24 tick, bool isUpper) internal pure returns (uint16) { unchecked { uint8 bit = termBit(tick); // Bump moves up for upper, but occurs at the bottom of the same tick for lower. uint16 shiftTerm = isUpper ? 1 : 0; return uint16(bitRelate(bit, isUpper)) + shiftTerm; } } /* @notice Converts a directional bitmap position, to a cardinal bitmap position. For * example the 20th bit for a sell (right-to-left) would be the 235th bit in * the bitmap. * @param bit - The directional-oriented index in the 256-bit bitmap. * @param isUpper - If true, the direction is left-to-right, if false right-to-left. * @return The cardinal (left-to-right) index in the bitmap. */ function bitRelate (uint8 bit, bool isUpper) internal pure returns (uint8) { unchecked { return isUpper ? bit : (255 - bit); // 255 minus uint8 will never underflow } } /* @notice Converts a 16-bit tick base and an 8-bit terminus tick to a full 24-bit * tick index. */ function weldMezzTerm (int16 mezzBase, uint8 termBitArg) internal pure returns (int24) { unchecked { // First term will always be <= 0x8FFF00 and second term (as a uint8) will always // be positive and <= 0xFF. Therefore the sum will never overflow int24 return (int24(mezzBase) << 8) + int24(uint24(termBitArg)); } } /* @notice Converts an 8-bit lobby index and an 8-bit mezzanine bit into a 16-bit * tick base root. */ function weldLobbyMezz (int8 lobbyIdx, uint8 mezzBitArg) internal pure returns (int16) { unchecked { // First term will always be <= 0x8F00 and second term (as a uint) will always // be positive and <= 0xFF. Therefore the sum will never overflow int24 return (int16(lobbyIdx) << 8) + int16(uint16(mezzBitArg)); } } /* @notice Converts an 8-bit lobby index, an 8-bit mezzanine bit, and an 8-bit * terminus bit into a full 24-bit tick index. */ function weldLobbyMezzTerm (int8 lobbyIdx, uint8 mezzBitArg, uint8 termBitArg) internal pure returns (int24) { unchecked { // First term will always be <= 0x8F0000. Second term, starting as a uint8 // will always be positive and <= 0xFF00. Thir term will always be positive // and <= 0xFF. Therefore the sum will never overflow int24 return (int24(lobbyIdx) << 16) + (int24(uint24(mezzBitArg)) << 8) + int24(uint24(termBitArg)); } } /* @notice Converts an 8-bit lobby index, an 8-bit mezzanine bit, and an 8-bit * terminus bit into a full 24-bit tick index. */ function weldLobbyPosMezzTerm (uint8 lobbyWord, uint8 mezzBitArg, uint8 termBitArg) internal pure returns (int24) { return weldLobbyMezzTerm(Bitmaps.uncastBitmapIndex(lobbyWord), mezzBitArg, termBitArg); } /* @notice The minimum and maximum 24-bit integers are used to represent -/+ * infinity range. We have to reserve these bits as non-standard range for when * price shifts past the last representable tick. * @param tick The tick index value being tested * @return True if the tick index represents a positive or negative infinity. */ function isTickFinite (int24 tick) internal pure returns (bool) { return tick > type(int24).min && tick < type(int24).max; } /* @notice Returns the zero horizon point for the full 24-bit tick index. */ function zeroTick (bool isUpper) internal pure returns (int24) { return isUpper ? type(int24).max : type(int24).min; } /* @notice Returns the zero horizon point equivalent for the first 16-bits of the * tick index. */ function zeroMezz (bool isUpper) internal pure returns (int16) { return isUpper ? type(int16).max : type(int16).min; } /* @notice Returns the zero point equivalent for the terminus bit (last 8-bits) of * the tick index. */ function zeroTerm (bool isUpper) internal pure returns (uint8) { return isUpper ? type(uint8).max : 0; } }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.8.19; /// @title BitMath /// @dev This library provides functionality for computing bit properties of an unsigned integer library BitMath { /// @notice Returns the index of the most significant bit of the number, /// where the least significant bit is at index 0 and the most significant bit is at index 255 /// @dev The function satisfies the property: /// x >= 2**mostSignificantBit(x) and x < 2**(mostSignificantBit(x)+1) /// @param x the value for which to compute the most significant bit, must be greater than 0 /// @return r the index of the most significant bit function mostSignificantBit(uint256 x) internal pure returns (uint8 r) { // Set to unchecked, but the original UniV3 library was written in a pre-checked version of Solidity unchecked{ require(x > 0); if (x >= 0x100000000000000000000000000000000) { x >>= 128; r += 128; } if (x >= 0x10000000000000000) { x >>= 64; r += 64; } if (x >= 0x100000000) { x >>= 32; r += 32; } if (x >= 0x10000) { x >>= 16; r += 16; } if (x >= 0x100) { x >>= 8; r += 8; } if (x >= 0x10) { x >>= 4; r += 4; } if (x >= 0x4) { x >>= 2; r += 2; } if (x >= 0x2) r += 1; } } /// @notice Returns the index of the least significant bit of the number, /// where the least significant bit is at index 0 and the most significant bit is at index 255 /// @dev The function satisfies the property: /// (x & 2**leastSignificantBit(x)) != 0 and (x & (2**(leastSignificantBit(x)) - 1)) == 0) /// @param x the value for which to compute the least significant bit, must be greater than 0 /// @return r the index of the least significant bit function leastSignificantBit(uint256 x) internal pure returns (uint8 r) { // Set to unchecked, but the original UniV3 library was written in a pre-checked version of Solidity unchecked { require(x > 0); r = 255; if (x & type(uint128).max > 0) { r -= 128; } else { x >>= 128; } if (x & type(uint64).max > 0) { r -= 64; } else { x >>= 64; } if (x & type(uint32).max > 0) { r -= 32; } else { x >>= 32; } if (x & type(uint16).max > 0) { r -= 16; } else { x >>= 16; } if (x & type(uint8).max > 0) { r -= 8; } else { x >>= 8; } if (x & 0xf > 0) { r -= 4; } else { x >>= 4; } if (x & 0x3 > 0) { r -= 2; } else { x >>= 2; } if (x & 0x1 > 0) r -= 1; } } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; pragma experimental ABIEncoderV2; import "./SafeCast.sol"; import "./PoolSpecs.sol"; import "./PriceGrid.sol"; import "./CurveMath.sol"; /* @title Trade flow chaining library * @notice Provides common conventions and utility functions for aggregating * and backfilling the user <-> pool flow of token assets within a single * pre-defined pair of assets. */ library Chaining { using SafeCast for int128; using SafeCast for uint128; using CurveMath for uint128; using TickMath for int24; using LiquidityMath for uint128; using CurveMath for CurveMath.CurveState; /* Used as an indicator code by long-form orders to indicate how a given sub- * directive should size relative to some pre-existing cumulative collateral flow * from all the actions on the pool. * evaluation of the long form order. Types supported: * * NO_ROLL_TYPE - No rolling fill. Evaluation will treat the set quantity as a * pre-fixed value in the native domain (i.e. tokens for swaps and liquidity * units for LP actions). * * ROLL_PASS_POS_TYPE - Rolling fill, but against a fixed token collateral target. * Difference with NO_ROLL_TYPE, is the set quantity will denominate as the unit * of the rolling quantity. I.e. represents token collateral instead of * liquidity units on LP actions. * * ROLL_PASS_NEG_TYPE - Same as ROLL_PASS_POS_TYPE, but rolling quantity will be * negative. * * ROLL_FRAC_TYPE - Fills a fixed-point fraction of the cumulatve rolling flow. * E.g. can swap 50% of the tokens returned from previous LP burn. * Denominated in fixed point basis points (1/10,000). * * ROLL_DEBIT_TYPE - Fills the cumulative rolling flow with a fixed offset in the * direction of user debit. E.g. can swap-buy all the tokens * needed, plus slightly more. * * ROLL_CREDIT_TYPE - Same as above, but offset in the direction of user credit. * E.g. can swap-sell all but X tokens from a previous burn * operation.*/ uint8 constant NO_ROLL_TYPE = 0; uint8 constant ROLL_PASS_POS_TYPE = 1; uint8 constant ROLL_PASS_NEG_TYPE = 2; uint8 constant ROLL_FRAC_TYPE = 4; uint8 constant ROLL_DEBIT_TYPE = 5; uint8 constant ROLL_CREDIT_TYPE = 6; /* @notice Common convention that defines the full execution context for * any arbitrary sequence of tradable actions (swap/mint/burn) within * a single pool. * * @param pool_ - The pre-queried specifications for the pool's market specs * @param improve_ - The pre-queries specification for off-grid price improvement * requirements. (May be zero if user didn't request price improvement.) * @param roll_ - The base target to use for any quantities that are set as * open-ended rolling gaps. */ struct ExecCntx { PoolSpecs.PoolCursor pool_; PriceGrid.ImproveSettings improve_; RollTarget roll_; } /* @notice In certain contexts CrocSwap provides the ability for the user to * substitute pre-fixed quantity fields with empty "rolling" fields that are * back-filled based on some cumulative flow across the execution. For example * a swap may specify to buy however much of quote token was demanded by an * earlier mint action on the pool. This struct provides the context for which * rolling flow to target if/when those back-fills are used. * * @param inBaseQty_ If true, rolling quantity targets will use the cumulative * flows on the base-side token in the pair. If false, will use the quote-side * token flows. * @param prePairBal_ Specifies a pre-set rolling flow offset to add/subtract to * the cumulative flow within the pair. Useful for starting with a preset target * from a previous pool or pair in the chain. */ struct RollTarget { bool inBaseQty_; int128 prePairBal_; } /* @notice Represents the accumulated flow between user and pool within a transaction. * * @param baseFlow_ Represents the cumulative base side token flow. Negative for * flow going to the user, positive for flow going to the pool. * @param quoteFlow_ The cumulative quote side token flow. * @param baseProto_ The total amount of base side tokens being collected as protocol * fees. The above baseFlow_ value is inclusive of this quantity. * @param quoteProto_ The total amount of quote tokens being collected as protocol * fees. The above quoteFlow_ value is inclusive of this quantity. */ struct PairFlow { int128 baseFlow_; int128 quoteFlow_; uint128 baseProto_; uint128 quoteProto_; } /* @notice Increments a PairFlow accumulator with a set of pre-determined flows. * @param flow The PairFlow object being accumulated. Function writes to this * structure. * @param base The base side token flows. Negative when going to the user, positive * for flows going to the pool. * @param quote The quote side token flows. Negative when going to the user, positive * for flows going to the pool. */ function accumFlow (PairFlow memory flow, int128 base, int128 quote) internal pure { flow.baseFlow_ += base; flow.quoteFlow_ += quote; } /* @notice Increments a PairFlow accumulator with the flows from another PairFlow * object. * @param accum The PairFlow object being accumulated. Function writes to this * structure. * @param flow The PairFlow input, whose flow is being added to the accumulator. */ function foldFlow (PairFlow memory accum, PairFlow memory flow) internal pure { accum.baseFlow_ += flow.baseFlow_; accum.quoteFlow_ += flow.quoteFlow_; accum.baseProto_ += flow.baseProto_; accum.quoteProto_ += flow.quoteProto_; } /* @notice Increments a PairFlow accumulator with the flows from a swap leg. * @param flow The PairFlow object being accumulated. Function writes to this * structure. * @param inBaseQty Whether the swap was denominated in base or quote side tokens. * @param base The base side token flows. Negative when going to the user, positive * for flows going to the pool. * @param quote The quote side token flows. Negative when going to the user, positive * for flows going to the pool. * @param proto The amount of protocol fees collected by the swap operation. (The * total flows must be inclusive of this value). */ function accumSwap (PairFlow memory flow, bool inBaseQty, int128 base, int128 quote, uint128 proto) internal pure { accumFlow(flow, base, quote); if (inBaseQty) { flow.quoteProto_ += proto; } else { flow.baseProto_ += proto; } } /* @notice Computes the amount of ambient liquidity to mint/burn in order to * neutralize the previously accumulated flow in the pair. * * @dev Note that because of integer rounding liquidity can't exactly neutralize * a fixed flow of tokens. Therefore this function always rounds in favor of * leaving the user with a very small collateral credit. With a credit they can * use the dust discard feature at settlement to avoid any token transfer. * * @param roll Indicates the context for the type of roll target that the call * should target. (See RollTarget struct above.) * @param dir The ambient liquidity directive the liquidity is applied to * @param curve The liquidity curve that is being minted or burned against. * @param flow The previously accumulated flow on this pair. Based on the context * above, this function will target the accumulated flow contained herein. * * @return liq The amount of ambient liquidity to mint/burn to meet the target. * @return isAdd If true, then liquidity must be minted to neutralize rolling flow, * If false, then liquidity must be burned. */ function plugLiquidity (RollTarget memory roll, Directives.AmbientDirective memory dir, CurveMath.CurveState memory curve, PairFlow memory flow) internal pure { if (dir.rollType_ != NO_ROLL_TYPE) { (uint128 collateral, bool isAdd) = collateralDemand(roll, flow, dir.rollType_, dir.liquidity_); uint128 liq = sizeAmbientLiq (collateral, isAdd, curve.priceRoot_, roll.inBaseQty_); (dir.liquidity_, dir.isAdd_) = (liq, isAdd); } } /* @notice Computes the amount of concentrated liquidity to mint/burn in order to * neutralize the previously accumulated flow in the pair. * * @dev Note that concentrated liquidity is represented as lots 1024. The results of * this function will always conform to that multiple. Because of integer rounding * it's impossible to guarantee a liquidity value that exactly neutralizes an * arbitrary token flow quantity. Therefore this function always rounds in favor of * leaving the user with a very small collateral credit. With a credit they can * use the dust discard feature at settlement to avoid any token transfer. * * @param roll Indicates the context for the type of roll target that the call * should target. (See RollTarget struct above.) * @param bend The concentrated range order directive the liquidity is applied to * @param curve The liquidity curve that is being minted or burned against. * @param flow The previously accumulated flow on this pair. Based on the context * above, this function will target the accumulated flow contained herein. * @param lowTick The tick index of the lower bound of the concentrated liquidity * @param highTick The tick index of the upper bound of the concentrated liquidity * * @return seed The amount of ambient liquidity seeds to mint/burn to meet the * target. * @return isAdd If true, then liquidity must be minted to neutralize rolling flow, * If false, then liquidity must be burned. */ function plugLiquidity (RollTarget memory roll, Directives.ConcentratedDirective memory bend, CurveMath.CurveState memory curve, int24 lowTick, int24 highTick, PairFlow memory flow) internal pure { if (bend.rollType_ == NO_ROLL_TYPE) { return; } (uint128 collateral, bool isAdd) = collateralDemand(roll, flow, bend.rollType_, bend.liquidity_); uint128 liq = sizeConcLiq(collateral, isAdd, curve.priceRoot_, lowTick, highTick, roll.inBaseQty_); (bend.liquidity_, bend.isAdd_) = (liq, isAdd); } /* @notice Calculates the amount of ambient liquidity that a fixed amount of token * collateral maps to into the the pool. * * @dev Will always round liquidity conservatively. That is when being used in an add * liquidity context, user can be assured that the liquidity requires slightly * less than their collateral commitment. And when liquidity is being removed * collateral will be slightly higher for the amount of removed liquidity. * * @param collateral The amount of collateral (either base of quote) tokens that we * want to size liquidity for. * @param isAdd Indicates whether the liquidity is being added or removed. Necessary * to make sure that we round conservatively. * @param priceRoot The current price in the pool. * @param inBaseQty True if the collateral is a base token value, false if quote * token. * @return The amount of liquidity, in sqrt(X*Y) units, supported by this * collateral. */ function sizeAmbientLiq (uint128 collateral, bool isAdd, uint128 priceRoot, bool inBaseQty) internal pure returns (uint128) { uint128 liq = bufferCollateral(collateral, isAdd) .liquiditySupported(inBaseQty, priceRoot); return isAdd ? liq : (liq + 1); } /* @notice Same as sizeAmbientLiq() (see above), but calculates for concentrated * liquidity in a given range. * * @param collateral The amount of collateral (either base of quote) tokens that we * want to size liquidity for. * @param isAdd Indicates whether the liquidity is being added or removed. Necessary * to make sure that we round conservatively. * @param priceRoot The current price in the pool. * @param lowTick The tick index of the lower bound of the concentrated liquidity * range. * @param highTick The tick index of the upper bound. * @param inBaseQty True if the collateral is a base token value, false if quote * token. * @return The amount of concentrated liquidity (in sqrt(X*Y) units) supported in * the given tick range. */ function sizeConcLiq (uint128 collateral, bool isAdd, uint128 priceRoot, int24 lowTick, int24 highTick, bool inBaseQty) internal pure returns (uint128) { (uint128 bidPrice, uint128 askPrice) = determinePriceRange(priceRoot, lowTick, highTick, inBaseQty); uint128 liq = bufferCollateral(collateral, isAdd) .liquiditySupported(inBaseQty, bidPrice, askPrice); return isAdd ? liq.shaveRoundLots() : liq.shaveRoundLotsUp(); } // Represents a small, economically meaningless amount of token wei that makes sure // we're always leaving the user with a collateral credit. function bufferCollateral (uint128 collateral, bool isAdd) private pure returns (uint128) { uint128 BUFFER_COLLATERAL = 4; if (isAdd) { // This ternary switch always produces non-negative result, preventing underflow return collateral < BUFFER_COLLATERAL ? 0 : collateral - BUFFER_COLLATERAL; } else { // This ternary switch prevents buffering into an overflow return collateral > type(uint128).max - 4 ? type(uint128).max : collateral + BUFFER_COLLATERAL; } } /* @notice Converts a swap that's indicated to be a rolling gap-fill into one * with quantity and direction set to neutralize hitherto accumulated rolling * flow. E.g. if the user previously performed a buy swap, this would output * a sell swap with an exactly opposite quantity. * * @param roll Indicates the context for the type of roll target that the call * should target. (See RollTarget struct above.) * @param swap The templated SwapDirective object. This function will update the * object with the quantity, direction, and (if necessary) price needed to gap-fill * the rolling flow accumulator. * @param flow The previously accumulated flow on this pair. Based on the context * above, this function will target the accumulated flow contained herein. */ function plugSwapGap (RollTarget memory roll, Directives.SwapDirective memory swap, PairFlow memory flow) internal pure { if (swap.rollType_ != NO_ROLL_TYPE) { int128 plugQty = scaleRoll(roll, flow, swap.rollType_, swap.qty_); overwriteSwap(swap, plugQty); } } /* This function will overwrite the swap directive template to plug the * rolling qty. This obviously involves writing the swap quantity. It * may also possibly flip the swap direction, which is useful in certain * complex scenarios where the user can't exactly predict the direction' * of the roll. * * If rolling plug flips the swap direction, then the limit price will * be set in the wrong direction and the trade will fail. In this case * we disable limitPrice. This is fine because rolling swaps are only * used in the composite code path, where the user can set their output * limits at the settle layer. */ function overwriteSwap (Directives.SwapDirective memory swap, int128 rollQty) private pure { bool prevDir = swap.isBuy_; swap.isBuy_ = swap.inBaseQty_ ? (rollQty < 0) : (rollQty > 0); swap.qty_ = rollQty > 0 ? uint128(rollQty) : uint128(-rollQty); if (prevDir != swap.isBuy_) { swap.limitPrice_ = swap.isBuy_ ? TickMath.MAX_SQRT_RATIO : TickMath.MIN_SQRT_RATIO; } } /* @notice Calculates the total amount of collateral and its direction, that we should * be targeting to neutralize when sizing a liquidity gap-fill. */ function collateralDemand (RollTarget memory roll, PairFlow memory flow, uint8 rollType, uint128 nextQty) private pure returns (uint128 collateral, bool isAdd) { int128 collatFlow = scaleRoll(roll, flow, rollType, nextQty); isAdd = collatFlow < 0; collateral = collatFlow > 0 ? uint128(collatFlow) : uint128(-collatFlow); } /* @notice Calculates the effective bid/ask committed collateral range related * to a concentrated liquidity range order. The calculation is different depending on * whether the curve price is inside or outside the specified tick range. (See below) */ function determinePriceRange (uint128 curvePrice, int24 lowTick, int24 highTick, bool inBase) private pure returns (uint128 bidPrice, uint128 askPrice) { bidPrice = lowTick.getSqrtRatioAtTick(); askPrice = highTick.getSqrtRatioAtTick(); /* The required reserve collateral for a range order is a function of whether * the order is in-range or out-of-range. For in range orders the reserves are * determined based on the distance between the current price and range boundary * price: * Lower range Curve Price Upper range * | | | * <-----------*******************O*******************-------------> * -------------------- * Base token reserves * * For out of range orders the reserve collateral is a function of the entire * width of the range. * * Lower range Upper range Curve Price * | | | * <-----------**************************-----------------O----> * -------------------------- * Base token reserves * * And if the curve is out of range on the opposite side, the reserve collateral * would be zero, and therefore it's impossible to map a non-zero amount of tokens * to liquidity (and function reverts) * * Curve Price Lower range Upper range * | | | * <------O---------------------**************************----------------------> * ZERO base tokens */ if (curvePrice <= bidPrice) { require(!inBase); } else if (curvePrice >= askPrice) { require(inBase); } else if (inBase) { askPrice = curvePrice; } else { bidPrice = curvePrice; } } /* @notice Sums the total rolling balance that should be targeted to be neutralized. * Includes both the accumulated flow in the pair and the pre-pair starting balance * set in the RollTarget context (if any). */ function totalBalance (RollTarget memory roll, PairFlow memory flow) private pure returns (int128) { int128 pairFlow = (roll.inBaseQty_ ? flow.baseFlow_ : flow.quoteFlow_); return roll.prePairBal_ + pairFlow; } /* @notice Given a cumulative rolling flow, calculates a gap-fill quantity based on * rolling target parameters. * * @param roll The rolling target schematic, set at the begining of the pair hop. * @param flow The cumulative collateral flow accumulated in this pair hop so far. * @param rollType The type of rolling gap-fill to target (see indicator comments * above) * @param target The rolling gap-fill target, contextualized by rollType value. * @return The size optimally scaled to match the rolling gap-fill target. */ function scaleRoll (RollTarget memory roll, PairFlow memory flow, uint8 rollType, uint128 target) private pure returns (int128) { int128 rollGap = totalBalance(roll, flow); return scalePlug(rollGap, rollType, target); } /* @notice Given a fixed rolling gap, scales the next incremental size to achieve * a specific user-defined target. * * @param rollGap The rolling gap that exists prior to this leg of the long-form order. * @param rollType The type of rolling gap-fill to target (see indicator comments * above) * @param target The rolling gap-fill target, contextualized by rollType value. * @return The size optimally scaled to match the rolling gap-fill target. */ function scalePlug (int128 rollGap, uint8 rollType, uint128 target) private pure returns (int128) { if (rollType == ROLL_PASS_POS_TYPE) { return int128(target); } else if (rollType == ROLL_PASS_NEG_TYPE) { return -int128(target); } else if (rollType == ROLL_FRAC_TYPE) { return int128(int256(rollGap) * int256(int128(target)) / 10000); } else if (rollType == ROLL_DEBIT_TYPE) { return rollGap + int128(target); } else { return rollGap - int128(target); } } /* @notice Convenience function to round up flows pinned to liquidity. Will safely * (i.e. only in the debit direction) round up the flow to the user-specified * qty. This is primarily useful for mints where the user specifies a token * qty, that gets cast to liquidity, that then gets converted back to * a token quantity amount. Because of fixed-point rounding the latter will * be slightly smaller than the fixed specified amount. For usability and gas * optimization the user will likely want to just pay the full amount. */ function pinFlow (int128 baseFlow, int128 quoteFlow, uint128 uQty, bool inBase) internal pure returns (int128, int128) { int128 qty = uQty.toInt128Sign(); if (inBase && int128(qty) > baseFlow) { baseFlow = int128(qty); } else if (!inBase && int128(qty) > quoteFlow) { quoteFlow = int128(qty); } return (baseFlow, quoteFlow); } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; import "./FixedPoint.sol"; import "./TickMath.sol"; import "./SafeCast.sol"; /* @title Compounding math library * @notice Library provides convenient math functionality for various transformations * and reverse transformations related to compound growth. */ library CompoundMath { using SafeCast for uint256; /* @notice Provides a safe lower-bound approximation of the square root of (1+x) * based on a two-term Taylor series expansion. The purpose is to calculate * the square root for small compound growth rates. * * Both the input and output values are passed as the growth rate *excluding* * the 1.0 multiplier base. For example assume the input (X) is 0.1, then the * output Y is: * (1 + Y) = sqrt(1+X) * (1 + Y) = sqrt(1 + 0.1) * (1 + Y) = 1.0488 (approximately) * Y = 0.0488 (approximately) * In the example the square root of 10% compound growth is 4.88% * * Another example, assume the input (X) is 0.6, then the output (Y) is: * (1 + Y) = sqrt(1+X) * (1 + Y) = sqrt(1 + 0.6) * (1 + Y) = 1.264 (approximately) * Y = 0.264 (approximately) * In the example the square root of 60% growth is 26.4% compound growth * * Another example, assume the input (X) is 0.018, then the output (Y) is: * (1 + Y) = sqrt(1+X) * (1 + Y) = sqrt(1 + 0.018) * (1 + Y) = 1.00896 (approximately) * Y = 0.00896 (approximately) * In the example the square root of 1.8% growth is 0.896% compound growth * * @dev Due to approximation error, only safe to use on input in the range of * [0,1). Will always round down from the true real value. * * @param x The value of x in (1+x). Represented as a Q16.48 fixed-point * @returns The value of y for which (1+y) = sqrt(1+x). Represented as Q16.48 fixed point * */ function approxSqrtCompound (uint64 x64) internal pure returns (uint64) { // Taylor series error becomes too large above 2.0. Approx is still conservative // but the angel's share becomes unreasonable. require(x64 < FixedPoint.Q48); unchecked { uint256 x = uint256(x64); // Shift by 48, to bring x^2 back in fixed point precision uint256 xSq = (x * x) >> 48; // x * x never overflows 256 bits, because x is 64 bits uint256 linear = x >> 1; // Linear Taylor series term is x/2 uint256 quad = xSq >> 3; // Quadratic Tayler series term ix x^2/8; // This will always fit in 64 bits because result is smaller than original/ // Will always be greater than 0, because x^2 < x for x < 1 return uint64(linear - quad); } } /* @notice Computes the result from compounding two cumulative growth rates. * @dev Rounds down from the real value. Caps the result if type exceeds the max * fixed-point value. * @param x The compounded growth rate as in (1+x). Represted as Q16.48 fixed-point. * @param y The compounded growth rate as in (1+y). Represted as Q16.48 fixed-point. * @returns The cumulative compounded growth rate as in (1+z) = (1+x)*(1+y). * Represented as Q16.48 fixed-point. */ function compoundStack (uint64 x, uint64 y) internal pure returns (uint64) { unchecked { uint256 ONE = FixedPoint.Q48; uint256 num = (ONE + x) * (ONE + y); // Never overflows 256-bits because x and y are 64 bits uint256 term = num >> 48; // Divide by 48-bit ONE uint256 z = term - ONE; // term will always be >= ONE if (z >= type(uint64).max) { return type(uint64).max; } return uint64(z); } } /* @notice Computes the result from backing out a compounded growth value from * an existing value. The inverse of compoundStack(). * @dev Rounds down from the real value. * @param val The fixed price representing the starting value that we want * to back out a pre-growth seed from. * @param deflator The compounded growth rate to back out, as in (1+g). Represented * as Q16.48 fixed-point * @returns The pre-growth value as in val/(1+g). Rounded down as an unsigned * integer. */ function compoundShrink (uint64 val, uint64 deflator) internal pure returns (uint64) { unchecked { uint256 ONE = FixedPoint.Q48; uint256 multFactor = ONE + deflator; // Never overflows because both fit inside 64 bits uint256 num = uint256(val) << 48; // multiply by 48-bit ONE uint256 z = num / multFactor; // multFactor will never be zero because it's bounded by 1 return uint64(z); // Will always fit in 64-bits because shrink can only decrease } } /* @notice Computes the implied compound growth rate based on the division of two * arbitrary quantities. * @dev Based on this function's use, calulated growth rate will always be * capped at 100%. The implied growth rate must always be non-negative. * @param inflated The larger value to be divided. Any 128-bit integer or fixed point * @param seed The smaller value to use as a divisor. Any 128-bit integer or fixed * point. * @returns The cumulative compounded growth rate as in (1+z) = (1+x)/(1+y). * Represeted as Q16.48. */ function compoundDivide (uint128 inflated, uint128 seed) internal pure returns (uint64) { // Otherwise arithmetic doesn't safely fit in 256 -bit require(inflated < type(uint208).max && inflated >= seed); unchecked { uint256 ONE = FixedPoint.Q48; uint256 num = uint256(inflated) << 48; uint256 z = (num / seed) - ONE; // Never underflows because num is always greater than seed if (z >= ONE) { return uint64(ONE); } return uint64(z); } } /* @notice Calculates a final price from applying a growth rate to a starting price. * @dev Always rounds in the direction of @shiftUp * @param price The starting price to be compounded. Q64.64 fixed point. * @param growth The compounded growth rate to apply, as in (1+g). Represented * as Q16.48 fixed-point * @param shiftUp If true compounds the starting price up, so the result will be * greater. If false, compounds the price down so the result will be * smaller than the original price. * @returns The post-growth price as in price*(1+g) (or price*(1-g) if shiftUp is * false). Q64.64 always rounded in the direction of shiftUp. */ function compoundPrice (uint128 price, uint64 growth, bool shiftUp) internal pure returns (uint128) { unchecked { uint256 ONE = FixedPoint.Q48; uint256 multFactor = ONE + growth; // Guaranteed to fit in 65-bits if (shiftUp) { uint256 num = uint256(price) * multFactor; // Guaranteed to fit in 193 bits uint256 z = num >> 48; // De-scale by the 48-bit growth precision return (z+1).toUint128(); // Round in the price shift } else { uint256 num = uint256(price) << 48; // No need to safe cast, since this will be smaller than original price return uint128(num / multFactor); } } } /* @notice Inflates a starting value by a cumulative growth rate. * @dev Rounds down from the real value. Result is capped at max(uint128). * @param seed The pre-inflated starting value as unsigned integer * @param growth Cumulative growth rate as Q16.48 fixed-point * @return The ending value = seed * (1 + growth). Rounded down to nearest * integer value */ function inflateLiqSeed (uint128 seed, uint64 growth) internal pure returns (uint128) { unchecked { uint256 ONE = FixedPoint.Q48; uint256 num = uint256(seed) * uint256(ONE + growth); // Guaranteed to fit in 256 uint256 inflated = num >> 48; // De-scale by the 48-bit growth precision; if (inflated > type(uint128).max) { return type(uint128).max; } return uint128(inflated); } } /* @notice Deflates a starting value by a cumulative growth rate. * @dev Rounds down from the real value. * @param liq The post-inflated liquidity as unsigned integer * @param growth Cumulative growth rate as Q16.48 fixed-point * @return The ending value = liq / (1 + growth). Rounded down to nearest * integer value */ function deflateLiqSeed (uint128 liq, uint64 growth) internal pure returns (uint128) { unchecked { uint256 ONE = FixedPoint.Q48; uint256 num = uint256(liq) << 48; uint256 deflated = num / (ONE + growth); // Guaranteed to fit in 256-bits // No need to safe cast-- will allways be smaller than starting return uint128(deflated); } } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; pragma experimental ABIEncoderV2; import './SafeCast.sol'; import './FixedPoint.sol'; import './LiquidityMath.sol'; import './CompoundMath.sol'; import './CurveMath.sol'; /* @title Curve fee assimilation library * @notice Provides functionality for incorporating arbitrary token fees into * a locally stable constant-product liquidity curve. */ library CurveAssimilate { using LiquidityMath for uint128; using CompoundMath for uint128; using CompoundMath for uint64; using SafeCast for uint256; using FixedPoint for uint128; using CurveMath for CurveMath.CurveState; /* @notice Converts token-based fees into ambient liquidity on the curve, * adjusting the price accordingly. * * @dev The user is responsible to make sure that the price shift will never * exceed the locally stable range of the liquidity curve. I.e. that * the price won't cross a book level bump. Because fees are only a tiny * fraction of swap notional, the best approach is to only collect fees * on the segment of the notional up to the level bump price limit. If * a swap spans multiple bumps, then call this function separtely on a * per-segment basis. * * @param curve The pre-assimilated state of the consant-product AMM liquidity * curve. This in memory structure will be updated to reflect the impact of * the assimilation. * @param feesPaid The pre-calculated fees to be collected and incorporated * as liquidity into the curve. Must be denominated (and colleted) on the * opposite pair side as the swap denomination. * @param isSwapInBase Set to true, if the swap is denominated in the base * token of the pair. (And therefore fees are denominated in quote token) */ function assimilateLiq (CurveMath.CurveState memory curve, uint128 feesPaid, bool isSwapInBase) internal pure { // In zero liquidity curves, it makes no sense to assimilate, since // it will run prices to infinity. uint128 liq = CurveMath.activeLiquidity(curve); if (liq == 0) { return; } bool feesInBase = !isSwapInBase; uint128 feesToLiq = shaveForPrecision(liq, curve.priceRoot_, feesPaid, feesInBase); uint64 inflator = calcLiqInflator(liq, curve.priceRoot_, feesToLiq, feesInBase); if (inflator > 0) { stepToLiquidity(curve, inflator, feesInBase); } } /* @notice Converts a fixed fee collection into a constant product liquidity * multiplier. * @dev To be conservative, every fixed point calculation step rounds down. * Because of this the result can be an arbitrary epsilon smaller than * the real formula. * @return The imputed percent growth to aggregate liquidity resulting from * assimilating these fees into the virtual reserves. Represented as * Q16.48 fixed-point, where the result G is used as a (1+G) multiplier. */ function calcLiqInflator (uint128 liq, uint128 price, uint128 feesPaid, bool inBaseQty) private pure returns (uint64) { // First calculate the virtual reserves at the curve's current price... uint128 reserve = CurveMath.reserveAtPrice(liq, price, inBaseQty); // ...Then use that to calculate how much the liqudity would grow assuming the // fees were added as reserves into an equivalent constant-product AMM curve. return calcReserveInflator(reserve, feesPaid); } /* @notice Converts a fixed delta change in the virtual reserves to a percent * change in the AMM curve's active liquidity. * * @dev Inflators above will 100% result in reverted transactions. */ function calcReserveInflator (uint128 reserve, uint128 feesPaid) private pure returns (uint64 inflator) { // Short-circuit when virtual reserves are smaller than fees. This can only // occur when liquidity is extremely small, and so is economically // meanignless. But guarantees numerical stability. if (reserve == 0 || feesPaid > reserve) { return 0; } uint128 nextReserve = reserve + feesPaid; uint64 inflatorRoot = nextReserve.compoundDivide(reserve); // Since Liquidity is represented as Sqrt(X*Y) the growth rate of liquidity is // Sqrt(X'/X) where X' = X + delta(X) inflator = inflatorRoot.approxSqrtCompound(); // Important. The price precision buffer calcualted in assimilateLiq assumes // liquidity will never expand by a factor of 2.0 (i.e. inflator over 1.0 in // Q16.48). See the shaveForPrecision() function comments for more discussion require(inflator < FixedPoint.Q48, "IF"); } /* @notice Adjusts the fees assimilated into the liquidity curve. This is done to * hold out a small amount of collateral that doesn't expand the liquidity * in the curve. That's necessary so we have slack in the virtual reserves to * prevent under-collateralization resulting from fixed point precision rounding * on the price shift. * * @dev Price can round up to one precision unit (2^-64) away from the true real * value. Therefore we have to over-collateralize the existing liquidity by * enough to buffer the virtual reserves by this amount. Economically this is * almost always a meaningless amount. Often just 1 wei (rounded up) for all but * the biggest or most extreme priced curves. * * @return The amount of reward fees available to assimilate into the liquidity * curve after deducting the precision over-collaterilization allocation. */ function shaveForPrecision (uint128 liq, uint128 price, uint128 feesPaid, bool isFeesInBase) private pure returns (uint128) { // The precision buffer is calculated on curve precision, before curve liquidity // expands from fee assimilation. Therefore we upper bound the precision buffer to // account for maximum possible liquidity expansion. // // We set a factor of 2.0, as the bound because that would represnet swap fees // in excess of the entire virtual reserve of the curve. This still allows any // size impact swap (because liquidity fees cannot exceed 100%). The only restrction // is extremely large swaps where fees are collected in input tokens (i.e. fixed // output swaps) // // See the require statement calcReserveInflator function, for where this check // is enforced. uint128 MAX_LIQ_EXPANSION = 2; uint128 bufferTokens = MAX_LIQ_EXPANSION * CurveMath.priceToTokenPrecision (liq, price, isFeesInBase); unchecked { return feesPaid <= bufferTokens ? 0 : feesPaid - bufferTokens; // Condition assures never underflow } } /* @notice Given a targeted aggregate liquidity inflator, affects that change in * the curve object by expanding the ambient seeds, and adjusting the cumulative * growth accumulators as needed. * * @dev To be conservative, a number of fixed point calculations will round down * relative to the exact mathematical liquidity value. This is to prevent * under-collateralization from over-expanding liquidity relative to virtual * reserves available to the pool. This means the curve's liquidity grows slightly * less than mathematical exact calculation would imply. * * @dev Price is always rounded further in the direction of the shift. This * shifts the collateralization burden in the direction of the fee-token. * This makes sure that the opposite token's collateral requirements is * unchanged. The fee token should be sufficiently over-collateralized from * a previous adjustment made in shaveForPrecision() * * @param curve The current state of the liquidity curve, will be updated to reflect * the assimilated liquidity from fee accumulation. * @param inflator The incremental growth in total curve liquidity contributed by this * swaps paid fees. * @param feesInBase If true, indicates swap paid fees in base token. */ function stepToLiquidity (CurveMath.CurveState memory curve, uint64 inflator, bool feesInBase) private pure { curve.priceRoot_ = CompoundMath.compoundPrice (curve.priceRoot_, inflator, feesInBase); // The formula for Liquidity is // L = A + C // = S * (1 + G) + C // (where A is ambient liqudity, S is ambient seeds, G is ambient growth, // and C is conc. liquidity) // // Liquidity growth is distributed pro-rata, between the ambient and concentrated // terms. Therefore ambient-side growth is reflected by inflating the growth rate: // A' = A * (1 + I) // = S * (1 + G) * (1 + I) // (where A' is the post transaction ambient liquidity, and I is the liquidity // inflator for this transaction) // // Note that if the deflator reaches its maximum value (equivalent to 2^16), then // this value will cease accumulating new rewards. Essentially all fees attributable // to ambient liquidity will be burned. Economically speaking, this is unlikely to happen // for any meaningful pool, but be aware. See the Ambient Rewards section of the // documentation at docs/CurveBound.md in the repo for more discussion. curve.seedDeflator_ = curve.seedDeflator_ .compoundStack(inflator); // Now compute the increase in ambient seed rewards to concentrated liquidity. // Rewards stored as ambient seeds, but collected in the form of liquidity: // Ar = Sr * (1 + G) // Sr = Ar / (1 + G) // (where Ar are concentrated rewards in ambient liquidity, and Sr are // concentrated rewards denominated in ambient seeds) // // Note that there's a minor difference from using the post-inflated cumulative // ambient growth (G) calculated in the previous step. This rounds the rewards // growth down, which increases numerical over-collateralization. // Concentrated rewards are represented as a rate of per unit ambient growth // in seeds. Therefore to calculate the marginal increase in concentrated liquidity // rewards we deflate the marginal increase in total liquidity by the seed-to-liquidity // deflator uint64 concRewards = inflator.compoundShrink(curve.seedDeflator_); // Represents the total number of new ambient liquidity seeds that are created from // the swap fees accumulated as concentrated liquidity rewards. (All concentrated rewards // are converted to ambient seeds.) To calculate we take the marginal increase in concentrated // rewards on this swap and multiply by the total amount of active concentrated liquidity. uint128 newAmbientSeeds = uint256(curve.concLiq_.mulQ48(concRewards)) .toUint128(); // To be conservative in favor of over-collateralization, we want to round down the marginal // rewards. curve.concGrowth_ += roundDownConcRewards(concRewards, newAmbientSeeds); curve.ambientSeeds_ += newAmbientSeeds; } /* @notice To avoid over-promising rewards, we need to make sure that fixed-point * rounding effects don't round concentrated rewards growth more than ambient * seeds. Otherwise we could possibly reach a situation where burned rewards * exceed the the ambient seeds stored on the curve. * * @dev Functionally, the reward inflator is most likely higher precision than * the ambient seed injection. Therefore prevous fixed point math that rounds * down both could over-promise rewards realtive to backed seeds. To correct * for this, we have to shrink the rewards inflator by the precision unit's * fraction of the ambient injection. Thus guaranteeing that the adjusted rewards * inflator under-promises relative to backed seeds. */ function roundDownConcRewards (uint64 concInflator, uint128 newAmbientSeeds) private pure returns (uint64) { // No need to round down if the swap was too small for concentrated liquidity // to earn any rewards. if (newAmbientSeeds == 0) { return 0; } // We always want to make sure that the rewards accumulator is conservatively // rounded down relative to the actual liquidity being added to the curve. // // To shrink the rewards by ambient round down precision we use the formula: // R' = R * A / (A + 1) // (where R is the rewards inflator, and A is the ambient seed injection) // // Precision wise this all fits in 256-bit arithmetic, and is guaranteed to // cast to 64-bit result, since the result is always smaller than the original // inflator. return uint64(uint256(concInflator) * uint256(newAmbientSeeds) / uint256(newAmbientSeeds + 1)); } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; pragma experimental ABIEncoderV2; import './CurveMath.sol'; import './TickMath.sol'; /* @title Curve caching library. * @notice Certain values related to the CurveState aren't stored (to save storage), * but are relatively gas expensive to calculate. As such we want to cache these * calculations in memory whenever possible to avoid duplicated effort. This library * provides a convenient facility for that. */ library CurveCache { using TickMath for uint128; using CurveMath for CurveMath.CurveState; /* @notice Represents the underlying CurveState along with the tick price memory * cache, and associated bookeeping. * * @param curve_ The underlying CurveState object. * @params isTickClean_ If true, then the current price tick value is valid to use. * @params unsafePriceTick_ The price tick value (if previously cached). User should * not access directly, but use the pullPriceTick() helper function. */ struct Cache { CurveMath.CurveState curve_; bool isTickClean_; int24 unsafePriceTick_; } /* @notice Given a curve cache instance retrieves the price tick, if cached, or * calculates and cached if cache is dirty. */ function pullPriceTick (Cache memory cache) internal pure returns (int24) { if (!cache.isTickClean_) { cache.unsafePriceTick_ = cache.curve_.priceRoot_.getTickAtSqrtRatio(); cache.isTickClean_ = true; } return cache.unsafePriceTick_; } /* @notice Call on a curve cache object, when the underlying price has changed, and * therefore the cache should be conisdered dirty. */ function dirtyPrice (Cache memory cache) internal pure { cache.isTickClean_ = false; } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; pragma experimental ABIEncoderV2; import './SafeCast.sol'; import './FixedPoint.sol'; import './LiquidityMath.sol'; import './CompoundMath.sol'; /* @title Curve and swap math library * @notice Library that defines locally stable constant liquidity curves and * swap struct, as well as functions to derive impact and aggregate * liquidity measures on these objects. */ library CurveMath { using LiquidityMath for uint128; using CompoundMath for uint256; using SafeCast for uint256; using SafeCast for uint192; /* All CrocSwap swaps occur as legs across locally stable constant-product AMM * curves. For large moves across tick boundaries, the state of this curve might * change as range-bound liquidity is kicked in or out of the currently active * curve. But for small moves within tick boundaries (or between tick boundaries * with no liquidity bumps), the curve behaves like a classic constant-product AMM. * * CrocSwap tracks two types of liquidity. 1) Ambient liquidity that is non- * range bound and remains active at all prices from zero to infinity, until * removed by the staking user. 2) Concentrated liquidity that is tied to an * arbitrary lower<->upper tick range and is kicked out of the curve when the * price moves out of range. * * In the CrocSwap model all collected fees are directly incorporated as expanded * liquidity onto the curve itself. (See CurveAssimilate.sol for more on the * mechanics.) All accumulated fees are added as ambient-type liquidity, even those * fees that belong to the pro-rata share of the active concentrated liquidity. * This is because on an aggregate level, we can't break down the pro-rata share * of concentrated rewards to the potentially near infinite concentrated range * possibilities. * * Because of this concentrated liquidity can be flatly represented as 1:1 with * contributed liquidity. Ambient liquidity, in contrast, deflates over time as * it accumulates rewards. Therefore it's represented in terms of seed amount, * i.e. the equivalent of 1 unit of ambient liquidity contributed at the inception * of the pool. As fees accumulate the conversion rate from seed to liquidity * continues to increase. * * Finally concentrated liquidity rewards are represented in terms of accumulated * ambient seeds. This automatically takes care of the compounding of ambient * rewards compounded on top of concentrated rewards. * * @param priceRoot_ The square root of the price ratio exchange rate between the * base and quote-side tokens in the AMM curve. (represented in Q64.64 fixed point) * @param ambientSeeds_ The total ambient liquidity seeds in the current curve. * (Inflated by seed deflator to get efective ambient liquidity) * @param concLiq_ The total concentrated liquidity active and in range at the * current state of the curve. * @param seedDeflator_ The cumulative growth rate (represented as Q16.48 fixed * point) of a hypothetical 1-unit of ambient liquidity held in the pool since * inception. * @param concGrowth_ The cumulative rewards growth rate (represented as Q16.48 * fixed point) of hypothetical 1 unit of concentrated liquidity in range in the * pool since inception. * * @dev Price ratio is stored as a square root because it makes reserve calculation * arithmetic much easier. To be conservative with collateral these growth * rates should always be rounded down from their real-value results. Some * minor lower-bound approximation is fine, since all it will result in is * slightly smaller reward payouts. */ struct CurveState { uint128 priceRoot_; uint128 ambientSeeds_; uint128 concLiq_; uint64 seedDeflator_; uint64 concGrowth_; } /* @notice Calculates the total amount of liquidity represented by the liquidity * curve object. * @dev Result always rounds down from the real value, *assuming* that the fee * accumulation fields are conservative lower-bound rounded. * @param curve - The currently active liqudity curve state. Remember this curve * state is only known to be valid within the current tick. * @return - The total scalar liquidity. Equivalent to sqrt(X*Y) in an equivalent * constant-product AMM. */ function activeLiquidity (CurveState memory curve) internal pure returns (uint128) { uint128 ambient = CompoundMath.inflateLiqSeed (curve.ambientSeeds_, curve.seedDeflator_); return LiquidityMath.addLiq(ambient, curve.concLiq_); } /* @notice Similar to calcLimitFlows(), except returns the max possible flow in the * *opposite* direction. I.e. if inBaseQty_ is True, returns the quote token flow * for the swap. And vice versa.. * * @dev The fixed-point result approximates the real valued formula with close but * directionally unpredicable precision. It could be slightly above or slightly * below. In the case of zero flows this could be substantially over. This * function should not be used in any context with strict directional boundness * requirements. */ function calcLimitCounter (CurveState memory curve, uint128 swapQty, bool inBaseQty, uint128 limitPrice) internal pure returns (uint128) { bool isBuy = limitPrice > curve.priceRoot_; uint128 denomFlow = calcLimitFlows(curve, swapQty, inBaseQty, limitPrice); return invertFlow(activeLiquidity(curve), curve.priceRoot_, denomFlow, isBuy, inBaseQty); } /* @notice Calculates the total quantity of tokens that can be swapped on the AMM * curve until either 1) the limit price is reached or 2) the swap fills its * entire remaining quantity. * * @dev This function does *NOT* account for the possibility of concentrated liq * being knocked in/out as the price on the AMM curve moves across tick boundaries. * It's the responsibility of the caller to properly check whether the limit price * is within the bounds of the locally stable curve. * * @dev As long as CurveState's fee accum fields are conservatively lower bounded, * and as long as limitPrice is accurate, then this function rounds down from the * true real value. At most this round down loss of precision is tightly bounded at * 2 wei. (See comments in deltaPriceQuote() function) * * @param curve - The current state of the liquidity curve. No guarantee that it's * liquidity stable through the entire limit range (see @dev above). Note that this * function does *not* update the curve struct object. * @param swapQty - The total remaining quantity left in the swap. * @param inBaseQty - Whether the swap quantity is denomianted in base or quote side * token. * @param limitPrice - The highest (lowest) acceptable ending price of the AMM curve * for a buy (sell) swap. Represented as Q64.64 fixed point square root of the * price. * * @return - The maximum executable swap flow (rounded down by fixed precision). * Denominated on the token side based on inBaseQty param. Will * always return unsigned magnitude regardless of the direction. User * can easily determine based on swap context. */ function calcLimitFlows (CurveState memory curve, uint128 swapQty, bool inBaseQty, uint128 limitPrice) internal pure returns (uint128) { uint128 limitFlow = calcLimitFlows(curve, inBaseQty, limitPrice); return limitFlow > swapQty ? swapQty : limitFlow; } function calcLimitFlows (CurveState memory curve, bool inBaseQty, uint128 limitPrice) private pure returns (uint128) { uint128 liq = activeLiquidity(curve); return inBaseQty ? deltaBase(liq, curve.priceRoot_, limitPrice) : deltaQuote(liq, curve.priceRoot_, limitPrice); } /* @notice Calculates the change to base token reserves associated with a price * move along an AMM curve of constant liquidity. * * @dev Result is a tight lower-bound for fixed-point precision. Meaning if the * the returned limit is X, then X will be inside the limit price and (X+1) * will be outside the limit price. */ function deltaBase (uint128 liq, uint128 priceX, uint128 priceY) internal pure returns (uint128) { unchecked { uint128 priceDelta = priceX > priceY ? priceX - priceY : priceY - priceX; // Condition assures never underflows return reserveAtPrice(liq, priceDelta, true); } } /* @notice Calculates the change to quote token reserves associated with a price * move along an AMM curve of constant liquidity. * * @dev Result is almost always within a fixed-point precision unit from the true * real value. However in certain rare cases, the result could be up to 2 wei * below the true mathematical value. Caller should account for this */ function deltaQuote (uint128 liq, uint128 price, uint128 limitPrice) internal pure returns (uint128) { // For purposes of downstream calculations, we make sure that limit price is // larger. End result is symmetrical anyway if (limitPrice > price) { return calcQuoteDelta(liq, limitPrice, price); } else { return calcQuoteDelta(liq, price, limitPrice); } } /* The formula calculated is * F = L * d / (P*P') * (where F is the flow to the limit price, where L is liquidity, d is delta, * P is price and P' is limit price) * * Calculating this requires two stacked mulDiv. To meet the function's contract * we need to compute the result with tight fixed point boundaries at or below * 2 wei to conform to the function's contract. * * The fixed point calculation of flow is * F = mulDiv(mulDiv(...)) = FR - FF * (where F is the fixed point result of the formula, FR is the true real valued * result with inifnite precision, FF is the loss of precision fractional round * down, mulDiv(...) is a fixed point mulDiv call of the form X*Y/Z) * * The individual fixed point terms are * T1 = mulDiv(X1, Y1, Z1) = T1R - T1F * T2 = mulDiv(T1, Y2, Z2) = T2R - T2F * (where T1 and T2 are the fixed point results from the first and second term, * T1R and T2R are the real valued results from an infinite precision mulDiv, * T1F and T2F are the fractional round downs, X1/Y1/Z1/Y2/Z2 are the arbitrary * input terms in the fixed point calculation) * * Therefore the total loss of precision is * FF = T2F + T1F * T2R/T1 * * To guarantee a 2 wei precision loss boundary: * FF <= 2 * T2F + T1F * T2R/T1 <= 2 * T1F * T2R/T1 <= 1 (since T2F as a round-down is always < 1) * T2R/T1 <= 1 (since T1F as a round-down is always < 1) * Y2/Z2 >= 1 * Z2 >= Y2 */ function calcQuoteDelta (uint128 liq, uint128 priceBig, uint128 priceSmall) private pure returns (uint128) { uint128 priceDelta = priceBig - priceSmall; // This is cast to uint256 but is guaranteed to be less than 2^192 based off // the return type of divQ64 uint256 termOne = FixedPoint.divQ64(liq, priceSmall); // As long as the final result doesn't overflow from 128-bits, this term is // guaranteed not to overflow from 256 bits. That's because the final divisor // can be at most 128-bits, therefore this intermediate term must be 256 bits // or less. // // By definition priceBig is always larger than priceDelta. Therefore the above // condition of Z2 >= Y2 is satisfied and the equation caps at a maximum of 2 // wei of precision loss. uint256 termTwo = termOne * uint256(priceDelta) / uint256(priceBig); return termTwo.toUint128(); } /* @notice Returns the amount of virtual reserves give the price and liquidity of the * constant-product liquidity curve. * * @dev The actual pool probably holds significantly less collateral because of the * use of concentrated liquidity. * @dev Results always round down from the precise real-valued mathematical result. * * @param liq - The total active liquidity in AMM curve. Represented as sqrt(X*Y) * @param price - The current active (square root of) price of the AMM curve. * represnted as Q64.64 fixed point * @param inBaseQty - The side of the pool to calculate the virtual reserves for. * * @returns The virtual reserves of the token (rounded down to nearest integer). * Equivalent to the amount of tokens that would be held for an equivalent * classical constant- product AMM without concentrated liquidity. */ function reserveAtPrice (uint128 liq, uint128 price, bool inBaseQty) internal pure returns (uint128) { return (inBaseQty ? uint256(FixedPoint.mulQ64(liq, price)) : uint256(FixedPoint.divQ64(liq, price))).toUint128(); } /* @notice Calculated the amount of concentrated liquidity within a price range * supported by a fixed amount of collateral. Note that this calculates the * collateral only needed by one side of the pair. * * @dev Always rounds fixed-point arithmetic result down. * * @param collateral The total amount of token collateral being pledged. * @param inBase If true, the collateral represents the base-side token in the pair. * If false the quote side token. * @param priceX The price boundary of the concentrated liquidity position. * @param priceY The other price boundary of the concentrated liquidity position. * @returns The total amount of liquidity supported by the collateral. */ function liquiditySupported (uint128 collateral, bool inBase, uint128 priceX, uint128 priceY) internal pure returns (uint128) { if (!inBase) { return liquiditySupported(collateral, true, FixedPoint.recipQ64(priceX), FixedPoint.recipQ64(priceY)); } else { unchecked { uint128 priceDelta = priceX > priceY ? priceX - priceY : priceY - priceX; // Conditional assures never underflows return liquiditySupported(collateral, true, priceDelta); } } } /* @notice Calculated the amount of ambient liquidity supported by a fixed amount of * collateral. Note that this calculates the collateral only needed by one * side of the pair. * * @dev Always rounds fixed-point arithmetic result down. * * @param collateral The total amount of token collateral being pledged. * @param inBase If true, the collateral represents the base-side token in the pair. * If false the quote side token. * @param price The current (square root) price of the curve as Q64.64 fixed point. * @returns The total amount of ambient liquidity supported by the collateral. */ function liquiditySupported (uint128 collateral, bool inBase, uint128 price) internal pure returns (uint128) { return inBase ? FixedPoint.divQ64(collateral, price).toUint128By192() : FixedPoint.mulQ64(collateral, price).toUint128By192(); } /* @dev The fixed point arithmetic results in output that's a close approximation * to the true real value, but could be skewed in either direction. The output * from this function should not be consumed in any context that requires strict * boundness. */ function invertFlow (uint128 liq, uint128 price, uint128 denomFlow, bool isBuy, bool inBaseQty) private pure returns (uint128) { if (liq == 0) { return 0; } uint256 invertReserve = reserveAtPrice(liq, price, !inBaseQty); uint256 initReserve = reserveAtPrice(liq, price, inBaseQty); unchecked { uint256 endReserve = (isBuy == inBaseQty) ? initReserve + denomFlow : // Will always fit in 256-bits initReserve - denomFlow; // flow is always less than total reserves if (endReserve == 0) { return type(uint128).max; } uint256 endInvert = uint256(liq) * uint256(liq) / endReserve; return (endInvert > invertReserve ? endInvert - invertReserve : invertReserve - endInvert).toUint128(); } } /* @notice Computes the amount of token over-collateralization needed to buffer any * loss of precision rounding in the fixed price arithmetic on curve price. This * is necessary because price occurs in different units than tokens, and we can't * assume a single wei is sufficient to buffer one price unit. * * @dev In practice the price unit precision is almost always smaller than the token * token precision. Therefore the result is usually just 1 wei. The exception are * pools where liquidity is very high or price is very low. * * @param liq The total liquidity in the curve. * @param price The (square root) price of the curve in Q64.64 fixed point * @param inBase If true calculate the token precision on the base side of the pair. * Otherwise, calculate on the quote token side. * * @return The conservative upper bound in number of tokens that should be * burned to over-collateralize a single precision unit of price rounding. If * the price arithmetic involves multiple units of precision loss, this number * should be multiplied by that factor. */ function priceToTokenPrecision (uint128 liq, uint128 price, bool inBase) internal pure returns (uint128) { unchecked { // To provide more base token collateral than price precision rounding: // delta(B) >= L * delta(P) // delta(P) <= 2^-64 (64 bit precision rounding) // delta(B) >= L * 2^-64 // (where L is liquidity, B is base token reserves, P is price) if (inBase) { // Since liq is shifted right by 64 bits, adding one can never overflow return (liq >> 64) + 1; } else { // Calculate the quote reservs at the current price and a one unit price step, // then take the difference as the minimum required quote tokens needed to // buffer that price step. uint192 step = FixedPoint.divQ64(liq, price - 1); uint192 start = FixedPoint.divQ64(liq, price); // next reserves will always be equal or greater than start reserves, so the // subtraction will never underflow. uint192 delta = step - start; // Round tokens up conservative. // This will never overflow because 192 bit nums incremented by 1 will always fit in // 256 bits. uint256 deltaRound = uint256(delta) + 1; return deltaRound.toUint128(); } } } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; pragma experimental ABIEncoderV2; import './SafeCast.sol'; import './FixedPoint.sol'; import './LiquidityMath.sol'; import './CompoundMath.sol'; import './CurveMath.sol'; /* @title Curve roll library * @notice Provides functionality for rolling swap flows onto a constant-product * AMM liquidity curve. */ library CurveRoll { using SafeCast for uint256; using SafeCast for uint128; using LiquidityMath for uint128; using CompoundMath for uint256; using CurveMath for CurveMath.CurveState; using CurveMath for uint128; /* @notice Applies a given flow onto a constant product AMM curve, adjusts the curve * price, and outputs accumulator deltas on both sides. * * @dev Note that this function does *NOT* check whether the curve is liquidity * stable through the flow impact. It's the callers job to make sure that the * impact doesn't cross through any tick barrier that knocks concentrated liquidity * in/out. * * @param curve - The current state of the active liquidity curve. After calling * this struct will be updated with the post-swap price. Note that none of the * fee accumulator fields are adjusted. This function does *not* collect or apply * liquidity fees. It's the callers responsibility to handle fees outside this * call. * @param flow - The amount of tokens to swap on this leg. In certain cases this * number may be a fixed point estimate based on a price target. Collateral safety * is guaranteed with up to 2 wei of precision loss. * @param inBaseQty - If true, the above flow applies to the base-side tokens in the * pair. If false, applies to the quote-side tokens. * @param isBuy - If true, the flows are paying base tokens to the pool and receiving * quote tokens. (Hence pushing the price up.) If false, vice versa. * @param swapQty - The total quantity left on the swap across all legs. May or may * not be equal to flow, or could be left depending on whether this * leg will fill the entire quantity. * * @return baseFlow - The signed flow of the base-side tokens. Negative means the flow * is being paid from the pool to the user. Positive means the flow is * being paid from the user to the pool. * @return quoteFlow - The signed flow of the quote-side tokens. * @return qtyLeft - The amount of swapQty remaining after the flow from this leg is * processed. */ function rollFlow (CurveMath.CurveState memory curve, uint128 flow, bool inBaseQty, bool isBuy, uint128 swapQty) internal pure returns (int128, int128, uint128) { (uint128 counterFlow, uint128 nextPrice) = deriveImpact (curve, flow, inBaseQty, isBuy); (int128 paidFlow, int128 paidCounter) = signFlow (flow, counterFlow, inBaseQty, isBuy); return setCurvePos(curve, inBaseQty, isBuy, swapQty, nextPrice, paidFlow, paidCounter); } /* @notice Moves a curve to a pre-determined price target, and calculates the flows * as necessary to reach the target. The final curve will end at exactly that price * and the flows are set to guarantee incremental collateral safety. * * @dev Note that this function does *NOT* check whether the curve is liquidity * stable through the swap impact. It's the callers job to make sure that the * impact doesn't cross through any tick barrier that knocks concentrated liquidity * in/out. * * @param curve - The current state of the active liquidity curve. After calling * this struct will be updated with the post-swap price. Note that none of the * fee accumulator fields are adjusted. This function does *not* collect or apply * liquidity fees. It's the callers responsibility to handle fees outside this * call. * @param price - The target limit price that the curve is being rolled to. Defined * as Q64.64 fixed point. * @param inBaseQty - If true, the above flow applies to the base-side tokens in the * pair. If false, applies to the quote-side tokens. * @param isBuy - If true, the flows are paying base tokens to the pool and receiving * quote tokens. (Hence pushing the price up.) If false, vice versa. * @param swapQty - The total quantity left on the swap across all legs. May or may * not be equal to flow, or could be left depending on whether this * leg will fill the entire quantity. * * @return baseFlow - The signed flow of the base-side tokens. Negative means the flow * is being paid from the pool to the user. Positive means the flow is * being paid from the user to the pool. * @return quoteFlow - The signed flow of the quote-side tokens. * @return qtyLeft - The amount of swapQty remaining after the flow from this leg is * processed. */ function rollPrice (CurveMath.CurveState memory curve, uint128 price, bool inBaseQty, bool isBuy, uint128 swapQty) internal pure returns (int128, int128, uint128) { (uint128 flow, uint128 counterFlow) = deriveDemand(curve, price, inBaseQty); (int128 paidFlow, int128 paidCounter) = signFixed (flow, counterFlow, inBaseQty, isBuy); return setCurvePos(curve, inBaseQty, isBuy, swapQty, price, paidFlow, paidCounter); } /* @notice Called when a curve has reached its a bump barrier. Because the * barrier occurs at the final price in the tick, we need to "shave the price" * over into the next tick. The curve has kicked in liquidity that's only active * below this price, and we need the price to reflect the correct tick. So we burn * an economically meaningless amount of collateral token wei to shift the price * down by exactly one unit of precision into the next tick. */ function shaveAtBump (CurveMath.CurveState memory curve, bool inBaseQty, bool isBuy, uint128 swapLeft) pure internal returns (int128, int128, uint128) { uint128 burnDown = CurveMath.priceToTokenPrecision (curve.activeLiquidity(), curve.priceRoot_, inBaseQty); require(swapLeft > burnDown, "BD"); if (isBuy) { return setShaveUp(curve, inBaseQty, burnDown); } else { return setShaveDown(curve, inBaseQty, burnDown); } } /* @notice After calculating a burn down amount of collateral, roll the curve over * into the next tick below the current tick. * * @dev This is used to handle the situation when we've reached the end of a liquidity * range, and need to safely move the curve by one price unit to move it over into * the next liquidity range. Although a single price unit is almost always economically * de minims, there are small flows needed to move the curve price while remaining safely * over-collateralized. * * @param curve The liquidity curve, which will be adjusted to move the price one unit. * @param inBaseQty If true indicates that the swap is made with fixed base tokens and floating quote * tokens. * @param burnDown The pre-calculated amount of tokens needed to maintain over-collateralization when * moving the curve by one price unit. * * @return paidBase The additional amount of base tokens that the swapper should pay to the curve to * move the price one unit. * @return paidQuote The additional amount of quote tokens the swapper should pay to the curve. * @return burnSwap The amount of tokens to remove from the remaining fixed leg of the swap quantity. */ function setShaveDown (CurveMath.CurveState memory curve, bool inBaseQty, uint128 burnDown) private pure returns (int128 paidBase, int128 paidQuote, uint128 burnSwap) { unchecked { if (curve.priceRoot_ > TickMath.MIN_SQRT_RATIO) { curve.priceRoot_ -= 1; // MIN_SQRT is well above uint128 0 } // When moving the price down at constant liquidity, no additional base tokens are required for // collateralization paidBase = 0; // When moving the price down at constant liquidity, the swapper must pay a small amount of additional // quote tokens to keep the curve over-collateralized. paidQuote = burnDown.toInt128Sign(); // If the fixed swap leg is in base tokens, then this has zero impact, if the swap leg is in quote // tokens then we have to adjust the deduct the quote tokens the user paid above from the remaining swap // quantity burnSwap = inBaseQty ? 0 : burnDown; } } /* @notice After calculating a burn down amount of collateral, roll the curve over * into the next tick above the current tick. */ function setShaveUp (CurveMath.CurveState memory curve, bool inBaseQty, uint128 burnDown) private pure returns (int128 paidBase, int128 paidQuote, uint128 burnSwap) { unchecked { if (curve.priceRoot_ < TickMath.MAX_SQRT_RATIO - 1) { curve.priceRoot_ += 1; // MAX_SQRT is well below uint128.max } // When moving the price up at constant liquidity, no additional quote tokens are required for // collateralization paidQuote = 0; // When moving the price up at constant liquidity, the swapper must pay a small amount of additional // base tokens to keep the curve over-collateralized. paidBase = burnDown.toInt128Sign(); // If the fixed swap leg is in quote tokens, then this has zero impact, if the swap leg is in base // tokens then we have to adjust the deduct the quote tokens the user paid above from the remaining swap // quantity burnSwap = inBaseQty ? burnDown : 0; } } /* @notice After previously calculating the denominated and counter-denominated flows, * this function assigns those to the correct side of the pair and decrements * the total swap quantity by the amount spent. */ function setCurvePos (CurveMath.CurveState memory curve, bool inBaseQty, bool isBuy, uint128 swapQty, uint128 price, int128 paidFlow, int128 paidCounter) private pure returns (int128 paidBase, int128 paidQuote, uint128 qtyLeft) { uint128 spent = flowToSpent(paidFlow, inBaseQty, isBuy); if (spent >= swapQty) { qtyLeft = 0; } else { qtyLeft = swapQty - spent; } paidBase = (inBaseQty ? paidFlow : paidCounter); paidQuote = (inBaseQty ? paidCounter : paidFlow); curve.priceRoot_ = price; } /* @notice Convert a signed paid flow to a decrement to apply to swap qty left. */ function flowToSpent (int128 paidFlow, bool inBaseQty, bool isBuy) private pure returns (uint128) { int128 spent = (inBaseQty == isBuy) ? paidFlow : -paidFlow; if (spent < 0) { return 0; } return uint128(spent); } /* @notice Calculates the flow and counterflow associated with moving the constant * product curve to a target price. * @dev Both sides of the flow are rounded down at up to 2 wei of precision loss * (see CurveMath.sol). The results should not be used directly without * buffering the counterflow in the direction of collateral support. */ function deriveDemand (CurveMath.CurveState memory curve, uint128 price, bool inBaseQty) private pure returns (uint128 flow, uint128 counterFlow) { uint128 liq = curve.activeLiquidity(); uint128 baseFlow = liq.deltaBase(curve.priceRoot_, price); uint128 quoteFlow = liq.deltaQuote(curve.priceRoot_, price); if (inBaseQty) { (flow, counterFlow) = (baseFlow, quoteFlow); } else { (flow, counterFlow) = (quoteFlow, baseFlow); } } /* @notice Given a fixed swap flow on a cosntant product AMM curve, calculates * the final price and counterflow. This function assumes that the AMM curve is * constant product stable through the impact range. It's the caller's * responsibility to check that we're not passing liquidity bump tick boundaries. * * @dev The price and counter-flow guarantee collateral stability on the AMM curve. * Because of fixed-point effects the price may be arbitarily rounded, but the * counter-flow will always be set correctly to match. The result of this function * is based on the AMM curve being constant through the entire range. Note that * this function only calulcates a result it does *not* write into the Curve or * Swap structs. * * @param curve The constant-product AMM curve * @param flow The fixed token flow from the side the swap is denominated in. * @param inBaseQty If true, the flow is denominated in base-side tokens. * @param isBuy If true, the flows are paying base tokens to the pool and receiving * quote tokens. * * @return counterFlow The magnitude of token flow on the opposite side the swap * is denominated in. Note that this value is *not* signed. Also * note that this value is always rounded down. * @return nextPrice The ending price of the curve assuming the full flow is * processed. Note that this value is *not* written into the * curve struct. */ function deriveImpact (CurveMath.CurveState memory curve, uint128 flow, bool inBaseQty, bool isBuy) internal pure returns (uint128 counterFlow, uint128 nextPrice) { uint128 liq = curve.activeLiquidity(); nextPrice = deriveFlowPrice(curve.priceRoot_, liq, flow, inBaseQty, isBuy); /* We calculate the counterflow exactly off the computed price. Ultimately safe * collateralization only cares about the price, not the contravening flow. * Therefore we always compute based on the final, rounded price, not from the * original fixed flow. */ counterFlow = !inBaseQty ? liq.deltaBase(curve.priceRoot_, nextPrice) : liq.deltaQuote(curve.priceRoot_, nextPrice); } /* @dev The end price is always rounded to the inside of the flow token: * * Flow | Dir | Price Roudning | Loss of Precision * --------------------------------------------------------------- * Base | Buy | Down | 1 wei * Base | Sell | Down | 1 wei * Quote | Buy | Up | Arbitrary * Quote | Sell | Up | Arbitrary * * This guarantees that the pool is adaquately collateralized given the flow of the * fixed side. Because of the arbitrary roudning, it's critical that the counter- * flow is computed using the exact price returned by this function, and not * independently. */ function deriveFlowPrice (uint128 price, uint128 liq, uint128 flow, bool inBaseQty, bool isBuy) private pure returns (uint128) { uint128 curvePrice = inBaseQty ? calcBaseFlowPrice(price, liq, flow, isBuy) : calcQuoteFlowPrice(price, liq, flow, isBuy); if (curvePrice >= TickMath.MAX_SQRT_RATIO) { return TickMath.MAX_SQRT_RATIO - 1;} if (curvePrice < TickMath.MIN_SQRT_RATIO) { return TickMath.MIN_SQRT_RATIO; } return curvePrice; } /* Because the base flow is fixed, we want to always set the price in favor of * base token over-collateralization. Upstream, we'll independently set quote token * flows based off the price calculated here. Since higher price increases base * collateral, we round price down regardless of whether the fixed base flow is a * buy or a sell. * * This seems counterintuitive when base token is the output, but even then moving * the price further down will increase the quote token input and over-collateralize * the base token. The max loss of precision is 1 unit of fixed-point price. */ function calcBaseFlowPrice (uint128 price, uint128 liq, uint128 flow, bool isBuy) private pure returns (uint128) { if (liq == 0) { return type(uint128).max; } uint192 deltaCalc = FixedPoint.divQ64(flow, liq); if (deltaCalc > type(uint128).max) { return type(uint128).max; } uint128 priceDelta = uint128(deltaCalc); /* For a fixed amount of base flow tokens, the resulting price should be conservatively * rounded down. Since Price = [Base Reserves]/[Quote Reserves], rounding price down * is equivalent to rounding the curve to be over collateralized relative to the actual * physical base tokens. */ if (isBuy) { // Since priceDelta is rounded down to the lower unit, this equation rounds down the // the price by up to 1 unit return price + priceDelta; } else { if (priceDelta >= price) { return 0; } // priceDelta is rounded down by a maximum of 1 unit, so adding 1 to the subtracted // priceDelta value rounds price down by up to 1 unit. return price - (priceDelta + 1); } } /* The same rounding logic as calcBaseFlowPrice applies, but because it's the * opposite side we want to conservatively round the price *up*, regardless of * whether it's a buy or sell. * * Calculating flow price for quote flow is more complex because the flow delta * applies to the inverse of the price. So when calculating the inverse, we make * sure to round in the direction that rounds up the final price. */ function calcQuoteFlowPrice (uint128 price, uint128 liq, uint128 flow, bool isBuy) private pure returns (uint128) { // Since this is a term in the quotient rounding down, rounds up the final price uint128 invPrice = FixedPoint.recipQ64(price); // This is also a quotient term so we use this function's round down logic uint128 invNext = calcBaseFlowPrice(invPrice, liq, flow, !isBuy); if (invNext == 0) { return TickMath.MAX_SQRT_RATIO; } return FixedPoint.recipQ64(invNext) + 1; } // Max round precision loss on token flow is 2 wei, but a 4 wei cushion provides // extra margin and is economically meaningless. int128 constant ROUND_PRECISION_WEI = 4; /* @notice Correctly assigns the signed direction to the unsigned flow and counter * flow magnitudes that were previously computed for a fixed flow swap. Positive * sign implies the flow is being received by the pool, negative that it's being * received by the user. */ function signFlow (uint128 flowMagn, uint128 counterMagn, bool inBaseQty, bool isBuy) private pure returns (int128 flow, int128 counter) { (flow, counter) = signMagn(flowMagn, counterMagn, inBaseQty, isBuy); // Conservatively round directional counterflow in the direction of the pool's // collateral. Don't round swap flow because that's a fixed target. counter = counter + ROUND_PRECISION_WEI; } /* @notice Same as signFlow, but used for the flow from a price target swap leg. */ function signFixed (uint128 flowMagn, uint128 counterMagn, bool inBaseQty, bool isBuy) private pure returns (int128 flow, int128 counter) { (flow, counter) = signMagn(flowMagn, counterMagn, inBaseQty, isBuy); // In a price target, bothsides of the flow are floating, and have to be rounded // in pool's favor to conservatively accomodate the price precision. flow = flow + ROUND_PRECISION_WEI; counter = counter + ROUND_PRECISION_WEI; } /* @notice Takes an unsigned flow magntiude and correctly signs it based on the * directional and denomination of the flows. */ function signMagn (uint128 flowMagn, uint128 counterMagn, bool inBaseQty, bool isBuy) private pure returns (int128 flow, int128 counter) { if (inBaseQty == isBuy) { (flow, counter) = (flowMagn.toInt128Sign(), -counterMagn.toInt128Sign()); } else { (flow, counter) = (-flowMagn.toInt128Sign(), counterMagn.toInt128Sign()); } } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; pragma experimental ABIEncoderV2; import "./SafeCast.sol"; /* @title Directive library * @notice This library defines common structs and associated helper functions for * user defined trade action directives. */ library Directives { using SafeCast for int256; using SafeCast for uint256; /* @notice Defines a single requested swap on a pre-specified pool. * * @dev A directive indicating no swap action must set *both* qty and limitPrice to * zero. qty=0 alone will indicate the use of a flexible back-filled rolling * quantity. * * @param isBuy_ If true, swap converts base-side token to quote-side token. * Vice-versa if false. * @param inBaseQty_ If true, swap quantity is denominated in base-side token. * If false in quote side token. * @param rollType_ The flavor of rolling gap fill that should be applied (if any) * to this leg of the directive. See Chaining.sol for list of * rolling type codes. * @param qty_ The total amount to be swapped. (Or rolling target if rollType_ is * enabled) * @param limitPrice_ The maximum (minimum) *price to pay, if a buy (sell) swap * *at the margin*. I.e. the swap will keep exeucting until the curve * reaches this price (or exhausts the specified quantity.) Represented * as the square root of the pool's price ratio in Q64.64 fixed-point. */ struct SwapDirective { bool isBuy_; bool inBaseQty_; uint8 rollType_; uint128 qty_; uint128 limitPrice_; } /* @notice Defines a sequence of mint/burn actions related to concentrated liquidity * range orders on a single pool. * * @param lowTick_ A single tick index that defines one side of the range order * boundary for all range orders in this directive. * @param highTick_ The tick index of the other side of the boundary of the range * order. * @param isAdd_ If true, the action mints new concentrated liquidity. If false, it * burns pre-existing concentrated liquidity. * @param isTickRel_ If true indicates the low and high tick value should be take * relative to the current price tick. E.g. -5 indicates 5 ticks * below the current tick. Otherwise, high and low tick values are * absolute tick index values. * @param rollType_ The flavor of rolling gap fill that should be applied (if any) * to this leg of the directive. See Chaining.sol for list of * rolling type codes. * @param liquidity_ The total amount of concentrated liquidity to add/remove. * Represented as the equivalent of sqrt(X*Y) liquidity for the * equivalent constant-product AMM curve. If rolling is turned * on, this is instead interpreted as a rolling target value. */ struct ConcentratedDirective { int24 lowTick_; int24 highTick_; bool isAdd_; bool isTickRel_; uint8 rollType_; uint128 liquidity_; } /* @notice Along with a root open tick from above defines a single range order mint * or burn action. /* @notice Defines a directive related to the mint/burn of ambient liquidity on a * single pre-specified curve. * * @dev A directive indicating no ambient mint/burn must set *both* isAdd to false and * liquidity to zero. liquidity=0 alone will indicate the use of a flxeible * back-filled rolling quantity in place. * * @param isAdd_ If true, the action mints new ambient liquidity. If false, burns * pre-existing liquidity in the curve. * @param rollType_ The flavor of rolling gap fill that should be applied (if any) * to this leg of the directive. See Chaining.sol for list of * rolling type codes. * @param liquidity_ The total amount of ambient liquidity to add/remove. * Represented as the equivalent of sqrt(X*Y) liquidity for a * constant-product AMM curve. (If this and rollType_ are zero, * this is a non-action.) */ struct AmbientDirective { bool isAdd_; uint8 rollType_; uint128 liquidity_; } /* @param rollExit_ If set to true, use the exit side of the pair's tokens when * calculating rolling back-fill quantities. * @param swapDefer_ If set to true, execute the swap directive *after* the passive * mint/burn directives for the pool. If false, swap executes first. * @param offsetSurplus_ If set to true offset any rolling back-fill quantities with * the client's pre-existing surplus collateral at the dex. */ struct ChainingFlags { bool rollExit_; bool swapDefer_; bool offsetSurplus_; } /* @notice Defines a full suite of trade action directives to be executed on a single * pool within a pre-specified pair. * @param poolIdx_ The pool type index that identified the pool to be operated on in * this pair. * @param ambient_ Directive related to ambient liquidity actions (if any). * @param conc_ Directives related to concentrated liquidity range orders (if any). * @param swap_ Directive for the swap action on the pool (if any). * @param chain_ Flags related to chaining order of the directive actions and how * rolling back fill is calculated. */ struct PoolDirective { uint256 poolIdx_; AmbientDirective ambient_; ConcentratedDirective[] conc_; SwapDirective swap_; ChainingFlags chain_; } /* @notice Specifies the settlement procedures between user and dex related to * a single token within a chain of hops in a sequence of one or more * pairs. The same struct is used for the entry/exit terminal tokens as * well as intermediate tokens between pairs. * * @param token_ The tracker address to the token in the pair. (If set to zero * specifies native Ethereum as the pair asset.) * @param limitQty_ A net flow limit that the user expects the execution to meet * or exceed. Otherwise the transaction is reverted. Negative specifies a minimum * credit from the pool to the user. Positive a maximum debit from user to the * pool. * @param dustThresh_ A threshold, below which the user requests no transaction is * sent as part of a credit. (Debits are always collected.) Used to avoid * unnecessary gas cost of a token transfer on an economically meaningless value. * @param useSurplus_ If set to true the settlement should attempt to complete using * the client's surplus collateral balance at the dex. */ struct SettlementChannel { address token_; int128 limitQty_; uint128 dustThresh_; bool useSurplus_; } /* @notice Specified if and how off-grid price improvement is being requested. (Note * that even if requested, there may be no price improvement set for the * token. To avoid wasted gas, user should check off-chain.) * @param isEnabled_ By default, no price improvement is set, avoiding the gas cost * of a storage query. If true, indicates that the user wants to query the * price improvement settings. * @param useBaseSide_ If true requests price improvement from the base-side token * in the pair. Otherwise, requested on the quote-side token. */ struct PriceImproveReq { bool isEnabled_; bool useBaseSide_; } /* @notice Defines a full directive related to a single hop in a sequence of pairs. * @param pools_ Defines directives on one or more pools on the pair. * @param settle_ Defines the settlement for the token on the *exit* side of the hop. * (The entry side is defined in the previous hop, or the open directive if * this is the first hop in the sequence.) * @param improve_ Off-grid price improvement settings. */ struct HopDirective { PoolDirective[] pools_; SettlementChannel settle_; PriceImproveReq improve_; } /* @notice Top-level trade order directive, encompassing an arbitrary collection of * of swap, mints, and burns across multiple pools within a chained sequence of * pairs. * @param open_ Defines the token and settlement for the entry token in the first hop * in the chain. * @param hops_ Defines a sequence of directives on pairs that will be executed in the * order specified by this array. */ struct OrderDirective { SettlementChannel open_; HopDirective[] hops_; } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; pragma experimental ABIEncoderV2; import "./Directives.sol"; /* @title Order encoding library * @notice Provides facilities for encoding and decoding user specified order directive * structures to/from raw transaction bytes. */ library OrderEncoding { // Preamble code that begins at the start of long-form orders. Allows us to support // alternative message schemas in the future. To start all encoded long-form orders // must start with this code in the first character position. uint8 constant LONG_FORM_SCHEMA = 1; /* @notice Parses raw bytes into an OrderDirective struct in memory. * * @dev In general the array lengths and arithmetic in this function and child * functions are unchecked/unsanitized. The only use of this function is to * parse a user-supplied string into constituent commands. If a user supplies * malformed data it will have no impact on the state of the contract besides * the internally safe swap/mint/burn calls. */ function decodeOrder (bytes calldata input) internal pure returns (Directives.OrderDirective memory dir) { uint offset = 0; uint8 cnt; uint8 schemaType; (schemaType, dir.open_.token_, dir.open_.limitQty_, dir.open_.dustThresh_, dir.open_.useSurplus_, cnt) = abi.decode(input[offset:(offset+32*6)], (uint8, address, int128, uint128, bool, uint8)); unchecked { // 0 + 32*6 is well with bounds of 256 bits offset += 32*6; } require(schemaType == LONG_FORM_SCHEMA); dir.hops_ = new Directives.HopDirective[](cnt); unchecked { // An iterate by 1 loop will run out of gas far before overflowing 256 bits for (uint i = 0; i < cnt; ++i) { offset = parseHop(dir.hops_[i], input, offset); } } } /* @notice Parses an offset bytestream into a single HopDirective in memory and * increments the offset accordingly. */ function parseHop (Directives.HopDirective memory hop, bytes calldata input, uint256 offset) private pure returns (uint256 next) { next = offset; uint8 poolCnt; poolCnt = abi.decode(input[next:(next+32)], (uint8)); unchecked { next += 32; } hop.pools_ = new Directives.PoolDirective[](poolCnt); unchecked { // An iterate by 1 loop will run out of gas far before overflowing 256 bits for (uint i = 0; i < poolCnt; ++i) { next = parsePool(hop.pools_[i], input, next); } } return parseSettle(hop, input, next); } /* @notice Parses the settlement fields in a hop directive. */ function parseSettle (Directives.HopDirective memory hop, bytes calldata input, uint256 offset) private pure returns (uint256) { (hop.settle_.token_, hop.settle_.limitQty_, hop.settle_.dustThresh_, hop.settle_.useSurplus_, hop.improve_.isEnabled_, hop.improve_.useBaseSide_) = abi.decode(input[offset:(offset+32*6)], (address, int128, uint128, bool, bool, bool)); unchecked { // Incrementing by 192 will run out of gas far before overflowing 256-bits return offset + 32*6; } } /* @notice Parses an offset bytestream into a single PoolDirective in memory and increments the offset accordingly. */ function parsePool (Directives.PoolDirective memory pair, bytes calldata input, uint256 offset) private pure returns (uint256 next) { uint concCnt; next = offset; (pair.poolIdx_, pair.ambient_.isAdd_, pair.ambient_.rollType_, pair.ambient_.liquidity_, concCnt) = abi.decode(input[next:(next+32*5)], (uint256, bool, uint8, uint128, uint8)); unchecked { // Incrementing by 160 will run out of gas far before overflowing 256-bits next += 32*5; } pair.conc_ = new Directives.ConcentratedDirective[](concCnt); unchecked { // An iterate by 1 loop will run out of gas far before overflowing 256 bits for (uint i = 0; i < concCnt; ++i) { next = parseConcentrated(pair.conc_[i], input, next); } } (pair.swap_.isBuy_, pair.swap_.inBaseQty_, pair.swap_.rollType_, pair.swap_.qty_, pair.swap_.limitPrice_) = abi.decode(input[next:(next+32*5)], (bool, bool, uint8, uint128, uint128)); unchecked { // Incrementing by 160 will run out of gas far before overlowing 256 bits next += 32*5; } (pair.chain_.rollExit_, pair.chain_.swapDefer_, pair.chain_.offsetSurplus_) = abi.decode(input[next:(next+32*3)], (bool, bool, bool)); unchecked { // Incrementing by 96 will run out of gas far before overlowing 256 bits next += 32*3; } } /* @notice Parses an offset bytestream into a single ConcentratedDirective in * memory and increments the offset accordingly. */ function parseConcentrated (Directives.ConcentratedDirective memory pass, bytes calldata input, uint256 offset) private pure returns (uint256 next) { (pass.lowTick_, pass.highTick_, pass.isTickRel_, pass.isAdd_, pass.rollType_, pass.liquidity_) = abi.decode(input[offset:(offset+32*6)], (int24, int24, bool, bool, uint8, uint128)); unchecked { // Incrementing by 196 at a time should never overflow 256 bits next = offset + 32*6; } } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; /// @title FixedPoint128 /// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format) library FixedPoint { uint256 internal constant Q128 = 0x100000000000000000000000000000000; uint256 internal constant Q96 = 0x1000000000000000000000000; uint256 internal constant Q64 = 0x10000000000000000; uint256 internal constant Q48 = 0x1000000000000; /* @notice Multiplies two Q64.64 numbers by each other. */ function mulQ64 (uint128 x, uint128 y) internal pure returns (uint192) { unchecked { // 128 bit integers squared will always fit in 256-bits return uint192((uint256(x) * uint256(y)) >> 64); } } /* @notice Divides one Q64.64 number by another. */ function divQ64 (uint128 x, uint128 y) internal pure returns (uint192) { unchecked { // No overflow or underflow possible in the below operations return (uint192(x) << 64) / y; } } /* @notice Multiplies a Q64.64 by a Q16.48. */ function mulQ48 (uint128 x, uint64 y) internal pure returns (uint144) { unchecked { // 128 bit integers squared will always fit in 256-bits return uint144((uint256(x) * uint256(y)) >> 48); } } /* @notice Takes the reciprocal of a Q64.64 number. */ function recipQ64 (uint128 x) internal pure returns (uint128) { unchecked { // Only possible overflow possible is captured with a specific check uint256 div = uint256(FixedPoint.Q128) / uint256(x); require(div <= type(uint128).max); return uint128(div); } } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; /* @notice Defines structures and functions necessary to track knockout liquidity. * Knockout liquidity works like standard concentrated range liquidity, *except* * the position becomes inactive once the price of the curve breaches a certain * tick pivot. In that sense knockout liquidity behaves like a "non-reversible * limit order" seen in the traditional limit order book. */ library KnockoutLiq { /* @notice Defines a currently active knockout liquidity bump point that exists on * a specific AMM curve at a specific tick/direction. * * @param lots_ The total number of lots active in the knockout pivot. Note that this * number should always be included in the corresponding LevelBook lots. * * @param pivotTime_ The block time the first liquidity was created on the pivot * point. This resets every time the knockout is crossed, and is * therefore used to distinguish tranches of liquidity that were * added at the same tick but with different knockout times. * * @param rangeTicks_ The number of ticks wide the range order for the knockout * tranche. Unlike traditional concentrated liquidity, all knockout * liquidity in the same tranche must have the same width. This is * used to determine what counter-side tick to decrement liquidity * on when knocking out an order. */ struct KnockoutPivot { uint96 lots_; uint32 pivotTime_; uint16 rangeTicks_; } /* @notice Stores a cryptographically provable history of previous knockout events * at a given tick/direction. * * @dev To avoid unnecessary SSTORES, we Merkle at the same location instead of * growing an array. This allows users trying to claim a previously knockout * position to post a Merkle proof. (And since the underlying liquidity is * computable even without this proof, the only loss for those that don't are the * accumulated fees while the range liquidity was active.) * * @param merkleRoot_ The Merkle root of the prior entry in the chain. * @param pivotTime_ The pivot time of the last tranche to be knocked out at this tick * @param feeMileage_ The fee mileage for the range at the time the tranche was * knocked out. */ struct KnockoutMerkle { uint160 merkleRoot_; uint32 pivotTime_; uint64 feeMileage_; } /* @notice Represents a single user's knockout liquidity position. * @param lots_ The total number of liquidity lots in the position. * @param feeMileage_ The in-range cumulative fee mileage at the time the position was * created. * @param timestamp_ The block time the position was created (or when liquidity was * added to the position). */ struct KnockoutPos { uint96 lots_; uint64 feeMileage_; uint32 timestamp_; } /* @notice Represents the location of a knockout position inside a given AMM curve. * Necessary to recover a user's position in the storage. * * @param isBid_ If true, indicates that the knockout is on the bid side, i.e. will * knockout when price falls below the tick. * @param lowerTick The 24-bit tick index of the lower boundary of the knockout range order * @param upperTick The 24-bit tick index of the upper boundary of the knockout range order */ struct KnockoutPosLoc { bool isBid_; int24 lowerTick_; int24 upperTick_; } /* @notice Resets all fields on a existing pivot struct. */ function deletePivot (KnockoutPivot storage pivot) internal { pivot.lots_ = 0; pivot.pivotTime_ = 0; pivot.rangeTicks_ = 0; } /* @notice Encodes a hash key for a given knockout pivot point. * @param pool The hash index of the AMM pool. * @param isBid If true indicates the knockout pivot is on the bid side. * @param tick The tick index of the knockout pivot. * @return Unique hash key mapping to the pivot struct. */ function encodePivotKey (bytes32 pool, bool isBid, int24 tick) internal pure returns (bytes32) { return keccak256(abi.encode(pool, isBid, tick)); } /* @notice Encodes a hash key for a knockout pivot given a pos location struct. */ function encodePivotKey (KnockoutPosLoc memory loc, bytes32 pool) internal pure returns (bytes32) { return encodePivotKey(pool, loc.isBid_, knockoutTick(loc)); } /* @notice Determines which tick side is the pivot point based on whether the pivot * is a bid or ask. */ function knockoutTick (KnockoutPosLoc memory loc) internal pure returns (int24) { return loc.isBid_ ? loc.lowerTick_ : loc.upperTick_; } function tickRange (KnockoutPosLoc memory loc) internal pure returns (uint16) { uint24 range = uint24(loc.upperTick_ - loc.lowerTick_); require (range < type(uint16).max); return uint16(range); } /* @notice Encodes a hash key for a knockout position. * @param loc The location of the knockout position * @param pool The hash index of the AMM pool. * @param owner The claimant of the liquidity position. * @param pivotTime The timestamp of when the pivot tranche was created * @return Unique hash key to position. */ function encodePosKey (KnockoutPosLoc memory loc, bytes32 pool, address owner, uint32 pivotTime) internal pure returns (bytes32) { return keccak256(abi.encode(pool, owner, loc.isBid_, loc.lowerTick_, loc.upperTick_, pivotTime)); } /* @notice Commits a now-crossed Knockout pivot to the merkle history for that tick * location. * @param merkle The Merkle history object. Will be overwrriten by this function. * @param pivot The most recent pivot state. Should not call this unless the pivot has * just been knocked out. * @param feeMileage The in-range fee mileage at the time of knockout crossing. */ function commitKnockout (KnockoutMerkle storage merkle, KnockoutPivot memory pivot, uint64 feeMileage) internal { merkle.merkleRoot_ = rootLink(merkle, commitEntropySalt()); merkle.pivotTime_ = pivot.pivotTime_; merkle.feeMileage_ = feeMileage; } /* @notice Returns hard-to-fake entropy at commit time to prevent a long-range * birthday collission attack. * * @dev Knockout commits use 160-bit hashes for the Merkle chain. A birthday * collission attack could be carried with 2^80 SHA256 hashes for an approximate * cost of 10 billion dollars or 1 year of bitcoin mining. (See EIP-3607 for more * discussion.) This mitigates the risk of a long run attack by injecting 160 * bits of entropy from the block hash which can only be fully known at the time * a Merkle root is committed. * * Even if an attacker is the block builder and can manipulate blockhash, they * can only control as many bits of blockhash entropy as SHA256 hashes they can * calculate in O(block time). Practically speaking an attacker will not be able * to calculate more than 2^100 hashes at the scale of block times. * Therefore this salt injects a minimum of 60 bits of uncontrollable entropy, * and raises the cost of a long-range collision attack to 2^140 hashes, which * is outright infeasible. */ function commitEntropySalt() internal view returns (uint160) { return uint160(uint256(blockhash(block.number-1))); } /* @notice Converts the most recent Merkle state to a 160-bit Merkle root hash. */ function rootLink (KnockoutMerkle memory merkle, uint160 salt) private pure returns (uint160) { return rootLink(merkle.merkleRoot_, merkle.pivotTime_, merkle.feeMileage_, salt); } /* @notice Converts the most current Merkle state params to 160-bit Merkle hash.*/ function rootLink (uint160 root, uint32 pivotTime, uint64 feeMileage, uint160 salt) private pure returns (uint160) { return rootLink(root, encodeChainLink(pivotTime, feeMileage, salt)); } /* @notice Hashes together the previous Merkle root with the encoded chain step. */ function rootLink (uint160 root, uint256 chainLink) private pure returns (uint160) { bytes32 hash = keccak256(abi.encode(root, chainLink)); return uint160(uint256(hash) >> 96); } /* @notice Tightly packs the 32-bit pivot time with the 64-bit fee mileage and the salt. */ function encodeChainLink (uint32 pivotTime, uint64 feeMileage, uint160 salt) private pure returns (uint256) { return (uint256(salt) << 96) + (uint256(pivotTime) << 64) + uint256(feeMileage); } /* @notice Decodes a tightly packed chain link into the pivot time and fee mileage */ function decodeChainLink (uint256 entry) private pure returns (uint32 pivotTime, uint64 feeMileage) { pivotTime = uint32((entry << 160) >> 224); feeMileage = uint64((entry << 192) >> 192); } /* @notice Verifies a Merkle proof for a previous knockout commitment. * * @param merkle The current Merkle chain for the pivot tick. * @param proofRoot The Merkle root the proof is starting at. * @param proof A proof that starts at the point in history the user wants to prove * and includes the encoded 96-bit chain entries (see encodeChainLink()) * up to the current Merkle state. * * @return The 32-bit Knockout tranche pivot time and 64-bit fee mileage at the start of * the proof. */ function proveHistory (KnockoutMerkle memory merkle, uint160 proofRoot, uint256[] memory proof) internal pure returns (uint32, uint64) { // If we're only looking at the most recent knockout, it's still stored raw // and doesn't need a proof. return proof.length == 0 ? (merkle.pivotTime_, merkle.feeMileage_) : proveSteps(merkle, proofRoot, proof); } /* @notice Verifies a non-empty Merkle proof. */ function proveSteps (KnockoutMerkle memory merkle, uint160 proofRoot, uint256[] memory proof) private pure returns (uint32, uint64) { uint160 incrRoot = proofRoot; unchecked { // Iterate by 1 loop will run out of gas far before overflowing 256 bits for (uint i = 0; i < proof.length; ++i) { incrRoot = rootLink(incrRoot, proof[i]); } } require(incrRoot == merkle.merkleRoot_, "KP"); return decodeChainLink(proof[0]); } /* @notice Verifies that a given knockout location is valid relative to the curve * price and the pool's current knockout parameters. If not, the call will * revert * * @param loc The location for the proposed knockout liquidity candidate. * @param priceTick The tick index of the curves current price. * * @param loc The tightly packed knockout parameters related to the pool. The fields * are set in the following order from most to least significant bit: * [8] [7] [6][5] [4][3][2][1] * Unusued On-Grid Flag PlaceType OrderWidth * * The field types are as follows: * OrderWidth - The width of new knockout pivots in ticks represented by * power of two. * PlaceType - Restricts where new knockout pivots can be placed * relative to curve price. Uses the following codes: * 0 - Disabled. No knockout pivots allowed. * 1 - Knockout bids (asks) must be placed with upper (lower) tick * below (above) the current curve price. * 2 - Knockout bids (asks) must be placed with lower (upper) tick * below (above) the current curve price. * * On-Grid Flag - If set requires that any new knockout range order can only * be placed on a tick index that's a multiple of the width. * Can be used to restrict density of knockout orders, beyond * the normal pool tick size. */ function assertValidPos (KnockoutPosLoc memory loc, int24 priceTick, uint8 knockoutBits) internal pure { (bool enabled, uint8 width, bool inside, bool onGrid) = unpackBits(knockoutBits); require(enabled && gridOkay(loc, width, onGrid) && spreadOkay(loc, priceTick, inside), "KV"); } /* @notice Evaluates whether the placement and width of a knockout pivot candidate * conforms to the grid parameters. */ function gridOkay (KnockoutPosLoc memory loc, uint8 widthBits, bool mustBeOnGrid) private pure returns (bool) { uint24 width = uint24(loc.upperTick_ - loc.lowerTick_); bool rightWidth = width == uint24(1) << widthBits; int24 tick = loc.upperTick_; uint24 absTick = tick > 0 ? uint24(tick) : uint24(-tick); bool onGrid = (!mustBeOnGrid) || (absTick >> widthBits) << widthBits == absTick; return rightWidth && onGrid; } /* @notice Evaluates whether the placement of a knockout pivot candidates conforms * to the parameters relative to the curve's current price tick. */ function spreadOkay (KnockoutPosLoc memory loc, int24 priceTick, bool inside) internal pure returns (bool) { // Checks to see whether the range order is placed directionally correct relative // to the current tick price. If inside is true, then the range order can be placed // with the curve price inside the range. // Otherwise bids must have the entire range below the curve price, and asks must // have the entire range above the curve price. if (loc.isBid_) { int24 refTick = inside ? loc.lowerTick_ : loc.upperTick_; return refTick <= priceTick; } else { int24 refTick = inside ? loc.upperTick_ : loc.lowerTick_; return refTick > priceTick; } } /* @notice Decodes the tightly packed bits in pool knockout parameters. * @return enabled True if new knockout pivots are enabled at all. * @return widthBits The width of new knockout pivots in ticks to the power of two. * @return inside True if knockout range order can be minted with the current curve * price inside the tick range. If false, knockout range orders can * only be minted with the full range is outside the current curve * price. * @return onGrid True if new knockout range orders are restricted to ticks that * are multiples of the width size. */ function unpackBits (uint8 knockoutBits) private pure returns (bool enabled, uint8 widthBits, bool inside, bool onGrid) { widthBits = uint8(knockoutBits & 0x0F); uint8 flagBits = uint8(knockoutBits & 0x30) >> 4; enabled = flagBits > 0; inside = flagBits >= 2; onGrid = knockoutBits & 0x40 > 0; } }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.8.19; import './SafeCast.sol'; import './TickMath.sol'; /// @title Math library for liquidity library LiquidityMath { /// @notice Add a signed liquidity delta to liquidity and revert if it overflows or underflows /// @param x The liquidity before change /// @param y The delta by which liquidity should be changed /// @return z The liquidity delta function addDelta(uint128 x, int128 y) internal pure returns (uint128 z) { unchecked { // Arithmetic checks done explicitly if (y < 0) { require((z = x - uint128(-y)) < x); } else { require((z = x + uint128(y)) >= x); } } } /// @notice Add an unsigned liquidity delta to liquidity and revert if it overflows or underflows /// @param x The liquidity before change /// @param y The delta by which liquidity should be changed /// @return z The liquidity delta function addLiq(uint128 x, uint128 y) internal pure returns (uint128 z) { unchecked { // Arithmetic checks done explicitly require((z = x + y) >= x); } } /// @notice Add an unsigned liquidity delta to liquidity and revert if it overflows or underflows /// @param x The liquidity before change /// @param y The delta by which liquidity should be changed /// @return z The liquidity delta function addLots(uint96 x, uint96 y) internal pure returns (uint96 z) { unchecked { // Arithmetic checks done explicitly require((z = x + y) >= x); } } /// @notice Subtract an unsigned liquidity delta to liquidity and revert if it overflows or underflows /// @param x The liquidity before change /// @param y The delta by which liquidity should be changed /// @return z The liquidity delta function minusDelta(uint128 x, uint128 y) internal pure returns (uint128 z) { z = x - y; } /* @notice Same as minusDelta, but operates on lots of liquidity rather than outright * liquiidty. */ function minusLots(uint96 x, uint96 y) internal pure returns (uint96 z) { z = x - y; } /* In certain contexts we need to represent liquidity, but don't have the full 128 * bits or precision. The compromise is to use "lots" of liquidity, which is liquidity * represented as multiples of 1024. Usually in those contexts, max lots is capped at * 2^96 (equivalent to 2^106 of liquidity.) * * More explanation, along with examples can be found in the documentation at * docs/LiquidityLots.md in the project respository. */ uint16 constant LOT_SIZE = 1024; uint8 constant LOT_SIZE_BITS = 10; /* By utilizing the least significant digit of the liquidity lots value, we can * support special types of "knockout" liquidity, that when crossed trigger specific * calls. The aggregate knockout liquidity will always sum to an odd number of lots * whereas all vanilla resting liquidity will have an even number of lots. That * means we can test whether any level has knockout liquidity simply by seeing if the * the total sum is an odd number. * * More explanation, along with examples can be found in the documentation at * docs/LiquidityLots.md in the project respository. */ uint96 constant KNOCKOUT_FLAG_MASK = 0x1; uint8 constant LOT_ACTIVE_BITS = 11; /* @notice Converts raw liquidity to lots of resting liquidity. (See comment above * defining lots. */ function liquidityToLots (uint128 liq) internal pure returns (uint96) { uint256 lots = liq >> LOT_SIZE_BITS; uint256 liqTrunc = lots << LOT_SIZE_BITS; bool hasEmptyMask = (lots & KNOCKOUT_FLAG_MASK == 0); require(hasEmptyMask && liqTrunc == liq && lots < type(uint96).max, "FD"); return uint96(lots); } /* @notice Checks if an aggergate lots counter contains a knockout liquidity component * by checking the least significant bit. * * @dev Note that it's critical that the sum *total* of knockout lots on any * given level be an odd number. Don't add two odd knockout lots together * without renormalzing, because they'll sum to an even lot quantity. */ function hasKnockoutLiq (uint96 lots) internal pure returns (bool) { return lots & KNOCKOUT_FLAG_MASK > 0; } /* @notice Truncates an existing liquidity quantity into a quantity that's a multiple * of the 2048-multiplier defining even-sized lots of liquidity. */ function shaveRoundLots (uint128 liq) internal pure returns (uint128) { return (liq >> LOT_ACTIVE_BITS) << LOT_ACTIVE_BITS; } /* @notice Truncates an existing liquidity quantity into a quantity that's a multiple * of the 2048-multiplier defining even-sized lots of liquidity, but rounds up * to the next multiple of 2048. */ function shaveRoundLotsUp (uint128 liq) internal pure returns (uint128 result) { unchecked { require((liq & 0xfffffffffffffffffffffffffffff800) != 0xfffffffffffffffffffffffffffff800, "overflow"); // By shifting down 11 bits, adding the one will always fit in 128 bits uint128 roundUp = (liq >> LOT_ACTIVE_BITS) + 1; return (roundUp << LOT_ACTIVE_BITS); } } /* @notice Given a number of lots of liquidity converts to raw liquidity value. */ function lotsToLiquidity (uint96 lots) internal pure returns (uint128) { uint96 realLots = lots & ~KNOCKOUT_FLAG_MASK; return uint128(realLots) << LOT_SIZE_BITS; } /* @notice Given a positive and negative delta lots value net out the raw liquidity * delta. */ function netLotsOnLiquidity (uint96 incrLots, uint96 decrLots) internal pure returns (int128) { unchecked { // Original values are 96-bits, every possible difference will fit in signed-128 bits return lotToNetLiq(incrLots) - lotToNetLiq(decrLots); } } /* @notice Given an amount of lots of liquidity converts to a signed raw liquidity * delta. (Which by definition is always positive.) */ function lotToNetLiq (uint96 lots) internal pure returns (int128) { return int128(lotsToLiquidity(lots)); } /* @notice Blends the weighted average of two fee reward accumulators based on the * relative size of two liquidity position. * * @dev To be conservative in terms of rewards/collateral, this function always * rounds up to 2 units of precision. We need mileage rounded up, so reward payouts * are rounded down. However this could lead to the technically "impossible" * situation where the mileage on a subsequent rewards burn is smaller than the * blended mileage in the liquidity postion. Technically this shouldn't happen * because mileage only increases through time. However this is a non-consequential * failure. burnPosLiq() just treats it as a zero reward situation, and the staker * loses an economically non-meaningful amount of rewards on the burn. */ function blendMileage (uint64 mileageX, uint128 liqX, uint64 mileageY, uint128 liqY) internal pure returns (uint64) { if (liqY == 0) { return mileageX; } if (liqX == 0) { return mileageY; } if (mileageX == mileageY) { return mileageX; } uint64 termX = calcBlend(mileageX, liqX, liqX + liqY); uint64 termY = calcBlend(mileageY, liqY, liqX + liqY); // With mileage we want to be conservative on the upside. Under-estimating // mileage means overpaying rewards. So, round up the fractional weights. return (termX + 1) + (termY + 1); } /* @notice Blends the weighted average of two 72-bit fee reward accumulators based on the * relative size of two liquidity position. * * @dev See dev notes from blendMileage() method. Same logic applies. */ function blendMileage72 (uint72 mileageX, uint128 liqX, uint72 mileageY, uint128 liqY) internal pure returns (uint72) { if (liqY == 0) { return mileageX; } if (liqX == 0) { return mileageY; } if (mileageX == mileageY) { return mileageX; } uint72 termX = calcBlend72(mileageX, liqX, liqX + liqY); uint72 termY = calcBlend72(mileageY, liqY, liqX + liqY); // With mileage we want to be conservative on the upside. Under-estimating // mileage means overpaying rewards. So, round up the fractional weights. return (termX + 1) + (termY + 1); } /* @notice Calculates a weighted blend of adding incremental rewards mileage. */ function calcBlend (uint64 mileage, uint128 weight, uint128 total) private pure returns (uint64) { unchecked { // Intermediate results will always fit in 256-bits // Can safely cast, because result will always be smaller than original since // weight is less than total. return uint64(uint256(mileage) * uint256(weight) / uint256(total)); } } /* @notice Calculates a weighted blend of adding incremental rewards mileage. */ function calcBlend72 (uint72 mileage, uint128 weight, uint128 total) private pure returns (uint72) { unchecked { // Intermediate results will always fit in 256-bits // Can safely cast, because result will always be smaller than original since // weight is less than total. return uint72(uint256(mileage) * uint256(weight) / uint256(total)); } } /* @dev Computes a rounding safe calculation of the accumulated rewards rate based on * a beginning and end mileage counter. */ function deltaRewardsRate (uint64 feeMileage, uint64 oldMileage) internal pure returns (uint64) { uint64 REWARD_ROUND_DOWN = 2; if (feeMileage > oldMileage + REWARD_ROUND_DOWN) { return feeMileage - oldMileage - REWARD_ROUND_DOWN; } else { return 0; } } /* @dev Computes a rounding safe calculation of the accumulated rewards for 72 bit mileage * delta snapshots. */ function deltaRewardsRate72 (uint72 feeMileage, uint72 oldMileage) internal pure returns (uint64) { uint72 REWARD_ROUND_DOWN = 2; if (feeMileage > oldMileage + REWARD_ROUND_DOWN) { uint72 mileageDelta = feeMileage - oldMileage - REWARD_ROUND_DOWN; // In practice a the cumulative growth in a curve, and therefore in any range, // in the curve can never exceed 2^64. So in practice this mileage delta should // never exceed 2^64. However, to be on the safe side we cap it to prevent // overflow. return mileageDelta > type(uint64).max ? type(uint64).max : uint64(mileageDelta); } else { return 0; } } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; pragma experimental ABIEncoderV2; /* @title Pool specification library. * @notice Library for defining, querying, and encoding the specifications of the * parameters of a pool type. */ library PoolSpecs { /* @notice Specifcations of the parameters of a single pool type. Any given pair * may have many different pool types, each of which may operate as segmented * markets with different underlying behavior to the AMM. * * @param schema_ Placeholder that defines the structure of the poolSpecs object in * in storage. Because slots initialize zero, 0 is used for an * unitialized or disabled pool. 1 is the only currently used schema * (for the below struct), but allows for upgradeability in the future * * @param feeRate_ The overall fee (liquidity fees + protocol fees inclusive) that * swappers pay to the pool as a fraction of notional. Represented as an * integer representing hundredths of a basis point. I.e. a 0.25% fee * would be 2500 * * @param protocolTake_ The fraction of the fee rate that goes to the protocol fee * (the rest accumulates as a liquidity fee to LPs). Represented in units * of 1/256. Since uint8 can represent up to 255, protocol could take * as much as 99.6% of liquidity fees. However currently the protocol * set function prohibits values above 128, i.e. 50% of liquidity fees. * (See set ProtocolTakeRate in PoolRegistry.sol) * * @param tickSize The minimum granularity of price ticks defining a grid, on which * range orders may be placed. (Outside off-grid price improvement facility.) * For example a value of 50 would mean that range order bounds could only * be placed on every 50th price tick, guaranteeing a minimum separation of * 0.005% (50 one basis point ticks) between bump points. * * @param jitThresh_ Sets the minimum TTL for concentrated LP positions in the pool. * Represented in units of 10 seconds (as measured by block time) * E.g. a value of 5 equates to a minimum TTL of 50 seconds. * Attempts to burn or partially burn an LP position in less than * N seconds (as measured in block.timestamp) after a position was * minted (or had its liquidity increased) will revert. If set to * 0, atomically flashed liquidity that mints->burns in the same * block is enabled. * * @param knockoutBits_ Defines the parameters for where and how knockout liquidity * is allowed in the pool. (See KnockoutLiq library for a full * description of the bit field.) * * @param oracleFlags_ Bitmap flags to indicate the pool's oracle permission * requirements. Current implementation only uses the least * significant bit, which if on checks oracle permission on every * pool related call. Otherwise pool is permissionless. */ struct Pool { uint8 schema_; uint16 feeRate_; uint8 protocolTake_; uint16 tickSize_; uint8 jitThresh_; uint8 knockoutBits_; uint8 oracleFlags_; } uint8 constant BASE_SCHEMA = 1; uint8 constant DISABLED_SCHEMA = 0; /* @notice Convenience struct that's used to gather all useful context about on a * specific pool. * @param head_ The full specification for the pool. (See struct Pool comments above.) * @param hash_ The keccak256 hash used to encode the full pool location. * @param oracle_ The permission oracle associated with this pool (0 if pool is * permissionless.) */ struct PoolCursor { Pool head_; bytes32 hash_; address oracle_; } /* @notice Given a mapping of pools, a base/quote token pair and a pool type index, * copies the pool specification to memory. */ function queryPool (mapping(bytes32 => Pool) storage pools, address tokenX, address tokenY, uint256 poolIdx) internal view returns (PoolCursor memory specs) { bytes32 key = encodeKey(tokenX, tokenY, poolIdx); Pool memory pool = pools[key]; address oracle = oracleForPool(poolIdx, pool.oracleFlags_); return PoolCursor ({head_: pool, hash_: key, oracle_: oracle}); } /* @notice Given a mapping of pools, a base/quote token pair and a pool type index, * retrieves a storage reference to the pool specification. */ function selectPool (mapping(bytes32 => Pool) storage pools, address tokenX, address tokenY, uint256 poolIdx) internal view returns (Pool storage specs) { bytes32 key = encodeKey(tokenX, tokenY, poolIdx); return pools[key]; } /* @notice Writes a pool specification for a pair and pool type combination. */ function writePool (mapping(bytes32 => Pool) storage pools, address tokenX, address tokenY, uint256 poolIdx, Pool memory val) internal { bytes32 key = encodeKey(tokenX, tokenY, poolIdx); pools[key] = val; } /* @notice Hashes the key associated with a pool for a base/quote asset pair and * a specific pool type index. */ function encodeKey (address tokenX, address tokenY, uint256 poolIdx) internal pure returns (bytes32) { require(tokenX < tokenY); return keccak256(abi.encode(tokenX, tokenY, poolIdx)); } /* @notice Returns the permission oracle associated with the pool (or 0 if pool is * permissionless. * * @dev The oracle (if enabled on pool settings) is always deterministically based * on the first 160-bits of the pool type value. This means users can know * ahead of time if a pool can be oracled by checking the bits in the pool * index. */ function oracleForPool (uint256 poolIdx, uint8 oracleFlags) internal pure returns (address) { uint8 ORACLE_ENABLED_MASK = 0x1; bool oracleEnabled = (oracleFlags & ORACLE_ENABLED_MASK == 1); return oracleEnabled ? address(uint160(poolIdx >> 96)) : address(0); } /* @notice Constructs a cryptographically unique virtual address based off a base * address (either virtual or real), and a salt unique to the base address. * Can be used to create synthetic tokens, users, etc. * * @param base The address of the base root. * @param salt A salt unique to the base token tracker contract. * * @return A synthetic token address corresponding to the specific virtual address. */ function virtualizeAddress (address base, uint256 salt) internal pure returns (address) { bytes32 hash = keccak256(abi.encode(base, salt)); uint160 hashTrail = uint160((uint256(hash) << 96) >> 96); return address(hashTrail); } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; import './TickMath.sol'; import './FixedPoint.sol'; import './SafeCast.sol'; import './CurveMath.sol'; import './Directives.sol'; /* @title Price grid library. * @notice Functionality for tick-defined price grids and facilities for off-grid * price improvement. */ library PriceGrid { using TickMath for int24; using SafeCast for uint256; using SafeCast for uint192; /* @notice Defines the off-grid price improvement options (if any) available to * the user for new range orders on a specific pair. * * @param inBase_ If true the collateral thresholds apply to the base-side tokens. * If false, applies to the quote-side tokens. * @param unitCollateral_ The minimum collateral commitment required for an off-grid * range order *per tick* that's off grid. * @param awayTicks_ The maximum number of ticks away from the current price that an * off-grid range order is allowed. */ struct ImproveSettings { bool inBase_; uint128 unitCollateral_; uint16 awayTicks_; } /* @notice Asserts that a given range order is either on grid or eligble for off-grid * price improvement. * * @param set The off-grid price improvement requirements active for this pool. * @param lowTick The lower tick index of the range order. * @param highTick The upper tick index of the range order. * @param liquidity The amount of liquidity in the range order. * @param gridSize The grid size associated with the pool in ticks. * @param priceTick The price tick of the current price in the pool. * * @return Returns false if the range is on-grid, and true if the range order * is off-grid but eligible for price improvement. (If off-grid and * ineligible, the transaction will revert.) */ function verifyFit (ImproveSettings memory set, int24 lowTick, int24 highTick, uint128 liquidity, uint16 gridSize, int24 priceTick) internal pure returns (bool) { if (!isOnGrid(lowTick, highTick, gridSize)) { uint128 thresh = improveThresh(set, gridSize, priceTick, lowTick, highTick); require(liquidity >= thresh, "D"); return true; } return false; } /* @notice Asserts that a given range order is on grid. * @param lowTick The lower tick index of the range order. * @param highTick The upper tick index of the range order. * @param gridSize The grid size associated with the pool in ticks. */ function verifyFit (int24 lowTick, int24 highTick, uint16 gridSize) internal pure { require(isOnGrid(lowTick, highTick, gridSize), "D"); } /* @notice Returns true if the boundaries of a range order occur on the tick grid. * @param lowerTick The lower tick index of the range order. * @param upperTick The upper tick index of the range order. * @param gridSize The grid size associated with the pool in ticks. */ function isOnGrid (int24 lowerTick, int24 upperTick, uint16 gridSize) internal pure returns (bool) { int24 tickNorm = int24(uint24(gridSize)); return lowerTick % tickNorm == 0 && upperTick % tickNorm == 0; } /* @notice Calculates the minimum liquidity required for a range order to be eligible * for off-grid price improvement. * @param set The off-grid price improvement requirements active for this pool. * @param tickSize The size of the grid in tick granularity. * @param priceTick The price tick of the current price in the pool. * @param bidTick The lower tick index of the range order. * @param askTick The upper tick index of the range order. * @return The elibility threshold represented as newly minted liquidity. */ function improveThresh (ImproveSettings memory set, uint16 tickSize, int24 priceTick, int24 bidTick, int24 askTick) internal pure returns (uint128) { require(bidTick < askTick); return canImprove(set, priceTick, bidTick, askTick) ? improvableThresh(set, tickSize, bidTick, askTick) : type(uint128).max; } /* @notice Calculated the liquidity threshold for price improvement, assuming that * the order is eligible. */ function improvableThresh (ImproveSettings memory set, uint16 tickSize, int24 bidTick, int24 askTick) private pure returns (uint128) { uint24 unitClip = clipInside(tickSize, bidTick, askTick); if (unitClip > 0) { return liqForClip(set, unitClip, bidTick); } else { uint24 bidWing = clipBelow(tickSize, bidTick); uint24 askWing = clipAbove(tickSize, askTick); return liqForWing(set, bidWing, bidTick) + liqForWing(set, askWing, askTick); } } /* @notice Calculates the liquidity threshold for a range where both boundaries * are off grid. */ function liqForClip (ImproveSettings memory set, uint24 wingSize, int24 refTick) private pure returns (uint128 liqDemand) { // If neither side is tethered to the grid the gas burden is twice as high // because there's two out-of-band crossings return 2 * liqForWing(set, wingSize, refTick); } /* @notice Calculates the liquidity threshold for a range where one boundary is * off grid and one boundary is on grid. */ function liqForWing (ImproveSettings memory set, uint24 wingSize, int24 refTick) private pure returns (uint128) { if (wingSize == 0) { return 0; } uint128 collateral = set.unitCollateral_; return convertToLiq(collateral, refTick, wingSize, set.inBase_); } /* @notice Given a range boundary determines the number of encompassed ticks * that are off-grid. */ function clipInside (uint16 tickSize, int24 bidTick, int24 askTick) internal pure returns (uint24) { require(bidTick < askTick); if (bidTick < 0 && askTick < 0) { return clipInside(tickSize, -askTick, -bidTick); } else if (bidTick < 0 && askTick >= 0) { return 0; } else { return clipNorm(uint24(tickSize), uint24(bidTick), uint24(askTick)); } } /* @notice Determines off-grid tick size from a normalized range boundary that's * safe for modular arithmetic. */ function clipNorm (uint24 tickSize, uint24 bidTick, uint24 askTick) internal pure returns (uint24) { if (bidTick % tickSize == 0 || askTick % tickSize == 0) { return 0; } else if ((bidTick / tickSize) != (askTick / tickSize)) { return 0; } else { return askTick - bidTick; } } /* @notice Returns the number of off-grid ticks associated with the left side of * a multi-grid spanning range order. */ function clipBelow (uint16 tickSize, int24 bidTick) internal pure returns (uint24) { if (bidTick < 0) { return clipAbove(tickSize, -bidTick); } if (bidTick == 0) { return 0; } uint24 bidNorm = uint24(bidTick); uint24 tickNorm = uint24(tickSize); uint24 gridTick = ((bidNorm - 1) / tickNorm + 1) * tickNorm; return gridTick - bidNorm; } /* @notice Returns the number of off-grid ticks associated with the right side of * a multi-grid spanning range order. */ function clipAbove (uint16 tickSize, int24 askTick) internal pure returns (uint24) { if (askTick < 0) { return clipBelow(tickSize, -askTick); } uint24 askNorm = uint24(askTick); uint24 tickNorm = uint24(tickSize); uint24 gridTick = (askNorm / tickNorm) * tickNorm; return askNorm - gridTick; } /* We're converting from generalized collateral requirements to position-specific * liquidity requirements. This is approximately the inversion of calculating * collateral given liquidity. Therefore, we can just use the pre-existing CurveMath. * We're not worried about exact results in this context anyway. Remember this is * only being used to set an approximate economic threshold for allowing users to * add liquidity inside the grid. */ function convertToLiq (uint128 collateral, int24 tick, uint24 wingSize, bool inBase) private pure returns (uint128) { uint128 priceTick = tick.getSqrtRatioAtTick(); uint128 priceWing = (tick + int24(wingSize)).getSqrtRatioAtTick(); return CurveMath.liquiditySupported(collateral, inBase, priceTick, priceWing); } /* @notice Returns true if the range order is within proximity to the curve's price * tick enough to be eligible for off-grid price improvement. */ function canImprove (ImproveSettings memory set, int24 priceTick, int24 bidTick, int24 askTick) private pure returns (bool) { if (set.unitCollateral_ == 0) { return false; } uint24 bidDist = diffTicks(bidTick, priceTick); uint24 askDist = diffTicks(priceTick, askTick); return bidDist <= set.awayTicks_ && askDist <= set.awayTicks_; } function diffTicks (int24 tickX, int24 tickY) private pure returns (uint24) { return tickY > tickX ? uint24(tickY - tickX) : uint24(tickX - tickY); } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; import './SafeCast.sol'; /* @title Protocol Command library. * * @notice To allow for flexibility and upgradeability the top-level interface to the Croc * dex contract contains a general purpose encoding scheme. User commands specify a * proxy contract index, and input is passed raw and unformatted. Each proxy contract * is free to specify its own input format, but by convention many proxy contracts * adhere to a specification where the first 32 bytes of the input encodes a sub-command * code. This library contains all of these sub-command codes in a single location for * easy lookup. */ library ProtocolCmd { //////////////////////////////////////////////////////////////////////////// // Privileged commands invokable by direct governance only. //////////////////////////////////////////////////////////////////////////// // Code for transferring authority in the underlying CrocSwapDex contract. uint8 constant AUTHORITY_TRANSFER_CODE = 20; // Code to upgrade one of the sidecar proxy contracts on CrocSwapDex. uint8 constant UPGRADE_DEX_CODE = 21; // Code to force hot path to use the proxy contract uint8 constant HOT_OPEN_CODE = 22; // Code to toggle on or off emergency safe mode uint8 constant SAFE_MODE_CODE = 23; // Code to collect accumulated protocol fees for the treasury. uint8 constant COLLECT_TREASURY_CODE = 40; // Code to set the protocol treasury uint8 constant SET_TREASURY_CODE = 41; //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// // General purpose policy commands. //////////////////////////////////////////////////////////////////////////// // Code to disable a given pool template uint8 constant DISABLE_TEMPLATE_CODE = 109; // Code to set pool type template uint8 constant POOL_TEMPLATE_CODE = 110; // Code to revise parameters on pre-existing pool uint8 constant POOL_REVISE_CODE = 111; // Code to set the liquidity burn on pool initialization uint8 constant INIT_POOL_LIQ_CODE = 112; // Code to set/reset the off-grid liquidity threshold. uint8 constant OFF_GRID_CODE = 113; // Code to set the protocol take rate uint8 constant SET_TAKE_CODE = 114; // Code to resync the protocol take rate on an extant pool uint8 constant RESYNC_TAKE_CODE = 115; uint8 constant RELAYER_TAKE_CODE = 116; //////////////////////////////////////////////////////////////////////////// function encodeHotPath (bool open) internal pure returns (bytes memory) { return abi.encode(HOT_OPEN_CODE, open); } function encodeSafeMode (bool safeMode) internal pure returns (bytes memory) { return abi.encode(SAFE_MODE_CODE, safeMode); } } library UserCmd { //////////////////////////////////////////////////////////////////////////// // General purpose cold path codes //////////////////////////////////////////////////////////////////////////// uint8 constant INIT_POOL_CODE = 71; uint8 constant APPROVE_ROUTER_CODE = 72; uint8 constant DEPOSIT_SURPLUS_CODE = 73; uint8 constant DISBURSE_SURPLUS_CODE = 74; uint8 constant TRANSFER_SURPLUS_CODE = 75; uint8 constant SIDE_POCKET_CODE = 76; uint8 constant DEPOSIT_VIRTUAL_CODE = 77; uint8 constant DISBURSE_VIRTUAL_CODE = 78; uint8 constant RESET_NONCE = 80; uint8 constant RESET_NONCE_COND = 81; uint8 constant GATE_ORACLE_COND = 82; uint8 constant DEPOSIT_PERMIT_CODE = 83; //////////////////////////////////////////////////////////////////////////// // LP action warm path command codes //////////////////////////////////////////////////////////////////////////// uint8 constant MINT_RANGE_LIQ_LP = 1; uint8 constant MINT_RANGE_BASE_LP = 11; uint8 constant MINT_RANGE_QUOTE_LP = 12; uint8 constant BURN_RANGE_LIQ_LP = 2; uint8 constant BURN_RANGE_BASE_LP = 21; uint8 constant BURN_RANGE_QUOTE_LP = 22; uint8 constant MINT_AMBIENT_LIQ_LP = 3; uint8 constant MINT_AMBIENT_BASE_LP = 31; uint8 constant MINT_AMBIENT_QUOTE_LP = 32; uint8 constant BURN_AMBIENT_LIQ_LP = 4; uint8 constant BURN_AMBIENT_BASE_LP = 41; uint8 constant BURN_AMBIENT_QUOTE_LP = 42; uint8 constant HARVEST_LP = 5; //////////////////////////////////////////////////////////////////////////// // Knockout LP command codes //////////////////////////////////////////////////////////////////////////// uint8 constant MINT_KNOCKOUT = 91; uint8 constant BURN_KNOCKOUT = 92; uint8 constant CLAIM_KNOCKOUT = 93; uint8 constant RECOVER_KNOCKOUT = 94; }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.8.19; /// @title Safe casting methods /// @notice Contains methods for safely casting between types library SafeCast { /// @notice Cast a uint256 to a uint160, revert on overflow /// @param y The uint256 to be downcasted /// @return z The downcasted integer, now type uint160 function toUint160(uint256 y) internal pure returns (uint160 z) { unchecked { // Explicit bounds check require((z = uint160(y)) == y); } } /// @notice Cast a uint256 to a uint128, revert on overflow /// @param y The uint256 to be downcasted /// @return z The downcasted integer, now type uint128 function toUint128(uint256 y) internal pure returns (uint128 z) { unchecked { // Explicit bounds check require((z = uint128(y)) == y); } } /// @notice Cast a uint192 to a uint128, revert on overflow /// @param y The uint192 to be downcasted /// @return z The downcasted integer, now type uint128 function toUint128By192(uint192 y) internal pure returns (uint128 z) { unchecked { // Explicit bounds check require((z = uint128(y)) == y); } } /// @notice Cast a uint144 to a uint128, revert on overflow /// @param y The uint144 to be downcasted /// @return z The downcasted integer, now type uint128 function toUint128By144(uint144 y) internal pure returns (uint128 z) { unchecked{ // Explicit bounds check require((z = uint128(y)) == y); } } /// @notice Cast a uint128 to a int128, revert on overflow /// @param y The uint128 to be casted /// @return z The casted integer, now type int128 function toInt128Sign(uint128 y) internal pure returns (int128 z) { unchecked { // Explicit bounds check require(y < 2**127); return int128(y); } } // Unix timestamp can fit into 32-bits until the year 2106. After which, internally // stored timestamps will stop increasing. Deployed contracts relying on this function // should be re-evaluated before that date. function timeUint32() internal view returns (uint32) { unchecked { // Explicit bounds check uint time = block.timestamp; if (time > type(uint32).max) { return type(uint32).max; } return uint32(time); } } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; pragma experimental ABIEncoderV2; import './TickMath.sol'; import './LiquidityMath.sol'; import './SafeCast.sol'; import './CurveMath.sol'; import './CurveAssimilate.sol'; import './CurveRoll.sol'; import './PoolSpecs.sol'; import './Directives.sol'; import './Chaining.sol'; /* @title Swap Curve library. * @notice Library contains functionality for fully applying a swap directive to * a locally stable AMM liquidty curve within the bounds of the stable range, * in a way that accumulates fees onto the curve's liquidity. */ library SwapCurve { using SafeCast for uint128; using CurveMath for CurveMath.CurveState; using CurveAssimilate for CurveMath.CurveState; using CurveRoll for CurveMath.CurveState; using Chaining for Chaining.PairFlow; /* @notice Applies the swap on to the liquidity curve, either fully exhausting * the swap or reaching the concentrated liquidity bounds or the user-specified * limit price. After calling, the curve and swap objects will be updated with * the swap price impact, the liquidity fees assimilated into the curve's ambient * liquidity, and the swap accumulators incremented with the cumulative flows. * * @param curve - The current in-range liquidity curve. After calling, price and * fee accumulation will be adjusted based on the swap processed in this leg. * @param accum - An accumulator for the asset pair the swap/curve applies to. * This object will be incremented with the flow processed on this leg. The swap * may or may not be fully exhausted. Caller should check the swap.qty_ field. @ @param swap - The user directive specifying the swap to execute on this curve. * Defines the direction, size, and limit price. After calling, the swapQty will * be decremented with the amount of size executed in this leg. * @param pool - The specifications for the pool's AMM curve, notably in this context * the fee rate and protocol take. * * @param bumpTick - The tick boundary, past which the constant product AMM * liquidity curve is no longer known to be valid. (Either because it represents * a liquidity bump point, or the end of a tick bitmap horizon.) The curve will * never move past this tick boundary in the call. Caller's responsibility is to * set this parameter in the correct direction. I.e. buys should be the boundary * from above and sells from below. Represented as a price tick index. */ function swapToLimit (CurveMath.CurveState memory curve, Chaining.PairFlow memory accum, Directives.SwapDirective memory swap, PoolSpecs.Pool memory pool, int24 bumpTick) pure internal { uint128 limitPrice = determineLimit(bumpTick, swap.limitPrice_, swap.isBuy_); uint128 startPrice = curve.priceRoot_; (int128 paidBase, int128 paidQuote, uint128 paidProto) = bookExchFees(curve, swap.qty_, pool, swap.inBaseQty_, limitPrice); accum.accumSwap(swap.inBaseQty_, paidBase, paidQuote, paidProto); // limitPrice is still valid even though curve has moved from ingesting liquidity // fees in bookExchFees(). That's because the collected fees are mathematically // capped at a fraction of the flow necessary to reach limitPrice. See // bookExchFees() comments. (This is also why we book fees before swapping, so we // don't run into the limitPrice when trying to ingest fees.) (paidBase, paidQuote, swap.qty_) = swapOverCurve (curve, swap.inBaseQty_, swap.isBuy_, swap.qty_, limitPrice); accum.accumSwap(swap.inBaseQty_, paidBase, paidQuote, 0); assertPriceDirection(swap.isBuy_, curve, startPrice); } /* @notice Validates the invariant that the price change is in the direction of the swap. */ function assertPriceDirection (bool isBuy, CurveMath.CurveState memory curve, uint128 startPrice) pure private { require(isBuy ? curve.priceRoot_ >= startPrice : curve.priceRoot_ <= startPrice); } /* @notice Calculates the exchange fee given a swap directive and limitPrice. Note * this assumes the curve is constant-product without liquidity bumps through the * whole range. Don't use this function if you're unable to guarantee that the AMM * curve is locally stable through the price impact. * * @param curve The current state of the AMM liquidity curve. Must be stable without * liquidity bumps through the price impact. * @param swapQty The quantity specified for this leg of the swap, may or may not be * fully executed depending on limitPrice. * @param feeRate The pool's fee as a proportion of notion executed. Represented as * a multiple of 0.0001% * @param protoTake The protocol's take as a share of the exchange fee. (Rest goes to * liquidity rewards.) Represented as 1/n (with zero a special case.) * @param inBaseQty If true the swap quantity is denominated as base-side tokens. If * false, quote-side tokens. * @param limitPrice The max (min) price this leg will swap to if it's a buy (sell). * Represented as the square root of price as a Q64.64 fixed-point. * * @return liqFee The total fees that's allocated as liquidity rewards accumulated * to liquidity providers in the pool (in the opposite side tokens of * the swap denomination). * @return protoFee The total fee accumulated as CrocSwap protocol fees. */ function calcFeeOverSwap (CurveMath.CurveState memory curve, uint128 swapQty, uint16 feeRate, uint8 protoTake, bool inBaseQty, uint128 limitPrice) internal pure returns (uint128 liqFee, uint128 protoFee) { uint128 flow = curve.calcLimitCounter(swapQty, inBaseQty, limitPrice); (liqFee, protoFee) = calcFeeOverFlow(flow, feeRate, protoTake); } /* @notice Give a pre-determined price limit, executes a fixed amount of swap * quantity into the liquidity curve. * * @dev Note that this function does *not* process liquidity fees, and those should * be collected and assimilated into the curve *before* calling this function. * Otherwise we may reach the end of the locally stable curve and not be able * to correctly account for the impact on the curve. * * @param curve The liquidity curve state being executed on. This object will update * with the post-swap impact. * @param inBaseQty If true, the swapQty param is denominated in base-side tokens. * @param isBuy If true, the swap is paying base tokens to the pool and receiving * quote tokens. * @param swapQty The total quantity to be swapped. May or may not be fully exhausted * depending on limitPrice. * @param limitPrice The max (min) price this leg will swap to if it's a buy (sell). * Represented as the square root of price as a Q64.64 fixed-point. * * @return paidBase The amount of base-side token flow associated with this leg of * the swap (not counting previously collected fees). If negative * pool is paying out base-tokens. If positive pool is collecting. * @return paidQuote The amount of quote-side token flow for this leg of the swap. * @return qtyLeft The total amount of swapQty left after this leg executes. If swap * fully executes, this value will be zero. */ function swapOverCurve (CurveMath.CurveState memory curve, bool inBaseQty, bool isBuy, uint128 swapQty, uint128 limitPrice) pure private returns (int128 paidBase, int128 paidQuote, uint128 qtyLeft) { // Invariant check swap direction matches price direction require(isBuy ? limitPrice >= curve.priceRoot_ : limitPrice <= curve.priceRoot_); uint128 realFlows = curve.calcLimitFlows(swapQty, inBaseQty, limitPrice); bool hitsLimit = realFlows < swapQty; if (hitsLimit) { (paidBase, paidQuote, qtyLeft) = curve.rollPrice (limitPrice, inBaseQty, isBuy, swapQty); assertPriceEndStable(curve, qtyLeft, limitPrice); } else { uint128 startPrice = curve.priceRoot_; (paidBase, paidQuote, qtyLeft) = curve.rollFlow (realFlows, inBaseQty, isBuy, swapQty); assertFlowEndStable(curve, qtyLeft, isBuy, limitPrice); assertPriceDirection(isBuy, curve, startPrice); } } /* In rare corner cases, swap can result in a corrupt end state. This occurs * when the swap flow lands within in a rounding error of the limit price. That * potentially creates an error where we're swapping through a curve price range * without supported liquidity. * * The other corner case is the flow based swap not exhausting liquidity for some * code or rounding reason. The upstream logic uses the exhaustion of the swap qty * to determine whether a liquidity bump was reached. In this case it would try to * inappropriately kick in liquidity at a bump the price hasn't reached. * * In both cases the condition is so astronomically rare that we just crash the * transaction. */ function assertFlowEndStable (CurveMath.CurveState memory curve, uint128 qtyLeft, bool isBuy, uint128 limitPrice) pure private { bool insideLimit = isBuy ? curve.priceRoot_ < limitPrice : curve.priceRoot_ > limitPrice; bool hasNone = qtyLeft == 0; require(insideLimit && hasNone, "RF"); } /* Similar to asserFlowEndStable() but for limit-bound swap legs. Due to rounding * effects we may also simultaneously exhaust the flow at the same exact point we * reach the limit barrier. This could corrupt the upstream logic which uses the * remaining qty to determine whether we've reached a tick bump. * * In this case the corner case would mean it would fail to kick in new liquidity * that's required by reaching the tick bump limit. Again this is so astronomically * rare for non-pathological curves that we just crash the transaction. */ function assertPriceEndStable (CurveMath.CurveState memory curve, uint128 qtyLeft, uint128 limitPrice) pure private { bool atLimit = curve.priceRoot_ == limitPrice; bool hasRemaining = qtyLeft > 0; require(atLimit && hasRemaining, "RP"); } /* @notice Determines an effective limit price given the combination of swap- * specified limit, tick liquidity bump boundary on the locally stable AMM curve, * and the numerical boundaries of the price field. Always picks the value that's * most to the inside of the swap direction. */ function determineLimit (int24 bumpTick, uint128 limitPrice, bool isBuy) pure private returns (uint128) { unchecked { uint128 bounded = boundLimit(bumpTick, limitPrice, isBuy); if (bounded < TickMath.MIN_SQRT_RATIO) return TickMath.MIN_SQRT_RATIO; if (bounded >= TickMath.MAX_SQRT_RATIO) return TickMath.MAX_SQRT_RATIO - 1; // Well above 0, cannot underflow return bounded; } } /* @notice Finds the effective max (min) swap limit price giving a bump tick index * boundary and a user specified limitPrice. * * @dev Because the mapping from ticks to bumps always occur at the lowest price unit * inside a tick, there is an asymmetry between the lower and upper bump tick arg. * The lower bump tick is the lowest tick *inclusive* for which liquidity is active. * The upper bump tick is the *next* tick above where liquidity is active. Therefore * the lower liquidity price maps to the bump tick price, whereas the upper liquidity * price bound maps to one unit less than the bump tick price. * * Lower bump price Upper bump price * | | * ------X******************************************+X----------------- * | | * Min liquidity prce Max liquidity price */ function boundLimit (int24 bumpTick, uint128 limitPrice, bool isBuy) pure private returns (uint128) { unchecked { if (bumpTick <= TickMath.MIN_TICK || bumpTick >= TickMath.MAX_TICK) { return limitPrice; } else if (isBuy) { /* See comment above. Upper bound liquidity is last active at the price one unit * below the upper tick price. */ uint128 TICK_STEP_SHAVE_DOWN = 1; // Valid uint128 root prices are always well above 0. uint128 bumpPrice = TickMath.getSqrtRatioAtTick(bumpTick) - TICK_STEP_SHAVE_DOWN; return bumpPrice < limitPrice ? bumpPrice : limitPrice; } else { uint128 bumpPrice = TickMath.getSqrtRatioAtTick(bumpTick); return bumpPrice > limitPrice ? bumpPrice : limitPrice; } } } /* @notice Calculates exchange fee charge based off an estimate of the predicted * order flow on this leg of the swap. * * @dev Note that the process of collecting the exchange fee itself alters the * structure of the curve, because those fees assimilate as liquidity into the * curve new liquidity. As such the flow used to pro-rate fees is only an estimate * of the actual flow that winds up executed. This means that fees are not exact * relative to realized flows. But because fees only have a small impact on the * curve, they'll tend to be very close. Getting fee exactly correct doesn't * matter, and either over or undershooting is fine from a collateral stability * perspective. */ function bookExchFees (CurveMath.CurveState memory curve, uint128 swapQty, PoolSpecs.Pool memory pool, bool inBaseQty, uint128 limitPrice) pure private returns (int128, int128, uint128) { (uint128 liqFees, uint128 exchFees) = calcFeeOverSwap (curve, swapQty, pool.feeRate_, pool.protocolTake_, inBaseQty, limitPrice); /* We can guarantee that the price shift associated with the liquidity * assimilation is safe. The limit price boundary is by definition within the * tick price boundary of the locally stable AMM curve (see determineLimit() * function). The liquidity assimilation flow is mathematically capped within * the limit price flow, because liquidity fees are a small fraction of swap * flows. */ curve.assimilateLiq(liqFees, inBaseQty); return assignFees(liqFees, exchFees, inBaseQty); } /* @notice Correctly applies the liquidity and protocol fees to the correct side in * in th pair, given how the swap is denominated. */ function assignFees (uint128 liqFees, uint128 exchFees, bool inBaseQty) pure private returns (int128 paidBase, int128 paidQuote, uint128 paidProto) { unchecked { // Safe for unchecked because total fees are always previously calculated in // 128-bit space uint128 totalFees = liqFees + exchFees; if (inBaseQty) { paidQuote = totalFees.toInt128Sign(); } else { paidBase = totalFees.toInt128Sign(); } paidProto = exchFees; } } /* @notice Given a fixed flow and a fee rate, calculates the owed liquidty and * protocol fees. */ function calcFeeOverFlow (uint128 flow, uint16 feeRate, uint8 protoProp) private pure returns (uint128 liqFee, uint128 protoFee) { unchecked { uint256 FEE_BP_MULT = 1_000_000; // Guaranteed to fit in 256 bit arithmetic. Safe to cast back to uint128 // because fees will never be larger than the underlying flow. uint256 totalFee = (uint256(flow) * feeRate) / FEE_BP_MULT; protoFee = uint128(totalFee * protoProp / 256); liqFee = uint128(totalFee) - protoFee; } } }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.8.19; /// @title Math library for computing sqrt prices from ticks and vice versa /// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.64 numbers. Supports /// prices between 2**-96 and 2**120 library TickMath { /// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-96 int24 internal constant MIN_TICK = -665454; /// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**120 int24 internal constant MAX_TICK = 831818; /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK). The reason we don't set this as min(uint128) is so that single precicion moves represent a small fraction. uint128 internal constant MIN_SQRT_RATIO = 65538; /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK) uint128 internal constant MAX_SQRT_RATIO = 21267430153580247136652501917186561138; /// @notice Calculates sqrt(1.0001^tick) * 2^64 /// @dev Throws if tick < MIN_TICK or tick > MAX_TICK /// @param tick The input tick for the above formula /// @return sqrtPriceX64 A Fixed point Q64.64 number representing the sqrt of the ratio of the two assets (token1/token0) /// at the given tick function getSqrtRatioAtTick(int24 tick) internal pure returns (uint128 sqrtPriceX64) { // Set to unchecked, but the original UniV3 library was written in a pre-checked version of Solidity unchecked { require(tick >= MIN_TICK && tick <= MAX_TICK); uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick)); uint256 ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000; if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128; if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128; if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128; if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128; if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128; if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128; if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128; if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128; if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128; if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128; if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128; if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128; if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128; if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128; if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128; if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128; if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128; if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128; if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128; if (tick > 0) ratio = type(uint256).max / ratio; // this divides by 1<<64 rounding up to go from a Q128.128 to a Q64.64 // we then downcast because we know the result always fits within 128 bits due to our tick input constraint // we round up in the division so getTickAtSqrtRatio of the output price is always consistent sqrtPriceX64 = uint128((ratio >> 64) + (ratio % (1 << 64) == 0 ? 0 : 1)); } } /// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio /// @dev Throws in case sqrtPriceX64 < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may /// ever return. /// @param sqrtPriceX64 The sqrt ratio for which to compute the tick as a Q64.64 /// @return tick The greatest tick for which the ratio is less than or equal to the input ratio function getTickAtSqrtRatio(uint128 sqrtPriceX64) internal pure returns (int24 tick) { // Set to unchecked, but the original UniV3 library was written in a pre-checked version of Solidity unchecked { // second inequality must be < because the price can never reach the price at the max tick require(sqrtPriceX64 >= MIN_SQRT_RATIO && sqrtPriceX64 < MAX_SQRT_RATIO); uint256 ratio = uint256(sqrtPriceX64) << 64; uint256 r = ratio; uint256 msb = 0; assembly { let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) msb := or(msb, f) r := shr(f, r) } assembly { let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF)) msb := or(msb, f) r := shr(f, r) } assembly { let f := shl(5, gt(r, 0xFFFFFFFF)) msb := or(msb, f) r := shr(f, r) } assembly { let f := shl(4, gt(r, 0xFFFF)) msb := or(msb, f) r := shr(f, r) } assembly { let f := shl(3, gt(r, 0xFF)) msb := or(msb, f) r := shr(f, r) } assembly { let f := shl(2, gt(r, 0xF)) msb := or(msb, f) r := shr(f, r) } assembly { let f := shl(1, gt(r, 0x3)) msb := or(msb, f) r := shr(f, r) } assembly { let f := gt(r, 0x1) msb := or(msb, f) } if (msb >= 128) r = ratio >> (msb - 127); else r = ratio << (127 - msb); int256 log_2 = (int256(msb) - 128) << 64; assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(63, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(62, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(61, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(60, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(59, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(58, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(57, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(56, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(55, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(54, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(53, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(52, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(51, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(50, f)) } int256 log_sqrt10001 = log_2 * 255738958999603826347141; // 128.128 number int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128); int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128); tick = tickLow == tickHi ? tickLow : getSqrtRatioAtTick(tickHi) <= sqrtPriceX64 ? tickHi : tickLow; } } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; pragma experimental ABIEncoderV2; import './Chaining.sol'; /* @title Token flow library * @notice Provides a facility for joining token flows for trades that occur on an * arbitrary long chain of overlapping pairs. */ library TokenFlow { /* @notice Represents the current hop within a chain of pair hops. * @param baseToken_ The base token in the current pair. (If zero native Ethereum) * @param quoteToken_ The quote token in the current pair. * @param isBaseFront_ If true, then the base side of the pair represents the entry * token on this hop in the chain. * @param legFlow_ - Represents the total flow from the exit side on the previous pair * hop in the chain. * @param flow_ - Accumulator to collect the flow on this pair hop. */ struct PairSeq { address baseToken_; address quoteToken_; bool isBaseFront_; int128 legFlow_; Chaining.PairFlow flow_; } /* @notice Moves the PairSeq cursor object onto the next pair in a hop. * * @dev Note that this doesn't process, roll or reset flows. All of the * bookkeeping related to this and settlement should be done *before* calling * this on the next pair. * * @param seq The cursor object, pair tokens will be updated after call. * @param tokenFront The token associated with the front or entry of the chain's * next pair hop. * @param tokenBack The token associated with the back or exit of the chain's * next pair hop. */ function nextHop (PairSeq memory seq, address tokenFront, address tokenBack) pure internal { seq.isBaseFront_ = tokenFront < tokenBack; if (seq.isBaseFront_) { seq.baseToken_ = tokenFront; seq.quoteToken_ = tokenBack; } else { seq.quoteToken_ = tokenFront; seq.baseToken_ = tokenBack; } } /* @notice Returns the token at the front/entry side of the pair hop. */ function frontToken (PairSeq memory seq) internal pure returns (address) { return seq.isBaseFront_ ? seq.baseToken_ : seq.quoteToken_; } /* @notice Returns the token at the back/exit side of the pair hop. */ function backToken (PairSeq memory seq) internal pure returns (address) { return seq.isBaseFront_ ? seq.quoteToken_ : seq.baseToken_; } /* @notice Called when all the flows have been tallied and finalized for this * pair hop in the chain. Resets and rolls the object and returns the net * flows to be settled between user and exchange. * * @param seq The PairSeq cursor object. Aftering calling the object will be updated * to have the back/exit flow rolled into the leg for the next hop, and * the previous accumulators will be reset. * * @return clippedFlow The net flow (inclusive of the rolled leg flow from the * previous hop) on the front/entry side of the pair to be * settled. Negative indicates credit from dex to user, positive * indicates debit from user to dex.*/ function clipFlow (PairSeq memory seq) internal pure returns (int128 clippedFlow) { (int128 frontAccum, int128 backAccum) = seq.isBaseFront_ ? (seq.flow_.baseFlow_, seq.flow_.quoteFlow_) : (seq.flow_.quoteFlow_, seq.flow_.baseFlow_); clippedFlow = seq.legFlow_ + frontAccum; seq.legFlow_ = backAccum; seq.flow_.baseFlow_ = 0; seq.flow_.quoteFlow_ = 0; seq.flow_.baseProto_ = 0; seq.flow_.quoteProto_ = 0; } /* @notice Returns the final flow to be settled associated with the closing leg at * the end of the chain of pair hops. Negative means credit from dex to user. * Positive is debit from user to dex. */ function closeFlow (PairSeq memory seq) internal pure returns (int128) { return seq.legFlow_; } /* @notice If true, indicates that the asset-specifying address represents native * Ethereum. Otherwise it should be the valid address of the ERC20 token * tracker. */ function isEtherNative (address token) internal pure returns (bool) { return token == address(0); } }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.8.19; import '../interfaces/IERC20Minimal.sol'; /// @title TransferHelper /// @notice Contains helper methods for interacting with ERC20 tokens that do not consistently return true/false library TransferHelper { /// @notice Transfers tokens from msg.sender to a recipient /// @dev Calls transfer on token contract, errors with TF if transfer fails /// @param token The contract address of the token which will be transferred /// @param to The recipient of the transfer /// @param value The value of the transfer function safeTransfer( address token, address to, uint256 value ) internal { (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Minimal.transfer.selector, to, value)); require(success && (data.length == 0 || abi.decode(data, (bool))), "TF"); } /// @notice Transfers tokens from msg.sender to a recipient /// @dev Calls transferFrom on token contract, errors with TF if transfer fails /// @param token The contract address of the token which will be transferred /// @param from The sender address of the transfer /// @param to The recipient of the transfer /// @param value The value of the transfer function safeTransferFrom( address token, address from, address to, uint256 value ) internal { (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Minimal.transferFrom.selector, from, to, value)); require(success && (data.length == 0 || abi.decode(data, (bool))), "TF"); } // @notice Transfers native Ether to a recipient. // @dev errors with TF if transfer fails function safeEtherSend( address to, uint256 value ) internal { (bool success, ) = to.call{value: value}(""); require(success, "TF"); } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; pragma experimental ABIEncoderV2; import "./StorageLayout.sol"; import "../interfaces/ICrocCondOracle.sol"; /* @title Agent mask mixin. * @notice Maps and manages surplus balances, nonces, and external router approvals * based on the wallet addresses of end-users. */ contract AgentMask is StorageLayout { using SafeCast for uint256; /* @notice Standard re-entrant gate for an unprivileged order called directly * by the user. * * @dev lockHolder_ account is set to msg.sender, and therefore this call will * touch the positions, tokens, and liquidity owned by msg.sender. */ modifier reEntrantLock() { require(lockHolder_ == address(0)); lockHolder_ = msg.sender; _; lockHolder_ = address(0); resetMsgVal(); } /* @notice Re-entrant gate for privileged protocol authority commands. */ modifier protocolOnly (bool sudo) { require(msg.sender == authority_ && lockHolder_ == address(0)); lockHolder_ = msg.sender; sudoMode_ = sudo; _; lockHolder_ = address(0); sudoMode_ = false; resetMsgVal(); } /* @notice Re-entrant gate for an order called by external router on behalf of a * third party client. Requires the user to have previously approved the * router. * * @dev lockHolder_ is set to the client address directly supplied by the caller. * (The client address must always directly approve the msg.sender contract to * act on its behalf.) Therefore this call (if approved) will touch the positions, * tokens, and liquidity owned by client address. * * @param client The client who's order the router is calling on behalf of. * @param callPath The proxy sidecar callpath the agent is requesting to call on the user's behalf */ modifier reEntrantApproved (address client, uint16 callPath) { stepAgentNonce(client, msg.sender, callPath); require(lockHolder_ == address(0)); lockHolder_ = client; _; lockHolder_ = address(0); resetMsgVal(); } /* @notice Re-entrant gate for a relayer calling an order that was signed off-chain * using the EIP-712 standard. * * @dev lockHolder_ is set to the address whose private key signed the ECDSA * signature. Regardless of which address is msg.sender, all operations inside * this call will touch the positions, tokens, and liquidity owned by the * signing address. */ modifier reEntrantAgent (CrocRelayerCall memory call, bytes calldata signature) { require(lockHolder_ == address(0)); lockHolder_ = lockSigner(call, signature); _; lockHolder_ = address(0); resetMsgVal(); } struct CrocRelayerCall { uint16 callpath; bytes cmd; bytes conds; bytes tip; } /* @notice Atomically returns the msg.value of the transaction and marks the funds as * spent. This provides a layer of safety to prevent msg.value from being spent * twice in a single transaction. * @dev For safety msg.value should *never* be accessed in any way outside this function. * This assures that if msg.value is used at one point in the callpath it isn't * inadvertantly used at another point, because that would trigger a revert. */ function popMsgVal() internal returns (uint128 msgVal) { require(msgValSpent_ == false, "DS"); msgVal = msg.value.toUint128(); msgValSpent_ = true; } /* @dev This should only be called when the top-level contract call is fully out-of-scope. * Otherwise the risk is msg.val could be double spent. */ function resetMsgVal() private { msgValSpent_ = false; } /* @notice Given the order, evaluation conditionals, and off-chain signature, recovers * the client address if valid or reverts the transactions. */ function lockSigner (CrocRelayerCall memory call, bytes calldata signature) private returns (address client) { client = verifySignature(call, signature); checkRelayConditions(client, call.conds); } /* @notice Verifies that the conditions signed by the user are met at evaluation time, * and if necessary increments the nonce. * * @param client The client who's order is being evaluated on behalf of. * @param deadline The deadline (in block time) that the order must be evaluated by. * @param alive The live time (in block time) that the order cannot be evaluated * before. * @param salt A salt to apply when checking the nonce. Allows users to sign * an arbitrary number of multiple nonce tracks, so they don't have * to wait for unrelated orders. * @param nonce The replay-attack prevention nonce. Two orders with the same salt * and nonce cannot be evaluated (unless the user explicitly resets * the nonce). A nonce cannot be evaluated until prior orders at * lower nonces haven been successfully evaluated. * @param relayer Address of the relayer the user requires to evaluate the order. * Must match either msg.sender or tx.origin. If zero, the order * does not require a specific relayer. */ function checkRelayConditions (address client, bytes memory conds) internal { (uint48 deadline, uint48 alive, bytes32 salt, uint32 nonce, address relayer) = abi.decode(conds, (uint48, uint48, bytes32, uint32, address)); require(block.timestamp <= deadline); require(block.timestamp >= alive); require(relayer == address(0) || relayer == msg.sender || relayer == tx.origin); stepNonce(client, salt, nonce); } /* @notice Verifies the supplied signature matches the EIP-712 compatible data. * * @dev Note that the ECDSA signature is malleable, because (v, r, s) are unrestricted. * However this is not an issue, because the raw signature itself is not used as an * index or nonce in any form. A malicious attacker *could* change the signature, but * could not change the plaintext checksum being signed. * * If a malleable signature was submitted, either it would arrive before the honest * signature, in which case the call parameters would be identical. Or it would arrive after * the honest signature, in which case the call parameter would be rejected becaue it * used an expired nonce. In no state of the world does a malleable signature make a * replay attack possible. */ function verifySignature (CrocRelayerCall memory call, bytes calldata signature) internal view returns (address client) { (uint8 v, bytes32 r, bytes32 s) = abi.decode(signature, (uint8, bytes32, bytes32)); bytes32 checksum = checksumHash(call); client = ecrecover(checksum, v, r, s); require(client != address(0)); } /* @notice Calculates the EIP-712 hash to check the signature against. */ function checksumHash (CrocRelayerCall memory call) private view returns (bytes32) { bytes32 hash = contentHash(call); return keccak256(abi.encodePacked ("\x19\x01", domainHash(), hash)); } bytes32 constant CALL_SIG_HASH = keccak256("CrocRelayerCall(uint8 callpath,bytes cmd,bytes conds,bytes tip)"); bytes32 constant DOMAIN_SIG_HASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); bytes32 constant APP_NAME_HASH = keccak256("CrocSwap"); bytes32 constant VERSION_HASH = keccak256("1.0"); /* @notice Calculates the EIP-712 typedStruct hash. */ function contentHash (CrocRelayerCall memory call) private pure returns (bytes32) { return keccak256( abi.encode (CALL_SIG_HASH, call.callpath, keccak256(call.cmd), keccak256(call.conds), keccak256(call.tip))); } /* @notice Calculates the EIP-712 domain hash. */ function domainHash() private view returns (bytes32) { return keccak256( abi.encode (DOMAIN_SIG_HASH, APP_NAME_HASH, VERSION_HASH, block.chainid, address(this))); } /* @notice Returns the payer and receiver of any settlement collateral flows. * @return debit The address that will be paying any debits to the pool. * @return credit The address that will receive any credits from the pool. */ function agentsSettle() internal view returns (address debit, address credit) { (debit, credit) = (lockHolder_, lockHolder_); } /* @notice Approves an external router or agent to act on a user's behalf. * @param router The address of the external agent. * @param nCalls The number of calls the external router is authorized to make. Set * to uint32.max for unlimited. * @param callPath The specific proxy sidecar callpath that the router is approved for */ function approveAgent (address router, uint32 nCalls, uint16 callPath) internal { bytes32 key = agentKey(lockHolder_, router, callPath); UserBalance storage bal = userBals_[key]; bal.agentCallsLeft_ = nCalls; } /* @notice Sets the nonce index related to EIP-712 off-chain calls. * @param nonceSalt The nonce system is multi-dimensional, which allows relayers to * pass along arbitrary ordered messages when they come from * unrelated streams. This value corresponds to the specific nonce * dimension. * @param nonce The nonce index value the nonce will be reset to. */ function resetNonce (bytes32 nonceSalt, uint32 nonce) internal { UserBalance storage bal = userBals_[nonceKey(lockHolder_, nonceSalt)]; require(nonce >= bal.nonce_, "NI"); bal.nonce_ = nonce; } /* @notice Same as resetNonce but conditions on the successful call return to an * external oracle. Useful for certain times that a user wants to pre-sign * a transaction, but not let it be executable unless an arbitrary condition * is met. * @param nonceSalt The nonce system is multi-dimensional, which allows relayers to * pass along arbitrary ordered messages when they come from * unrelated streams. This value corresponds to the specific nonce * dimension. * @param nonce The nonce index value the nonce will be reset to. * @param oracle The address of the external oracle (must conform to ICrocNonceOracle * interface. * @param args Arbitrary calldata passed to the oracle condition call. */ function resetNonceCond (bytes32 salt, uint32 nonce, address oracle, bytes memory args) internal { bool canProceed = ICrocNonceOracle(oracle).checkCrocNonceSet (lockHolder_, salt, nonce, args); require(canProceed, "ON"); resetNonce(salt, nonce); } /* @notice Flat call that checks an external oracle and reverts the transaction if the * oracle call fails. Useful in a multicall context, where we want to pre- * condition on some external requirement. * @param oracle The address of the external oracle (must conform to ICrocCondOracle * interface. * @param args Arbitrary calldata passed to the oracle condition call. */ function checkGateOracle (address oracle, bytes memory args) internal { bool canProceed = ICrocCondOracle(oracle).checkCrocCond (lockHolder_, args); require(canProceed, "OG"); } /* @notice Compare-and-swap the nCalls on a single external agent call. Checks that * the agent is authorized to perform another call, and if so decrements the * number of remaining calls. * @param client The client the agent is making the call on behalf of. * @param agent The address of the external agent making the call. * @param callPath The proxy sidecar the call is being made on. */ function stepAgentNonce (address client, address agent, uint16 callPath) internal { UserBalance storage bal = userBals_[agentKey(client, agent, callPath)]; if (bal.agentCallsLeft_ < type(uint32).max) { require(bal.agentCallsLeft_ > 0); --bal.agentCallsLeft_; } } /* @notice Compare-and-swap the nonce on a single EIP-712 signed transaction. Checks * that the nonce matches the current nonce for the user/salt, and atomically * increments the nonce. * @param client The client the agent is making the call on behalf of. * @param salt The multidimensional nonce dimension the call is being applied to. * @param nonce The nonce the EIP-712 message is signed for. This must match the * current nonce or the transaction will fail. */ function stepNonce (address client, bytes32 nonceSalt, uint32 nonce) internal { UserBalance storage bal = userBals_[nonceKey(client, nonceSalt)]; require(bal.nonce_ == nonce); ++bal.nonce_; } /* @notice Called within the context of an EIP-712 transaction, where the underlying * client pays the relayer for having mined the transaction. (If the cmd byte * data is empty, no tip is paid). * * @dev Thie call will always occur at the *end* of a transaction. So the user must * have sufficient balance in their surplus collateral to cover the tip by the * completion of the transaction. * * @param token The token the tip is being paid in. This will always be paid from the * user's surplus collateral balance. * @param tip The amount the user is paying in tip. If protocol fee is turned on this * is the *total* amount paid. The relayer will receive this less protocol * fee. Tip can also be set to uint128.max, and will pay the full amount * of the client's surplus collateral balance. * @param recv The receiver of the tip. This will always be paid to this account's * surplus collateral balance. Also supports generic magic values for * generic relayer payment: * 0x100 - Paid to the msg.sender, regardless of who made the dex call * 0x200 - Paid to the tx.origin, regardless of who sent tx. */ function tipRelayer (bytes memory tipCmd) internal { if (tipCmd.length == 0) { return; } (address token, uint128 tip, address recv) = abi.decode(tipCmd, (address, uint128, address)); recv = maskTipRecv(recv); bytes32 fromKey = tokenKey(lockHolder_, token); bytes32 toKey = tokenKey(recv, token); if (tip == type(uint128).max) { tip = userBals_[fromKey].surplusCollateral_; } require(userBals_[fromKey].surplusCollateral_ >= tip); uint128 protoFee = tip * relayerTakeRate_ / 256; uint128 relayerTip = tip - protoFee; userBals_[fromKey].surplusCollateral_ -= tip; userBals_[toKey].surplusCollateral_ += relayerTip; if (protoFee > 0) { feesAccum_[token] += protoFee; } } address constant MAGIC_SENDER_TIP = address(256); address constant MAGIC_ORIGIN_TIP = address(512); /* @notice Converts the user's tip recv argument to the actual address to be paid. * In practice this means that the magic values for msg.sender and tx.origin * are converted to those value's actual address for the transaction. */ function maskTipRecv (address recv) view private returns (address) { if (recv == MAGIC_SENDER_TIP) { recv = msg.sender; } else if (recv == MAGIC_ORIGIN_TIP) { recv = tx.origin; } return recv; } /* @notice Given a user address and a salt returns a new virtualized user address. * Useful when we want multiple synthetic accounts tied to a single address.*/ function virtualizeUser (address client, uint256 salt) internal pure returns (address) { if (salt == 0) { return client; } else { return PoolSpecs.virtualizeAddress(client, salt); } } /* @notice Returns the user balance key given a user account an an inner salt. */ function nonceKey (address user, bytes32 innerKey) pure internal returns (bytes32) { return keccak256(abi.encode(user, innerKey)); } /* @notice Returns a token balance key given a user and token address. */ function tokenKey (address user, address token) pure internal returns (bytes32) { return keccak256(abi.encode(user, token)); } /* @notice Returns a token balance key given a user, token and an arbitrary salt. */ function tokenKey (address user, address token, uint256 salt) pure internal returns (bytes32) { return tokenKey(user, PoolSpecs.virtualizeAddress(token, salt)); } /* @notice Returns an agent key given a user, an agent address and a specific * call path. */ function agentKey (address user, address agent, uint16 callPath) pure internal returns (bytes32) { return keccak256(abi.encode(user, agent, callPath)); } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; pragma experimental ABIEncoderV2; import '../libraries/LiquidityMath.sol'; import '../libraries/KnockoutLiq.sol'; import './LevelBook.sol'; import './PoolRegistry.sol'; import './AgentMask.sol'; /* @title Knockout Counter * @notice Manages the knockout liquidity pivots and positions. Responsible for minting * burning, knocking out, and claiming knockout liquidity, and adjusting bump * points in LevelBook accordingly. *Not* responsible for managing liquidity on * the curve or debiting/creditiing collateral. Knockout liquidity positions * should be separately managed from ordinary liquidity, but knockout liquidity * should be aggregated with AMM/bump point liquidity. */ contract KnockoutCounter is LevelBook, PoolRegistry, AgentMask { using SafeCast for uint128; using LiquidityMath for uint128; using LiquidityMath for uint96; using LiquidityMath for uint64; using KnockoutLiq for KnockoutLiq.KnockoutMerkle; using KnockoutLiq for KnockoutLiq.KnockoutPivot; using KnockoutLiq for KnockoutLiq.KnockoutPosLoc; /* @notice Emitted at any point a pivot is knocked out. User can use the history * of these logs to reconstructo the Merkle history necessary to claim * their fees. */ event CrocKnockoutCross (bytes32 indexed pool, int24 indexed tick, bool isBid, uint32 pivotTime, uint64 feeMileage, uint160 commitEntropy); /* @notice Called when a given knockout pivot is crossed. Performs the book-keeping * related to reseting the pivot object and committing the Merkle history. * Does *not* adjust the liquidity on the bump point or curve, caller is * responsible for that upstream. * * @dev This function must only be called *after* the AMM curve has crossed the * tick and fee odometer on the tick has been updated to reflect the update. * * @param pool The hash index of the AMM pool. * @param isBid If true, indicates that it's a bid pivot being knocked out (i.e. * that price is moving down through the pivot) * @param tick The tick index of the knockout pivot. * @param feeMileage The in range fee mileage at the point the pivot was crossed. */ function crossKnockout (bytes32 pool, bool isBid, int24 tick, uint64 feeGlobal) internal { bytes32 lvlKey = KnockoutLiq.encodePivotKey(pool, isBid, tick); KnockoutLiq.KnockoutPivot storage pivot = knockoutPivots_[lvlKey]; KnockoutLiq.KnockoutMerkle storage merkle = knockoutMerkles_[lvlKey]; unmarkPivot(pool, isBid, tick); uint64 feeRange = knockoutRangeLiq(pool, pivot, isBid, tick, feeGlobal); merkle.commitKnockout(pivot, feeRange); emit CrocKnockoutCross(pool, tick, isBid, merkle.pivotTime_, merkle.feeMileage_, KnockoutLiq.commitEntropySalt()); pivot.deletePivot(); // Nice little SSTORE refund for the swapper } /* @notice Removes the liquidity at the AMM curve's bump points as part of a pivot * being knocked out by a level cross. */ function knockoutRangeLiq (bytes32 pool, KnockoutLiq.KnockoutPivot memory pivot, bool isBid, int24 tick, uint64 feeGlobal) private returns (uint64 feeRange) { // Unchecked because min/max tick are well within uint16 of int24 bounds unchecked { int24 offset = int24(uint24(pivot.rangeTicks_)); int24 priceTick = isBid ? tick-1 : tick; int24 lowerTick = isBid ? tick : tick - offset; int24 upperTick = !isBid ? tick : tick + offset; feeRange = removeBookLiq(pool, priceTick, lowerTick, upperTick, pivot.lots_, feeGlobal); } } /* @notice Mints a new knockout liquidity position (or adds liquidity to a pre- * existing position. * * @param pool The cursor for the pool knockout liquidity is being added to. * @param knockoutBits The current knockout parameter flags in the pool's settings. * @param curveTick The 24-bit tick index of the current curve price in the pool * @param feeGlobal The global cumulative concentrated liquidity fee mileage for * the curve at mint time. * @param loc The position on the curve the knockout liquidity is being added * to. (See comments for struct for full explanation of fields) * @param lots The amount of liquidity lots (in lots of 1024-units of * sqrt(X*Y) liquidity) being added to the knockout position. * * @return pivotTime The time tranche of the pivot the liquidity was added to. * @return newPivot If true indicates that this is the first active liquidity at the * pivot. */ function addKnockoutLiq (bytes32 pool, uint8 knockoutBits, int24 curveTick, uint64 feeGlobal, KnockoutLiq.KnockoutPosLoc memory loc, uint96 lots) internal returns (uint32 pivotTime, bool newPivot) { (pivotTime, newPivot) = injectPivot(pool, knockoutBits, loc, lots, curveTick); uint64 feeRange = addBookLiq(pool, curveTick, loc.lowerTick_, loc.upperTick_, lots, feeGlobal); if (newPivot) { markPivot(pool, loc); } insertPosition(pool, loc, lots, feeRange, pivotTime); } /* @notice Burns pre-exisitng knockout liquidity, but only if the liqudity is still * alive. (Knocked out positions should use claimKnockout() instead). * * @param pool The cursor for the pool knockout liquidity is being added to. * @param curveTick The 24-bit tick index of the current curve price in the pool * @param feeGlobal The global cumulative concentrated liquidity fee mileage for * the curve at mint time. * @param loc The position on the curve the knockout liquidity is being claimed * from. (See comments for struct for full explanation of fields) * to. (See comments for struct for full explanation of fields) * @param lots The amount of liquidity lots (in lots of 1024-units of * sqrt(X*Y) liquidity) being added to the knockout position. * * @return killsPivot If true indicates that removing this liquidity means the pivot * has no remaining liquidity. * @return pivotTime The tranche time of the underlying pivot the liquidity was * removed from. * @return rewards The concentrated liquidity rewards accumulated to the * position. */ function rmKnockoutLiq (bytes32 pool, int24 curveTick, uint64 feeGlobal, KnockoutLiq.KnockoutPosLoc memory loc, uint96 lots) internal returns (bool killsPivot, uint32 pivotTime, uint64 rewards) { (pivotTime, killsPivot) = recallPivot(pool, loc, lots); if (killsPivot) { unmarkPivot(pool, loc); } uint64 feeRange = removeBookLiq(pool, curveTick, loc.lowerTick_, loc.upperTick_, lots, feeGlobal); rewards = removePosition(pool, loc, lots, feeRange, pivotTime); } /* @notice Marks the tick level as containing a knockout pivot. * @dev This is done by switching on the least significant bit in the bump point. * Based on the spec of liquidity lots (see LiquidityMath.sol), this least * significant bit should *not* be treated as actual liquidity, but rather just * an unrelated flag indicating that the level has a corresponding active * knockout pivot. */ function markPivot (bytes32 pool, KnockoutLiq.KnockoutPosLoc memory loc) private { if (loc.isBid_) { BookLevel storage lvl = fetchLevel(pool, loc.lowerTick_); lvl.bidLots_ = lvl.bidLots_ | uint96(0x1); } else { BookLevel storage lvl = fetchLevel(pool, loc.upperTick_); lvl.askLots_ = lvl.askLots_ | uint96(0x1); } } /* @notice Removes the mark on the book level related to the presence of knockout * liquidity. */ function unmarkPivot (bytes32 pool, KnockoutLiq.KnockoutPosLoc memory loc) private { if (loc.isBid_) { unmarkPivot(pool, true, loc.lowerTick_); } else { unmarkPivot(pool, false, loc.upperTick_); } } /* @notice Removes the mark on the book level related to the presence of knockout * liquidity. */ function unmarkPivot (bytes32 pool, bool isBid, int24 tick) private { BookLevel storage lvl = fetchLevel(pool, tick); if (isBid) { lvl.bidLots_ = lvl.bidLots_ & ~uint96(0x1); } else { lvl.askLots_ = lvl.askLots_ & ~uint96(0x1); } } /* @notice Claims the collateral and rewards for a position that has been fully * knocked out. (I.e. is no longer active because knockout tick was crossed) * * @param pool The cursor for the pool knockout liquidity is being added to. * @param loc The position on the curve the knockout liquidity is being claimed * from. (See comments for struct for full explanation of fields) * @param merkleRoot The root of the Merkle proof to recover the accumulted fees. * @param merkleProof The user-supplied proof for the accumulated fees earned by * the knockout pivot. (Transaction will revert if proof is bad) * * @return lots The liquidity (in 1024-unit lots) claimable by the underlying * position. Note that this liquidity should be converted to * collateral at the knockout price *not* the current curve price). * @return rewards The in-range concentrated liquidity rewards earned by the position. */ function claimPostKnockout (bytes32 pool, KnockoutLiq.KnockoutPosLoc memory loc, uint160 merkleRoot, uint256[] memory merkleProof) internal returns (uint96 lots, uint64 rewards) { (uint32 pivotTime, uint64 feeSnap) = proveKnockout(pool, loc, merkleRoot, merkleProof); (lots, rewards) = claimPosition(pool, loc, feeSnap, pivotTime); } /* @notice Like claimKnockout(), but avoids the need for Merkle proof altogether. * This means the underlying collateral is recoverable, but user renounces * all claims to the accumulated rewards. * * @dev This might be used when the calldata cost of the Merkle proof exceeds * the value of the accumulated rewards. * * @param pool The cursor for the pool knockout liquidity is being added to. * @param loc The position on the curve the knockout liquidity is being claimed * from. (See comments for struct for full explanation of fields) * @param pivotTime The pivot trache the position was minted at. User-supplied value * must match the position's stored value. Used to verify that the * tranche is no longer active (otherwise use burnKnockout()) * @return lots The liquidity (in 1024-unit lots) claimable by the underlying * position. Note that this liquidity should be converted to * collateral at the knockout price *not* the current curve price).*/ function recoverPostKnockout (bytes32 pool, KnockoutLiq.KnockoutPosLoc memory loc, uint32 pivotTime) internal returns (uint96 lots) { confirmPivotDead(pool, loc, pivotTime); (lots, ) = claimPosition(pool, loc, 0, pivotTime); } /* @notice Inserts the tracking data for the individual position being minted. * @param pool The hash of the pool the liquidity applies to. * @param loc The context/location data of the knockout liquidity position. * @param lots The amount of liquidity minted to the position. * @param feeRange The cumulative fee mileage for the concentrated liquidity range * at current mint time. * @param pivotTime The time corresponding to the underlying pivot creation. */ function insertPosition (bytes32 pool, KnockoutLiq.KnockoutPosLoc memory loc, uint96 lots, uint64 feeRange, uint32 pivotTime) private { bytes32 posKey = loc.encodePosKey(pool, lockHolder_, pivotTime); KnockoutLiq.KnockoutPos storage pos = knockoutPos_[posKey]; uint64 mileage = feeRange.blendMileage(lots, pos.feeMileage_, pos.lots_); pos.lots_ += lots; pos.feeMileage_ = mileage; pos.timestamp_ = SafeCast.timeUint32(); } /* @notice Removes the tracking data for an individual knockout liquidity position. * @dev Should only be called when the underlying knockout pivot *is still active* * @param pool The hash of the pool the liquidity applies to. * @param loc The context/location data of the knockout liquidity position. * @param lots The amount of liquidity burned from the position. * @param feeRange The cumulative fee mileage for the concentrated liquidity range * at current mint time. * @param pivotTime The time corresponding to the underlying pivot creation. * @return feeRewards The accumulated fee rewards rate on the position. */ function removePosition (bytes32 pool, KnockoutLiq.KnockoutPosLoc memory loc, uint96 lots, uint64 feeRange, uint32 pivotTime) private returns (uint64 feeRewards) { bytes32 posKey = loc.encodePosKey(pool, lockHolder_, pivotTime); KnockoutLiq.KnockoutPos storage pos = knockoutPos_[posKey]; feeRewards = feeRange.deltaRewardsRate(pos.feeMileage_); assertJitSafe(pos.timestamp_, pool); require(lots <= pos.lots_, "KB"); if (lots == pos.lots_) { // Get SSTORE refund on full burn pos.lots_ = 0; pos.feeMileage_ = 0; pos.timestamp_ = 0; } else { pos.lots_ -= lots; } } /* @notice Removes the tracking data for an individual knockout liquidity position * that's being claimed post knockout. * @dev Should only be called *after* the underlying pivot is knocked out. * @param pool The hash of the pool the liquidity applies to. * @param loc The context/location data of the knockout liquidity position. * @param feeRange The cumulative fee mileage for the concentrated liquidity range * at current mint time. * @param pivotTime The time corresponding to the underlying pivot creation. * @return lots The amount of liquidity lots in the underlying position. * @return feeRewards The accumulated fee rewards rate on the position. */ function claimPosition (bytes32 pool, KnockoutLiq.KnockoutPosLoc memory loc, uint64 feeRange, uint32 pivotTime) private returns (uint96 lots, uint64 feeRewards) { bytes32 posKey = loc.encodePosKey(pool, lockHolder_, pivotTime); KnockoutLiq.KnockoutPos storage pos = knockoutPos_[posKey]; lots = pos.lots_; if (feeRange > 0) { feeRewards = feeRange - pos.feeMileage_; } // Get SSTORE refund on full burn pos.lots_ = 0; pos.feeMileage_ = 0; pos.timestamp_ = 0; } /* @notice Creates a new pivot or updates a previous pivot for newly minted knockout * liquidity. * @param pool The pool the knockout liquidity applies to. * @param loc The context/location of the newly minted knockout liquidity. * @param liq The amount of liquidity being minted to the position. * @param curveTick The tick index of the current price in the curve. * @return bookLiq The amount of liquidity that must be contributed to the range in * the book. This amount could possibly be different than liq, so * it's very important that this value is used to adjust the curve * and collect collateral. * @return pivotTime The time tranche of the pivot the liquidity is added to. Either * the current time if liquidity creates a new pivot, or the * timestamp of when the previous tranche was created. */ function injectPivot (bytes32 pool, uint8 knockoutBits, KnockoutLiq.KnockoutPosLoc memory loc, uint96 lots, int24 curveTick) private returns (uint32 pivotTime, bool newPivot) { bytes32 lvlKey = loc.encodePivotKey(pool); KnockoutLiq.KnockoutPivot storage pivot = knockoutPivots_[lvlKey]; newPivot = (pivot.lots_ == 0); // If mint represents the first position in a new pivot perorm book keeping // related to setting the time tranch, warming up the Merkle slot, and verifying // that the pivot position is valid relative to the pool's current parameters. if (newPivot) { pivotTime = SafeCast.timeUint32(); freshenMerkle(knockoutMerkles_[lvlKey]); loc.assertValidPos(curveTick, knockoutBits); // Should optimize to a single SSTORE call. pivot.lots_ = lots; pivot.pivotTime_ = pivotTime; pivot.rangeTicks_ = loc.tickRange(); } else { pivot.lots_ += lots; pivotTime = pivot.pivotTime_; require(pivot.rangeTicks_ == loc.tickRange(), "KR"); } } /* @notice Called to withdraw liquidity from an open knockout pivot. (If pivot was * already knocked out, do not use this function. * @param pool The pool the knockout liquidity applies to. * @param loc The context/location of the newly minted knockout liquidity. * @param liq The amount of liquidity being minted to the position. * @return bookLiq The amount of liquidity that shoudl be removed from the book. * This amount could possibly be different than liq, so it's very * important that this value is used to adjust the AMM curve. * @return pivotTime The tranche timestamp of the current knockout pivot. */ function recallPivot (bytes32 pool, KnockoutLiq.KnockoutPosLoc memory loc, uint96 lots) private returns (uint32 pivotTime, bool killsPivot) { bytes32 lvlKey = KnockoutLiq.encodePivotKey(pool, loc.isBid_, loc.knockoutTick()); KnockoutLiq.KnockoutPivot storage pivot = knockoutPivots_[lvlKey]; pivotTime = pivot.pivotTime_; require(lots <= pivot.lots_, "KB"); killsPivot = (lots == pivot.lots_); if (killsPivot) { // Get the SSTORE refund when completely burning the level pivot.lots_ = 0; pivot.pivotTime_ = 0; pivot.rangeTicks_ = 0; } else { pivot.lots_ -= lots; } } /* @notice Call on the corresponding Merkle root when creating a new pivot at a * tick/time tranche. */ function freshenMerkle (KnockoutLiq.KnockoutMerkle storage merkle) private { // Knockout tranches are uniquely identified by block times. There is a // rare corner case where multiple knockouts are created, crossed and // created again at the same tick all within the same block/time. require(merkle.pivotTime_ != SafeCast.timeUint32(), "KT"); // Warm up the slot so that the SSTORE fresh is paid by the LP, not // the swapper. This means all Merkle histories begin with a root of 1 if(merkle.merkleRoot_ == 0) { merkle.merkleRoot_ = 1; } } /* @notice Asserts that a given pivot tranche being claimed as knocked out, was * in fact knocked out. Used when the user doesn't have or doesn't want to * present a Merkle proof. * * @dev Relies on two guarantees. 1) base Merkle time is always increasing, * because pivots are created, and therefore knocked out, in monotonically * increasing time order. 2) Tranches will never be created at the same time- * stamp as the most recent Merkle commitment. Therefore a pivot tranche * has been knocked out if and only if the most recent Merkle commitment has * an equal of greater timestamp. */ function confirmPivotDead (bytes32 pool, KnockoutLiq.KnockoutPosLoc memory loc, uint32 pivotTime) private view { bytes32 lvlKey = KnockoutLiq.encodePivotKey(pool, loc.isBid_, loc.knockoutTick()); KnockoutLiq.KnockoutMerkle storage merkle = knockoutMerkles_[lvlKey]; require(merkle.pivotTime_ >= pivotTime, "KA"); } /* @notice Verifies the user-supplied Merkle proof. (See proveHistory() in * KnockoutLiq library). If proof is wrong, transaction will revert. * * @return pivotTime The pivot time from the verified proof. Caller is responsible * for making sure this matches the pivotTime in the position * being claimed. * @return feeSnap The in-range fee mileage at Merkle commitment time, i.e. when the * pivot was knocked out. */ function proveKnockout (bytes32 pool, KnockoutLiq.KnockoutPosLoc memory loc, uint160 root, uint256[] memory proof) private view returns (uint32 pivotTime, uint64 feeSnap) { bytes32 lvlKey = KnockoutLiq.encodePivotKey(pool, loc.isBid_, loc.knockoutTick()); KnockoutLiq.KnockoutMerkle storage merkle = knockoutMerkles_[lvlKey]; (pivotTime, feeSnap) = merkle.proveHistory(root, proof); } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; pragma experimental ABIEncoderV2; import '../libraries/LiquidityMath.sol'; import '../libraries/TickMath.sol'; import './TickCensus.sol'; import './StorageLayout.sol'; import 'hardhat/console.sol'; /* @title Level Book Mixin * @notice Mixin contract that tracks the aggregate liquidity bumps and in-range reward * accumulators on a per-tick basis. */ contract LevelBook is TickCensus { using SafeCast for uint128; using LiquidityMath for uint128; using LiquidityMath for uint96; /* Book level structure exists one-to-one on a tick basis (though could possibly be * zero-valued). For each tick we have to track three values: * bidLots_ - The change to concentrated liquidity that's added to the AMM curve when * price moves into the tick from below, and removed when price moves * into the tick from above. Denominated in lot-units which are 1024 multiples * of liquidity units. * askLots_ - The change to concentrated liquidity that's added to the AMM curve when * price moves into the tick from above, and removed when price moves * into the tick from below. Denominated in lot-units which are 1024 multiples * of liquidity units. * feeOdometer_ - The liquidity fee rewards accumulator that's checkpointed * whenever the price crosses the tick boundary. Used to calculate the * cumulative fee rewards on any arbitrary lower-upper tick range. This is * generically represented as a per-liquidity unit 128-bit fixed point * cumulative growth rate. */ /* @notice Called when the curve price moves through the tick boundary. Performs * the necessary accumulator checkpointing and deriving the liquidity bump. * * @dev Note that this function call is *not* idempotent. It's the callers * responsibility to only call once per tick cross direction. Otherwise * behavior is undefined. This is safe to call with non-initialized zero * ticks but should generally be avoided for gas efficiency reasons. * * @param poolIdx - The hash index of the pool being traded on. * @param tick - The 24-bit tick index being crossed. * @param isBuy - If true indicates that price is crossing the tick boundary from * below. If false, means tick is being crossed from above. * @param feeGlobal - The up-to-date global fee reward accumulator value. Used to * checkpoint the tick rewards for calculating accumulated rewards * in a range. Represented as 128-bit fixed point cumulative * growth rate per unit of liquidity. * * @return liqDelta - The net change in concentrated liquidity that should be applied * to the AMM curve following this level cross. * @return knockoutFlag - Indicates that the liquidity of the cross level has a * knockout flag toggled. Upstream caller should handle * appropriately */ function crossLevel (bytes32 poolIdx, int24 tick, bool isBuy, uint64 feeGlobal) internal returns (int128 liqDelta, bool knockoutFlag) { BookLevel storage lvl = fetchLevel(poolIdx, tick); int128 crossDelta = LiquidityMath.netLotsOnLiquidity (lvl.bidLots_, lvl.askLots_); liqDelta = isBuy ? crossDelta : -crossDelta; lvl.feeOdometer_ = feeGlobal - lvl.feeOdometer_; knockoutFlag = isBuy ? lvl.askLots_.hasKnockoutLiq() : lvl.bidLots_.hasKnockoutLiq(); } /* @notice Retrieves the level book state associated with the tick. */ function levelState (bytes32 poolIdx, int24 tick) internal view returns (BookLevel memory) { return levels_[keccak256(abi.encodePacked(poolIdx, tick))]; } /* @notice Retrieves a storage pointer to the level associated with the tick. */ function fetchLevel (bytes32 poolIdx, int24 tick) internal view returns (BookLevel storage) { return levels_[keccak256(abi.encodePacked(poolIdx, tick))]; } /* @notice Deletes the level at the tick. */ function deleteLevel (bytes32 poolIdx, int24 tick) private { delete levels_[keccak256(abi.encodePacked(poolIdx, tick))]; } /* @notice Adds the liquidity associated with a new range order into the associated * book levels, initializing the level structs if necessary. * * @param poolIdx - The index of the pool the liquidity is being added to. * @param midTick - The tick index associated with the current price of the AMM curve * @param bidTick - The tick index for the lower bound of the range order. * @param askTick - The tick index for the upper bound of the range order. * @param lots - The amount of liquidity (in 1024 unit lots) being added by the range order. * @param feeGlobal - The up-to-date global fee rewards growth accumulator. * Represented as 128-bit fixed point growth rate. * * @return feeOdometer - Returns the current fee reward accumulator value for the * range specified by the order. This is necessary, so we consumers of this mixin * can subtract the rewards accumulated before the order was added. */ function addBookLiq (bytes32 poolIdx, int24 midTick, int24 bidTick, int24 askTick, uint96 lots, uint64 feeGlobal) internal returns (uint64 feeOdometer) { // Make sure to init before add, because init logic relies on pre-add liquidity initLevel(poolIdx, midTick, bidTick, feeGlobal); initLevel(poolIdx, midTick, askTick, feeGlobal); addBid(poolIdx, bidTick, lots); addAsk(poolIdx, askTick, lots); feeOdometer = clockFeeOdometer(poolIdx, midTick, bidTick, askTick, feeGlobal); } /* @dev Near identical to addBookLiq() above but uses higher precision 72-bit * range positions and fee odometer values. */ function addBookLiq72 (bytes32 poolIdx, int24 midTick, int24 bidTick, int24 askTick, uint96 lots, uint64 feeGlobal) internal returns (uint72 feeOdometer) { // Make sure to init before add, because init logic relies on pre-add liquidity initLevel(poolIdx, midTick, bidTick, feeGlobal); initLevel(poolIdx, midTick, askTick, feeGlobal); addBid(poolIdx, bidTick, lots); addAsk(poolIdx, askTick, lots); feeOdometer = clockFeeOdometer72(poolIdx, midTick, bidTick, askTick, feeGlobal); } /* @notice Call when removing liquidity associated with a specific range order. * Decrements the associated tick levels as necessary. * * @param poolIdx - The index of the pool the liquidity is being removed from. * @param midTick - The tick index associated with the current price of the AMM curve * @param bidTick - The tick index for the lower bound of the range order. * @param askTick - The tick index for the upper bound of the range order. * @param liq - The amount of liquidity being added by the range order. * @param feeGlobal - The up-to-date global fee rewards growth accumulator. * Represented as 128-bit fixed point growth rate. * * @return feeOdometer - Returns the current fee reward accumulator value for the * range specified by the order. Note that this returns the accumulated rewards * from the range history, including *before* the order was added. It's the * downstream user's responsibility to adjust this value with the odometer clock * from addBookLiq to correctly calculate the rewards accumulated over the * lifetime of the order. */ function removeBookLiq (bytes32 poolIdx, int24 midTick, int24 bidTick, int24 askTick, uint96 lots, uint64 feeGlobal) internal returns (uint64 feeOdometer) { bool deleteBid = removeBid(poolIdx, bidTick, lots); bool deleteAsk = removeAsk(poolIdx, askTick, lots); feeOdometer = clockFeeOdometer(poolIdx, midTick, bidTick, askTick, feeGlobal); if (deleteBid) { deleteLevel(poolIdx, bidTick); } if (deleteAsk) { deleteLevel(poolIdx, askTick); } } /* @dev Near identical to removeBookLiq() above but uses higher precision 72-bit * range positions and fee odometer values. */ function removeBookLiq72 (bytes32 poolIdx, int24 midTick, int24 bidTick, int24 askTick, uint96 lots, uint64 feeGlobal) internal returns (uint72 feeOdometer) { bool deleteBid = removeBid(poolIdx, bidTick, lots); bool deleteAsk = removeAsk(poolIdx, askTick, lots); feeOdometer = clockFeeOdometer72(poolIdx, midTick, bidTick, askTick, feeGlobal); if (deleteBid) { deleteLevel(poolIdx, bidTick); } if (deleteAsk) { deleteLevel(poolIdx, askTick); } } /* @notice Initializes a new level, including marking the tick as active in the * bitmap, if the level doesn't previously exist. */ function initLevel (bytes32 poolIdx, int24 midTick, int24 tick, uint64 feeGlobal) private { BookLevel storage lvl = fetchLevel(poolIdx, tick); if (lvl.bidLots_ == 0 && lvl.askLots_ == 0) { if (tick >= midTick) { lvl.feeOdometer_ = feeGlobal; } bookmarkTick(poolIdx, tick); } } /* @notice Increments bid liquidity on a previously existing level. */ function addBid (bytes32 poolIdx, int24 tick, uint96 incrLots) private { BookLevel storage lvl = fetchLevel(poolIdx, tick); uint96 prevLiq = lvl.bidLots_; uint96 newLiq = prevLiq.addLots(incrLots); lvl.bidLots_ = newLiq; } /* @notice Increments ask liquidity on a previously existing level. */ function addAsk (bytes32 poolIdx, int24 tick, uint96 incrLots) private { BookLevel storage lvl = fetchLevel(poolIdx, tick); uint96 prevLiq = lvl.askLots_; uint96 newLiq = prevLiq.addLots(incrLots); lvl.askLots_ = newLiq; } /* @notice Decrements bid liquidity on a level, and also removes the level from * the tick bitmap if necessary. */ function removeBid (bytes32 poolIdx, int24 tick, uint96 subLots) private returns (bool) { BookLevel storage lvl = fetchLevel(poolIdx, tick); uint96 prevLiq = lvl.bidLots_; uint96 newLiq = prevLiq.minusLots(subLots); // A level should only be marked inactive in the tick bitmap if *both* bid and // ask liquidity are zero. lvl.bidLots_ = newLiq; if (newLiq == 0 && lvl.askLots_ == 0) { forgetTick(poolIdx, tick); return true; } return false; } /* @notice Decrements ask liquidity on a level, and also removes the level from * the tick bitmap if necessary. */ function removeAsk (bytes32 poolIdx, int24 tick, uint96 subLots) private returns (bool) { BookLevel storage lvl = fetchLevel(poolIdx, tick); uint96 prevLiq = lvl.askLots_; uint96 newLiq = prevLiq.minusLots(subLots); lvl.askLots_ = newLiq; if (newLiq == 0 && lvl.bidLots_ == 0) { forgetTick(poolIdx, tick); return true; } return false; } /* @notice Calculates the current accumulated fee rewards in a given concentrated * liquidity tick range. The difference between this value at two different * times is guaranteed to reflect the accumulated rewards in the tick range * between those two times. * * For more explanation on how the fee rewards accumulated is calculated for * a given range order, reference the documenation at [docs/FeeOdometer.md] * in the project repository. * * @dev This returned result only has meaning when compared against the result * from the same method call on the same range at a different time. Any * given range could have an arbitrary offset relative to the pool's actual * cumulative rewards. * * @param poolIdx The hash key specifying the pool being operated on. * @param currentTick The price tick of the curve's current price * @param lowerTick The prick tick of the lower boundary of the range order * @param upperTick The prick tick of the upper boundary of the range order * @param feeGlobal The cumulative rewards accumulated to a single unit of * concentrated liquidity that was active since pool inception. * * @return The cumulative growth rate to a single unit of concentrated liquidity * within the range. (Adjusted for an arbitrary offset that stays consistent * over time. Only use this number to compare growth in the range over two * points in time) */ function clockFeeOdometer (bytes32 poolIdx, int24 currentTick, int24 lowerTick, int24 upperTick, uint64 feeGlobal) internal view returns (uint64) { uint64 feeLower = pivotFeeBelow(poolIdx, lowerTick, currentTick, feeGlobal); uint64 feeUpper = pivotFeeBelow(poolIdx, upperTick, currentTick, feeGlobal); // This is unchecked because we often rely on circular overflow arithmetic // when ticks are initialized at different times. Remember the output of this // function is only used to compare across time. unchecked { return feeUpper - feeLower; } } /* @notice Snapshots a value cumulative fee accumulation in a tick range to be * used when calculating the growth of a position over time. See description * in clockFeeOdomter() for more details. * * @dev Unlike clockFeeOdomter() the returned snapshot is a 72-bit representation. * Each snapshot adds a uint64 max offset to avoid the possibility of underflow. * Because the offset is included on every calculation, it will always cancel out * in the rewards delta calculation. */ function clockFeeOdometer72 (bytes32 poolIdx, int24 currentTick, int24 lowerTick, int24 upperTick, uint64 feeGlobal) internal view returns (uint72) { uint72 feeLower = pivotFeeBelow(poolIdx, lowerTick, currentTick, feeGlobal); uint72 feeUpper = pivotFeeBelow(poolIdx, upperTick, currentTick, feeGlobal); // Set to be large enough, so the delta between the uint64 values from pivotFeeBelow() // will never underflow the snapshot. uint72 fixedOffset = type(uint64).max; return (fixedOffset + feeUpper) - feeLower; } /* @dev Internally we checkpoint the last global accumulator value from the last * time the level was crossed. Because fees can only accumulate when price * is in range, the checkpoint represents the global fees that accumulated * on the outside of the tick level. (Though this may be faked for fees that * that accumulated prior to level initialization. It doesn't matter, because * all we use this value for is calculating the delta of fee accumulation * between two different post-initialization points in time.) * * For more explanation on how the per-tick fee odometer related to the * cumulative fees in a give range, reference the documenation at * [docs/FeeOdometer.md] in the project repository. */ function pivotFeeBelow (bytes32 poolIdx, int24 lvlTick, int24 currentTick, uint64 feeGlobal) private view returns (uint64) { BookLevel storage lvl = fetchLevel(poolIdx, lvlTick); return lvlTick <= currentTick ? lvl.feeOdometer_ : feeGlobal - lvl.feeOdometer_; } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; pragma experimental ABIEncoderV2; import '../libraries/TickMath.sol'; import '../libraries/FixedPoint.sol'; import '../libraries/LiquidityMath.sol'; import '../libraries/SafeCast.sol'; import '../libraries/PoolSpecs.sol'; import '../libraries/CurveMath.sol'; import '../libraries/CurveCache.sol'; import './StorageLayout.sol'; /* @title Liquidity Curve Mixin * @notice Tracks the state of the locally stable constant product AMM liquid curve * for the pool. Applies any adjustment to the curve as needed, either from * new or removed positions or pre-determined liquidity bumps that occur * when crossing tick boundaries. */ contract LiquidityCurve is StorageLayout { using SafeCast for uint128; using SafeCast for uint192; using SafeCast for uint144; using LiquidityMath for uint128; using CurveMath for uint128; using CurveMath for CurveMath.CurveState; /* @notice Copies the current state of the curve in EVM storage to a memory clone. * @dev Use for light-weight gas ergonomics when iterarively operating on the * curve. But it's the callers responsibility to persist the changes back * to storage when complete. */ function snapCurve (bytes32 poolIdx) view internal returns (CurveMath.CurveState memory curve) { curve = curves_[poolIdx]; require(curve.priceRoot_ > 0); } /* @notice Snapshots the curve for pool initialization operation. * @dev This only skips the initialization check from snapCurve() does *not* assert * that the curve was not previously initialized. That's the caller's * responsibility */ function snapCurveInit (bytes32 poolIdx) view internal returns (CurveMath.CurveState memory) { return curves_[poolIdx]; } /* @notice Snapshots the curve to memory, but verifies that the price occurs within * a pre-specified price range. If not, reverts the entire transaction. */ function snapCurveInRange (bytes32 poolIdx, uint128 minPrice, uint128 maxPrice) view internal returns (CurveMath.CurveState memory curve) { curve = snapCurve(poolIdx); require(curve.priceRoot_ >= minPrice && curve.priceRoot_ <= maxPrice, "RC"); } /* @notice Writes a CurveState modified in memory back into persistent storage. * Use for the working copy from snapCurve when finalized. */ function commitCurve (bytes32 poolIdx, CurveMath.CurveState memory curve) internal { curves_[poolIdx] = curve; } /* @notice Called whenever a user adds a fixed amount of concentrated liquidity * to the curve. This must be called regardless of whether the liquidity is * in-range at the current curve price or not. * @dev After being called this will alter the curve to reflect the new liquidity, * but it's the callers responsibility to make sure that the required * collateral is actually collected. * * @param curve The liquidity curve object that range liquidity will be added to. * @param liquidity The amount of liquidity being added. Represented in the form of * sqrt(X*Y) where X,Y are the virtual reserves of the tokens in a * constant product AMM. Calculate the same whether in-range or not. * @param lowerTick The tick index corresponding to the bottom of the concentrated * liquidity range. * @param upperTick The tick index corresponding to the bottom of the concentrated * liquidity range. * * @return base - The amount of base token collateral that must be collected * following the addition of this liquidity. * @return quote - The amount of quote token collateral that must be collected * following the addition of this liquidity. */ function liquidityReceivable (CurveMath.CurveState memory curve, uint128 liquidity, int24 lowerTick, int24 upperTick) internal pure returns (uint128, uint128) { (uint128 base, uint128 quote, bool inRange) = liquidityFlows(curve.priceRoot_, liquidity, lowerTick, upperTick); bumpConcentrated(curve, liquidity, inRange); return chargeConservative(base, quote, inRange); } /* @notice Equivalent to above, but used when adding non-range bound constant * product ambient liquidity. * @dev Like above, it's the caller's responsibility to collect the necessary * collateral to add to the pool. * * @param curve The liquidity curve object that ambient liquidity will be added to. * @param seeds The number of ambient seeds being added. Note that this is * denominated as seeds *not* liquidity. The amount of liquidity * contributed will be based on the current seed->liquidity conversion * rate on the curve. (See CurveMath.sol.) * @return The base and quote token flows from the user required to add this amount * of liquidity to the curve. */ function liquidityReceivable (CurveMath.CurveState memory curve, uint128 seeds) internal pure returns (uint128, uint128) { (uint128 base, uint128 quote) = liquidityFlows(curve, seeds); bumpAmbient(curve, seeds); return chargeConservative(base, quote, true); } /* @notice Called when liquidity is being removed from the pool Adjusts the curve * accordingly and calculates the amount of collateral payable to the user. * This must be called for all removes regardless of whether the liquidity * is in range or not. * @dev It's the caller's responsibility to actually return the collateral to the * user. This method will only calculate what's owed, but won't actually pay it. * * * @param curve The liquidity curve object that concentrated liquidity will be * removed from. * @param liquidity The amount of liquidity being removed, whether in-range or not. * Represented in the form of sqrt(X*Y) where x,Y are the virtual * reserves of a constant product AMM. * @param rewardRate The total cumulative earned but unclaimed rewards on the staked * liquidity. Used to increment the payout with the rewards, and * burn the ambient liquidity tied to the rewards. (See * CurveMath.sol for more.) Represented as a 128-bit fixed point * cumulative growth rate of ambient seeds per unit of liquidity. * @param lowerTick The tick index corresponding to the bottom of the concentrated * liquidity range. * @param upperTick The tick index corresponding to the bottom of the concentrated * liquidity range. * * @return base - The amount of base token collateral that can be paid out following * the removal of the liquidity. Always rounded down to favor * collateral stability. * @return quote - The amount of base token collateral that can be paid out following * the removal of the liquidity. Always rounded down to favor * collateral stability. */ function liquidityPayable (CurveMath.CurveState memory curve, uint128 liquidity, uint64 rewardRate, int24 lowerTick, int24 upperTick) internal pure returns (uint128 base, uint128 quote) { (base, quote) = liquidityPayable(curve, liquidity, lowerTick, upperTick); (base, quote) = stackRewards(base, quote, curve, liquidity, rewardRate); } function stackRewards (uint128 base, uint128 quote, CurveMath.CurveState memory curve, uint128 liquidity, uint64 rewardRate) internal pure returns (uint128, uint128) { if (rewardRate > 0 && rewardRate <= curve.concGrowth_) { // Round down reward sees on payout, in contrast to rounding them up on // incremental accumulation (see CurveAssimilate.sol). This mathematicaly // guarantees that we never try to burn more tokens than exist on the curve. uint128 rewards = FixedPoint.mulQ48(liquidity, rewardRate).toUint128By144(); if (rewards > 0 && rewards < curve.ambientSeeds_) { (uint128 baseRewards, uint128 quoteRewards) = liquidityPayable(curve, rewards); base += baseRewards; quote += quoteRewards; } } return (base, quote); } /* @notice The same as the above liquidityPayable() but called when accumulated * rewards are zero. */ function liquidityPayable (CurveMath.CurveState memory curve, uint128 liquidity, int24 lowerTick, int24 upperTick) internal pure returns (uint128 base, uint128 quote) { bool inRange; (base, quote, inRange) = liquidityFlows(curve.priceRoot_, liquidity, lowerTick, upperTick); bumpConcentrated(curve, -(liquidity.toInt128Sign()), inRange); } /* @notice Same as above liquidityPayable() but used for non-range based ambient * constant product liquidity. * * @param curve The liquidity curve object that ambient liquidity will be * removed from. * @param seeds The number of ambient seeds being added. Note that this is * denominated as seeds *not* liquidity. The amount of liquidity * contributed will be based on the current seed->liquidity conversion * rate on the curve. (See CurveMath.sol.) * @return base - The amount of base token collateral that can be paid out following * the removal of the liquidity. Always rounded down to favor * collateral stability. * @return quote - The amount of base token collateral that can be paid out following * the removal of the liquidity. Always rounded down to favor * collateral stability. */ function liquidityPayable (CurveMath.CurveState memory curve, uint128 seeds) internal pure returns (uint128 base, uint128 quote) { (base, quote) = liquidityFlows(curve, seeds); bumpAmbient(curve, -(seeds.toInt128Sign())); } function liquidityHeldPayable (CurveMath.CurveState memory curve, uint128 liquidity, uint64 rewards, KnockoutLiq.KnockoutPosLoc memory loc) internal pure returns (uint128 base, uint128 quote) { (base, quote) = liquidityHeldPayable(liquidity, loc); (base, quote) = stackRewards(base, quote, curve, liquidity, rewards); } function liquidityHeldPayable (uint128 liquidity, KnockoutLiq.KnockoutPosLoc memory loc) internal pure returns (uint128 base, uint128 quote) { (uint128 bidPrice, uint128 askPrice) = translateTickRange (loc.lowerTick_, loc.upperTick_); if (loc.isBid_) { quote = liquidity.deltaQuote(bidPrice, askPrice); } else { base = liquidity.deltaBase(bidPrice, askPrice); } } /* @notice Directly increments the ambient liquidity on the curve. */ function bumpAmbient (CurveMath.CurveState memory curve, uint128 seedDelta) private pure { bumpAmbient(curve, seedDelta.toInt128Sign()); } /* @notice Directly increments the ambient liquidity on the curve. */ function bumpAmbient (CurveMath.CurveState memory curve, int128 seedDelta) private pure { curve.ambientSeeds_ = curve.ambientSeeds_.addDelta(seedDelta); } /* @notice Directly increments the concentrated liquidity on the curve, depending * on whether it's in range. */ function bumpConcentrated (CurveMath.CurveState memory curve, uint128 liqDelta, bool inRange) private pure { bumpConcentrated(curve, liqDelta.toInt128Sign(), inRange); } /* @notice Directly increments the concentrated liquidity on the curve, depending * on whether it's in range. */ function bumpConcentrated (CurveMath.CurveState memory curve, int128 liqDelta, bool inRange) private pure { if (inRange) { curve.concLiq_ = curve.concLiq_.addDelta(liqDelta); } } /* @notice Calculates the liquidity flows associated with the concentrated liquidity * from a range order. * @dev Uses fixed-point math that rounds down up to 2 wei from the true real valued * flows. Safe to pay this flow, but when pool is receiving caller must make sure * to round up for collateral safety. */ function liquidityFlows (uint128 price, uint128 liquidity, int24 bidTick, int24 askTick) private pure returns (uint128 baseDebit, uint128 quoteDebit, bool inRange) { (uint128 bidPrice, uint128 askPrice) = translateTickRange(bidTick, askTick); if (price < bidPrice) { quoteDebit = liquidity.deltaQuote(bidPrice, askPrice); } else if (price >= askPrice) { baseDebit = liquidity.deltaBase(bidPrice, askPrice); } else { quoteDebit = liquidity.deltaQuote(price, askPrice); baseDebit = liquidity.deltaBase(bidPrice, price); inRange = true; } } /* @notice Calculates the liquidity flows associated with the concentrated liquidity * from a range order. * @dev Uses fixed-point math that rounds down at each division. Because there are * divisions, max precision loss is under 2 wei. Safe to pay this flow, but when * when pool is receiving, caller must make sure to round up for collateral * safety. */ function liquidityFlows (CurveMath.CurveState memory curve, uint128 seeds) private pure returns (uint128 baseDebit, uint128 quoteDebit) { uint128 liq = CompoundMath.inflateLiqSeed(seeds, curve.seedDeflator_); baseDebit = FixedPoint.mulQ64(liq, curve.priceRoot_).toUint128By192(); quoteDebit = FixedPoint.divQ64(liq, curve.priceRoot_).toUint128By192(); } /* @notice Called exactly once at the initializing of the pool. Initializes the * liquidity curve at an arbitrary price. * @dev Throws error if price was already initialized. * * @param curve The liquidity curve for the pool being initialized. * @param priceRoot - Square root of the price. Represented as Q64.64 fixed point. */ function initPrice (CurveMath.CurveState memory curve, uint128 priceRoot) internal pure { int24 tick = TickMath.getTickAtSqrtRatio(priceRoot); require(tick >= TickMath.MIN_TICK && tick <= TickMath.MAX_TICK, "R"); require(curve.priceRoot_ == 0, "N"); curve.priceRoot_ = priceRoot; } /* @notice Converts a price tick index range into a range of prices. */ function translateTickRange (int24 lowerTick, int24 upperTick) private pure returns (uint128 bidPrice, uint128 askPrice) { require(upperTick > lowerTick); require(lowerTick >= TickMath.MIN_TICK); require(upperTick <= TickMath.MAX_TICK); bidPrice = TickMath.getSqrtRatioAtTick(lowerTick); askPrice = TickMath.getSqrtRatioAtTick(upperTick); } // Need to support at least 2 wei of precision round down when calculating quote // token reserve deltas. (See CurveMath's deltaPriceQuote() function.) 4 gives us a // safe cushion and is economically meaningless. uint8 constant TOKEN_ROUND = 4; /* @notice Rounds liquidity flows up in cases where we want to be conservative with * collateral. */ function chargeConservative (uint128 liqBase, uint128 liqQuote, bool inRange) private pure returns (uint128, uint128) { return ((liqBase > 0 || inRange) ? liqBase + TOKEN_ROUND : 0, (liqQuote > 0 || inRange) ? liqQuote + TOKEN_ROUND : 0); } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; import '../libraries/Directives.sol'; import '../libraries/PoolSpecs.sol'; import '../libraries/PriceGrid.sol'; import '../libraries/SwapCurve.sol'; import '../libraries/CurveMath.sol'; import '../libraries/CurveRoll.sol'; import '../libraries/CurveCache.sol'; import '../libraries/Chaining.sol'; import './PositionRegistrar.sol'; import './LiquidityCurve.sol'; import './LevelBook.sol'; import './TradeMatcher.sol'; /* @title Market sequencer. * @notice Mixin class that's responsibile for coordinating one or multiple sequetial * trade actions within a single liqudity pool. */ contract MarketSequencer is TradeMatcher { using SafeCast for int256; using SafeCast for int128; using SafeCast for uint256; using SafeCast for uint128; using TickMath for uint128; using PoolSpecs for PoolSpecs.Pool; using SwapCurve for CurveMath.CurveState; using CurveRoll for CurveMath.CurveState; using CurveMath for CurveMath.CurveState; using CurveCache for CurveCache.Cache; using Directives for Directives.ConcentratedDirective; using PriceGrid for PriceGrid.ImproveSettings; using Chaining for Chaining.PairFlow; using Chaining for Chaining.RollTarget; /* @notice Performs a sequence of an arbitrary potential combination of mints, * burns, and swaps on a single pool. * * @param flow Output accumulator, into which we'll net and and add the token flows * associated with the trade actions in this call. * @param dir A directive specifying an arbitrary sequences of action. * @param cntx Provides the execution context for the operation, including the pool * to execute on and it's pre-loaded specs, off-grid price improvement * settings, and parameters for rolling gap-failled quantities if they * appear in the directive. */ function tradeOverPool (Chaining.PairFlow memory flow, Directives.PoolDirective memory dir, Chaining.ExecCntx memory cntx) internal { // To avoid repeatedly loading and storing the curve on each operation, we load // it once into memory... CurveCache.Cache memory curve; curve.curve_ = snapCurve(cntx.pool_.hash_); applyToCurve(flow, dir, curve, cntx); /// ...Then check it back into storage when complete commitCurve(cntx.pool_.hash_, curve.curve_); } /* @notice Performs a single swap over the pool. * @param dir The user-specified directive governing the size, direction and limit * price of the swap to be performed. * @param pool The pre-loaded speciication and hash of the pool to be swapped against. * @return flow The net token flows generated by the swap. */ function swapOverPool (Directives.SwapDirective memory dir, PoolSpecs.PoolCursor memory pool) internal returns (Chaining.PairFlow memory flow) { CurveMath.CurveState memory curve = snapCurve(pool.hash_); sweepSwapLiq(flow, curve, curve.priceRoot_.getTickAtSqrtRatio(), dir, pool); commitCurve(pool.hash_, curve); } /* @notice Mints concentrated liquidity in the form of a range order on to the pool. * * @param bidTick The price tick associated with the lower boundary of the range * order. * @param askTick The price tick associated with the upper boundary of the range * order. * @param liq The amount of liquidity being minted represented as the equivalent to * sqrt(X*Y) in a constant product AMM pool. * @param pool The pre-loaded speciication and hash of the pool to be swapped against. * @param minPrice The minimum acceptable curve price to mint liquidity. If curve * price falls outside this point, the transaction is reverted. * @param maxPrice The maximum acceptable curve price to mint liquidity. If curve * price falls outside this point, the transaction is reverted. * @param lpConduit The address of the ICrocLpConduit that the liquidity will be * assigned to (0 for user owned liquidity). * * @return baseFlow The total amount of base-side token collateral that must be * committed to the pool as part of the mint. Will always be * positive as it's paid to the pool from the user. * @return quoteFlow The total amount of quote-side token collateral that must be * committed to the pool as part of the mint. */ function mintOverPool (int24 bidTick, int24 askTick, uint128 liq, PoolSpecs.PoolCursor memory pool, uint128 minPrice, uint128 maxPrice, address lpConduit) internal returns (int128 baseFlow, int128 quoteFlow) { CurveMath.CurveState memory curve = snapCurveInRange (pool.hash_, minPrice, maxPrice); (baseFlow, quoteFlow) = mintRange(curve, curve.priceRoot_.getTickAtSqrtRatio(), bidTick, askTick, liq, pool.hash_, lpConduit); PriceGrid.verifyFit(bidTick, askTick, pool.head_.tickSize_); commitCurve(pool.hash_, curve); } /* @notice Burns concentrated liquidity in the form of a range order on to the pool. * * @param bidTick The price tick associated with the lower boundary of the range * order. * @param askTick The price tick associated with the upper boundary of the range * order. * @param liq The amount of liquidity to burn represented as the equivalent to * sqrt(X*Y) in a constant product AMM pool. * @param pool The pre-loaded speciication and hash of the pool to be swapped against. * @param minPrice The minimum acceptable curve price to mint liquidity. If curve * price falls outside this point, the transaction is reverted. * @param maxPrice The maximum acceptable curve price to mint liquidity. If curve * price falls outside this point, the transaction is reverted. * * @return baseFlow The total amount of base-side token collateral that is returned * from the pool as part of the burn. Will always be * negative as it's paid from the pool to the user. * @return quoteFlow The total amount of quote-side token collateral that is returned * from the pool as part of the burn. */ function burnOverPool (int24 bidTick, int24 askTick, uint128 liq, PoolSpecs.PoolCursor memory pool, uint128 minPrice, uint128 maxPrice, address lpConduit) internal returns (int128 baseFlow, int128 quoteFlow) { CurveMath.CurveState memory curve = snapCurveInRange (pool.hash_, minPrice, maxPrice); (baseFlow, quoteFlow) = burnRange(curve, curve.priceRoot_.getTickAtSqrtRatio(), bidTick, askTick, liq, pool.hash_, lpConduit); commitCurve(pool.hash_, curve); } /* @notice Harvests rewards from a concentrated liquidity position. * * @param bidTick The price tick associated with the lower boundary of the range * order. * @param askTick The price tick associated with the upper boundary of the range * order. * @param pool The pre-loaded speciication and hash of the pool to be swapped against. * @param minPrice The minimum acceptable curve price to mint liquidity. If curve * price falls outside this point, the transaction is reverted. * @param maxPrice The maximum acceptable curve price to mint liquidity. If curve * price falls outside this point, the transaction is reverted. * * @return baseFlow The total amount of base-side token collateral that is returned * from the pool as part of the burn. Will always be * negative as it's paid from the pool to the user. * @return quoteFlow The total amount of quote-side token collateral that is returned * from the pool as part of the burn. */ function harvestOverPool (int24 bidTick, int24 askTick, PoolSpecs.PoolCursor memory pool, uint128 minPrice, uint128 maxPrice, address lpConduit) internal returns (int128 baseFlow, int128 quoteFlow) { CurveMath.CurveState memory curve = snapCurveInRange (pool.hash_, minPrice, maxPrice); (baseFlow, quoteFlow) = harvestRange(curve, curve.priceRoot_.getTickAtSqrtRatio(), bidTick, askTick, pool.hash_, lpConduit); commitCurve(pool.hash_, curve); } /* @notice Mints ambient liquidity on to the pool's curve. * * @param liq The amount of liquidity being minted represented as the equivalent to * sqrt(X*Y) in a constant product AMM pool. * @param pool The pre-loaded speciication and hash of the pool to be swapped against. * @param minPrice The minimum acceptable curve price to mint liquidity. If curve * price falls outside this point, the transaction is reverted. * @param maxPrice The maximum acceptable curve price to mint liquidity. If curve * price falls outside this point, the transaction is reverted. * @param lpConduit The address of the ICrocLpConduit that the liquidity will be * assigned to (0 for user owned liquidity). * * @return baseFlow The total amount of base-side token collateral that must be * committed to the pool as part of the mint. Will always be * positive as it's paid to the pool from the user. * @return quoteFlow The total amount of quote-side token collateral that must be * committed to the pool as part of the mint. */ function mintOverPool (uint128 liq, PoolSpecs.PoolCursor memory pool, uint128 minPrice, uint128 maxPrice, address lpConduit) internal returns (int128 baseFlow, int128 quoteFlow) { CurveMath.CurveState memory curve = snapCurveInRange (pool.hash_, minPrice, maxPrice); (baseFlow, quoteFlow) = mintAmbient(curve, liq, pool.hash_, lpConduit); commitCurve(pool.hash_, curve); } /* @notice Burns ambient liquidity on to the pool's curve. * * @param liq The amount of liquidity to burn represented as the equivalent to * sqrt(X*Y) in a constant product AMM pool. * @param pool The pre-loaded speciication and hash of the pool to be swapped against. * @param minPrice The minimum acceptable curve price to mint liquidity. If curve * price falls outside this point, the transaction is reverted. * @param maxPrice The maximum acceptable curve price to mint liquidity. If curve * price falls outside this point, the transaction is reverted. * * @return baseFlow The total amount of base-side token collateral that is returned * from the pool as part of the burn. Will always be negative * as it's paid from the pool to the user. * @return quoteFlow The total amount of quote-side token collateral that is returned * from the pool as part of the burn. */ function burnOverPool (uint128 liq, PoolSpecs.PoolCursor memory pool, uint128 minPrice, uint128 maxPrice, address lpConduit) internal returns (int128 baseFlow, int128 quoteFlow) { CurveMath.CurveState memory curve = snapCurveInRange (pool.hash_, minPrice, maxPrice); (baseFlow, quoteFlow) = burnAmbient(curve, liq, pool.hash_, lpConduit); commitCurve(pool.hash_, curve); } /* @notice Initializes a new liquidity curve for the pool. * @dev This does *not* check whether the curve was previously initialized. It's * the caller's responsibility to make sure this is never called on an already * initialized pool. * * @param pool The pre-loaded speciication and hash of the pool to be swapped against. * @param price The initial price to set the curve at. Represented as the square root * of price in Q64.64 fixed point. * @param initLiq The initial ambient liquidity commitment that will be permanetely * locked in the pool. Represeted as sqrt(X*Y) constant-product AMM * liquidity. * * @return baseFlow The total amount of base-side token collateral that must be * committed to the pool as part of the mint. Will always be * positive as it's paid to the pool from the user. * @return quoteFlow The total amount of quote-side token collateral that must be * committed to the pool as part of the mint. */ function initCurve (PoolSpecs.PoolCursor memory pool, uint128 price, uint128 initLiq) internal returns (int128 baseFlow, int128 quoteFlow) { CurveMath.CurveState memory curve = snapCurveInit(pool.hash_); initPrice(curve, price); if (initLiq == 0) { initLiq = 1; } (baseFlow, quoteFlow) = lockAmbient(curve, initLiq); commitCurve(pool.hash_, curve); } /* @notice Appplies the pool directive on to a pre-loaded liquidity curve. */ function applyToCurve (Chaining.PairFlow memory flow, Directives.PoolDirective memory dir, CurveCache.Cache memory curve, Chaining.ExecCntx memory cntx) private { if (!dir.chain_.swapDefer_) { applySwap(flow, dir.swap_, curve, cntx); } applyAmbient(flow, dir.ambient_, curve, cntx); applyConcentrated(flow, dir.conc_, curve, cntx); if (dir.chain_.swapDefer_) { applySwap(flow, dir.swap_, curve, cntx); } } /* @notice Applies the swap directive on to a pre-loaded liquidity curve. */ function applySwap (Chaining.PairFlow memory flow, Directives.SwapDirective memory dir, CurveCache.Cache memory curve, Chaining.ExecCntx memory cntx) private { cntx.roll_.plugSwapGap(dir, flow); if (dir.qty_ != 0) { callSwap(flow, curve, dir, cntx.pool_); } } /* @notice Applies an ambient liquidity directive to a pre-loaded liquidity curve. */ function applyAmbient (Chaining.PairFlow memory flow, Directives.AmbientDirective memory dir, CurveCache.Cache memory curve, Chaining.ExecCntx memory cntx) private { cntx.roll_.plugLiquidity(dir, curve.curve_, flow); if (dir.liquidity_ > 0) { (int128 base, int128 quote) = dir.isAdd_ ? callMintAmbient(curve, dir.liquidity_, cntx.pool_.hash_) : callBurnAmbient(curve, dir.liquidity_, cntx.pool_.hash_); flow.accumFlow(base, quote); } } /* @notice Applies zero, one or a series of concentrated liquidity directives to a * pre-loaded liquidity curve. */ function applyConcentrated (Chaining.PairFlow memory flow, Directives.ConcentratedDirective[] memory dirs, CurveCache.Cache memory curve, Chaining.ExecCntx memory cntx) private { unchecked { // Only arithmetic in block is ++i which will never overflow for (uint i = 0; i < dirs.length; ++i) { (int128 nextBase, int128 nextQuote) = applyConcentrated (curve, flow, cntx, dirs[i]); flow.accumFlow(nextBase, nextQuote); } } } /* Applies a single concentrated liquidity range order to the liquidity curve. */ function applyConcentrated (CurveCache.Cache memory curve, Chaining.PairFlow memory flow, Chaining.ExecCntx memory cntx, Directives.ConcentratedDirective memory bend) private returns (int128, int128) { // If ticks are relative, normalize against current pool price. if (bend.isTickRel_) { int24 priceTick = curve.pullPriceTick(); bend.lowTick_ = priceTick + bend.lowTick_; bend.highTick_ = priceTick + bend.highTick_; require((bend.lowTick_ >= TickMath.MIN_TICK) && (bend.highTick_ <= TickMath.MAX_TICK) && (bend.lowTick_ <= bend.highTick_), "RT"); } // If liquidity is set based on rolling balance, dynamically set in base // liquidity space. cntx.roll_.plugLiquidity(bend, curve.curve_, bend.lowTick_, bend.highTick_, flow); if (bend.isAdd_) { bool offGrid = cntx.improve_.verifyFit(bend.lowTick_, bend.highTick_, bend.liquidity_, cntx.pool_.head_.tickSize_, curve.pullPriceTick()); // Off-grid positions are only eligible when the LP has committed // to a minimum liquidity commitment above some threshold. This opens // up the possibility of a user minting an off-grid LP position above the // the threshold, then partially burning the position to resize the position *below* // the threhsold. // To prevent this all off-grid positions are marked as atomic which prevents partial // (but not full) burns. An off-grid LP wishing to reduce their position must fully // burn the position, then mint a new position, which will be checked that it meets // the size threshold at mint time. if (offGrid) { markPosAtomic(lockHolder_, cntx.pool_.hash_, bend.lowTick_, bend.highTick_); } } if (bend.liquidity_ == 0) { return (0, 0); } return bend.isAdd_ ? callMintRange(curve, bend.lowTick_, bend.highTick_, bend.liquidity_, cntx.pool_.hash_) : callBurnRange(curve, bend.lowTick_, bend.highTick_, bend.liquidity_, cntx.pool_.hash_); } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; import '../libraries/Directives.sol'; import '../libraries/PoolSpecs.sol'; import '../libraries/PriceGrid.sol'; import '../interfaces/ICrocPermitOracle.sol'; import './StorageLayout.sol'; /* @title Pool registry mixin * @notice Provides a facility for registering and querying pool types on pairs and * generalized pool templates for pools yet to be initialized. */ contract PoolRegistry is StorageLayout { using PoolSpecs for uint8; using PoolSpecs for PoolSpecs.Pool; uint8 constant SWAP_ACT_CODE = 1; uint8 constant MINT_ACT_CODE = 2; uint8 constant BURN_ACT_CODE = 3; uint8 constant COMP_ACT_CODE = 4; /* @notice Tests whether the given swap by the given user is authorized on this * specific pool. If not, reverts the transaction. If pool is permissionless * this function will just noop. */ function verifyPermitSwap (PoolSpecs.PoolCursor memory pool, address base, address quote, bool isBuy, bool inBaseQty, uint128 qty) internal { if (pool.oracle_ != address(0)) { uint16 discount = ICrocPermitOracle(pool.oracle_) .checkApprovedForCrocSwap(lockHolder_, msg.sender, base, quote, isBuy, inBaseQty, qty, pool.head_.feeRate_); applyDiscount(pool, discount); } } /* @notice Tests whether the given mint by the given user is authorized on this * specific pool. If not, reverts the transaction. If pool is permissionless * this function will just noop. */ function verifyPermitMint (PoolSpecs.PoolCursor memory pool, address base, address quote, int24 bidTick, int24 askTick, uint128 liq) internal { if (pool.oracle_ != address(0)) { bool approved = ICrocPermitOracle(pool.oracle_) .checkApprovedForCrocMint(lockHolder_, msg.sender, base, quote, bidTick, askTick, liq); require(approved, "Z"); } } /* @notice Tests whether the given burn by the given user is authorized on this * specific pool. If not, reverts the transaction. If pool is permissionless * this function will just noop. */ function verifyPermitBurn (PoolSpecs.PoolCursor memory pool, address base, address quote, int24 bidTick, int24 askTick, uint128 liq) internal { if (pool.oracle_ != address(0)) { bool approved = ICrocPermitOracle(pool.oracle_) .checkApprovedForCrocBurn(lockHolder_, msg.sender, base, quote, bidTick, askTick, liq); require(approved, "Z"); } } /* @notice Tests whether the given pool directive by the given user is authorized on * this specific pool. If not, reverts the transaction. If pool is * permissionless this function will just noop. */ function verifyPermit (PoolSpecs.PoolCursor memory pool, address base, address quote, Directives.AmbientDirective memory ambient, Directives.SwapDirective memory swap, Directives.ConcentratedDirective[] memory concs) internal { if (pool.oracle_ != address(0)) { uint16 discount = ICrocPermitOracle(pool.oracle_) .checkApprovedForCrocPool(lockHolder_, msg.sender, base, quote, ambient, swap, concs, pool.head_.feeRate_); applyDiscount(pool, discount); } } function applyDiscount (PoolSpecs.PoolCursor memory pool, uint16 discount) private pure { // Convention from permit oracle return value. Uses 0 for non-approved (meaning we // should rever), 1 for 0 discount, 2 for 0.0001% discount, and so on uint16 DISCOUNT_OFFSET = 1; require(discount > 0, "Z"); pool.head_.feeRate_ -= (discount - DISCOUNT_OFFSET); } /* @notice Tests whether the given initialization by the given user is authorized on this * specific pool. If not, reverts the transaction. If pool is permissionless * this function will just noop. */ function verifyPermitInit (PoolSpecs.PoolCursor memory pool, address base, address quote, uint256 poolIdx) internal { if (pool.oracle_ != address(0)) { bool approved = ICrocPermitOracle(pool.oracle_). checkApprovedForCrocInit(lockHolder_, msg.sender, base, quote, poolIdx); require(approved, "Z"); } } /* @notice Creates (or resets if previously existed) a new pool template associated * with an arbitrary pool index. After calling, any pair's pool initialized * at this index will be created using this template. * * @dev Previously existing pools at this index will *not* be updated by this * call, and must be individually reset. This is only a consideration if the * template is being reset, as a pool can't be created at an index beore a * template exists. * * @param poolIdx The arbitrary index for which this template will be created. After * calling, any user will be able to initialize a pool with this * template in any pair by using this pool index. * @param feeRate The pool's exchange fee as a percent of notional swapped. * Represented as a multiple of 0.0001%. * @param tickSize The tick grid size for range orders in the pool. (Template can * also be disabled by setting this to zero.) * @param jitThresh The minimum time (in seconds) a concentrated LP position must * rest before it can be burned. * @param knockout The knockout liquidity bit flags for the pool. (See KnockoutLiq library) * @param oracleFlags The permissioned oracle flags for the pool. */ function setPoolTemplate (uint256 poolIdx, uint16 feeRate, uint16 tickSize, uint8 jitThresh, uint8 knockout, uint8 oracleFlags) internal { PoolSpecs.Pool storage templ = templates_[poolIdx]; templ.schema_ = PoolSpecs.BASE_SCHEMA; templ.feeRate_ = feeRate; templ.tickSize_ = tickSize; templ.jitThresh_ = jitThresh; templ.knockoutBits_ = knockout; templ.oracleFlags_ = oracleFlags; // If template is set to use a permissioned oracle, validate that the oracle address is a // valid oracle contract address oracle = PoolSpecs.oracleForPool(poolIdx, oracleFlags); if (oracle != address(0)) { require(oracle.code.length > 0 && ICrocPermitOracle(oracle).acceptsPermitOracle(), "Oracle"); } } function disablePoolTemplate (uint256 poolIdx) internal { PoolSpecs.Pool storage templ = templates_[poolIdx]; templ.schema_ = PoolSpecs.DISABLED_SCHEMA; } /* @notice Resets the parameters on a previously existing pool in a specific pair. * * @dev We do not allow the permitOracle to be changed after the pool has been * initialized. That would give the protocol authority too much power to * arbitrarily lock LPs out of their funds. * * @param base The base-side token specification of the pair containing the pool. * @param quote The quote-side token specification of the pair containing the pool. * @param poolIdx The pool type index value. * @param feeRate The pool's exchange fee as a percent of notional swapped. * Represented as a multiple of 0.0001%. * @param tickSize The tick grid size for range orders in the pool. * @param jitThresh The minimum time (in seconds) a concentrated LP position must * rest before it can be burned. * @param knockoutBits The knockout liquiidity parameter bit flags for the pool. */ function setPoolSpecs (address base, address quote, uint256 poolIdx, uint16 feeRate, uint16 tickSize, uint8 jitThresh, uint8 knockoutBits) internal { PoolSpecs.Pool storage pool = selectPool(base, quote, poolIdx); pool.feeRate_ = feeRate; pool.tickSize_ = tickSize; pool.jitThresh_ = jitThresh; pool.knockoutBits_ = knockoutBits; } // 10 million represents a sensible upper bound on initial pool, considering that the highest // price token per wei is USDC and similar 6-digit stablecoins. So 10 million in that context // represents about $10 worth of burned value. Considering that the initial liquidity commitment // should be economic de minims, because it's permenately locked, we wouldn't want to be much // higher than this. uint128 constant MAX_INIT_POOL_LIQ = 10_000_000; /* @notice The creation of every new pool requires the pool initializer to * permanetely lock in a token amount of liquidity (possibly zero). This is * set to be economically meaningless for normal cases but prevent the * creation of pools for tokens that don't exist or make it expensive to * create pools at extremely wrong prices. This function sets that liquidity * ante value that determines how much liquidity must be locked at * initialization time. */ function setNewPoolLiq (uint128 liqAnte) internal { require(liqAnte > 0 && liqAnte < MAX_INIT_POOL_LIQ, "Init liq"); newPoolLiq_ = liqAnte; } function setProtocolTakeRate (uint8 takeRate) internal { require(takeRate <= MAX_TAKE_RATE, "TR"); protocolTakeRate_ = takeRate; } function setRelayerTakeRate (uint8 takeRate) internal { require(takeRate <= MAX_TAKE_RATE, "TR"); relayerTakeRate_ = takeRate; } function resyncProtocolTake (address base, address quote, uint256 poolIdx) internal { PoolSpecs.Pool storage pool = selectPool(base, quote, poolIdx); pool.protocolTake_ = protocolTakeRate_; } /* @notice Sets the off-grid price improvement thresholds for a specific token. Once * set this will apply to every pool in every pair over this token. The * stored settings for a token can be initialized, then later reset * arbitararily. * * @param token The token these settings apply to (if 0x0, they apply to native * Eth pairs) * @param unitTickCollateral The collateral threshold per off-grid tick. * @param awayTickTol The maximum ticks away from the current price that an off-grid * range order can apply. */ function setPriceImprove (address token, uint128 unitTickCollateral, uint16 awayTickTol) internal { improves_[token].unitCollateral_ = unitTickCollateral; improves_[token].awayTicks_ = awayTickTol; } /* @notice This is called during the initialization of a new pool. It registers the * pool for this pair and type in storage for later access. Note that the * caller still needs to actually construct the curve, collect the required * collateral, etc. All this does is storage the pool specs. * * @param base The base-side token (or 0x0 for native Eth) defining the pair. * @param quote The quote-side token defining the pair. * @param poolIdx The pool type index for the newly created pool. The pool specs will * be created from the current template for this index. (If no * template exists, this call will revert the transaction.) * * @return pool The pool specs associated with the newly created pool. * @return liqAnte The required amount of liquidity that the user must permanetely * lock to create the pool. (See setNewPoolLiq() above) */ function registerPool (address base, address quote, uint256 poolIdx) internal returns (PoolSpecs.PoolCursor memory, uint128) { assertPoolFresh(base, quote, poolIdx); PoolSpecs.Pool memory template = queryTemplate(poolIdx); template.protocolTake_ = protocolTakeRate_; PoolSpecs.writePool(pools_, base, quote, poolIdx, template); return (queryPool(base, quote, poolIdx), newPoolLiq_); } /* @notice This returns the off-grid price improvement settings (if any) for the * the side of the pair the user requests. (Or none, to save on gas, * if the user doesn't explicitly request price improvement). * * @param req The user specificed price improvement request. * @param base The base-side token defining the pair. * @param quote The quote-side token defining the pair. * @return The price grid improvement thresholds (if any) for off-grid liquidity * positions. */ function queryPriceImprove (Directives.PriceImproveReq memory req, address base, address quote) view internal returns (PriceGrid.ImproveSettings memory dest) { if (req.isEnabled_) { address token = req.useBaseSide_ ? base : quote; dest.inBase_ = req.useBaseSide_; dest.unitCollateral_ = improves_[token].unitCollateral_; dest.awayTicks_ = improves_[token].awayTicks_; } } /* @notice Looks up and returns the pool specs associated with the pair and pool type * * @dev If no pool exists, this call reverts the transaction. * * @param base The base-side token defining the pair. * @param quote The quote-side token defining the pair. * @param poolIdx The pool type index. * @return The current spec parameters for the pool. */ function queryPool (address base, address quote, uint256 poolIdx) internal view returns (PoolSpecs.PoolCursor memory pool) { pool = PoolSpecs.queryPool(pools_, base, quote, poolIdx); require(isPoolInit(pool), "PI"); } function assertPoolFresh (address base, address quote, uint256 poolIdx) internal view { PoolSpecs.PoolCursor memory pool = PoolSpecs.queryPool(pools_, base, quote, poolIdx); require(!isPoolInit(pool), "PF"); } /* @notice Checks if a given position is JIT eligible based on its mint timestamp. * If not, the transaction will revert. * * @dev Because JIT window is capped at 8-bit integers, we can avoid the SLOAD * for all positions older than 2550 seconds, which are the vast majority. * * @param posTime The block time the position was created or had its liquidity * increased. * @param poolIdx The hash index of the AMM curve pool. */ function assertJitSafe (uint32 posTime, bytes32 poolIdx) internal view { uint32 JIT_UNIT_SECONDS = 10; uint32 elapsedSecs = SafeCast.timeUint32() - posTime; uint32 elapsedUnits = elapsedSecs / JIT_UNIT_SECONDS; if (elapsedUnits <= type(uint8).max) { require(elapsedUnits >= pools_[poolIdx].jitThresh_, "J"); } } /* @notice Looks up and returns a storage pointer associated with the pair and pool * type. * * @param base The base-side token defining the pair. * @param quote The quote-side token defining the pair. * @param poolIdx The pool type index. * @return Storage reference to the specs for the pool. */ function selectPool (address base, address quote, uint256 poolIdx) private view returns (PoolSpecs.Pool storage pool) { pool = PoolSpecs.selectPool(pools_, base, quote, poolIdx); require(isPoolInit(pool), "PI"); } /* @notice Looks up and returns the pool template associated with the pool type * index. If no template exists (or it was disabled after initialization) * this call reverts the transaction. */ function queryTemplate (uint256 poolIdx) private view returns (PoolSpecs.Pool memory template) { template = templates_[poolIdx]; require(isPoolInit(template), "PT"); } /* @notice Returns true if the pool spec object represents an initailized pool * that hasn't been disabled. */ function isPoolInit (PoolSpecs.Pool memory pool) private pure returns (bool) { require(pool.schema_ <= PoolSpecs.BASE_SCHEMA, "IPS"); return pool.schema_ == PoolSpecs.BASE_SCHEMA; } /* @notice Returns true if the pool cursor represents an initailized pool that * hasn't been disabled. */ function isPoolInit (PoolSpecs.PoolCursor memory pool) private pure returns (bool) { require(pool.head_.schema_ <= PoolSpecs.BASE_SCHEMA, "IPS"); return pool.head_.schema_ == PoolSpecs.BASE_SCHEMA; } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; import '../libraries/SafeCast.sol'; import '../libraries/LiquidityMath.sol'; import '../libraries/CompoundMath.sol'; import './StorageLayout.sol'; import './PoolRegistry.sol'; /* @title Position registrar mixin * @notice Tracks the individual positions of liquidity miners, including fee * accumulation checkpoints for fair distribution of rewards. */ contract PositionRegistrar is PoolRegistry { using SafeCast for uint256; using SafeCast for uint144; using CompoundMath for uint128; using LiquidityMath for uint64; using LiquidityMath for uint72; using LiquidityMath for uint128; /* The six things we need to know for each concentrated liquidity position are: * 1) Owner * 2) The pool the position is on. * 3) Lower tick bound on the range * 4) Upper tick bound on the range * 5) Total liquidity * 6) Fee accumulation mileage for the position's range checkpointed at the last * update. Used to correctly distribute in-range liquidity rewards. * Of these 1-4 constitute the unique key. If a user adds a new position with the * same owner and the same range, it can be represented by incrementing 5 and * updating 6. */ /* @notice Hashes the owner of an ambient liquidity position to the position key. */ function encodePosKey (address owner, bytes32 poolIdx) internal pure returns (bytes32) { return keccak256(abi.encodePacked(owner, poolIdx)); } /* @notice Hashes the owner and concentrated liquidity range to the position key. */ function encodePosKey (address owner, bytes32 poolIdx, int24 lowerTick, int24 upperTick) internal pure returns (bytes32) { return keccak256(abi.encodePacked(owner, poolIdx, lowerTick, upperTick)); } /* @notice Returns the current position associated with the owner/range. If nothing * exists the result will have zero liquidity. */ function lookupPosition (address owner, bytes32 poolIdx, int24 lowerTick, int24 upperTick) internal view returns (RangePosition72 storage) { return positions72_[encodePosKey(owner, poolIdx, lowerTick, upperTick)]; } /* @notice Returns the current position associated with the owner's ambient * position. If nothing exists the result will have zero liquidity. */ function lookupPosition (address owner, bytes32 poolIdx) internal view returns (AmbientPosition storage) { return ambPositions_[encodePosKey(owner, poolIdx)]; } /* @notice Removes all or some liquidity associated with a position. Calculates * the cumulative rewards since last update, and updates the fee mileage * (if position still have active liquidity). * * @param owner The bytes32 owning the position. * @param poolIdx The hash key of the pool the position lives on. * @param lowerTick The 24-bit tick index constituting the lower range of the * concentrated liquidity position. * @param upperTick The 24-bit tick index constituting the upper range of the * concentrated liquidity position. * @param burnLiq The amount of liquidity to remove from the position. Caller is * is responsible for making sure the position has at least this much * liquidity in place. * @param feeMileage The up-to-date fee mileage associated with the range. If the * position is still active after this call, this new value will * be checkpointed on the position. * * @return rewards The rewards accumulated between the current and last checkpoined * fee mileage. */ function burnPosLiq (address owner, bytes32 poolIdx, int24 lowerTick, int24 upperTick, uint128 burnLiq, uint72 feeMileage) internal returns (uint64) { RangePosition72 storage pos = lookupPosition(owner, poolIdx, lowerTick, upperTick); assertJitSafe(pos.timestamp_, poolIdx); return decrementLiq(pos, burnLiq, feeMileage); } /* @notice Removes all or some liquidity associated with a an ambient position. * * @param owner The bytes32 owning the position. * @param poolIdx The hash key of the pool the position lives on. * @param burnLiq The amount of liquidity to remove from the position. Caller is free * to oversize this number and it will just cap at the position size. * @param ambientGrowth The up-to-date ambient liquidity seed deflator for the curve. * * @return burnSeeds The total number of ambient seeds that have been removed with * this operation. */ function burnPosLiq (address owner, bytes32 poolIdx, uint128 burnLiq, uint64 ambientGrowth) internal returns (uint128 burnSeeds) { AmbientPosition storage pos = lookupPosition(owner, poolIdx); burnSeeds = burnLiq.deflateLiqSeed(ambientGrowth); if (burnSeeds >= pos.seeds_) { burnSeeds = pos.seeds_; // Solidity optimizer should convert this to a single refunded SSTORE pos.seeds_ = 0; pos.timestamp_ = 0; } else { pos.seeds_ -= burnSeeds; // Decreasing liquidity does not lose time priority } } /* @notice Decrements a range order position with the amount of liquidity being * burned, and calculates the incremental rewards mileage. */ function decrementLiq (RangePosition72 storage pos, uint128 burnLiq, uint72 feeMileage) internal returns (uint64 rewards) { uint128 liq = pos.liquidity_; uint128 nextLiq = LiquidityMath.minusDelta(liq, burnLiq); rewards = feeMileage.deltaRewardsRate72(pos.feeMileage_); if (nextLiq > 0) { // Partial burn. Check that it's allowed on this position. require(pos.atomicLiq_ == false, "OR"); pos.liquidity_ = nextLiq; // No need to adjust the position's mileage checkpoint. Rewards are in per // unit of liquidity, so the pro-rata rewards of the remaining liquidity // (if any) remain unnaffected. } else { // Solidity optimizer should convert this to a single refunded SSTORE pos.liquidity_ = 0; pos.feeMileage_ = 0; pos.timestamp_ = 0; pos.atomicLiq_ = false; } } /* @notice Harvests all of the rewards on a concentrated liquidity position and * resets the accumulated fees to zero. * * @param owner The bytes32 owning the position. * @param poolIdx The hash key of the pool the position lives on. * @param lowerTick The lower tick of the LP position * @param upperTick The upper tick of the LP position. * @param feeMileage The current accumulated fee rewards rate for the position range * * @return rewards The total number of ambient seeds to collect as rewards */ function harvestPosLiq (address owner, bytes32 poolIdx, int24 lowerTick, int24 upperTick, uint72 feeMileage) internal returns (uint128 rewards) { RangePosition72 storage pos = lookupPosition(owner, poolIdx, lowerTick, upperTick); uint72 oldMileage = pos.feeMileage_; // Technically feeMileage should never be less than oldMileage, but we need to // handle it because it can happen due to fixed-point effects. // (See blendMileage() function.) if (feeMileage > oldMileage) { uint64 rewardsRate = feeMileage.deltaRewardsRate72(oldMileage); rewards = FixedPoint.mulQ48(pos.liquidity_, rewardsRate).toUint128By144(); pos.feeMileage_ = feeMileage; } } /* @notice Marks a flag on a speciic position that indicates that it's liquidity * is atomic. I.e. the position size cannot be partially reduced, only * removed entirely. */ function markPosAtomic (address owner, bytes32 poolIdx, int24 lowTick, int24 highTick) internal { RangePosition72 storage pos = lookupPosition(owner, poolIdx, lowTick, highTick); pos.atomicLiq_ = true; } /* @notice Adds liquidity to a given concentrated liquidity position, creating the * position if necessary. * * @param owner The bytes32 owning the position. * @param poolIdx The index of the pool the position belongs to * @param lowerTick The 24-bit tick index constituting the lower range of the * concentrated liquidity position. * @param upperTick The 24-bit tick index constituting the upper range of the * concentrated liquidity position. * @param liqAdd The amount of liquidity to add to the position. If no liquidity * previously exists, position will be created. * @param feeMileage The up-to-date fee mileage associated with the range. If the * position will be checkpointed with this value. */ function mintPosLiq (address owner, bytes32 poolIdx, int24 lowerTick, int24 upperTick, uint128 liqAdd, uint72 feeMileage) internal { RangePosition72 storage pos = lookupPosition(owner, poolIdx, lowerTick, upperTick); incrementPosLiq(pos, liqAdd, feeMileage); } /* @notice Adds ambient liquidity to a give position, creating a new position tracker * if necessry. * * @param owner The address of the owner of the liquidity position. * @param poolIdx The hash key of the pool the position lives on. * @param liqAdd The amount of liquidity to add to the position. * @param ambientGrowth The up-to-date ambient liquidity seed deflator for the curve. * * @return seeds The total number of ambient seeds that this incremental liquidity * corresponds to. */ function mintPosLiq (address owner, bytes32 poolIdx, uint128 liqAdd, uint64 ambientGrowth) internal returns (uint128 seeds) { AmbientPosition storage pos = lookupPosition(owner, poolIdx); seeds = liqAdd.deflateLiqSeed(ambientGrowth); pos.seeds_ = pos.seeds_.addLiq(seeds); pos.timestamp_ = SafeCast.timeUint32(); // Increase liquidity loses time priority. } function incrementPosLiq (RangePosition72 storage pos, uint128 liqAdd, uint72 feeMileage) private { uint128 liq = pos.liquidity_; uint72 oldMileage; if (liq > 0) { oldMileage = pos.feeMileage_; } else { oldMileage = 0; } uint128 liqNext = liq.addLiq(liqAdd); uint72 mileage = feeMileage.blendMileage72(liqAdd, oldMileage, liq); uint32 stamp = SafeCast.timeUint32(); // Below should get optimized to a single SSTORE... pos.liquidity_ = liqNext; pos.feeMileage_ = mileage; pos.timestamp_ = stamp; } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; import '../libraries/TransferHelper.sol'; import '../libraries/TokenFlow.sol'; import '../libraries/SafeCast.sol'; import './StorageLayout.sol'; /* @title Protocol Account Mixin * @notice Tracks and pays out the accumulated protocol fees across the entire exchange * These are the fees belonging to the CrocSwap protocol, not the liquidity * miners. * @dev Unlike liquidity fees, protocol fees are accumulated as resting tokens * instead of ambient liquidity. */ contract ProtocolAccount is StorageLayout { using SafeCast for uint256; using TokenFlow for address; /* @notice Called at the completion of a swap event, incrementing any protocol * fees accumulated in the swap. */ function accumProtocolFees (TokenFlow.PairSeq memory accum) internal { accumProtocolFees(accum.flow_, accum.baseToken_, accum.quoteToken_); } /* @notice Increments the protocol's account with the fees collected on the pair. */ function accumProtocolFees (Chaining.PairFlow memory accum, address base, address quote) internal { if (accum.baseProto_ > 0) { feesAccum_[base] += accum.baseProto_; } if (accum.quoteProto_ > 0) { feesAccum_[quote] += accum.quoteProto_; } } /* @notice Pays out the earned, but unclaimed protocol fees in the pool. * @param recv - The receiver of the protocol fees. * @param token - The token address of the quote token. */ function disburseProtocolFees (address recv, address token) internal { uint128 collected = feesAccum_[token]; feesAccum_[token] = 0; if (collected > 0) { bytes32 payoutKey = keccak256(abi.encode(recv, token)); userBals_[payoutKey].surplusCollateral_ += collected; } } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; import './StorageLayout.sol'; import '../libraries/CurveCache.sol'; import '../libraries/Chaining.sol'; import '../libraries/Directives.sol'; /* @title Proxy Caller * @notice Because of the Ethereum contract limit, much of the CrocSwap code is pushed * into sidecar proxy contracts, which is involed with DELEGATECALLs. The code * moved to these sidecars is less gas critical than the code in the core contract. * This provides a facility for invoking proxy conjtracts in a consistent way by * setting up the DELEGATECALLs in a standard and safe manner. */ contract ProxyCaller is StorageLayout { using CurveCache for CurveCache.Cache; using CurveMath for CurveMath.CurveState; using Chaining for Chaining.PairFlow; /* @notice Passes through the protocolCmd call to a sidecar proxy. */ function callProtocolCmd (uint16 proxyIdx, bytes calldata input) internal returns (bytes memory) { assertProxy(proxyIdx); (bool success, bytes memory output) = proxyPaths_[proxyIdx].delegatecall( abi.encodeWithSignature("protocolCmd(bytes)", input)); return verifyCallResult(success, output); } /* @notice Passes through the userCmd call to a sidecar proxy. */ function callUserCmd (uint16 proxyIdx, bytes calldata input) internal returns (bytes memory) { assertProxy(proxyIdx); (bool success, bytes memory output) = proxyPaths_[proxyIdx].delegatecall( abi.encodeWithSignature("userCmd(bytes)", input)); return verifyCallResult(success, output); } function callUserCmdMem (uint16 proxyIdx, bytes memory input) internal returns (bytes memory) { assertProxy(proxyIdx); (bool success, bytes memory output) = proxyPaths_[proxyIdx].delegatecall( abi.encodeWithSignature("userCmd(bytes)", input)); return verifyCallResult(success, output); } function assertProxy (uint16 proxyIdx) private view { require(proxyPaths_[proxyIdx] != address(0)); require(!inSafeMode_ || proxyIdx == CrocSlots.SAFE_MODE_PROXY_PATH || proxyIdx == CrocSlots.BOOT_PROXY_IDX); } function verifyCallResult (bool success, bytes memory returndata) internal pure returns (bytes memory) { // On success pass through the return data if (success) { return returndata; } else if (returndata.length > 0) { // If DELEGATECALL failed bubble up the error message assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { // If failed with no error, then bubble up the empty revert revert(); } } /* @notice Invokes mintAmbient() call in MicroPaths sidecar and relays the result. */ function callMintAmbient (CurveCache.Cache memory curve, uint128 liq, bytes32 poolHash) internal returns (int128 basePaid, int128 quotePaid) { (bool success, bytes memory output) = proxyPaths_[CrocSlots.MICRO_PROXY_IDX].delegatecall (abi.encodeWithSignature ("mintAmbient(uint128,uint128,uint128,uint64,uint64,uint128,bytes32)", curve.curve_.priceRoot_, curve.curve_.ambientSeeds_, curve.curve_.concLiq_, curve.curve_.seedDeflator_, curve.curve_.concGrowth_, liq, poolHash)); require(success); (basePaid, quotePaid, curve.curve_.ambientSeeds_) = abi.decode(output, (int128, int128, uint128)); } /* @notice Invokes burnAmbient() call in MicroPaths sidecar and relays the result. */ function callBurnAmbient (CurveCache.Cache memory curve, uint128 liq, bytes32 poolHash) internal returns (int128 basePaid, int128 quotePaid) { (bool success, bytes memory output) = proxyPaths_[CrocSlots.MICRO_PROXY_IDX].delegatecall (abi.encodeWithSignature ("burnAmbient(uint128,uint128,uint128,uint64,uint64,uint128,bytes32)", curve.curve_.priceRoot_, curve.curve_.ambientSeeds_, curve.curve_.concLiq_, curve.curve_.seedDeflator_, curve.curve_.concGrowth_, liq, poolHash)); require(success); (basePaid, quotePaid, curve.curve_.ambientSeeds_) = abi.decode(output, (int128, int128, uint128)); } /* @notice Invokes mintRange() call in MicroPaths sidecar and relays the result. */ function callMintRange (CurveCache.Cache memory curve, int24 bidTick, int24 askTick, uint128 liq, bytes32 poolHash) internal returns (int128 basePaid, int128 quotePaid) { (bool success, bytes memory output) = proxyPaths_[CrocSlots.MICRO_PROXY_IDX].delegatecall (abi.encodeWithSignature ("mintRange(uint128,int24,uint128,uint128,uint64,uint64,int24,int24,uint128,bytes32)", curve.curve_.priceRoot_, curve.pullPriceTick(), curve.curve_.ambientSeeds_, curve.curve_.concLiq_, curve.curve_.seedDeflator_, curve.curve_.concGrowth_, bidTick, askTick, liq, poolHash)); require(success); (basePaid, quotePaid, curve.curve_.ambientSeeds_, curve.curve_.concLiq_) = abi.decode(output, (int128, int128, uint128, uint128)); } /* @notice Invokes burnRange() call in MicroPaths sidecar and relays the result. */ function callBurnRange (CurveCache.Cache memory curve, int24 bidTick, int24 askTick, uint128 liq, bytes32 poolHash) internal returns (int128 basePaid, int128 quotePaid) { (bool success, bytes memory output) = proxyPaths_[CrocSlots.MICRO_PROXY_IDX].delegatecall (abi.encodeWithSignature ("burnRange(uint128,int24,uint128,uint128,uint64,uint64,int24,int24,uint128,bytes32)", curve.curve_.priceRoot_, curve.pullPriceTick(), curve.curve_.ambientSeeds_, curve.curve_.concLiq_, curve.curve_.seedDeflator_, curve.curve_.concGrowth_, bidTick, askTick, liq, poolHash)); require(success); (basePaid, quotePaid, curve.curve_.ambientSeeds_, curve.curve_.concLiq_) = abi.decode(output, (int128, int128, uint128, uint128)); } /* @notice Invokes sweepSwap() call in MicroPaths sidecar and relays the result. */ function callSwap (Chaining.PairFlow memory accum, CurveCache.Cache memory curve, Directives.SwapDirective memory swap, PoolSpecs.PoolCursor memory pool) internal { (bool success, bytes memory output) = proxyPaths_[CrocSlots.MICRO_PROXY_IDX].delegatecall (abi.encodeWithSignature ("sweepSwap((uint128,uint128,uint128,uint64,uint64),int24,(bool,bool,uint8,uint128,uint128),((uint8,uint16,uint8,uint16,uint8,uint8,uint8),bytes32,address))", curve.curve_, curve.pullPriceTick(), swap, pool)); require(success); Chaining.PairFlow memory swapFlow; (swapFlow, curve.curve_.priceRoot_, curve.curve_.ambientSeeds_, curve.curve_.concLiq_, curve.curve_.seedDeflator_, curve.curve_.concGrowth_) = abi.decode(output, (Chaining.PairFlow, uint128, uint128, uint128, uint64, uint64)); // swap() is the only operation that can change curve price, so have to mark // the tick cache as dirty. curve.dirtyPrice(); accum.foldFlow(swapFlow); } function callCrossFlag (bytes32 poolHash, int24 tick, bool isBuy, uint64 feeGlobal) internal returns (int128 concLiqDelta) { require(proxyPaths_[CrocSlots.FLAG_CROSS_PROXY_IDX] != address(0)); (bool success, bytes memory cmd) = proxyPaths_[CrocSlots.FLAG_CROSS_PROXY_IDX].delegatecall (abi.encodeWithSignature ("crossCurveFlag(bytes32,int24,bool,uint64)", poolHash, tick, isBuy, feeGlobal)); require(success); concLiqDelta = abi.decode(cmd, (int128)); } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; pragma experimental ABIEncoderV2; import '../libraries/Directives.sol'; import '../libraries/TransferHelper.sol'; import '../libraries/TokenFlow.sol'; import './StorageLayout.sol'; import './AgentMask.sol'; /* @title Settle layer mixin * @notice Provides facilities for settling, previously determined, collateral flows * between the user and the exchange. Supports both ERC20 tokens as well as * native Ethereum as asset collateral. */ contract SettleLayer is AgentMask { using SafeCast for uint256; using SafeCast for uint128; using TokenFlow for address; /* @notice Completes the user<->exchange collateral settlement at the final hop * in the transaction. Settles both the token from the last leg in the chain * as well as closes out the previous net Ether flows. * * @dev This method settles any net Ether debits or credits in the ethFlows * argument, by consuming the native ETH attached in msg.value, using * popMsgVal(). popMsgVal() sets a transaction level flag, and to prevent * double spent will revert and fail the top level CrocSwapDex contract * call if ever called twice in the same transction. Therefore this method * must only be called at most once per transaction, otherwise the top-level * CrocSwapDex contract call will revert and fail. * * @param flow The net flow for this settlement leg. Negative for credits paid to * user, positive for debits. * @param dir The directive governing the details of how the user once the leg * settled. * @param ethFlows Any prior Ether-specific flows from previous legs. (This final * leg may also be denominated in Eth, and this param should *not* include * the current leg's value.) */ function settleFinal (int128 flow, Directives.SettlementChannel memory dir, int128 ethFlows) internal { (address debitor, address creditor) = agentsSettle(); settleFinal(debitor, creditor, flow, dir, ethFlows); } /* @notice Completes the user<->exchange collateral settlement on an intermediate hop * leg in the transaction. For ERC20 tokens the flow will be settled at this * call. For native Ether flows, the net flow will be returned to be deferred * until the settleFinal() call. This is because we potentially have multiple * native Eth settlement legs and want to avoid a msg.value double spend. * * @param flow The net flow for this settlement leg. Negative for credits paid to * user, positive for debits. * @param dir The directive governing the details of how the user once the leg * settled. * @return ethFlows Any native Eth flows associated with this leg. It's the caller's * responsibility to accumulate and sum this value for all calls, * then pass to settleFinal() at the end of the transaction. */ function settleLeg (int128 flow, Directives.SettlementChannel memory dir) internal returns (int128 ethFlows) { (address debitor, address creditor) = agentsSettle(); return settleLeg(debitor, creditor, flow, dir); } /* @notice Completes the user<->exchange collateral settlement at the final hop * in the transaction. Settles both the token from the last leg in the chain * as well as closes out the previous net Ether flows. * * @dev This call is the point where any Ether debit Because this actually collects any Ether debit (using msg.value), this * function must be called *exactly once* as the final settlement call in * a transaction. Otherwise, a double-spend is possible. * * @param debitor The address from which any debts to the exchange should be * collected. * @param creditor The address to which any credits owed to the user should be paid. * @param flow The net flow for this settlement leg. Negative for credits paid to * user, positive for debits. * @param dir The directive governing the details of how the user once the leg * settled. * @param ethFlows Any prior Ether-specific flows from previous legs. (This final * leg may also be denominated in Eth, and this param should *not* include * the current leg's value.) */ function settleFinal (address debitor, address creditor, int128 flow, Directives.SettlementChannel memory dir, int128 ethFlows) internal { ethFlows += settleLeg(debitor, creditor, flow, dir); transactEther(debitor, creditor, ethFlows, dir.useSurplus_); } /* @notice Completes the user<->exchange collateral settlement on an intermediate hop * leg in the transaction. For ERC20 tokens the flow will be settled at this * call. For native Ether flows, the net flow will be returned to be deferred * until the settleFinal() call. This is because we potentially have multiple * native Eth settlement legs and want to avoid a msg.value double spend. * * @param debitor The address from which any debts to the exchange should be * collected. * @param creditor The address to which any credits owed to the user should be paid. * @param flow The net flow for this settlement leg. Negative for credits paid to * user, positive for debits. * @param dir The directive governing the details of how the user once the leg * settled. * @return ethFlows Any native Eth flows associated with this leg. It's the caller's * responsibility to accumulate and sum this value for all calls, * then pass to settleFinal() at the end of the transaction. */ function settleLeg (address debitor, address creditor, int128 flow, Directives.SettlementChannel memory dir) internal returns (int128 ethFlows) { require(passesLimit(flow, dir.limitQty_), "K"); if (moreThanDust(flow, dir.dustThresh_)) { ethFlows = pumpFlow(debitor, creditor, flow, dir.token_, dir.useSurplus_); } } /* @notice Settle the collateral exchange associated with a single bilateral pair. * Useful and gas efficient when there's only one pair in the transaction. * @param base The ERC20 address of the base token collateral in the pair (if 0x0 * indicates that the collateral is native Eth). * @param quote The ERC20 address of the quote token collateral in the pair. * @param baseFlow The amount of flow associated with the base side of the pair. * Negative for credits paid to user, positive for debits. * @param quoteFlow The flow associated with the quote side of the pair. * @param reserveFlags Bitwise flags to indicate whether the base and/or quote flows * should be settled from caller's surplus collateral */ function settleFlows (address base, address quote, int128 baseFlow, int128 quoteFlow, uint8 reserveFlags) internal { (address debitor, address creditor) = agentsSettle(); settleFlat(debitor, creditor, base, baseFlow, quote, quoteFlow, reserveFlags); } /* @notice Settle the collateral exchange associated with a the initailization of * a new pool in the exchange. * @oaran recv The address that will be covering any debits associated with the * initialization of the pool. * @param base The ERC20 address of the base token collateral in the pair (if 0x0 * indicates that the collateral is native Eth). * @param baseFlow The amount of flow associated with the base side of the pair. * By convention negative for credits paid to user, positive for debits, * but will always be positive/debit for this operation. * @param quote The ERC20 address of the quote token collateral in the pair. * @param quoteFlow The flow associated with the quote side of the pair. */ function settleInitFlow (address recv, address base, int128 baseFlow, address quote, int128 quoteFlow) internal { (uint256 baseSnap, uint256 quoteSnap) = snapOpenBalance(base, quote); settleFlat(recv, recv, base, baseFlow, quote, quoteFlow, BOTH_RESERVE_FLAGS); assertCloseMatches(base, baseSnap, baseFlow); assertCloseMatches(quote, quoteSnap, quoteFlow); } /* @notice Settles the collateral exchanged associated with the flow in a single * pair. * @dev This must only be used when no other pairs settle in the transaction. */ function settleFlat (address debitor, address creditor, address base, int128 baseFlow, address quote, int128 quoteFlow, uint8 reserveFlags) private { if (base.isEtherNative()) { transactEther(debitor, creditor, baseFlow, useReservesBase(reserveFlags)); } else { transactToken(debitor, creditor, baseFlow, base, useReservesBase(reserveFlags)); } // Because Ether native trapdoor is 0x0 address, and because base is always // smaller of the two addresses, native ETH will always appear on the base // side. transactToken(debitor, creditor, quoteFlow, quote, useReservesQuote(reserveFlags)); } function useReservesBase (uint8 reserveFlags) private pure returns (bool) { return reserveFlags & BASE_RESERVE_FLAG > 0; } function useReservesQuote (uint8 reserveFlags) private pure returns (bool) { return reserveFlags & QUOTE_RESERVE_FLAG > 0; } uint8 constant NO_RESERVE_FLAGS = 0x0; uint8 constant BASE_RESERVE_FLAG = 0x1; uint8 constant QUOTE_RESERVE_FLAG = 0x2; uint8 constant BOTH_RESERVE_FLAGS = 0x3; /* @notice Performs check to make sure the new balance matches the expected * transfer amount. */ function assertCloseMatches (address token, uint256 open, int128 expected) private view { if (token != address(0)) { uint256 close = IERC20Minimal(token).balanceOf(address(this)); require(close >= open && expected >= 0 && close - open >= uint128(expected), "TD"); } } /* @notice Snapshots the DEX contract's ERC20 token balance at call time. */ function snapOpenBalance (address base, address quote) private view returns (uint256 openBase, uint256 openQuote) { openBase = base == address(0) ? 0 : IERC20Minimal(base).balanceOf(address(this)); openQuote = IERC20Minimal(quote).balanceOf(address(this)); } /* @notice Given a pre-determined amount of flow, settles according to collateral * type and settlement specification. */ function pumpFlow (address debitor, address creditor, int128 flow, address token, bool useReserves) private returns (int128) { if (token.isEtherNative()) { return flow; } else { transactToken(debitor, creditor, flow, token, useReserves); return 0; } } function querySurplus (address user, address token) internal view returns (uint128) { bytes32 key = tokenKey(user, token); return userBals_[key].surplusCollateral_; } /* @notice Returns true if the flow represents a debit owed from the user to the * exchange. */ function isDebit (int128 flow) private pure returns (bool) { return flow > 0; } /* @notice Returns true if the flow represents a credit owed from the exchange to the * user. */ function isCredit (int128 flow) private pure returns (bool) { return flow < 0; } /* @notice Called to settle a net balance of native Ether. * @dev Becaue this settles against msg.value, it's very important to *never* call * this twice in any single transaction, to avoid double-spend. * * @param debitor The address to collect any net debit from. * @param creditor The address to pay out any net credit to. * @param flow The total net balance to be settled. Negative indicates credit to the * user. Positive debit to the exchange. * @para useReserves If true, any settlement is first done against the user's surplus * collateral account at the exchange rather than sending Ether. */ function transactEther (address debitor, address creditor, int128 flow, bool useReserves) private { // This is the only point in a standard transaction where msg.value is accessed. uint128 recvEth = popMsgVal(); if (flow != 0) { transactFlow(debitor, creditor, flow, address(0), recvEth, useReserves); } else { refundEther(creditor, recvEth); } } /* @notice Called to settle a net balance of ERC20 tokens * @dev transactEther Unlike transactEther this can be called multiple times, even * on the same token. * * @param debitor The address to collect any net debit from. * @param creditor The address to pay out any net credit to. * @param flow The total net balance to be settled. Negative indicates credit to the * user. Positive debit to the exchange. * @param token The address of the token's ERC20 tracker. * @para useReserves If true, any settlement is first done against the user's surplus * collateral account at the exchange. */ function transactToken (address debitor, address creditor, int128 flow, address token, bool useReserves) private { require(!token.isEtherNative()); // Since this is a token settlement, we defer booking any native ETH in msg.value uint128 bookedEth = 0; transactFlow(debitor, creditor, flow, token, bookedEth, useReserves); } /* @notice Handles the single sided settlement of a token or native ETH flow. */ function transactFlow (address debitor, address creditor, int128 flow, address token, uint128 bookedEth, bool useReserves) private { if (isDebit(flow)) { debitUser(debitor, uint128(flow), token, bookedEth, useReserves); } else if (isCredit(flow)) { creditUser(creditor, uint128(-flow), token, bookedEth, useReserves); } } /* @notice Collects a collateral debit from the user depending on the asset type * and the settlement specifcation. */ function debitUser (address recv, uint128 value, address token, uint128 bookedEth, bool useReserves) private { if (useReserves) { uint128 remainder = debitSurplus(recv, value, token); debitRemainder(recv, remainder, token, bookedEth); } else { debitTransfer(recv, value, token, bookedEth); } } /* @notice Collects the remaining debit (if any) after the user's surplus collateral * balance has been exhausted. */ function debitRemainder (address recv, uint128 remainder, address token, uint128 bookedEth) private { if (remainder > 0) { debitTransfer(recv, remainder, token, bookedEth); } else if (token.isEtherNative()) { refundEther(recv, bookedEth); } } /* @notice Pays out a collateral credit to the user depending on asset type and * settlement specification. */ function creditUser (address recv, uint128 value, address token, uint128 bookedEth, bool useReserves) private { if (useReserves) { creditSurplus(recv, value, token); creditRemainder(recv, token, bookedEth); } else { creditTransfer(recv, value, token, bookedEth); } } /* @notice Handles any refund necessary after a credit has been paid to the user's * surplus collateral balance. */ function creditRemainder (address recv, address token, uint128 bookedEth) private { if (token.isEtherNative()) { refundEther(recv, bookedEth); } } /* @notice Settles a credit with an external transfer to user. */ function creditTransfer (address recv, uint128 value, address token, uint128 bookedEth) internal { if (token.isEtherNative()) { payEther(recv, value, bookedEth); } else { TransferHelper.safeTransfer(token, recv, value); } } /* @notice Settles a debit with an external transfer from user. */ function debitTransfer (address recv, uint128 value, address token, uint128 bookedEth) internal { if (token.isEtherNative()) { collectEther(recv, value, bookedEth); } else { collectToken(recv, value, token); } } /* @notice Pays a native Ethereum credit to the user (and refunds any overpay in * the transction, since by definition they have no debit.) */ function payEther (address recv, uint128 value, uint128 overpay) private { TransferHelper.safeEtherSend(recv, value + overpay); } /* @notice Collects a debt in the form of native Ether. Since the only way to pay * Ether is as msg.value, this function checks that's sufficient to cover * the debt and pays the difference as a refund. * @dev Because of the risk of double-spend, this must *never* be called more than * once in a transaction. * @param recv The address to send any over-payment refunds to. * @param value The amount of Ether owed to the exchange. msg.value must exceed * this threshold. * @param paidEth The amount of Ether paid by the user in this transaction (usually * msg.value) */ function collectEther (address recv, uint128 value, uint128 paidEth) private { require(paidEth >= value, "EC"); uint128 overpay = paidEth - value; refundEther(recv, overpay); } /* @notice Refunds any overpaid native Eth (if any) */ function refundEther (address recv, uint128 overpay) private { if (overpay > 0) { TransferHelper.safeEtherSend(recv, overpay); } } /* @notice Collects a token debt from a specfic debtor. * @dev Note that this function does *not* assert that the post-transfer balance * is correct. CrocSwap is not safe to use for any fee-on-transfer tokens * or any other tokens that break ERC20 transfer functionality. * * @param recv The address of the debtor being collected from. * @param value The total amount of tokens being collected. * @param token The address of the ERC20 token tracker. */ function collectToken (address recv, uint128 value, address token) private { TransferHelper.safeTransferFrom(token, recv, address(this), value); } /* @notice Credits a user's surplus collateral account at the exchange (instead of * directly sending the tokens to their address) */ function creditSurplus (address recv, uint128 value, address token) private { bytes32 key = tokenKey(recv, token); userBals_[key].surplusCollateral_ += value; } /* @notice Debits the tokens owed from the user's pre-existing surplus collateral * balance at the exchange. * @return remainder The amount of the debit that cannot be satisfied by surplus * collateral alone (0 othersize). */ function debitSurplus (address recv, uint128 value, address token) private returns (uint128 remainder) { bytes32 key = tokenKey(recv, token); UserBalance storage bal = userBals_[key]; uint128 balance = bal.surplusCollateral_; if (balance > value) { bal.surplusCollateral_ -= value; } else { bal.surplusCollateral_ = 0; remainder = value - balance; } } /* @notice Returns true if the net settled flow is equal or better to the user's * minimum expected amount. (Otherwise upstream should revert the tx.) */ function passesLimit (int128 flow, int128 limitQty) private pure returns (bool) { return flow <= limitQty; } /* @notice If true, determines that the settlement flow should be ignored because * it's economically meaningless and not worth transacting. */ function moreThanDust (int128 flow, uint128 dustThresh) private pure returns (bool) { if (isDebit(flow)) { return true; } else { return uint128(-flow) > dustThresh; } } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; pragma experimental ABIEncoderV2; import '../libraries/Directives.sol'; import '../libraries/PoolSpecs.sol'; import '../libraries/PriceGrid.sol'; import '../libraries/KnockoutLiq.sol'; /* @title Storage layout base layer * * @notice Only exists to enforce a single consistent storage layout. Not * designed to be externally used. All storage in any CrocSwap contract * is defined here. That allows easy use of delegatecall() to move code * over the 24kb into proxy contracts. * * @dev Any contract or mixin with local defined storage variables *must* * define those storage variables here and inherit this mixin. Failure * to do this may lead to storage layout inconsistencies between proxy * contracts. */ contract StorageLayout { // Re-entrant lock. Should always be reset to 0x0 after the end of every // top-level call. Any top-level call should fail on if this value is non- // zero. // // Inside a call this address is always set to the beneficial owner that // the call is being made on behalf of. Therefore any positions, tokens, // or liquidity can only be accessed if and only if they're owned by the // value lockHolder_ is currently set to. // // In the case of third party relayer or router calls, this value should // always be set to the *client* that the call is being made for, and never // the msg.sender caller that is acting on the client behalf's. (Of course // for security, third party calls made on a client's behalf must always // be authorized by the client either by pre-approval or signature.) address internal lockHolder_; // Indicates whether a given protocolCmd() call is operating in escalated // privileged mode. *Must* always be reset to false after every call. bool internal sudoMode_; bool internal msgValSpent_; // If set to false, then the embedded hot-path (swap()) is not enabled and // users must use the hot proxy for the hot-path. By default set to true. bool internal hotPathOpen_; bool internal inSafeMode_; // The protocol take rate for relayer tips. Represented in 1/256 fractions uint8 internal relayerTakeRate_; // Slots for sidecar proxy contracts address[65536] internal proxyPaths_; // Address of the current dex protocol authority. Can be transferred address internal authority_; /**************************************************************/ // LevelBook /**************************************************************/ struct BookLevel { uint96 bidLots_; uint96 askLots_; uint64 feeOdometer_; } mapping(bytes32 => BookLevel) internal levels_; /**************************************************************/ /**************************************************************/ // Knockout Counters /**************************************************************/ mapping(bytes32 => KnockoutLiq.KnockoutPivot) internal knockoutPivots_; mapping(bytes32 => KnockoutLiq.KnockoutMerkle) internal knockoutMerkles_; mapping(bytes32 => KnockoutLiq.KnockoutPos) internal knockoutPos_; /**************************************************************/ /**************************************************************/ // TickCensus /**************************************************************/ mapping(bytes32 => uint256) internal mezzanine_; mapping(bytes32 => uint256) internal terminus_; /**************************************************************/ /**************************************************************/ // PoolRegistry /**************************************************************/ mapping(uint256 => PoolSpecs.Pool) internal templates_; mapping(bytes32 => PoolSpecs.Pool) internal pools_; mapping(address => PriceGrid.ImproveSettings) internal improves_; uint128 internal newPoolLiq_; uint8 internal protocolTakeRate_; /**************************************************************/ /**************************************************************/ // ProtocolAccount /**************************************************************/ mapping(address => uint128) internal feesAccum_; /**************************************************************/ /**************************************************************/ // PositionRegistrar /**************************************************************/ struct RangePosition { uint128 liquidity_; uint64 feeMileage_; uint32 timestamp_; bool atomicLiq_; } struct RangePosition72 { uint128 liquidity_; uint72 feeMileage_; uint32 timestamp_; bool atomicLiq_; } struct AmbientPosition { uint128 seeds_; uint32 timestamp_; } mapping(bytes32 => RangePosition) internal positions_; mapping(bytes32 => AmbientPosition) internal ambPositions_; /**************************************************************/ /**************************************************************/ // LiquidityCurve /**************************************************************/ mapping(bytes32 => CurveMath.CurveState) internal curves_; /**************************************************************/ /**************************************************************/ // UserBalance settings /**************************************************************/ struct UserBalance { // Multiple loosely related fields are grouped together to allow // off-chain users to optimize calls to minimize cold SLOADS by // hashing needed data to the same slots. uint128 surplusCollateral_; uint32 nonce_; uint32 agentCallsLeft_; } mapping(bytes32 => UserBalance) internal userBals_; /**************************************************************/ address treasury_; uint64 treasuryStartTime_; // Since take rate is represented in 1/256, this represents a maximum possible take // rate of 50%. uint8 MAX_TAKE_RATE = 128; mapping(bytes32 => RangePosition72) internal positions72_; } /* @notice Contains the storage or storage hash offsets of the fields and sidecars * in StorageLayer. * * @dev Note that if the struct of StorageLayer changes, these slot locations *will* * change, and the values below will have to be manually updated. */ library CrocSlots { // Slot location of storage slots and/or hash map storage slot offsets. Values below // can be used to directly read state in CrocSwapDex by other contracts. uint constant public AUTHORITY_SLOT = 0; uint constant public LVL_MAP_SLOT = 65538; uint constant public KO_PIVOT_SLOT = 65539; uint constant public KO_MERKLE_SLOT = 65540; uint constant public KO_POS_SLOT = 65541; uint constant public POOL_TEMPL_SLOT = 65544; uint constant public POOL_PARAM_SLOT = 65545; uint constant public FEE_MAP_SLOT = 65548; uint constant public POS_MAP_SLOT = 65549; uint constant public AMB_MAP_SLOT = 65550; uint constant public CURVE_MAP_SLOT = 65551; uint constant public BAL_MAP_SLOT = 65552; uint constant public POS_MAP_SLOT_72 = 65554; // The slots of the currently attached sidecar proxy contracts. These are set by // covention and should be expanded over time as more sidecars are installed. For // backwards compatibility, upgraders should never break existing interface on // a pre-existing proxy sidecar. uint16 constant BOOT_PROXY_IDX = 0; uint16 constant SWAP_PROXY_IDX = 1; uint16 constant LP_PROXY_IDX = 128; uint16 constant COLD_PROXY_IDX = 3; uint16 constant LONG_PROXY_IDX = 130; uint16 constant MICRO_PROXY_IDX = 131; uint16 constant MULTICALL_PROXY_IDX = 6; uint16 constant KNOCKOUT_LP_PROXY_IDX = 7; uint16 constant FLAG_CROSS_PROXY_IDX = 3500; uint16 constant SAFE_MODE_PROXY_PATH = 9999; // Used as proxy contracts by previous deployments. These slots should not be re-used // to preserve backwards compatibility. uint16 constant LP_PROXY_LEGACY_IDX = 2; uint16 constant LONG_PROXY_LEGACY_IDX = 4; uint16 constant MICRO_PROXY_LEGACY_IDX = 5; } // Not used in production. Just used so we can easily check struct size in hardhat. contract StoragePrototypes is StorageLayout { UserBalance bal_; CurveMath.CurveState curve_; RangePosition pos_; AmbientPosition amb_; BookLevel lvl_; }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; import '../libraries/BitMath.sol'; import '../libraries/Bitmaps.sol'; import '../libraries/TickMath.sol'; import './StorageLayout.sol'; /* @title Tick census mixin. * * @notice Tracks which tick indices have an active liquidity bump, making it gas * efficient for random read and writes, and to find the next bump tick boundary * on the curve. * * @dev Note that this mixin works with the full set of possible int24 values. * Whereas other parts of the protocol set a MIN_TICK and MAX_TICK that are * that well within the type bounds of int24. It's the responsibility of * calling code to assure that ticks being set are within the MIN_TICK and * MAX_TICK, and this library does *not* provide those checks. */ contract TickCensus is StorageLayout { using Bitmaps for uint256; using Bitmaps for int24; /* Tick positions are stored in three layers of 8-bit/256-slot bitmaps. Recursively * they indicate whether any given 24-bit tick index is active. * The first layer (lobby) represents the 8-bit tick root. If we did store this * layer, we'd only need a single 256-bit bitmap per pool. However we do *not* * store this layer, because it adds an unnecessary SLOAD/SSTORE operation on * almost all operations. Instead users can query this layer by checking whether * mezzanine key is set for each bit. The tradeoff is that lobby bitmap queries * are no longer O(1) random access but O(N) seeks. However at most there are 256 * SLOAD on a lobby-layer seek, and spills at the lobby layer are rare (moving * between multiple lobby bits requires a 65,000% price change). This gas tradeoff * is virtually always justified. * * The second layer (mezzanine) maps whether each 16-bit tick root is set. An * entry will be set if and only if *any* tick index in the 8-bit range is set. * Because there are 256^2 slots, this is represented as a map from the first 8- * bits in the root to individual 8-bit/256-slot bitmaps for the middle 8-bits * at that root. * * The final layer (terminus) directly maps whether individual tick indices are * set. Because there are 256^3 possible slots, this is represnted as a mapping * from the first 16-bit tick root to individual 8-bit/256-slot bitmaps of the * terminal 8-bits within that root. */ /* @notice Returns the associated bitmap for the terminus position (bottom layer) * of the tick index. * @param poolIdx The hash key associated with the pool being queried. * @param tick A price tick index within the neighborhood that we want the bitmap for. * @return The bitmap of the 256-tick neighborhood. */ function terminusBitmap (bytes32 poolIdx, int24 tick) internal view returns (uint256) { bytes32 idx = encodeTerm(poolIdx, tick); return terminus_[idx]; } /* @notice Returns the associated bitmap for the mezzanine position (middle layer) * of the tick index. * @param poolIdx The hash key associated with the pool being queried. * @param tick A price tick index within the neighborhood that we want the bitmap for. * @return The mezzanine bitmap of the 65536-tick neighborhood. */ function mezzanineBitmap (bytes32 poolIdx, int24 tick) internal view returns (uint256) { bytes32 idx = encodeMezz(poolIdx, tick); return mezzanine_[idx]; } /* @notice Returns true if the tick index is currently set. Indicates an tick exists * at that index. * @param poolIdx The hash key associated with the pool being queried. * @param tick The price tick that we're querying. */ function hasTickBookmark (bytes32 poolIdx, int24 tick) internal view returns (bool) { uint256 bitmap = terminusBitmap(poolIdx, tick); uint8 term = tick.termBit(); return bitmap.isBitSet(term); } /* @notice Mark the tick index as active. * @dev Idempotent. Can be called repeatedly on previously initialized ticks. * @param poolIdx The hash key associated with the pool being queried. * @param tick The price tick that we're marking as enabled. */ function bookmarkTick (bytes32 poolIdx, int24 tick) internal { uint256 mezzMask = 1 << tick.mezzBit(); uint256 termMask = 1 << tick.termBit(); mezzanine_[encodeMezz(poolIdx, tick)] |= mezzMask; terminus_[encodeTerm(poolIdx, tick)] |= termMask; } /* @notice Unset the tick index as no longer active. Take care of any book keeping * related to the recursive bitmap levels. * @dev Idempontent. Can be called repeatedly even if tick was previously * forgotten. * @param poolIdx The hash key associated with the pool being queried. * @param tick The price tick that we're marking as disabled. */ function forgetTick (bytes32 poolIdx, int24 tick) internal { uint256 mezzMask = ~(1 << tick.mezzBit()); uint256 termMask = ~(1 << tick.termBit()); bytes32 termIdx = encodeTerm(poolIdx, tick); uint256 termUpdate = terminus_[termIdx] & termMask; terminus_[termIdx] = termUpdate; if (termUpdate == 0) { bytes32 mezzIdx = encodeMezz(poolIdx, tick); uint256 mezzUpdate = mezzanine_[mezzIdx] & mezzMask; mezzanine_[mezzIdx] = mezzUpdate; } } /* @notice Finds an inner-bound conservative liquidity tick boundary based on * the terminus map at a starting tick point. Because liquidity actually bumps * at the bottom of the tick, the result is assymetric on direction. When seeking * an upper barrier, it'll be the tick that we cross into. For lower barriers, it's * the tick that we cross out of, and therefore could even be the starting tick. * * @dev For gas efficiency this method only looks at a previously loaded terminus * bitmap. Often for moves of that size we don't even need to look past the * terminus boundary. So there's no point doing a mezzanine layer seek unless we * end up needing it. * * @param poolIdx The hash key associated with the pool being queried. * @param isUpper - If true indicates that we're looking for an upper boundary. * @param startTick - The current tick index that we're finding the boundary from. * * @return boundTick - The tick index that we can conservatively move to without * potentially hitting any currently active liquidity bump points. * @return isSpill - If true indicates that the boundary represents the end of the * inner terminus bitmap neighborhood. Based on this we have to actually check whether * we've reached teh true end of the liquidity range, or just the end of the known * neighborhood. */ function pinBitmap (bytes32 poolIdx, bool isUpper, int24 startTick) internal view returns (int24 boundTick, bool isSpill) { uint256 termBitmap = terminusBitmap(poolIdx, startTick); uint16 shiftTerm = startTick.termBump(isUpper); int16 tickMezz = startTick.mezzKey(); (boundTick, isSpill) = pinTermMezz (isUpper, shiftTerm, tickMezz, termBitmap); } /* @notice Formats the tick bit horizon index and sets the flag for whether it * represents whether the seeks spills over the terminus neighborhood */ function pinTermMezz (bool isUpper, uint16 shiftTerm, int16 tickMezz, uint256 termBitmap) private pure returns (int24 nextTick, bool spillBit) { (uint8 nextTerm, bool spillTrunc) = termBitmap.bitAfterTrunc(shiftTerm, isUpper); spillBit = doesSpillBit(isUpper, spillTrunc, termBitmap); nextTick = spillBit ? spillOverPin(isUpper, tickMezz) : Bitmaps.weldMezzTerm(tickMezz, nextTerm); } /* @notice Returns true if the tick seek reaches the end of the inner terminus * bitmap neighborhood. If that happens, it's like reaching the end of the map. * It's returned as the boundary point, but the the user must be aware that the tick * may or may not represent an active liquidity tick and check accordingly. */ function doesSpillBit (bool isUpper, bool spillTrunc, uint256 termBitmap) private pure returns (bool spillBit) { if (isUpper) { spillBit = spillTrunc; } else { bool bumpAtFloor = termBitmap.isBitSet(0); spillBit = bumpAtFloor ? false : spillTrunc; } } /* @notice Formats the censored horizon tick index when the seek has spilled out of * the terminus bitmap neighborhood. */ function spillOverPin (bool isUpper, int16 tickMezz) private pure returns (int24) { if (isUpper) { return tickMezz == Bitmaps.zeroMezz(isUpper) ? Bitmaps.zeroTick(isUpper) : Bitmaps.weldMezzTerm(tickMezz + 1, Bitmaps.zeroTerm(!isUpper)); } else { return Bitmaps.weldMezzTerm(tickMezz, 0); } } /* @notice Determines the next tick bump boundary tick starting using recursive * bitmap lookup. Follows the same up/down assymetry as pinBitmap(). Upper bump * is the tick being crossed *into*, lower bump is the tick being crossed *out of* * * @dev This is a much more gas heavy operation because it recursively looks * though all three layers of bitmaps. It should only be called if pinBitmap() * can't find the boundary in the terminus layer. * * @param poolIdx The hash key associated with the pool being queried. * @param borderTick - The current tick that we want to seek a tick liquidity * boundary from. For defined behavior this tick must occur at the border of * terminus bitmap. For lower borders, must be the tick from the start of the byte. * For upper borders, must be the tick past the end of the byte. Any spill result * from pinTermMezz() is safe. * @param isUpper - The direction of the boundary. If true seek an upper boundary. * * @return (int24) - The tick index of the next tick boundary with an active * liquidity bump. The result is assymetric boundary for upper/lower ticks. */ function seekMezzSpill (bytes32 poolIdx, int24 borderTick, bool isUpper) internal view returns (int24) { if (isUpper && borderTick == type(int24).max) { return type(int24).max; } if (!isUpper && borderTick == type(int24).min) { return type(int24).min; } (uint8 lobbyBorder, uint8 mezzBorder) = rootsForBorder(borderTick, isUpper); // Most common case is that the next neighboring bitmap on the border has // an active tick. So first check here to save gas in the hotpath. (int24 pin, bool spills) = seekAtTerm(poolIdx, lobbyBorder, mezzBorder, isUpper); if (!spills) { return pin; } // Next check to see if we can find a neighbor in the mezzanine. This almost // always happens except for very sparse pools. (pin, spills) = seekAtMezz(poolIdx, lobbyBorder, mezzBorder, isUpper); if (!spills) { return pin; } // Finally iterate through the lobby layer. return seekOverLobby(poolIdx, lobbyBorder, isUpper); } /* @notice Seeks the next tick bitmap by searching in the adjacent neighborhood. */ function seekAtTerm (bytes32 poolIdx, uint8 lobbyBit, uint8 mezzBit, bool isUpper) private view returns (int24, bool) { uint256 neighborBitmap = terminus_ [encodeTermWord(poolIdx, lobbyBit, mezzBit)]; (uint8 termBit, bool spills) = neighborBitmap.bitAfterTrunc(0, isUpper); if (spills) { return (0, true); } return (Bitmaps.weldLobbyPosMezzTerm(lobbyBit, mezzBit, termBit), false); } /* @notice Seeks the next tick bitmap by searching in the current mezzanine * neighborhood. * @dev This covers a span of 65 thousand ticks, so should capture most cases. */ function seekAtMezz (bytes32 poolIdx, uint8 lobbyBit, uint8 mezzBorder, bool isUpper) private view returns (int24, bool) { uint256 neighborMezz = mezzanine_ [encodeMezzWord(poolIdx, lobbyBit)]; uint8 mezzShift = Bitmaps.bitRelate(mezzBorder, isUpper); (uint8 mezzBit, bool spills) = neighborMezz.bitAfterTrunc(mezzShift, isUpper); if (spills) { return (0, true); } return seekAtTerm(poolIdx, lobbyBit, mezzBit, isUpper); } /* @notice Used when the tick is not contained in the mezzanine. We walk through the * the mezzanine tick bitmaps one by one until we find an active tick bit. */ function seekOverLobby (bytes32 poolIdx, uint8 lobbyBit, bool isUpper) private view returns (int24) { return isUpper ? seekLobbyUp(poolIdx, lobbyBit) : seekLobbyDown(poolIdx, lobbyBit); } /* Unlike the terminus and mezzanine layer, we don't store a bitmap at the lobby * layer. Instead we iterate through the top-level bits until we find an active * mezzanine. This requires a maximum of 256 iterations, and can be gas intensive. * However moves at this level represent 65,000% price changes and are very rare. */ function seekLobbyUp (bytes32 poolIdx, uint8 lobbyBit) private view returns (int24) { uint8 MAX_MEZZ = 0; unchecked { // Unchecked because we want idx to wrap around to 0, to check all 256 bits for (uint8 i = lobbyBit + 1; i > 0; ++i) { (int24 tick, bool spills) = seekAtMezz(poolIdx, i, MAX_MEZZ, true); if (!spills) { return tick; } } } return Bitmaps.zeroTick(true); } /* Same logic as seekLobbyUp(), but the inverse direction. */ function seekLobbyDown (bytes32 poolIdx, uint8 lobbyBit) private view returns (int24) { uint8 MIN_MEZZ = 255; unchecked { // Unchecked because we want idx to wrap around to 255, to check all 256 bits for (uint8 i = lobbyBit - 1; i < 255; --i) { (int24 tick, bool spills) = seekAtMezz(poolIdx, i, MIN_MEZZ, false); if (!spills) { return tick; } } } return Bitmaps.zeroTick(false); } /* @notice Splits out the lobby bits and the mezzanine bits from the 24-bit price * tick index associated with the type of border tick used in seekMezzSpill() * call */ function rootsForBorder (int24 borderTick, bool isUpper) private pure returns (uint8 lobbyBit, uint8 mezzBit) { // Because pinTermMezz returns a border *on* the previous bitmap, we need to // decrement by one to get the seek starting point. int24 pinTick = isUpper ? borderTick : (borderTick - 1); lobbyBit = pinTick.lobbyBit(); mezzBit = pinTick.mezzBit(); } /* @notice Encodes the hash key for the mezzanine neighborhood of the tick. */ function encodeMezz (bytes32 poolIdx, int24 tick) private pure returns (bytes32) { int8 wordPos = tick.lobbyKey(); return keccak256(abi.encodePacked(poolIdx, wordPos)); } /* @notice Encodes the hash key for the terminus neighborhood of the tick. */ function encodeTerm (bytes32 poolIdx, int24 tick) private pure returns (bytes32) { int16 wordPos = tick.mezzKey(); return keccak256(abi.encodePacked(poolIdx, wordPos)); } /* @notice Encodes the hash key for the mezzanine neighborhood of the first 8-bits * of a tick index. (This is all that's needed to determine mezzanine.) */ function encodeMezzWord (bytes32 poolIdx, int8 lobbyPos) private pure returns (bytes32) { return keccak256(abi.encodePacked(poolIdx, lobbyPos)); } /* @notice Encodes the hash key for the mezzanine neighborhood of the first 8-bits * of a tick index. (This is all that's needed to determine mezzanine.) */ function encodeMezzWord (bytes32 poolIdx, uint8 lobbyPos) private pure returns (bytes32) { return encodeMezzWord(poolIdx, Bitmaps.uncastBitmapIndex(lobbyPos)); } /* @notice Encodes the hash key for the terminus neighborhood of the first 16-bits * of a tick index. (This is all that's needed to determine terminus.) */ function encodeTermWord (bytes32 poolIdx, uint8 lobbyPos, uint8 mezzPos) private pure returns (bytes32) { int16 mezzIdx = Bitmaps.weldLobbyMezz (Bitmaps.uncastBitmapIndex(lobbyPos), mezzPos); return keccak256(abi.encodePacked(poolIdx, mezzIdx)); } }
// SPDX-License-Identifier: GPL-3 pragma solidity 0.8.19; import '../libraries/Directives.sol'; import '../libraries/PoolSpecs.sol'; import '../libraries/PriceGrid.sol'; import '../libraries/SwapCurve.sol'; import '../libraries/CurveMath.sol'; import '../libraries/CurveRoll.sol'; import '../libraries/Chaining.sol'; import '../interfaces/ICrocLpConduit.sol'; import './PositionRegistrar.sol'; import './LiquidityCurve.sol'; import './LevelBook.sol'; import './KnockoutCounter.sol'; import './ProxyCaller.sol'; import './AgentMask.sol'; /* @title Trade matcher mixin * @notice Provides a unified facility for calling the core atomic trade actions * on a pre-loaded liquidity curve: * 1) Mint amibent liquidity * 2) Mint range liquidity * 3) Burn ambient liquidity * 4) Burn range liquidity * 5) Swap */ contract TradeMatcher is PositionRegistrar, LiquidityCurve, KnockoutCounter, ProxyCaller { using SafeCast for int256; using SafeCast for int128; using SafeCast for uint256; using SafeCast for uint128; using TickMath for uint128; using LiquidityMath for uint96; using LiquidityMath for uint128; using PoolSpecs for PoolSpecs.Pool; using CurveRoll for CurveMath.CurveState; using CurveMath for CurveMath.CurveState; using SwapCurve for CurveMath.CurveState; using Directives for Directives.ConcentratedDirective; using Chaining for Chaining.PairFlow; /* @notice Mints ambient liquidity (i.e. liquidity that stays active at every * price point) on to the curve. * * @param curve The object representing the pre-loaded liquidity curve. Will be * updated in memory after this call, but it's the caller's * responsbility to check it back into storage. * @param liqAdded The amount of ambient liquidity being minted represented as * sqrt(X*Y) where X,Y are the collateral reserves in a constant- * product AMM * @param poolHash The hash indexing the pool this liquidity curve applies to. * @param lpOwner The address of the ICrocLpConduit the LP position will be * assigned to. (If zero the user will directly own the LP.) * * @return baseFlow The amount of base-side token collateral required by this * operations. Will always be positive indicating, a debit from * the user to the pool. * @return quoteFlow The amount of quote-side token collateral required by thhis * operation. */ function mintAmbient (CurveMath.CurveState memory curve, uint128 liqAdded, bytes32 poolHash, address lpOwner) internal returns (int128 baseFlow, int128 quoteFlow) { uint128 liqSeeds = mintPosLiq(lpOwner, poolHash, liqAdded, curve.seedDeflator_); depositConduit(poolHash, liqSeeds, curve.seedDeflator_, lpOwner); (uint128 base, uint128 quote) = liquidityReceivable(curve, liqSeeds); (baseFlow, quoteFlow) = signMintFlow(base, quote); } /* @notice Like mintAmbient(), but the liquidity is permanetely locked into the pool, * and therefore cannot be later burned by the user. */ function lockAmbient (CurveMath.CurveState memory curve, uint128 liqAdded) internal pure returns (int128, int128) { (uint128 base, uint128 quote) = liquidityReceivable(curve, liqAdded); return signMintFlow(base, quote); } /* @notice Burns ambient liquidity from the curve. * * @param curve The object representing the pre-loaded liquidity curve. Will be * updated in memory after this call, but it's the caller's * responsbility to check it back into storage. * @param liqAdded The amount of ambient liquidity being minted represented as * sqrt(X*Y) where X,Y are the collateral reserves in a constant- * product AMM * @param poolHash The hash indexing the pool this liquidity curve applies to. * * @return baseFlow The amount of base-side token collateral returned by this * operations. Will always be negative indicating, a credit from * the pool to the user. * @return quoteFlow The amount of quote-side token collateral returned by this * operation. */ function burnAmbient (CurveMath.CurveState memory curve, uint128 liqBurned, bytes32 poolHash, address lpOwner) internal returns (int128, int128) { uint128 liqSeeds = burnPosLiq(lpOwner, poolHash, liqBurned, curve.seedDeflator_); withdrawConduit(poolHash, liqSeeds, curve.seedDeflator_, lpOwner); (uint128 base, uint128 quote) = liquidityPayable(curve, liqSeeds); return signBurnFlow(base, quote); } /* @notice Mints concernated liquidity within a range on to the curve. * * @param curve The object representing the pre-loaded liquidity curve. Will be * updated in memory after this call, but it's the caller's * responsbility to check it back into storage. * @param prickTick The tick index of the curve's current price. * @param lowTick The tick index of the lower boundary of the range order. * @param highTick The tick index of the upper boundary of the range order. * @param liqAdded The amount of ambient liquidity being minted represented as * sqrt(X*Y) where X,Y are the collateral reserves in a constant- * product AMM * @param poolHash The hash indexing the pool this liquidity curve applies to. * @param lpConduit The address of the ICrocLpConduit the LP position will be * assigned to. (If zero the user will directly own the LP.) * * @return baseFlow The amount of base-side token collateral required by this * operations. Will always be positive indicating, a debit from * the user to the pool. * @return quoteFlow The amount of quote-side token collateral required by thhis * operation. */ function mintRange (CurveMath.CurveState memory curve, int24 priceTick, int24 lowTick, int24 highTick, uint128 liquidity, bytes32 poolHash, address lpOwner) internal returns (int128 baseFlow, int128 quoteFlow) { uint72 feeMileage = addBookLiq72(poolHash, priceTick, lowTick, highTick, liquidity.liquidityToLots(), curve.concGrowth_); mintPosLiq(lpOwner, poolHash, lowTick, highTick, liquidity, feeMileage); depositConduit(poolHash, lowTick, highTick, liquidity, feeMileage, lpOwner); (uint128 base, uint128 quote) = liquidityReceivable (curve, liquidity, lowTick, highTick); (baseFlow, quoteFlow) = signMintFlow(base, quote); } /* @notice Burns concernated liquidity within a specific range off of the curve. * * @param curve The object representing the pre-loaded liquidity curve. Will be * updated in memory after this call, but it's the caller's * responsbility to check it back into storage. * @param prickTick The tick index of the curve's current price. * @param lowTick The tick index of the lower boundary of the range order. * @param highTick The tick index of the upper boundary of the range order. * @param liqAdded The amount of ambient liquidity being minted represented as * sqrt(X*Y) where X,Y are the collateral reserves in a constant- * product AMM * @param poolHash The hash indexing the pool this liquidity curve applies to. * * @return baseFlow The amount of base-side token collateral returned by this * operations. Will always be negative indicating, a credit from * the pool to the user. * @return quoteFlow The amount of quote-side token collateral returned by this * operation. */ function burnRange (CurveMath.CurveState memory curve, int24 priceTick, int24 lowTick, int24 highTick, uint128 liquidity, bytes32 poolHash, address lpOwner) internal returns (int128, int128) { uint72 feeMileage = removeBookLiq72(poolHash, priceTick, lowTick, highTick, liquidity.liquidityToLots(), curve.concGrowth_); uint64 rewards = burnPosLiq(lpOwner, poolHash, lowTick, highTick, liquidity, feeMileage); withdrawConduit(poolHash, lowTick, highTick, liquidity, feeMileage, lpOwner); (uint128 base, uint128 quote) = liquidityPayable(curve, liquidity, rewards, lowTick, highTick); return signBurnFlow(base, quote); } /* @notice Dispatches the call to the ICrocLpConduit with the ambient liquidity * LP position that was minted. */ function depositConduit (bytes32 poolHash, uint128 liqSeeds, uint72 deflator, address lpConduit) private { // Equivalent to calling concentrated liquidity deposit with lowTick=0 and highTick=0 // Since a true range order can never have a width of zero, the receiving deposit // contract should recognize these values as always representing ambient liquidity int24 NA_LOW_TICK = 0; int24 NA_HIGH_TICK = 0; depositConduit(poolHash, NA_LOW_TICK, NA_HIGH_TICK, liqSeeds, deflator, lpConduit); } /* @notice Dispatches the call to the ICrocLpConduit with the concentrated liquidity * LP position that was minted. */ function depositConduit (bytes32 poolHash, int24 lowTick, int24 highTick, uint128 liq, uint72 mileage, address lpConduit) private { if (lpConduit != lockHolder_) { bool doesAccept = ICrocLpConduit(lpConduit). depositCrocLiq(lockHolder_, poolHash, lowTick, highTick, liq, mileage); require(doesAccept, "LP"); } } /* @notice Withdraws and sends ownership of the ambient liquidity to a third party conduit * explicitly nominated by the caller. */ function withdrawConduit (bytes32 poolHash, uint128 liqSeeds, uint72 deflator, address lpConduit) private { withdrawConduit(poolHash, 0, 0, liqSeeds, deflator, lpConduit); } /* @notice Withdraws and sends ownership of the liquidity to a third party conduit * explicitly nominated by the caller. */ function withdrawConduit (bytes32 poolHash, int24 lowTick, int24 highTick, uint128 liq, uint72 mileage, address lpConduit) private { if (lpConduit != lockHolder_) { bool doesAccept = ICrocLpConduit(lpConduit). withdrawCrocLiq(lockHolder_, poolHash, lowTick, highTick, liq, mileage); require(doesAccept, "LP"); } } /* @notice Mints a new knockout liquidity position, or adds to a previous position, * and updates the curve and debit flows accordingly. * * @param curve The current state of the liquidity curve. * @param priceTick The 24-bit tick of the pool's current price * @param loc The location of where to mint the knockout liquidity * @param liquidity The total amount of XY=K liquidity to mint. * @param poolHash The hash of the pool the curve applies to * @param knockoutBits The bitwise knockout parameters currently set on the pool. * * @return The incrmental base and quote debit flows from this action. */ function mintKnockout (CurveMath.CurveState memory curve, int24 priceTick, KnockoutLiq.KnockoutPosLoc memory loc, uint128 liquidity, bytes32 poolHash, uint8 knockoutBits) internal returns (int128 baseFlow, int128 quoteFlow) { addKnockoutLiq(poolHash, knockoutBits, priceTick, curve.concGrowth_, loc, liquidity.liquidityToLots()); (uint128 base, uint128 quote) = liquidityReceivable (curve, liquidity, loc.lowerTick_, loc.upperTick_); (baseFlow, quoteFlow) = signMintFlow(base, quote); } /* @notice Burns an existing knockout liquidity position and updates the curve * and flows accordingly. * * @param curve The current state of the liquidity curve. * @param priceTick The 24-bit tick of the pool's current price * @param loc The location of where to burn the knockout liquidity from * @param liquidity The total amount of XY=K liquidity to mint. * @param poolHash The hash of the pool the curve applies to * * @return The incrmental base and quote debit flows from this action. */ function burnKnockout (CurveMath.CurveState memory curve, int24 priceTick, KnockoutLiq.KnockoutPosLoc memory loc, uint128 liquidity, bytes32 poolHash) internal returns (int128 baseFlow, int128 quoteFlow) { (, , uint64 rewards) = rmKnockoutLiq(poolHash, priceTick, curve.concGrowth_, loc, liquidity.liquidityToLots()); (uint128 base, uint128 quote) = liquidityPayable (curve, liquidity, rewards, loc.lowerTick_, loc.upperTick_); (baseFlow, quoteFlow) = signBurnFlow(base, quote); } /* @notice Claims a post-knockout liquidity position using the ownership Merkle proof * supplied by the caller. * * @param curve The current state of the liquidity curve. * @param loc The location of where the post-knockout position was placed * @param root The root of the supplied Merkle proof * @param proof The Merkle proof that combined with the root must match the current * hash of the knockout slot * @param poolHash The hash of the pool the curve applies to * * @return The incrmental base and quote debit flows from this action. */ function claimKnockout (CurveMath.CurveState memory curve, KnockoutLiq.KnockoutPosLoc memory loc, uint160 root, uint256[] memory proof, bytes32 poolHash) internal returns (int128 baseFlow, int128 quoteFlow) { (uint96 lots, uint64 rewards) = claimPostKnockout(poolHash, loc, root, proof); uint128 liquidity = lots.lotsToLiquidity(); (uint128 base, uint128 quote) = liquidityHeldPayable (curve, liquidity, rewards, loc); (baseFlow, quoteFlow) = signBurnFlow(base, quote); } /* @notice Claims a post-knockout liquidity position using the ownership Merkle proof * supplied by the caller. * * @param curve The current state of the liquidity curve. * @param loc The location of where the post-knockout position was placed * @param root The root of the supplied Merkle proof * @param pivotTime The pivotTime of the knockout slot at the time the position was * minted. * @return The incrmental base and quote debit flows from this action. */ function recoverKnockout (KnockoutLiq.KnockoutPosLoc memory loc, uint32 pivotTime, bytes32 poolHash) internal returns (int128 baseFlow, int128 quoteFlow) { uint96 lots = recoverPostKnockout(poolHash, loc, pivotTime); uint128 liquidity = lots.lotsToLiquidity(); (uint128 base, uint128 quote) = liquidityHeldPayable(liquidity, loc); (baseFlow, quoteFlow) = signBurnFlow(base, quote); } /* @notice Harvests the accumulated rewards on a concentrated liquidity position. * * @param curve The object representing the pre-loaded liquidity curve. Will be * updated in memory after this call, but it's the caller's * responsbility to check it back into storage. * @param prickTick The tick index of the curve's current price. * @param lowTick The tick index of the lower boundary of the range order. * @param highTick The tick index of the upper boundary of the range order. * @param poolHash The hash indexing the pool this liquidity curve applies to. * * @return baseFlow The amount of base-side token collateral returned by this * operations. Will always be negative indicating, a credit from * the pool to the user. * @return quoteFlow The amount of quote-side token collateral returned by this * operation. */ function harvestRange (CurveMath.CurveState memory curve, int24 priceTick, int24 lowTick, int24 highTick, bytes32 poolHash, address lpOwner) internal returns (int128, int128) { uint72 feeMileage = clockFeeOdometer72(poolHash, priceTick, lowTick, highTick, curve.concGrowth_); uint128 rewards = harvestPosLiq(lpOwner, poolHash, lowTick, highTick, feeMileage); withdrawConduit(poolHash, lowTick, highTick, 0, feeMileage, lpOwner); (uint128 base, uint128 quote) = liquidityPayable(curve, rewards); return signBurnFlow(base, quote); } /* @notice Converts the unsigned flow associated with a mint operation to a pair * net settlement flow. (Will always be positive because a mint requires use * to pay collateral to the pool.) */ function signMintFlow (uint128 base, uint128 quote) private pure returns (int128, int128) { return (base.toInt128Sign(), quote.toInt128Sign()); } /* @notice Converts the unsigned flow associated with a burn operation to a pair * net settlement flow. (Will always be negative because a burn requires use * to pay collateral to the pool.) */ function signBurnFlow (uint128 base, uint128 quote) private pure returns (int128, int128){ return (-(base.toInt128Sign()), -(quote.toInt128Sign())); } /* @notice Executes the pending swap through the order book, adjusting the * liquidity curve and level book as needed based on the swap's impact. * * @dev This is probably the most complex single function in the codebase. For * small local moves, which don't cross extant levels in the book, it acts * like a constant-product AMM curve. For large swaps which cross levels, * it iteratively re-adjusts the AMM curve on every level cross, and performs * the necessary book-keeping on each crossed level entry. * * @param accum The accumulator for the flows generated by the executable swap. * The realized flows on the swap will be written into the memory-based * accumulator fields of this struct. The caller is responsible for * ultaimtely paying and collecting those flows. * @param curve The starting liquidity curve state. Any changes created by the * swap on this struct are updated in memory. But the caller is * responsible for committing the final state to EVM storage. * @param midTick The price tick associated with the current price on the curve. * @param swap The user specified directive governing the size, direction and limit * price of the swap to be executed. * @param pool The pool's market specification notably its swap fee rate and the * protocol take rate. */ function sweepSwapLiq (Chaining.PairFlow memory accum, CurveMath.CurveState memory curve, int24 midTick, Directives.SwapDirective memory swap, PoolSpecs.PoolCursor memory pool) internal { require(swap.isBuy_ ? curve.priceRoot_ <= swap.limitPrice_ : curve.priceRoot_ >= swap.limitPrice_, "SD"); // Keep iteratively executing more quantity until we either reach our limit price // or have zero quantity left to execute. bool doMore = hasSwapLeft(curve, swap); while (doMore) { // Swap to furthest point we can based on the local bitmap. Don't bother // seeking a bump outside the local neighborhood yet, because we're not sure // if the swap will exhaust the bitmap. (int24 bumpTick, bool spillsOver) = pinBitmap (pool.hash_, swap.isBuy_, midTick); curve.swapToLimit(accum, swap, pool.head_, bumpTick); // The swap can be in one of four states at this point: 1) qty exhausted, // 2) limit price reached, 3) bump or barrier point reached on the curve. // The former two indicate the swap is complete. The latter means we have to // find the next bump point and possibly adjust AMM liquidity. doMore = hasSwapLeft(curve, swap); if (doMore) { // The spillsOver variable indicates that we reached stopped because we // reached the end of the local bitmap, rather than actually hitting a // level bump. Therefore we should query the global bitmap, find the next // bump point, and keep swapping across the constant-product curve until // if/when we hit that point. if (spillsOver) { int24 liqTick = seekMezzSpill(pool.hash_, bumpTick, swap.isBuy_); bool tightSpill = (bumpTick == liqTick); bumpTick = liqTick; // In some corner cases the local bitmap border also happens to // be the next bump point. If so, we're done with this inner section. // Otherwise, we keep swapping since we still have some distance on // the curve to cover until we reach a bump point. if (!tightSpill) { curve.swapToLimit(accum, swap, pool.head_, bumpTick); doMore = hasSwapLeft(curve, swap); } } // Perform book-keeping related to crossing the level bump, update // the locally tracked tick of the curve price (rather than wastefully // we calculating it since we already know it), then begin the swap // loop again. if (doMore) { midTick = knockInTick(accum, bumpTick, curve, swap, pool.hash_); } } } } /* @notice Determines if we've terminated the swap execution. I.e. fully exhausted * the specified swap quantity *OR* hit the directive's limit price. */ function hasSwapLeft (CurveMath.CurveState memory curve, Directives.SwapDirective memory swap) private pure returns (bool) { bool inLimit = swap.isBuy_ ? curve.priceRoot_ < swap.limitPrice_ : curve.priceRoot_ > swap.limitPrice_; return inLimit && (swap.qty_ > 0); } /* @notice Performs all the necessary book keeping related to crossing an extant * level bump on the curve. * * @dev Note that this function updates the level book data structure directly on * the EVM storage. But it only updates the liquidity curve state *in memory*. * This is for gas efficiency reasons, as the same curve struct may be updated * many times in a single swap. The caller must take responsibility for * committing the final curve state back to EVM storage. * * @params bumpTick The tick index where the bump occurs. * @params isBuy The direction the bump happens from. If true, curve's price is * moving through the bump starting from a lower price and going to a * higher price. If false, the opposite. * @params curve The pre-bump state of the local constant-product AMM curve. Updated * to reflect the liquidity added/removed from rolling through the * bump. * @param swap The user directive governing the size, direction and limit price of the * swap to be executed. * @param poolHash The key hash mapping to the pool we're executive over. * * @return The tick index that the curve and its price are living in after the call * completes. */ function knockInTick (Chaining.PairFlow memory accum, int24 bumpTick, CurveMath.CurveState memory curve, Directives.SwapDirective memory swap, bytes32 poolHash) private returns (int24) { unchecked { if (!Bitmaps.isTickFinite(bumpTick)) { return bumpTick; } bumpLiquidity(curve, bumpTick, swap.isBuy_, poolHash); (int128 paidBase, int128 paidQuote, uint128 burnSwap) = curve.shaveAtBump(swap.inBaseQty_, swap.isBuy_, swap.qty_); accum.accumFlow(paidBase, paidQuote); // burn down qty from shaveAtBump is always validated to be less than remaining swap.qty_ // so this will never underflow swap.qty_ -= burnSwap; // When selling down, the next tick leg actually occurs *below* the bump tick // because the bump barrier is the first price on a tick. return swap.isBuy_ ? bumpTick : bumpTick - 1; // Valid ticks are well above {min(int128)-1}, so will never underflow } } /* @notice Performs the book-keeping related to crossing a concentrated liquidity * bump tick, and adjusts the in-memory curve object with the change of * AMM liquidity. */ function bumpLiquidity (CurveMath.CurveState memory curve, int24 bumpTick, bool isBuy, bytes32 poolHash) private { (int128 liqDelta, bool knockoutFlag) = crossLevel(poolHash, bumpTick, isBuy, curve.concGrowth_); curve.concLiq_ = curve.concLiq_.addDelta(liqDelta); if (knockoutFlag) { int128 knockoutDelta = callCrossFlag (poolHash, bumpTick, isBuy, curve.concGrowth_); curve.concLiq_ = curve.concLiq_.addDelta(knockoutDelta); } } }
// SPDX-License-Identifier: MIT pragma solidity >= 0.4.22 <0.9.0; library console { address constant CONSOLE_ADDRESS = address(0x000000000000000000636F6e736F6c652e6c6f67); function _sendLogPayload(bytes memory payload) private view { uint256 payloadLength = payload.length; address consoleAddress = CONSOLE_ADDRESS; assembly { let payloadStart := add(payload, 32) let r := staticcall(gas(), consoleAddress, payloadStart, payloadLength, 0, 0) } } function log() internal view { _sendLogPayload(abi.encodeWithSignature("log()")); } function logInt(int256 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(int256)", p0)); } function logUint(uint256 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256)", p0)); } function logString(string memory p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(string)", p0)); } function logBool(bool p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool)", p0)); } function logAddress(address p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(address)", p0)); } function logBytes(bytes memory p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes)", p0)); } function logBytes1(bytes1 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes1)", p0)); } function logBytes2(bytes2 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes2)", p0)); } function logBytes3(bytes3 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes3)", p0)); } function logBytes4(bytes4 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes4)", p0)); } function logBytes5(bytes5 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes5)", p0)); } function logBytes6(bytes6 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes6)", p0)); } function logBytes7(bytes7 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes7)", p0)); } function logBytes8(bytes8 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes8)", p0)); } function logBytes9(bytes9 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes9)", p0)); } function logBytes10(bytes10 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes10)", p0)); } function logBytes11(bytes11 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes11)", p0)); } function logBytes12(bytes12 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes12)", p0)); } function logBytes13(bytes13 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes13)", p0)); } function logBytes14(bytes14 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes14)", p0)); } function logBytes15(bytes15 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes15)", p0)); } function logBytes16(bytes16 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes16)", p0)); } function logBytes17(bytes17 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes17)", p0)); } function logBytes18(bytes18 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes18)", p0)); } function logBytes19(bytes19 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes19)", p0)); } function logBytes20(bytes20 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes20)", p0)); } function logBytes21(bytes21 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes21)", p0)); } function logBytes22(bytes22 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes22)", p0)); } function logBytes23(bytes23 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes23)", p0)); } function logBytes24(bytes24 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes24)", p0)); } function logBytes25(bytes25 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes25)", p0)); } function logBytes26(bytes26 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes26)", p0)); } function logBytes27(bytes27 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes27)", p0)); } function logBytes28(bytes28 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes28)", p0)); } function logBytes29(bytes29 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes29)", p0)); } function logBytes30(bytes30 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes30)", p0)); } function logBytes31(bytes31 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes31)", p0)); } function logBytes32(bytes32 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes32)", p0)); } function log(uint256 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256)", p0)); } function log(string memory p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(string)", p0)); } function log(bool p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool)", p0)); } function log(address p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(address)", p0)); } function log(uint256 p0, uint256 p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256)", p0, p1)); } function log(uint256 p0, string memory p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,string)", p0, p1)); } function log(uint256 p0, bool p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool)", p0, p1)); } function log(uint256 p0, address p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,address)", p0, p1)); } function log(string memory p0, uint256 p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint256)", p0, p1)); } function log(string memory p0, string memory p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string)", p0, p1)); } function log(string memory p0, bool p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool)", p0, p1)); } function log(string memory p0, address p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address)", p0, p1)); } function log(bool p0, uint256 p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256)", p0, p1)); } function log(bool p0, string memory p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string)", p0, p1)); } function log(bool p0, bool p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool)", p0, p1)); } function log(bool p0, address p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address)", p0, p1)); } function log(address p0, uint256 p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint256)", p0, p1)); } function log(address p0, string memory p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string)", p0, p1)); } function log(address p0, bool p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool)", p0, p1)); } function log(address p0, address p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address)", p0, p1)); } function log(uint256 p0, uint256 p1, uint256 p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256)", p0, p1, p2)); } function log(uint256 p0, uint256 p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string)", p0, p1, p2)); } function log(uint256 p0, uint256 p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool)", p0, p1, p2)); } function log(uint256 p0, uint256 p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address)", p0, p1, p2)); } function log(uint256 p0, string memory p1, uint256 p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256)", p0, p1, p2)); } function log(uint256 p0, string memory p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string)", p0, p1, p2)); } function log(uint256 p0, string memory p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool)", p0, p1, p2)); } function log(uint256 p0, string memory p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address)", p0, p1, p2)); } function log(uint256 p0, bool p1, uint256 p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256)", p0, p1, p2)); } function log(uint256 p0, bool p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string)", p0, p1, p2)); } function log(uint256 p0, bool p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool)", p0, p1, p2)); } function log(uint256 p0, bool p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address)", p0, p1, p2)); } function log(uint256 p0, address p1, uint256 p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256)", p0, p1, p2)); } function log(uint256 p0, address p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string)", p0, p1, p2)); } function log(uint256 p0, address p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool)", p0, p1, p2)); } function log(uint256 p0, address p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address)", p0, p1, p2)); } function log(string memory p0, uint256 p1, uint256 p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256)", p0, p1, p2)); } function log(string memory p0, uint256 p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string)", p0, p1, p2)); } function log(string memory p0, uint256 p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool)", p0, p1, p2)); } function log(string memory p0, uint256 p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address)", p0, p1, p2)); } function log(string memory p0, string memory p1, uint256 p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256)", p0, p1, p2)); } function log(string memory p0, string memory p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,string)", p0, p1, p2)); } function log(string memory p0, string memory p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,bool)", p0, p1, p2)); } function log(string memory p0, string memory p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,address)", p0, p1, p2)); } function log(string memory p0, bool p1, uint256 p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256)", p0, p1, p2)); } function log(string memory p0, bool p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,string)", p0, p1, p2)); } function log(string memory p0, bool p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool)", p0, p1, p2)); } function log(string memory p0, bool p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,address)", p0, p1, p2)); } function log(string memory p0, address p1, uint256 p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256)", p0, p1, p2)); } function log(string memory p0, address p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,string)", p0, p1, p2)); } function log(string memory p0, address p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,bool)", p0, p1, p2)); } function log(string memory p0, address p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,address)", p0, p1, p2)); } function log(bool p0, uint256 p1, uint256 p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256)", p0, p1, p2)); } function log(bool p0, uint256 p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string)", p0, p1, p2)); } function log(bool p0, uint256 p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool)", p0, p1, p2)); } function log(bool p0, uint256 p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address)", p0, p1, p2)); } function log(bool p0, string memory p1, uint256 p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256)", p0, p1, p2)); } function log(bool p0, string memory p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,string)", p0, p1, p2)); } function log(bool p0, string memory p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool)", p0, p1, p2)); } function log(bool p0, string memory p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,address)", p0, p1, p2)); } function log(bool p0, bool p1, uint256 p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256)", p0, p1, p2)); } function log(bool p0, bool p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string)", p0, p1, p2)); } function log(bool p0, bool p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool)", p0, p1, p2)); } function log(bool p0, bool p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address)", p0, p1, p2)); } function log(bool p0, address p1, uint256 p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256)", p0, p1, p2)); } function log(bool p0, address p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,string)", p0, p1, p2)); } function log(bool p0, address p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool)", p0, p1, p2)); } function log(bool p0, address p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,address)", p0, p1, p2)); } function log(address p0, uint256 p1, uint256 p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256)", p0, p1, p2)); } function log(address p0, uint256 p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string)", p0, p1, p2)); } function log(address p0, uint256 p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool)", p0, p1, p2)); } function log(address p0, uint256 p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address)", p0, p1, p2)); } function log(address p0, string memory p1, uint256 p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256)", p0, p1, p2)); } function log(address p0, string memory p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,string)", p0, p1, p2)); } function log(address p0, string memory p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,bool)", p0, p1, p2)); } function log(address p0, string memory p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,address)", p0, p1, p2)); } function log(address p0, bool p1, uint256 p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256)", p0, p1, p2)); } function log(address p0, bool p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,string)", p0, p1, p2)); } function log(address p0, bool p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool)", p0, p1, p2)); } function log(address p0, bool p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,address)", p0, p1, p2)); } function log(address p0, address p1, uint256 p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256)", p0, p1, p2)); } function log(address p0, address p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,string)", p0, p1, p2)); } function log(address p0, address p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,bool)", p0, p1, p2)); } function log(address p0, address p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,address)", p0, p1, p2)); } function log(uint256 p0, uint256 p1, uint256 p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, uint256 p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,string)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, uint256 p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,bool)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, uint256 p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,address)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, string memory p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,string)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,bool)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,address)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, bool p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,string)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,bool)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,address)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, address p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,string)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,bool)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,address)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, uint256 p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, uint256 p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,string)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, uint256 p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,bool)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, uint256 p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,address)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, string memory p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,string)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,bool)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,address)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, bool p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,string)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,bool)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,address)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, address p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,string)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,bool)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,address)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, uint256 p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, uint256 p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,string)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, uint256 p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,bool)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, uint256 p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,address)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, string memory p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,string)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,bool)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,address)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, bool p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,string)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,bool)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,address)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, address p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,string)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,bool)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,address)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, uint256 p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, uint256 p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,string)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, uint256 p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,bool)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, uint256 p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,address)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, string memory p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,string)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,bool)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,address)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, bool p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,string)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,bool)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,address)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, address p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,string)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,bool)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,address)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, uint256 p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,uint256)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, uint256 p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,string)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, uint256 p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,bool)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, uint256 p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,address)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, string memory p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,uint256)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,string)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,bool)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,address)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, bool p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,uint256)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,string)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,bool)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,address)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, address p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,uint256)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,string)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,bool)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,address)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, uint256 p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,uint256)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, uint256 p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,string)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, uint256 p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,bool)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, uint256 p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,address)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, string memory p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,string,uint256)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,string,string)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,string,bool)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,string,address)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, bool p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,uint256)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,string)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,bool)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,address)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, address p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,address,uint256)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,address,string)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,address,bool)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,address,address)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, uint256 p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,uint256)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, uint256 p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,string)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, uint256 p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,bool)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, uint256 p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,address)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, string memory p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,uint256)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,string)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,bool)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,address)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, bool p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,uint256)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,string)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,bool)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,address)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, address p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,uint256)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,string)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,bool)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,address)", p0, p1, p2, p3)); } function log(string memory p0, address p1, uint256 p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,uint256)", p0, p1, p2, p3)); } function log(string memory p0, address p1, uint256 p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,string)", p0, p1, p2, p3)); } function log(string memory p0, address p1, uint256 p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,bool)", p0, p1, p2, p3)); } function log(string memory p0, address p1, uint256 p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,address)", p0, p1, p2, p3)); } function log(string memory p0, address p1, string memory p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,string,uint256)", p0, p1, p2, p3)); } function log(string memory p0, address p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,string,string)", p0, p1, p2, p3)); } function log(string memory p0, address p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,string,bool)", p0, p1, p2, p3)); } function log(string memory p0, address p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,string,address)", p0, p1, p2, p3)); } function log(string memory p0, address p1, bool p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,uint256)", p0, p1, p2, p3)); } function log(string memory p0, address p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,string)", p0, p1, p2, p3)); } function log(string memory p0, address p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,bool)", p0, p1, p2, p3)); } function log(string memory p0, address p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,address)", p0, p1, p2, p3)); } function log(string memory p0, address p1, address p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,address,uint256)", p0, p1, p2, p3)); } function log(string memory p0, address p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,address,string)", p0, p1, p2, p3)); } function log(string memory p0, address p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,address,bool)", p0, p1, p2, p3)); } function log(string memory p0, address p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,address,address)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, uint256 p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,uint256)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, uint256 p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,string)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, uint256 p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,bool)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, uint256 p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,address)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, string memory p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,uint256)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,string)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,bool)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,address)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, bool p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,uint256)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,string)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,bool)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,address)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, address p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,uint256)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,string)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,bool)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,address)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, uint256 p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,uint256)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, uint256 p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,string)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, uint256 p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,bool)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, uint256 p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,address)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, string memory p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,uint256)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,string)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,bool)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,address)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, bool p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,uint256)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,string)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,bool)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,address)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, address p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,uint256)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,string)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,bool)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,address)", p0, p1, p2, p3)); } function log(bool p0, bool p1, uint256 p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,uint256)", p0, p1, p2, p3)); } function log(bool p0, bool p1, uint256 p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,string)", p0, p1, p2, p3)); } function log(bool p0, bool p1, uint256 p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,bool)", p0, p1, p2, p3)); } function log(bool p0, bool p1, uint256 p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,address)", p0, p1, p2, p3)); } function log(bool p0, bool p1, string memory p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,uint256)", p0, p1, p2, p3)); } function log(bool p0, bool p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,string)", p0, p1, p2, p3)); } function log(bool p0, bool p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,bool)", p0, p1, p2, p3)); } function log(bool p0, bool p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,address)", p0, p1, p2, p3)); } function log(bool p0, bool p1, bool p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,uint256)", p0, p1, p2, p3)); } function log(bool p0, bool p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,string)", p0, p1, p2, p3)); } function log(bool p0, bool p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,bool)", p0, p1, p2, p3)); } function log(bool p0, bool p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,address)", p0, p1, p2, p3)); } function log(bool p0, bool p1, address p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,uint256)", p0, p1, p2, p3)); } function log(bool p0, bool p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,string)", p0, p1, p2, p3)); } function log(bool p0, bool p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,bool)", p0, p1, p2, p3)); } function log(bool p0, bool p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,address)", p0, p1, p2, p3)); } function log(bool p0, address p1, uint256 p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,uint256)", p0, p1, p2, p3)); } function log(bool p0, address p1, uint256 p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,string)", p0, p1, p2, p3)); } function log(bool p0, address p1, uint256 p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,bool)", p0, p1, p2, p3)); } function log(bool p0, address p1, uint256 p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,address)", p0, p1, p2, p3)); } function log(bool p0, address p1, string memory p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,uint256)", p0, p1, p2, p3)); } function log(bool p0, address p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,string)", p0, p1, p2, p3)); } function log(bool p0, address p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,bool)", p0, p1, p2, p3)); } function log(bool p0, address p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,address)", p0, p1, p2, p3)); } function log(bool p0, address p1, bool p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,uint256)", p0, p1, p2, p3)); } function log(bool p0, address p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,string)", p0, p1, p2, p3)); } function log(bool p0, address p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,bool)", p0, p1, p2, p3)); } function log(bool p0, address p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,address)", p0, p1, p2, p3)); } function log(bool p0, address p1, address p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,uint256)", p0, p1, p2, p3)); } function log(bool p0, address p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,string)", p0, p1, p2, p3)); } function log(bool p0, address p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,bool)", p0, p1, p2, p3)); } function log(bool p0, address p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,address)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, uint256 p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,uint256)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, uint256 p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,string)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, uint256 p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,bool)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, uint256 p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,address)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, string memory p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,uint256)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,string)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,bool)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,address)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, bool p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,uint256)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,string)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,bool)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,address)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, address p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,uint256)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,string)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,bool)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,address)", p0, p1, p2, p3)); } function log(address p0, string memory p1, uint256 p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,uint256)", p0, p1, p2, p3)); } function log(address p0, string memory p1, uint256 p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,string)", p0, p1, p2, p3)); } function log(address p0, string memory p1, uint256 p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,bool)", p0, p1, p2, p3)); } function log(address p0, string memory p1, uint256 p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,address)", p0, p1, p2, p3)); } function log(address p0, string memory p1, string memory p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,string,uint256)", p0, p1, p2, p3)); } function log(address p0, string memory p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,string,string)", p0, p1, p2, p3)); } function log(address p0, string memory p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,string,bool)", p0, p1, p2, p3)); } function log(address p0, string memory p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,string,address)", p0, p1, p2, p3)); } function log(address p0, string memory p1, bool p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,uint256)", p0, p1, p2, p3)); } function log(address p0, string memory p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,string)", p0, p1, p2, p3)); } function log(address p0, string memory p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,bool)", p0, p1, p2, p3)); } function log(address p0, string memory p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,address)", p0, p1, p2, p3)); } function log(address p0, string memory p1, address p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,address,uint256)", p0, p1, p2, p3)); } function log(address p0, string memory p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,address,string)", p0, p1, p2, p3)); } function log(address p0, string memory p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,address,bool)", p0, p1, p2, p3)); } function log(address p0, string memory p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,address,address)", p0, p1, p2, p3)); } function log(address p0, bool p1, uint256 p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,uint256)", p0, p1, p2, p3)); } function log(address p0, bool p1, uint256 p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,string)", p0, p1, p2, p3)); } function log(address p0, bool p1, uint256 p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,bool)", p0, p1, p2, p3)); } function log(address p0, bool p1, uint256 p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,address)", p0, p1, p2, p3)); } function log(address p0, bool p1, string memory p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,uint256)", p0, p1, p2, p3)); } function log(address p0, bool p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,string)", p0, p1, p2, p3)); } function log(address p0, bool p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,bool)", p0, p1, p2, p3)); } function log(address p0, bool p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,address)", p0, p1, p2, p3)); } function log(address p0, bool p1, bool p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,uint256)", p0, p1, p2, p3)); } function log(address p0, bool p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,string)", p0, p1, p2, p3)); } function log(address p0, bool p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,bool)", p0, p1, p2, p3)); } function log(address p0, bool p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,address)", p0, p1, p2, p3)); } function log(address p0, bool p1, address p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,uint256)", p0, p1, p2, p3)); } function log(address p0, bool p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,string)", p0, p1, p2, p3)); } function log(address p0, bool p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,bool)", p0, p1, p2, p3)); } function log(address p0, bool p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,address)", p0, p1, p2, p3)); } function log(address p0, address p1, uint256 p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,uint256)", p0, p1, p2, p3)); } function log(address p0, address p1, uint256 p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,string)", p0, p1, p2, p3)); } function log(address p0, address p1, uint256 p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,bool)", p0, p1, p2, p3)); } function log(address p0, address p1, uint256 p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,address)", p0, p1, p2, p3)); } function log(address p0, address p1, string memory p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,string,uint256)", p0, p1, p2, p3)); } function log(address p0, address p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,string,string)", p0, p1, p2, p3)); } function log(address p0, address p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,string,bool)", p0, p1, p2, p3)); } function log(address p0, address p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,string,address)", p0, p1, p2, p3)); } function log(address p0, address p1, bool p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,uint256)", p0, p1, p2, p3)); } function log(address p0, address p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,string)", p0, p1, p2, p3)); } function log(address p0, address p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,bool)", p0, p1, p2, p3)); } function log(address p0, address p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,address)", p0, p1, p2, p3)); } function log(address p0, address p1, address p2, uint256 p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,address,uint256)", p0, p1, p2, p3)); } function log(address p0, address p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,address,string)", p0, p1, p2, p3)); } function log(address p0, address p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,address,bool)", p0, p1, p2, p3)); } function log(address p0, address p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,address,address)", p0, p1, p2, p3)); } }
{ "optimizer": { "enabled": true, "runs": 1000000 }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "libraries": {} }
[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"pool","type":"bytes32"},{"indexed":true,"internalType":"int24","name":"tick","type":"int24"},{"indexed":false,"internalType":"bool","name":"isBid","type":"bool"},{"indexed":false,"internalType":"uint32","name":"pivotTime","type":"uint32"},{"indexed":false,"internalType":"uint64","name":"feeMileage","type":"uint64"},{"indexed":false,"internalType":"uint160","name":"commitEntropy","type":"uint160"}],"name":"CrocKnockoutCross","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint16","name":"slot","type":"uint16"}],"name":"acceptCrocProxyRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"input","type":"bytes"}],"name":"userCmd","outputs":[{"internalType":"int128","name":"baseFlow","type":"int128"},{"internalType":"int128","name":"quoteFlow","type":"int128"}],"stateMutability":"payable","type":"function"}]
Contract Creation Code
608060405262010011805460ff60e01b1916600160e71b17905534801561002557600080fd5b50614e01806100356000396000f3fe6080604052600436106100295760003560e01c8063ac54c0fc1461002e578063f96dc78814610063575b600080fd5b34801561003a57600080fd5b5061004e610049366004614932565b610090565b60405190151581526020015b60405180910390f35b610076610071366004614972565b61009f565b60408051600f93840b81529190920b60208201520161005a565b61ffff81166080145b92915050565b60008060008060008060008060008060008060008e8e8101906100c29190614a27565b9a509a509a509a509a509a509a509a509a509a509a50600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610127575060005473ffffffffffffffffffffffffffffffffffffffff165b6101398b8b8b8b8b8b8b8b8b8a61019a565b909d509b5061014b8a8a8f8f8661051e565b7ffb06707626d33c17f01d8ca519595e2b6d7c970328373a4be77d3e86ac9e4fc48f8f8f8f6040516101809493929190614af4565b60405180910390a150505050505050505050509250929050565b6000807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60ff8d16016101e1576101d88b8b8b8b8b8b898c8c610550565b9150915061050f565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff560ff8d160161021e576101d88b8b8b8b8b60018c8a8d8d610592565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff460ff8d160161025b576101d88b8b8b8b8b60008c8a8d8d610592565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe60ff8d1601610296576101d88b8b8b8b8b8b898c8c6105e2565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeb60ff8d16016102d3576101d88b8b8b8b8b60018c8a8d8d610611565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffea60ff8d1601610310576101d88b8b8b8b8b60008c8a8d8d610611565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd60ff8d1601610349576101d88b8b8b89878a8a610658565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe160ff8d1601610384576101d88b8b8b60018a888b8b610697565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe060ff8d16016103bf576101d88b8b8b60008a888b8b610697565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc60ff8d16016103f8576101d88b8b8b89878a8a610703565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd760ff8d1601610433576101d88b8b8b60018a888b8b610731565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd660ff8d160161046e576101d88b8b8b60008a888b8b610731565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb60ff8d16016104a8576101d88b8b8b8b8b888b8b610788565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f496e76616c696420636f6d6d616e64000000000000000000000000000000000060448201526064015b60405180910390fd5b9a509a98505050505050505050565b60005473ffffffffffffffffffffffffffffffffffffffff1680610547818089888a89896107c9565b50505050505050565b60008060006105608c8c8c61081e565b9050610570818d8d8c8c8c6108f5565b61057f8989898489898c610a65565b9250925050995099975050505050505050565b60008060006105a68d8d8d8a8e8e8e610bb0565b90506000806105bc8f8f8f8f8f888e8e8e610550565b915091506105cc82828b8d610bee565b945094505050509a509a98505050505050505050565b60008060006105f28c8c8c61081e565b9050610602818d8d8c8c8c610c5c565b61057f8989898489898c610d1c565b60008060006106218d8d8d610e4b565b9050600061062e82610ed8565b9050600061064589600084600001518f8f8f610fad565b90506105cc8f8f8f8f8f868e8e8e6105e2565b60008060006106688a8a8a61081e565b9050610679818b8b6000808c6108f5565b610686878287878a611034565b925092505097509795505050505050565b60008060006106a78b8b8b610e4b565b905060006106b482610ed8565b905060006106c989600184600001518d611148565b90506000806106dd8f8f8f868e8e8e610658565b915091506106ed82828d8f610bee565b9650965050505050509850989650505050505050565b60008060006107138a8a8a61081e565b9050610724818b8b6000808c610c5c565b610686878287878a611198565b60008060006107418b8b8b610e4b565b9050600061074e82610ed8565b9050600061076389600084600001518d611148565b90506107748d8d8d848c8c8c610703565b945094505050509850989650505050505050565b60008060006107988b8b8b61081e565b90506107a9818c8c8b8b6000610c5c565b6107b788888388888b6111be565b92509250509850989650505050505050565b73ffffffffffffffffffffffffffffffffffffffff85166107fa576107f58787866001851615156112f3565b61080c565b61080c8787868860018616151561132f565b6105478787848660028616151561132f565b60408051610140810182526000606082018181526080830182905260a0830182905260c0830182905260e08301829052610100830182905261012083018290528252602082018190529181019190915261087d6201000985858561135f565b90506108888161148e565b6108ee576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f50490000000000000000000000000000000000000000000000000000000000006044820152606401610506565b9392505050565b604086015173ffffffffffffffffffffffffffffffffffffffff1615610a5d576040868101516000805492517f2dd148f100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff938416600482015233602482015288841660448201528784166064820152600287810b608483015286900b60a48201526fffffffffffffffffffffffffffffffff851660c4820152909290911690632dd148f19060e4015b6020604051808303816000875af11580156109d0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109f49190614b53565b905080610547576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600160248201527f5a000000000000000000000000000000000000000000000000000000000000006044820152606401610506565b505050505050565b6000806000610a798760200151878761150f565b9050610aac81610a9e83600001516fffffffffffffffffffffffffffffffff166115f9565b8c8c8c8c602001518a61194c565b8851606001519194509250610ac4908b908b906119cb565b60208088015160009081526201000f82526040908190208351928401516fffffffffffffffffffffffffffffffff9384167001000000000000000000000000000000009185168202178255918401516001909101805460608601516080870151939095167fffffffffffffffff0000000000000000000000000000000000000000000000009091161767ffffffffffffffff9485169093029290921777ffffffffffffffffffffffffffffffffffffffffffffffff16780100000000000000000000000000000000000000000000000093909116929092029190911790555b5097509795505050505050565b600080610bbe898989610e4b565b90506000610bcb82610ed8565b9050610be08760018360000151898989610fad565b9a9950505050505050505050565b6000806000610c0e856fffffffffffffffffffffffffffffffff16611a41565b9050838015610c22575086600f0b81600f0b135b15610c2f57809650610c4b565b83158015610c42575085600f0b81600f0b135b15610c4b578095505b868692509250505b94509492505050565b604086015173ffffffffffffffffffffffffffffffffffffffff1615610a5d576040868101516000805492517f2a179e9b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff938416600482015233602482015288841660448201528784166064820152600287810b608483015286900b60a48201526fffffffffffffffffffffffffffffffff851660c4820152909290911690632a179e9b9060e4016109b1565b6000806000610d308760200151878761150f565b9050610d6381610d5583600001516fffffffffffffffffffffffffffffffff166115f9565b8c8c8c8c602001518a611a75565b60208981015160009081526201000f82526040908190208551928601516fffffffffffffffffffffffffffffffff9384167001000000000000000000000000000000009185168202178255918601516001909101805460608801516080890151939095167fffffffffffffffff0000000000000000000000000000000000000000000000009091161767ffffffffffffffff9485169093029290921777ffffffffffffffffffffffffffffffffffffffffffffffff16780100000000000000000000000000000000000000000000000093909116929092029190911790559093509150610ba3565b60008273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1610610e8557600080fd5b6040805173ffffffffffffffffffffffffffffffffffffffff8087166020830152851691810191909152606081018390526080016040516020818303038152906040528051906020012090509392505050565b6040805160a0810182526000808252602082018190529181018290526060810182905260808101919091525060008181526201000f6020908152604091829020825160a08101845281546fffffffffffffffffffffffffffffffff808216808452700100000000000000000000000000000000928390048216958401959095526001909301549283169482019490945292810467ffffffffffffffff90811660608501527801000000000000000000000000000000000000000000000000909104166080830152610fa857600080fd5b919050565b6000806000610fbe87878787611afa565b915091506000610fee858484610fd48e8e611b9f565b6fffffffffffffffffffffffffffffffff16929190611c5d565b90508861101557611010816fffffffffffffffffffffffffffffffff16611cd7565b610be0565b6ffffffffffffffffffffffffffffff800169998505050505050505050565b60008060006110488760200151878761150f565b905061105a8189896020015187611d88565b60208981015160009081526201000f82526040908190208551928601516fffffffffffffffffffffffffffffffff9384167001000000000000000000000000000000009185168202178255918601516001909101805460608801516080890151939095167fffffffffffffffff0000000000000000000000000000000000000000000000009091161767ffffffffffffffff9485169093029290921777ffffffffffffffffffffffffffffffffffffffffffffffff16780100000000000000000000000000000000000000000000000093909116929092029190911790559093509150509550959350505050565b600080611173838561115a8989611b9f565b6fffffffffffffffffffffffffffffffff169190611de4565b90508461118a57611185816001614ba4565b61118c565b805b9150505b949350505050565b60008060006111ac8760200151878761150f565b905061105a8189896020015187611e3b565b60008060006111d28760200151878761150f565b9050611204816111f783600001516fffffffffffffffffffffffffffffffff166115f9565b8b8b8b6020015189611e97565b60208981015160009081526201000f82526040908190208551928601516fffffffffffffffffffffffffffffffff9384167001000000000000000000000000000000009185168202178255918601516001909101805460608801516080890151939095167fffffffffffffffff0000000000000000000000000000000000000000000000009091161767ffffffffffffffff9485169093029290921777ffffffffffffffffffffffffffffffffffffffffffffffff1678010000000000000000000000000000000000000000000000009390911692909202919091179055909350915050965096945050505050565b60006112fd611efd565b905082600f0b60001461131e5761131985858560008587611fd2565b611328565b6113288482612013565b5050505050565b73ffffffffffffffffffffffffffffffffffffffff821661134f57600080fd5b6000610a5d868686868587611fd2565b60408051610140810182526000606082018181526080830182905260a0830182905260c0830182905260e0830182905261010083018290526101208301829052825260208201819052918101829052906113ba858585610e4b565b600081815260208881526040808320815160e081018352905460ff808216835261ffff6101008304811695840195909552630100000082048116938301939093526401000000008104909316606082015266010000000000008304821660808201526701000000000000008304821660a0820152680100000000000000009092041660c082018190529293509161145290869061204b565b60408051606081018252938452602084019490945273ffffffffffffffffffffffffffffffffffffffff16928201929092529695505050505050565b805151600090600160ff9091161115611503576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600360248201527f49505300000000000000000000000000000000000000000000000000000000006044820152606401610506565b50515160ff1660011490565b6040805160a08101825260008082526020820181905291810182905260608101829052608081019190915261154384610ed8565b9050826fffffffffffffffffffffffffffffffff1681600001516fffffffffffffffffffffffffffffffff1610158015611593575080516fffffffffffffffffffffffffffffffff808416911611155b6108ee576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f52430000000000000000000000000000000000000000000000000000000000006044820152606401610506565b6000620100026fffffffffffffffffffffffffffffffff83161080159061164157506f0ffff5433e2b3d8211706e6102aa94726fffffffffffffffffffffffffffffffff8316105b61164a57600080fd5b77ffffffffffffffffffffffffffffffff0000000000000000604083901b166fffffffffffffffffffffffffffffffff811160071b81811c67ffffffffffffffff811160061b90811c63ffffffff811160051b90811c61ffff811160041b90811c60ff8111600390811b91821c600f811160021b90811c918211600190811b92831c979088119617909417909217179091171717608081106116f457607f810383901c91506116fe565b80607f0383901b91505b908002607f81811c60ff83811c9190911c800280831c81831c1c800280841c81841c1c800280851c81851c1c800280861c81861c1c800280871c81871c1c800280881c81881c1c800280891c81891c1c8002808a1c818a1c1c8002808b1c818b1c1c8002808c1c818c1c1c8002808d1c818d1c1c8002808e1c9c81901c9c909c1c80029c8d901c9e9d7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff808f0160401b60c09190911c678000000000000000161760c19b909b1c674000000000000000169a909a1760c29990991c672000000000000000169890981760c39790971c671000000000000000169690961760c49590951c670800000000000000169490941760c59390931c670400000000000000169290921760c69190911c670200000000000000161760c79190911c670100000000000000161760c89190911c6680000000000000161760c99190911c6640000000000000161760ca9190911c6620000000000000161760cb9190911c6610000000000000161760cc9190911c6608000000000000161760cd9190911c66040000000000001617693627a301d71055774c8581027ffffffffffffffffffffffffffffffffffd709b7e5480fba5a50fed5e62ffc5568101608090811d906fdb2df09e81959a81455e260799a0632f8301901d600281810b9083900b1461193d57886fffffffffffffffffffffffffffffffff166119198261206f565b6fffffffffffffffffffffffffffffffff161115611937578161193f565b8061193f565b815b9998505050505050505050565b600080600061197d858a8a8a6119738b6fffffffffffffffffffffffffffffffff166123e1565b8f608001516124b7565b905061198d84868a8a8a866124ff565b61199b85898989858961257e565b6000806119aa8c898c8c6126e3565b915091506119b88282612717565b909d909c509a5050505050505050505050565b6119d683838361275c565b611a3c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600160248201527f44000000000000000000000000000000000000000000000000000000000000006044820152606401610506565b505050565b60006f80000000000000000000000000000000826fffffffffffffffffffffffffffffffff1610611a7157600080fd5b5090565b6000806000611aa6858a8a8a611a9c8b6fffffffffffffffffffffffffffffffff166123e1565b8f6080015161278f565b90506000611ab885878b8b8b876127e9565b9050611ac8868a8a8a868a6128a5565b600080611ad88d8a858e8e612962565b91509150611ae68282612991565b955095505050505097509795505050505050565b600080611b098560020b61206f565b9150611b178460020b61206f565b9050816fffffffffffffffffffffffffffffffff16866fffffffffffffffffffffffffffffffff1611611b54578215611b4f57600080fd5b610c53565b806fffffffffffffffffffffffffffffffff16866fffffffffffffffffffffffffffffffff1610611b895782611b4f57600080fd5b8215611b96575084610c53565b94959350505050565b600060048215611bee57806fffffffffffffffffffffffffffffffff16846fffffffffffffffffffffffffffffffff1610611be357611bde8185614bcd565b611be6565b60005b915050610099565b611c0960046fffffffffffffffffffffffffffffffff614bcd565b6fffffffffffffffffffffffffffffffff16846fffffffffffffffffffffffffffffffff1611611c3d57611bde8185614ba4565b6fffffffffffffffffffffffffffffffff915050610099565b5092915050565b600083611c8857611c81856001611c73866129dc565b611c7c866129dc565b611c5d565b9050611190565b6000826fffffffffffffffffffffffffffffffff16846fffffffffffffffffffffffffffffffff1611611cbd57838303611cc1565b8284035b9050611ccf86600183611de4565b915050611190565b60006ffffffffffffffffffffffffffffff8008083169003611d55576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600860248201527f6f766572666c6f770000000000000000000000000000000000000000000000006044820152606401610506565b50600b90811c6e1fffffffffffffffffffffffffffff16600101901b7107fffffffffffffffffffffffffffffff8001690565b6000806000611d9d8486888a60600151612a31565b9050611db98582896060015167ffffffffffffffff1687612b0e565b600080611dc68984612b1f565b91509150611dd48282612717565b909a909950975050505050505050565b600082611e2e57611e296fffffffffffffffffffffffffffffffff8086169084160260401c5b77ffffffffffffffffffffffffffffffffffffffffffffffff16612b56565b611190565b611190611e0a8584612b8f565b6000806000611e508486888a60600151612bd8565b9050611e6c8582896060015167ffffffffffffffff1687612cca565b600080611e798984612cdf565b91509150611e878282612991565b9450945050505094509492505050565b6000806000611ead858989898d60800151612d1e565b90506000611ebe85878a8a86612d6a565b9050611ecf8689896000868a6128a5565b600080611edc8c84612cdf565b91509150611eea8282612991565b9550955050505050965096945050505050565b600080547501000000000000000000000000000000000000000000900460ff1615611f84576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f44530000000000000000000000000000000000000000000000000000000000006044820152606401610506565b611f8d34612ebd565b600080547fffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffff167501000000000000000000000000000000000000000000179055919050565b6000600f85900b1315611ff157611fec8685858585612edc565b610a5d565b6000600f85900b1215610a5d57610a5d8561200b86614bf6565b858585612f0f565b6fffffffffffffffffffffffffffffffff8116156120475761204782826fffffffffffffffffffffffffffffffff16612f37565b5050565b60006001828116811480612060576000612066565b606085901c5b95945050505050565b60007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5d892600283900b128015906120ac5750620cb14a600283900b13155b6120b557600080fd5b6000808360020b126120ca578260020b6120d2565b8260020b6000035b90506000816001166000036120f85770010000000000000000000000000000000061210a565b6ffffcb933bd6fad37aa2d162d1a5940015b70ffffffffffffffffffffffffffffffffff169050600282161561213e576ffff97272373d413259a46990580e213a0260801c5b600482161561215d576ffff2e50f5f656932ef12357cf3c7fdcc0260801c5b600882161561217c576fffe5caca7e10e4e61c3624eaa0941cd00260801c5b601082161561219b576fffcb9843d60f6159c9db58835c9266440260801c5b60208216156121ba576fff973b41fa98c081472e6896dfb254c00260801c5b60408216156121d9576fff2ea16466c96a3843ec78b326b528610260801c5b60808216156121f8576ffe5dee046a99a2a811c461f1969c30530260801c5b610100821615612218576ffcbe86c7900a88aedcffc83b479aa3a40260801c5b610200821615612238576ff987a7253ac413176f2b074cf7815e540260801c5b610400821615612258576ff3392b0822b70005940c7a398e4b70f30260801c5b610800821615612278576fe7159475a2c29b7443b29c7fa6e889d90260801c5b611000821615612298576fd097f3bdfd2022b8845ad8f792aa58250260801c5b6120008216156122b8576fa9f746462d870fdf8a65dc1f90e061e50260801c5b6140008216156122d8576f70d869a156d2a1b890bb3df62baf32f70260801c5b6180008216156122f8576f31be135f97d08fd981231505542fcfa60260801c5b62010000821615612319576f09aa508b5b7a84e1c677de54f3e99bc90260801c5b62020000821615612339576e5d6af8dedb81196699c329225ee6040260801c5b62040000821615612358576d2216e584f5fa1ea926041bedfe980260801c5b62080000821615612375576b048a170391f7dc42444e8fa20260801c5b60008460020b13156123b457807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff816123b0576123b0614c34565b0490505b680100000000000000008106156123cc5760016123cf565b60005b60ff16604082901c0192505050919050565b6000600a82901c6e3fffffffffffffffffffffffffffff8116906ffffffffffffffffffffffffffffffc00841690600116158080156124315750846fffffffffffffffffffffffffffffffff1682145b801561244857506bffffffffffffffffffffffff83105b6124ae576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f46440000000000000000000000000000000000000000000000000000000000006044820152606401610506565b50909392505050565b60006124c587878785613001565b6124d187878685613001565b6124dc8786856130ac565b6124e7878585613117565b6124f48787878786612d1e565b979650505050505050565b60408051606088901b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166020808301919091526034820188905260e887811b605484015286901b60578301528251603a818403018152605a9092018352815191810191909120600090815262010012909152206105478184846131a2565b60005473ffffffffffffffffffffffffffffffffffffffff828116911614610a5d57600080546040517f668e2cdb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff918216600482015260248101899052600288810b604483015287900b60648201526fffffffffffffffffffffffffffffffff8616608482015268ffffffffffffffffff851660a48201529083169063668e2cdb9060c4015b6020604051808303816000875af1158015612656573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061267a9190614b53565b905080610547576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f4c500000000000000000000000000000000000000000000000000000000000006044820152606401610506565b60008060008060006126fb89600001518989896132e4565b92509250925061270c8989836133e8565b611e8783838361340d565b600080612735846fffffffffffffffffffffffffffffffff16611a41565b612750846fffffffffffffffffffffffffffffffff16611a41565b915091505b9250929050565b600061ffff821661276d8186614c63565b60020b15801561206657506127828185614c63565b60020b1595945050505050565b60008061279d888786613484565b905060006127ac89878761353d565b90506127bb8989898988612d1e565b925081156127cd576127cd89886135ec565b80156127dd576127dd89876135ec565b50509695505050505050565b604080517fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606089901b166020808301919091526034820188905260e887811b605484015286901b60578301528251808303603a018152605a9092018352815191810191909120600090815262010012909152908120805461288e90790100000000000000000000000000000000000000000000000000900463ffffffff168861365b565b61289981858561371d565b98975050505050505050565b60005473ffffffffffffffffffffffffffffffffffffffff828116911614610a5d57600080546040517f93835c1e00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff918216600482015260248101899052600288810b604483015287900b60648201526fffffffffffffffffffffffffffffffff8616608482015268ffffffffffffffffff851660a4820152908316906393835c1e9060c401612637565b60008061297187878686613886565b909250905061298382828989896138d4565b909890975095505050505050565b6000806129af846fffffffffffffffffffffffffffffffff16611a41565b6129b890614bf6565b6129d3846fffffffffffffffffffffffffffffffff16611a41565b61275090614bf6565b600080826fffffffffffffffffffffffffffffffff1670010000000000000000000000000000000081612a1157612a11614c34565b0490506fffffffffffffffffffffffffffffffff81111561009957600080fd5b600080612a3e86866139c4565b9050612a5c6fffffffffffffffffffffffffffffffff8516846139ec565b8154909250612a7d906fffffffffffffffffffffffffffffffff1683613a3a565b81547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff91909116178155612ac1613a5f565b815463ffffffff91909116700100000000000000000000000000000000027fffffffffffffffffffffffff00000000ffffffffffffffffffffffffffffffff909116179055949350505050565b600080610a5d86838388888861257e565b600080600080612b2f8686613a79565b91509150612b3d8686613ad1565b612b498282600161340d565b9350935050509250929050565b806fffffffffffffffffffffffffffffffff811677ffffffffffffffffffffffffffffffffffffffffffffffff821614610fa857600080fd5b60006fffffffffffffffffffffffffffffffff821677ffffffffffffffffffffffffffffffff0000000000000000604085901b1681612bd057612bd0614c34565b049392505050565b600080612be586866139c4565b9050612c036fffffffffffffffffffffffffffffffff8516846139ec565b81549092506fffffffffffffffffffffffffffffffff90811690831610612c645780547fffffffffffffffffffffffff0000000000000000000000000000000000000000811682556fffffffffffffffffffffffffffffffff169150612cc1565b805482908290600090612c8a9084906fffffffffffffffffffffffffffffffff16614bcd565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff1602179055505b50949350505050565b612cd9846000808686866128a5565b50505050565b600080612cec8484613a79565b909250905061275584612d106fffffffffffffffffffffffffffffffff8616611a41565b612d1990614bf6565b613af0565b600080612d2d87868886613b2f565b67ffffffffffffffff1690506000612d4788868987613b2f565b67ffffffffffffffff908116915082612d608383614c85565b61193f9190614ca7565b60408051606087901b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166020808301919091526034820187905260e886811b605484015285901b60578301528251603a818403018152605a90920183528151918101919091206000908152620100129091529081208054700100000000000000000000000000000000900468ffffffffffffffffff908116908416811015612eb2576000612e2468ffffffffffffffffff861683613bae565b8354909150612e68906fffffffffffffffffffffffffffffffff1667ffffffffffffffff83160260301c5b71ffffffffffffffffffffffffffffffffffff16613c25565b83547fffffffffffffff000000000000000000ffffffffffffffffffffffffffffffff1670010000000000000000000000000000000068ffffffffffffffffff8816021784559350505b505095945050505050565b806fffffffffffffffffffffffffffffffff81168114610fa857600080fd5b8015612f03576000612eef868686613c58565b9050612efd86828686613d37565b50611328565b61132885858585613d85565b8015612f2b57612f20858585613db6565b611319858484613e35565b61132885858585613e5a565b60008273ffffffffffffffffffffffffffffffffffffffff168260405160006040518083038185875af1925050503d8060008114612f91576040519150601f19603f3d011682016040523d82523d6000602084013e612f96565b606091505b5050905080611a3c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f54460000000000000000000000000000000000000000000000000000000000006044820152606401610506565b600061300d8584613e9d565b80549091506bffffffffffffffffffffffff1615801561304a575080546c0100000000000000000000000090046bffffffffffffffffffffffff16155b15611328578360020b8360020b126130a257805477ffffffffffffffffffffffffffffffffffffffffffffffff16780100000000000000000000000000000000000000000000000067ffffffffffffffff8416021781555b6113288584613ef0565b60006130b88484613e9d565b80549091506bffffffffffffffffffffffff1660006130d78285613f78565b83547fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166bffffffffffffffffffffffff91909116179092555050505050565b60006131238484613e9d565b80549091506c0100000000000000000000000090046bffffffffffffffffffffffff1660006131528285613f78565b83546bffffffffffffffffffffffff9091166c01000000000000000000000000027fffffffffffffffff000000000000000000000000ffffffffffffffffffffffff909116179092555050505050565b82546fffffffffffffffffffffffffffffffff16600081156131e557508354700100000000000000000000000000000000900468ffffffffffffffffff166131e9565b5060005b60006132076fffffffffffffffffffffffffffffffff841686613a3a565b9050600061322268ffffffffffffffffff8616878587613f99565b9050600061322e613a5f565b885463ffffffff909116790100000000000000000000000000000000000000000000000000027fffffff00000000ffffffffffffffffffffffffffffffffffffffffffffffffff68ffffffffffffffffff909416700100000000000000000000000000000000027fffffffffffffff000000000000000000000000000000000000000000000000009092166fffffffffffffffffffffffffffffffff909516949094171791909116919091179095555050505050565b60008060008060006132f6878761404c565b91509150816fffffffffffffffffffffffffffffffff16896fffffffffffffffffffffffffffffffff16101561334a576133436fffffffffffffffffffffffffffffffff891683836140c3565b93506133dc565b806fffffffffffffffffffffffffffffffff16896fffffffffffffffffffffffffffffffff1610613399576133926fffffffffffffffffffffffffffffffff8916838361410e565b94506133dc565b6133b66fffffffffffffffffffffffffffffffff89168a836140c3565b93506133d56fffffffffffffffffffffffffffffffff8916838b61410e565b9450600192505b50509450945094915050565b611a3c83613407846fffffffffffffffffffffffffffffffff16611a41565b83614156565b6000806000856fffffffffffffffffffffffffffffffff16118061342e5750825b613439576000613444565b613444600486614ba4565b6000856fffffffffffffffffffffffffffffffff1611806134625750835b61346d576000613478565b613478600486614ba4565b91509150935093915050565b6000806134918585613e9d565b80549091506bffffffffffffffffffffffff1660006134b08286614199565b83547fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166bffffffffffffffffffffffff82169081178555909150158015613515575082546c0100000000000000000000000090046bffffffffffffffffffffffff16155b156135305761352487876141a5565b600193505050506108ee565b5060009695505050505050565b60008061354a8585613e9d565b80549091506c0100000000000000000000000090046bffffffffffffffffffffffff1660006135798286614199565b83547fffffffffffffffff000000000000000000000000ffffffffffffffffffffffff166c010000000000000000000000006bffffffffffffffffffffffff8316908102919091178555909150158015613515575082546bffffffffffffffffffffffff166135305761352487876141a5565b620100026000838360405160200161361192919091825260e81b602082015260230190565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181528151602092830120835290820192909252016000908120555050565b600a600083613668613a5f565b6136729190614cc9565b905060006136808383614ce6565b905060ff63ffffffff8216116113285760008481526201000960205260409020546601000000000000900460ff1663ffffffff82161015611328576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600160248201527f4a000000000000000000000000000000000000000000000000000000000000006044820152606401610506565b82546000906fffffffffffffffffffffffffffffffff168161373f828661422e565b86549091506137709068ffffffffffffffffff86811691700100000000000000000000000000000000900416613bae565b92506fffffffffffffffffffffffffffffffff8116156138565785547d010000000000000000000000000000000000000000000000000000000000900460ff1615613817576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f4f520000000000000000000000000000000000000000000000000000000000006044820152606401610506565b85547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff821617865561387d565b85547fffff0000000000000000000000000000000000000000000000000000000000001686555b50509392505050565b600080600061389b87600001518787876132e4565b919450925090506138ca876138c16fffffffffffffffffffffffffffffffff8916611a41565b61340790614bf6565b5094509492505050565b60008060008367ffffffffffffffff161180156139095750846080015167ffffffffffffffff168367ffffffffffffffff1611155b156139b957600061393a6fffffffffffffffffffffffffffffffff861667ffffffffffffffff86160260301c612e4f565b90506000816fffffffffffffffffffffffffffffffff16118015613985575085602001516fffffffffffffffffffffffffffffffff16816fffffffffffffffffffffffffffffffff16105b156139b7576000806139978884612cdf565b90925090506139a6828b614ba4565b99506139b2818a614ba4565b985050505b505b509495939450505050565b60006201000e60006139d6858561423a565b8152602001908152602001600020905092915050565b6000660100000000000075ffffffffffffffffffffffffffffffff000000000000603085901b168267ffffffffffffffff851683018281613a2f57613a2f614c34565b049695505050505050565b8082016fffffffffffffffffffffffffffffffff808416908216101561009957600080fd5b60004263ffffffff811115610fa85763ffffffff91505090565b6000806000613a8c848660600151614295565b8551909150613ab4906fffffffffffffffffffffffffffffffff80841691160260401c611e0a565b9250613ac7611e0a828760000151612b8f565b9150509250929050565b61204782612d19836fffffffffffffffffffffffffffffffff16611a41565b6020820151613b11906fffffffffffffffffffffffffffffffff16826142ea565b6fffffffffffffffffffffffffffffffff1660209092019190915250565b600080613b3c8686613e9d565b90508360020b8560020b1315613b7e578054611185907801000000000000000000000000000000000000000000000000900467ffffffffffffffff1684614d09565b547801000000000000000000000000000000000000000000000000900467ffffffffffffffff1695945050505050565b60006002613bbc8184614c85565b68ffffffffffffffffff168468ffffffffffffffffff161115611be357600081613be68587614ca7565b613bf09190614ca7565b905067ffffffffffffffff68ffffffffffffffffff821611613c125780613c1c565b67ffffffffffffffff5b92505050610099565b806fffffffffffffffffffffffffffffffff811671ffffffffffffffffffffffffffffffffffff821614610fa857600080fd5b600080613c658584614357565b600081815262010010602052604090208054919250906fffffffffffffffffffffffffffffffff908116908616811115613cfa57815486908390600090613cbf9084906fffffffffffffffffffffffffffffffff16614bcd565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff160217905550613d2d565b81547fffffffffffffffffffffffffffffffff00000000000000000000000000000000168255613d2a8187614bcd565b93505b5050509392505050565b6fffffffffffffffffffffffffffffffff831615613d6057613d5b84848484613d85565b612cd9565b73ffffffffffffffffffffffffffffffffffffffff8216612cd957612cd98482612013565b73ffffffffffffffffffffffffffffffffffffffff8216613dab57613d5b84848361438c565b612cd9848484614432565b6000613dc28483614357565b6000818152620100106020526040812080549293508592909190613df99084906fffffffffffffffffffffffffffffffff16614ba4565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff16021790555050505050565b73ffffffffffffffffffffffffffffffffffffffff8216611a3c57611a3c8382612013565b73ffffffffffffffffffffffffffffffffffffffff8216613e8057613d5b848483614450565b612cd98285856fffffffffffffffffffffffffffffffff16614475565b60006201000260008484604051602001613ec492919091825260e81b602082015260230190565b604051602081830303815290604052805190602001208152602001908152602001600020905092915050565b6000613efe8260020b6145de565b60ff166001901b90506000613f158360020b6145fa565b60ff166001901b905081620100066000613f2f8787614608565b81526020019081526020016000206000828254179250508190555080620100076000613f5b8787614653565b815260208101919091526040016000208054909117905550505050565b8082016bffffffffffffffffffffffff808416908216101561009957600080fd5b6000816fffffffffffffffffffffffffffffffff16600003613fbc575083611190565b836fffffffffffffffffffffffffffffffff16600003613fdd575081611190565b8268ffffffffffffffffff168568ffffffffffffffffff1603614001575083611190565b600061401786866140128682614ba4565b61467f565b9050600061402a8585614012818a614ba4565b9050614037816001614c85565b614042836001614c85565b6124f49190614c85565b6000808360020b8360020b1361406157600080fd5b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5d892600285900b121561409257600080fd5b620cb14a600284900b13156140a657600080fd5b6140af8461206f565b91506140ba8361206f565b90509250929050565b6000826fffffffffffffffffffffffffffffffff16826fffffffffffffffffffffffffffffffff161115614103576140fc8483856146ca565b90506108ee565b6140fc8484846146ca565b600080826fffffffffffffffffffffffffffffffff16846fffffffffffffffffffffffffffffffff161161414457838303614148565b8284035b905061206685826001614749565b8015611a3c57604083015161417d906fffffffffffffffffffffffffffffffff16836142ea565b6fffffffffffffffffffffffffffffffff166040840152505050565b60006108ee8284614d2a565b60006141b38260020b6145de565b60ff166001901b19905060006141cb8360020b6145fa565b60ff166001901b19905060006141e18585614653565b600081815262010007602052604081208054851690819055919250819003610a5d57600061420f8787614608565b6000908152620100066020526040902080548616905550505050505050565b60006108ee8284614bcd565b6040517fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606084901b166020820152603481018290526000906054015b60405160208183030381529060405280519060200120905092915050565b600066010000000000006fffffffffffffffffffffffffffffffff80851667ffffffffffffffff851683010290603082901c90811115612066576fffffffffffffffffffffffffffffffff9350505050610099565b60008082600f0b121561432157508082016fffffffffffffffffffffffffffffffff8084169082161061431c57600080fd5b610099565b826fffffffffffffffffffffffffffffffff168284019150816fffffffffffffffffffffffffffffffff16101561009957600080fd5b6040805173ffffffffffffffffffffffffffffffffffffffff8085166020830152831691810191909152600090606001614277565b816fffffffffffffffffffffffffffffffff16816fffffffffffffffffffffffffffffffff16101561441a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f45430000000000000000000000000000000000000000000000000000000000006044820152606401610506565b60006144268383614bcd565b9050612cd98482612013565b611a3c818430856fffffffffffffffffffffffffffffffff1661479c565b611a3c8361445e8385614ba4565b6fffffffffffffffffffffffffffffffff16612f37565b6040805173ffffffffffffffffffffffffffffffffffffffff8481166024830152604480830185905283518084039091018152606490920183526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb00000000000000000000000000000000000000000000000000000000179052915160009283929087169161450c9190614d4f565b6000604051808303816000865af19150503d8060008114614549576040519150601f19603f3d011682016040523d82523d6000602084013e61454e565b606091505b50915091508180156145785750805115806145785750808060200190518101906145789190614b53565b611328576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f54460000000000000000000000000000000000000000000000000000000000006044820152606401610506565b60006101006145f08360020b60081d90565b6100999190614d7e565b600061009961010083614c63565b600080600283900b60101d9050838160405160200161463492919091825260f81b602082015260210190565b6040516020818303038152906040528051906020012091505092915050565b600080600283900b60081d9050838160405160200161463492919091825260f01b602082015260220190565b6000816fffffffffffffffffffffffffffffffff16836fffffffffffffffffffffffffffffffff168568ffffffffffffffffff1602816146c1576146c1614c34565b04949350505050565b6000806146d78385614bcd565b905060006146e58685612b8f565b77ffffffffffffffffffffffffffffffffffffffffffffffff1690506000856fffffffffffffffffffffffffffffffff16836fffffffffffffffffffffffffffffffff16836147349190614da0565b61473e9190614db7565b9050613d2a81612ebd565b60006111908261477c5761475d8585612b8f565b77ffffffffffffffffffffffffffffffffffffffffffffffff16612ebd565b6fffffffffffffffffffffffffffffffff8086169085160260401c61475d565b6040805173ffffffffffffffffffffffffffffffffffffffff85811660248301528481166044830152606480830185905283518084039091018152608490920183526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f23b872dd00000000000000000000000000000000000000000000000000000000179052915160009283929088169161483b9190614d4f565b6000604051808303816000865af19150503d8060008114614878576040519150601f19603f3d011682016040523d82523d6000602084013e61487d565b606091505b50915091508180156148a75750805115806148a75750808060200190518101906148a79190614b53565b610a5d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f54460000000000000000000000000000000000000000000000000000000000006044820152606401610506565b73ffffffffffffffffffffffffffffffffffffffff8116811461492f57600080fd5b50565b6000806040838503121561494557600080fd5b82356149508161490d565b9150602083013561ffff8116811461496757600080fd5b809150509250929050565b6000806020838503121561498557600080fd5b823567ffffffffffffffff8082111561499d57600080fd5b818501915085601f8301126149b157600080fd5b8135818111156149c057600080fd5b8660208285010111156149d257600080fd5b60209290920196919550909350505050565b803560ff81168114610fa857600080fd5b8035600281900b8114610fa857600080fd5b80356fffffffffffffffffffffffffffffffff81168114610fa857600080fd5b60008060008060008060008060008060006101608c8e031215614a4957600080fd5b614a528c6149e4565b9a5060208c0135614a628161490d565b995060408c0135614a728161490d565b985060608c01359750614a8760808d016149f5565b9650614a9560a08d016149f5565b9550614aa360c08d01614a07565b9450614ab160e08d01614a07565b9350614ac06101008d01614a07565b9250614acf6101208d016149e4565b91506101408c0135614ae08161490d565b809150509295989b509295989b9093969950565b606081528360608201528385608083013760006080858301810191909152600f93840b60208301529190920b6040830152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0160101919050565b600060208284031215614b6557600080fd5b815180151581146108ee57600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6fffffffffffffffffffffffffffffffff818116838216019080821115611c5657611c56614b75565b6fffffffffffffffffffffffffffffffff828116828216039080821115611c5657611c56614b75565b600081600f0b7fffffffffffffffffffffffffffffffff800000000000000000000000000000008103614c2b57614c2b614b75565b60000392915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b60008260020b80614c7657614c76614c34565b808360020b0791505092915050565b68ffffffffffffffffff818116838216019080821115611c5657611c56614b75565b68ffffffffffffffffff828116828216039080821115611c5657611c56614b75565b63ffffffff828116828216039080821115611c5657611c56614b75565b600063ffffffff80841680614cfd57614cfd614c34565b92169190910492915050565b67ffffffffffffffff828116828216039080821115611c5657611c56614b75565b6bffffffffffffffffffffffff828116828216039080821115611c5657611c56614b75565b6000825160005b81811015614d705760208186018101518583015201614d56565b506000920191825250919050565b60008260010b80614d9157614d91614c34565b808360010b0791505092915050565b808202811582820484141761009957610099614b75565b600082614dc657614dc6614c34565b50049056fea2646970667358221220d2334152cd5a637eae5cc18ec8a2df20696c659ad28634d45efb4c46bc50c46964736f6c63430008130033
Deployed Bytecode
0x6080604052600436106100295760003560e01c8063ac54c0fc1461002e578063f96dc78814610063575b600080fd5b34801561003a57600080fd5b5061004e610049366004614932565b610090565b60405190151581526020015b60405180910390f35b610076610071366004614972565b61009f565b60408051600f93840b81529190920b60208201520161005a565b61ffff81166080145b92915050565b60008060008060008060008060008060008060008e8e8101906100c29190614a27565b9a509a509a509a509a509a509a509a509a509a509a50600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610127575060005473ffffffffffffffffffffffffffffffffffffffff165b6101398b8b8b8b8b8b8b8b8b8a61019a565b909d509b5061014b8a8a8f8f8661051e565b7ffb06707626d33c17f01d8ca519595e2b6d7c970328373a4be77d3e86ac9e4fc48f8f8f8f6040516101809493929190614af4565b60405180910390a150505050505050505050509250929050565b6000807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60ff8d16016101e1576101d88b8b8b8b8b8b898c8c610550565b9150915061050f565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff560ff8d160161021e576101d88b8b8b8b8b60018c8a8d8d610592565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff460ff8d160161025b576101d88b8b8b8b8b60008c8a8d8d610592565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe60ff8d1601610296576101d88b8b8b8b8b8b898c8c6105e2565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeb60ff8d16016102d3576101d88b8b8b8b8b60018c8a8d8d610611565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffea60ff8d1601610310576101d88b8b8b8b8b60008c8a8d8d610611565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd60ff8d1601610349576101d88b8b8b89878a8a610658565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe160ff8d1601610384576101d88b8b8b60018a888b8b610697565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe060ff8d16016103bf576101d88b8b8b60008a888b8b610697565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc60ff8d16016103f8576101d88b8b8b89878a8a610703565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd760ff8d1601610433576101d88b8b8b60018a888b8b610731565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd660ff8d160161046e576101d88b8b8b60008a888b8b610731565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb60ff8d16016104a8576101d88b8b8b8b8b888b8b610788565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f496e76616c696420636f6d6d616e64000000000000000000000000000000000060448201526064015b60405180910390fd5b9a509a98505050505050505050565b60005473ffffffffffffffffffffffffffffffffffffffff1680610547818089888a89896107c9565b50505050505050565b60008060006105608c8c8c61081e565b9050610570818d8d8c8c8c6108f5565b61057f8989898489898c610a65565b9250925050995099975050505050505050565b60008060006105a68d8d8d8a8e8e8e610bb0565b90506000806105bc8f8f8f8f8f888e8e8e610550565b915091506105cc82828b8d610bee565b945094505050509a509a98505050505050505050565b60008060006105f28c8c8c61081e565b9050610602818d8d8c8c8c610c5c565b61057f8989898489898c610d1c565b60008060006106218d8d8d610e4b565b9050600061062e82610ed8565b9050600061064589600084600001518f8f8f610fad565b90506105cc8f8f8f8f8f868e8e8e6105e2565b60008060006106688a8a8a61081e565b9050610679818b8b6000808c6108f5565b610686878287878a611034565b925092505097509795505050505050565b60008060006106a78b8b8b610e4b565b905060006106b482610ed8565b905060006106c989600184600001518d611148565b90506000806106dd8f8f8f868e8e8e610658565b915091506106ed82828d8f610bee565b9650965050505050509850989650505050505050565b60008060006107138a8a8a61081e565b9050610724818b8b6000808c610c5c565b610686878287878a611198565b60008060006107418b8b8b610e4b565b9050600061074e82610ed8565b9050600061076389600084600001518d611148565b90506107748d8d8d848c8c8c610703565b945094505050509850989650505050505050565b60008060006107988b8b8b61081e565b90506107a9818c8c8b8b6000610c5c565b6107b788888388888b6111be565b92509250509850989650505050505050565b73ffffffffffffffffffffffffffffffffffffffff85166107fa576107f58787866001851615156112f3565b61080c565b61080c8787868860018616151561132f565b6105478787848660028616151561132f565b60408051610140810182526000606082018181526080830182905260a0830182905260c0830182905260e08301829052610100830182905261012083018290528252602082018190529181019190915261087d6201000985858561135f565b90506108888161148e565b6108ee576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f50490000000000000000000000000000000000000000000000000000000000006044820152606401610506565b9392505050565b604086015173ffffffffffffffffffffffffffffffffffffffff1615610a5d576040868101516000805492517f2dd148f100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff938416600482015233602482015288841660448201528784166064820152600287810b608483015286900b60a48201526fffffffffffffffffffffffffffffffff851660c4820152909290911690632dd148f19060e4015b6020604051808303816000875af11580156109d0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109f49190614b53565b905080610547576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600160248201527f5a000000000000000000000000000000000000000000000000000000000000006044820152606401610506565b505050505050565b6000806000610a798760200151878761150f565b9050610aac81610a9e83600001516fffffffffffffffffffffffffffffffff166115f9565b8c8c8c8c602001518a61194c565b8851606001519194509250610ac4908b908b906119cb565b60208088015160009081526201000f82526040908190208351928401516fffffffffffffffffffffffffffffffff9384167001000000000000000000000000000000009185168202178255918401516001909101805460608601516080870151939095167fffffffffffffffff0000000000000000000000000000000000000000000000009091161767ffffffffffffffff9485169093029290921777ffffffffffffffffffffffffffffffffffffffffffffffff16780100000000000000000000000000000000000000000000000093909116929092029190911790555b5097509795505050505050565b600080610bbe898989610e4b565b90506000610bcb82610ed8565b9050610be08760018360000151898989610fad565b9a9950505050505050505050565b6000806000610c0e856fffffffffffffffffffffffffffffffff16611a41565b9050838015610c22575086600f0b81600f0b135b15610c2f57809650610c4b565b83158015610c42575085600f0b81600f0b135b15610c4b578095505b868692509250505b94509492505050565b604086015173ffffffffffffffffffffffffffffffffffffffff1615610a5d576040868101516000805492517f2a179e9b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff938416600482015233602482015288841660448201528784166064820152600287810b608483015286900b60a48201526fffffffffffffffffffffffffffffffff851660c4820152909290911690632a179e9b9060e4016109b1565b6000806000610d308760200151878761150f565b9050610d6381610d5583600001516fffffffffffffffffffffffffffffffff166115f9565b8c8c8c8c602001518a611a75565b60208981015160009081526201000f82526040908190208551928601516fffffffffffffffffffffffffffffffff9384167001000000000000000000000000000000009185168202178255918601516001909101805460608801516080890151939095167fffffffffffffffff0000000000000000000000000000000000000000000000009091161767ffffffffffffffff9485169093029290921777ffffffffffffffffffffffffffffffffffffffffffffffff16780100000000000000000000000000000000000000000000000093909116929092029190911790559093509150610ba3565b60008273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1610610e8557600080fd5b6040805173ffffffffffffffffffffffffffffffffffffffff8087166020830152851691810191909152606081018390526080016040516020818303038152906040528051906020012090509392505050565b6040805160a0810182526000808252602082018190529181018290526060810182905260808101919091525060008181526201000f6020908152604091829020825160a08101845281546fffffffffffffffffffffffffffffffff808216808452700100000000000000000000000000000000928390048216958401959095526001909301549283169482019490945292810467ffffffffffffffff90811660608501527801000000000000000000000000000000000000000000000000909104166080830152610fa857600080fd5b919050565b6000806000610fbe87878787611afa565b915091506000610fee858484610fd48e8e611b9f565b6fffffffffffffffffffffffffffffffff16929190611c5d565b90508861101557611010816fffffffffffffffffffffffffffffffff16611cd7565b610be0565b6ffffffffffffffffffffffffffffff800169998505050505050505050565b60008060006110488760200151878761150f565b905061105a8189896020015187611d88565b60208981015160009081526201000f82526040908190208551928601516fffffffffffffffffffffffffffffffff9384167001000000000000000000000000000000009185168202178255918601516001909101805460608801516080890151939095167fffffffffffffffff0000000000000000000000000000000000000000000000009091161767ffffffffffffffff9485169093029290921777ffffffffffffffffffffffffffffffffffffffffffffffff16780100000000000000000000000000000000000000000000000093909116929092029190911790559093509150509550959350505050565b600080611173838561115a8989611b9f565b6fffffffffffffffffffffffffffffffff169190611de4565b90508461118a57611185816001614ba4565b61118c565b805b9150505b949350505050565b60008060006111ac8760200151878761150f565b905061105a8189896020015187611e3b565b60008060006111d28760200151878761150f565b9050611204816111f783600001516fffffffffffffffffffffffffffffffff166115f9565b8b8b8b6020015189611e97565b60208981015160009081526201000f82526040908190208551928601516fffffffffffffffffffffffffffffffff9384167001000000000000000000000000000000009185168202178255918601516001909101805460608801516080890151939095167fffffffffffffffff0000000000000000000000000000000000000000000000009091161767ffffffffffffffff9485169093029290921777ffffffffffffffffffffffffffffffffffffffffffffffff1678010000000000000000000000000000000000000000000000009390911692909202919091179055909350915050965096945050505050565b60006112fd611efd565b905082600f0b60001461131e5761131985858560008587611fd2565b611328565b6113288482612013565b5050505050565b73ffffffffffffffffffffffffffffffffffffffff821661134f57600080fd5b6000610a5d868686868587611fd2565b60408051610140810182526000606082018181526080830182905260a0830182905260c0830182905260e0830182905261010083018290526101208301829052825260208201819052918101829052906113ba858585610e4b565b600081815260208881526040808320815160e081018352905460ff808216835261ffff6101008304811695840195909552630100000082048116938301939093526401000000008104909316606082015266010000000000008304821660808201526701000000000000008304821660a0820152680100000000000000009092041660c082018190529293509161145290869061204b565b60408051606081018252938452602084019490945273ffffffffffffffffffffffffffffffffffffffff16928201929092529695505050505050565b805151600090600160ff9091161115611503576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600360248201527f49505300000000000000000000000000000000000000000000000000000000006044820152606401610506565b50515160ff1660011490565b6040805160a08101825260008082526020820181905291810182905260608101829052608081019190915261154384610ed8565b9050826fffffffffffffffffffffffffffffffff1681600001516fffffffffffffffffffffffffffffffff1610158015611593575080516fffffffffffffffffffffffffffffffff808416911611155b6108ee576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f52430000000000000000000000000000000000000000000000000000000000006044820152606401610506565b6000620100026fffffffffffffffffffffffffffffffff83161080159061164157506f0ffff5433e2b3d8211706e6102aa94726fffffffffffffffffffffffffffffffff8316105b61164a57600080fd5b77ffffffffffffffffffffffffffffffff0000000000000000604083901b166fffffffffffffffffffffffffffffffff811160071b81811c67ffffffffffffffff811160061b90811c63ffffffff811160051b90811c61ffff811160041b90811c60ff8111600390811b91821c600f811160021b90811c918211600190811b92831c979088119617909417909217179091171717608081106116f457607f810383901c91506116fe565b80607f0383901b91505b908002607f81811c60ff83811c9190911c800280831c81831c1c800280841c81841c1c800280851c81851c1c800280861c81861c1c800280871c81871c1c800280881c81881c1c800280891c81891c1c8002808a1c818a1c1c8002808b1c818b1c1c8002808c1c818c1c1c8002808d1c818d1c1c8002808e1c9c81901c9c909c1c80029c8d901c9e9d7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff808f0160401b60c09190911c678000000000000000161760c19b909b1c674000000000000000169a909a1760c29990991c672000000000000000169890981760c39790971c671000000000000000169690961760c49590951c670800000000000000169490941760c59390931c670400000000000000169290921760c69190911c670200000000000000161760c79190911c670100000000000000161760c89190911c6680000000000000161760c99190911c6640000000000000161760ca9190911c6620000000000000161760cb9190911c6610000000000000161760cc9190911c6608000000000000161760cd9190911c66040000000000001617693627a301d71055774c8581027ffffffffffffffffffffffffffffffffffd709b7e5480fba5a50fed5e62ffc5568101608090811d906fdb2df09e81959a81455e260799a0632f8301901d600281810b9083900b1461193d57886fffffffffffffffffffffffffffffffff166119198261206f565b6fffffffffffffffffffffffffffffffff161115611937578161193f565b8061193f565b815b9998505050505050505050565b600080600061197d858a8a8a6119738b6fffffffffffffffffffffffffffffffff166123e1565b8f608001516124b7565b905061198d84868a8a8a866124ff565b61199b85898989858961257e565b6000806119aa8c898c8c6126e3565b915091506119b88282612717565b909d909c509a5050505050505050505050565b6119d683838361275c565b611a3c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600160248201527f44000000000000000000000000000000000000000000000000000000000000006044820152606401610506565b505050565b60006f80000000000000000000000000000000826fffffffffffffffffffffffffffffffff1610611a7157600080fd5b5090565b6000806000611aa6858a8a8a611a9c8b6fffffffffffffffffffffffffffffffff166123e1565b8f6080015161278f565b90506000611ab885878b8b8b876127e9565b9050611ac8868a8a8a868a6128a5565b600080611ad88d8a858e8e612962565b91509150611ae68282612991565b955095505050505097509795505050505050565b600080611b098560020b61206f565b9150611b178460020b61206f565b9050816fffffffffffffffffffffffffffffffff16866fffffffffffffffffffffffffffffffff1611611b54578215611b4f57600080fd5b610c53565b806fffffffffffffffffffffffffffffffff16866fffffffffffffffffffffffffffffffff1610611b895782611b4f57600080fd5b8215611b96575084610c53565b94959350505050565b600060048215611bee57806fffffffffffffffffffffffffffffffff16846fffffffffffffffffffffffffffffffff1610611be357611bde8185614bcd565b611be6565b60005b915050610099565b611c0960046fffffffffffffffffffffffffffffffff614bcd565b6fffffffffffffffffffffffffffffffff16846fffffffffffffffffffffffffffffffff1611611c3d57611bde8185614ba4565b6fffffffffffffffffffffffffffffffff915050610099565b5092915050565b600083611c8857611c81856001611c73866129dc565b611c7c866129dc565b611c5d565b9050611190565b6000826fffffffffffffffffffffffffffffffff16846fffffffffffffffffffffffffffffffff1611611cbd57838303611cc1565b8284035b9050611ccf86600183611de4565b915050611190565b60006ffffffffffffffffffffffffffffff8008083169003611d55576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600860248201527f6f766572666c6f770000000000000000000000000000000000000000000000006044820152606401610506565b50600b90811c6e1fffffffffffffffffffffffffffff16600101901b7107fffffffffffffffffffffffffffffff8001690565b6000806000611d9d8486888a60600151612a31565b9050611db98582896060015167ffffffffffffffff1687612b0e565b600080611dc68984612b1f565b91509150611dd48282612717565b909a909950975050505050505050565b600082611e2e57611e296fffffffffffffffffffffffffffffffff8086169084160260401c5b77ffffffffffffffffffffffffffffffffffffffffffffffff16612b56565b611190565b611190611e0a8584612b8f565b6000806000611e508486888a60600151612bd8565b9050611e6c8582896060015167ffffffffffffffff1687612cca565b600080611e798984612cdf565b91509150611e878282612991565b9450945050505094509492505050565b6000806000611ead858989898d60800151612d1e565b90506000611ebe85878a8a86612d6a565b9050611ecf8689896000868a6128a5565b600080611edc8c84612cdf565b91509150611eea8282612991565b9550955050505050965096945050505050565b600080547501000000000000000000000000000000000000000000900460ff1615611f84576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f44530000000000000000000000000000000000000000000000000000000000006044820152606401610506565b611f8d34612ebd565b600080547fffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffff167501000000000000000000000000000000000000000000179055919050565b6000600f85900b1315611ff157611fec8685858585612edc565b610a5d565b6000600f85900b1215610a5d57610a5d8561200b86614bf6565b858585612f0f565b6fffffffffffffffffffffffffffffffff8116156120475761204782826fffffffffffffffffffffffffffffffff16612f37565b5050565b60006001828116811480612060576000612066565b606085901c5b95945050505050565b60007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5d892600283900b128015906120ac5750620cb14a600283900b13155b6120b557600080fd5b6000808360020b126120ca578260020b6120d2565b8260020b6000035b90506000816001166000036120f85770010000000000000000000000000000000061210a565b6ffffcb933bd6fad37aa2d162d1a5940015b70ffffffffffffffffffffffffffffffffff169050600282161561213e576ffff97272373d413259a46990580e213a0260801c5b600482161561215d576ffff2e50f5f656932ef12357cf3c7fdcc0260801c5b600882161561217c576fffe5caca7e10e4e61c3624eaa0941cd00260801c5b601082161561219b576fffcb9843d60f6159c9db58835c9266440260801c5b60208216156121ba576fff973b41fa98c081472e6896dfb254c00260801c5b60408216156121d9576fff2ea16466c96a3843ec78b326b528610260801c5b60808216156121f8576ffe5dee046a99a2a811c461f1969c30530260801c5b610100821615612218576ffcbe86c7900a88aedcffc83b479aa3a40260801c5b610200821615612238576ff987a7253ac413176f2b074cf7815e540260801c5b610400821615612258576ff3392b0822b70005940c7a398e4b70f30260801c5b610800821615612278576fe7159475a2c29b7443b29c7fa6e889d90260801c5b611000821615612298576fd097f3bdfd2022b8845ad8f792aa58250260801c5b6120008216156122b8576fa9f746462d870fdf8a65dc1f90e061e50260801c5b6140008216156122d8576f70d869a156d2a1b890bb3df62baf32f70260801c5b6180008216156122f8576f31be135f97d08fd981231505542fcfa60260801c5b62010000821615612319576f09aa508b5b7a84e1c677de54f3e99bc90260801c5b62020000821615612339576e5d6af8dedb81196699c329225ee6040260801c5b62040000821615612358576d2216e584f5fa1ea926041bedfe980260801c5b62080000821615612375576b048a170391f7dc42444e8fa20260801c5b60008460020b13156123b457807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff816123b0576123b0614c34565b0490505b680100000000000000008106156123cc5760016123cf565b60005b60ff16604082901c0192505050919050565b6000600a82901c6e3fffffffffffffffffffffffffffff8116906ffffffffffffffffffffffffffffffc00841690600116158080156124315750846fffffffffffffffffffffffffffffffff1682145b801561244857506bffffffffffffffffffffffff83105b6124ae576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f46440000000000000000000000000000000000000000000000000000000000006044820152606401610506565b50909392505050565b60006124c587878785613001565b6124d187878685613001565b6124dc8786856130ac565b6124e7878585613117565b6124f48787878786612d1e565b979650505050505050565b60408051606088901b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166020808301919091526034820188905260e887811b605484015286901b60578301528251603a818403018152605a9092018352815191810191909120600090815262010012909152206105478184846131a2565b60005473ffffffffffffffffffffffffffffffffffffffff828116911614610a5d57600080546040517f668e2cdb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff918216600482015260248101899052600288810b604483015287900b60648201526fffffffffffffffffffffffffffffffff8616608482015268ffffffffffffffffff851660a48201529083169063668e2cdb9060c4015b6020604051808303816000875af1158015612656573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061267a9190614b53565b905080610547576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f4c500000000000000000000000000000000000000000000000000000000000006044820152606401610506565b60008060008060006126fb89600001518989896132e4565b92509250925061270c8989836133e8565b611e8783838361340d565b600080612735846fffffffffffffffffffffffffffffffff16611a41565b612750846fffffffffffffffffffffffffffffffff16611a41565b915091505b9250929050565b600061ffff821661276d8186614c63565b60020b15801561206657506127828185614c63565b60020b1595945050505050565b60008061279d888786613484565b905060006127ac89878761353d565b90506127bb8989898988612d1e565b925081156127cd576127cd89886135ec565b80156127dd576127dd89876135ec565b50509695505050505050565b604080517fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606089901b166020808301919091526034820188905260e887811b605484015286901b60578301528251808303603a018152605a9092018352815191810191909120600090815262010012909152908120805461288e90790100000000000000000000000000000000000000000000000000900463ffffffff168861365b565b61289981858561371d565b98975050505050505050565b60005473ffffffffffffffffffffffffffffffffffffffff828116911614610a5d57600080546040517f93835c1e00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff918216600482015260248101899052600288810b604483015287900b60648201526fffffffffffffffffffffffffffffffff8616608482015268ffffffffffffffffff851660a4820152908316906393835c1e9060c401612637565b60008061297187878686613886565b909250905061298382828989896138d4565b909890975095505050505050565b6000806129af846fffffffffffffffffffffffffffffffff16611a41565b6129b890614bf6565b6129d3846fffffffffffffffffffffffffffffffff16611a41565b61275090614bf6565b600080826fffffffffffffffffffffffffffffffff1670010000000000000000000000000000000081612a1157612a11614c34565b0490506fffffffffffffffffffffffffffffffff81111561009957600080fd5b600080612a3e86866139c4565b9050612a5c6fffffffffffffffffffffffffffffffff8516846139ec565b8154909250612a7d906fffffffffffffffffffffffffffffffff1683613a3a565b81547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff91909116178155612ac1613a5f565b815463ffffffff91909116700100000000000000000000000000000000027fffffffffffffffffffffffff00000000ffffffffffffffffffffffffffffffff909116179055949350505050565b600080610a5d86838388888861257e565b600080600080612b2f8686613a79565b91509150612b3d8686613ad1565b612b498282600161340d565b9350935050509250929050565b806fffffffffffffffffffffffffffffffff811677ffffffffffffffffffffffffffffffffffffffffffffffff821614610fa857600080fd5b60006fffffffffffffffffffffffffffffffff821677ffffffffffffffffffffffffffffffff0000000000000000604085901b1681612bd057612bd0614c34565b049392505050565b600080612be586866139c4565b9050612c036fffffffffffffffffffffffffffffffff8516846139ec565b81549092506fffffffffffffffffffffffffffffffff90811690831610612c645780547fffffffffffffffffffffffff0000000000000000000000000000000000000000811682556fffffffffffffffffffffffffffffffff169150612cc1565b805482908290600090612c8a9084906fffffffffffffffffffffffffffffffff16614bcd565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff1602179055505b50949350505050565b612cd9846000808686866128a5565b50505050565b600080612cec8484613a79565b909250905061275584612d106fffffffffffffffffffffffffffffffff8616611a41565b612d1990614bf6565b613af0565b600080612d2d87868886613b2f565b67ffffffffffffffff1690506000612d4788868987613b2f565b67ffffffffffffffff908116915082612d608383614c85565b61193f9190614ca7565b60408051606087901b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166020808301919091526034820187905260e886811b605484015285901b60578301528251603a818403018152605a90920183528151918101919091206000908152620100129091529081208054700100000000000000000000000000000000900468ffffffffffffffffff908116908416811015612eb2576000612e2468ffffffffffffffffff861683613bae565b8354909150612e68906fffffffffffffffffffffffffffffffff1667ffffffffffffffff83160260301c5b71ffffffffffffffffffffffffffffffffffff16613c25565b83547fffffffffffffff000000000000000000ffffffffffffffffffffffffffffffff1670010000000000000000000000000000000068ffffffffffffffffff8816021784559350505b505095945050505050565b806fffffffffffffffffffffffffffffffff81168114610fa857600080fd5b8015612f03576000612eef868686613c58565b9050612efd86828686613d37565b50611328565b61132885858585613d85565b8015612f2b57612f20858585613db6565b611319858484613e35565b61132885858585613e5a565b60008273ffffffffffffffffffffffffffffffffffffffff168260405160006040518083038185875af1925050503d8060008114612f91576040519150601f19603f3d011682016040523d82523d6000602084013e612f96565b606091505b5050905080611a3c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f54460000000000000000000000000000000000000000000000000000000000006044820152606401610506565b600061300d8584613e9d565b80549091506bffffffffffffffffffffffff1615801561304a575080546c0100000000000000000000000090046bffffffffffffffffffffffff16155b15611328578360020b8360020b126130a257805477ffffffffffffffffffffffffffffffffffffffffffffffff16780100000000000000000000000000000000000000000000000067ffffffffffffffff8416021781555b6113288584613ef0565b60006130b88484613e9d565b80549091506bffffffffffffffffffffffff1660006130d78285613f78565b83547fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166bffffffffffffffffffffffff91909116179092555050505050565b60006131238484613e9d565b80549091506c0100000000000000000000000090046bffffffffffffffffffffffff1660006131528285613f78565b83546bffffffffffffffffffffffff9091166c01000000000000000000000000027fffffffffffffffff000000000000000000000000ffffffffffffffffffffffff909116179092555050505050565b82546fffffffffffffffffffffffffffffffff16600081156131e557508354700100000000000000000000000000000000900468ffffffffffffffffff166131e9565b5060005b60006132076fffffffffffffffffffffffffffffffff841686613a3a565b9050600061322268ffffffffffffffffff8616878587613f99565b9050600061322e613a5f565b885463ffffffff909116790100000000000000000000000000000000000000000000000000027fffffff00000000ffffffffffffffffffffffffffffffffffffffffffffffffff68ffffffffffffffffff909416700100000000000000000000000000000000027fffffffffffffff000000000000000000000000000000000000000000000000009092166fffffffffffffffffffffffffffffffff909516949094171791909116919091179095555050505050565b60008060008060006132f6878761404c565b91509150816fffffffffffffffffffffffffffffffff16896fffffffffffffffffffffffffffffffff16101561334a576133436fffffffffffffffffffffffffffffffff891683836140c3565b93506133dc565b806fffffffffffffffffffffffffffffffff16896fffffffffffffffffffffffffffffffff1610613399576133926fffffffffffffffffffffffffffffffff8916838361410e565b94506133dc565b6133b66fffffffffffffffffffffffffffffffff89168a836140c3565b93506133d56fffffffffffffffffffffffffffffffff8916838b61410e565b9450600192505b50509450945094915050565b611a3c83613407846fffffffffffffffffffffffffffffffff16611a41565b83614156565b6000806000856fffffffffffffffffffffffffffffffff16118061342e5750825b613439576000613444565b613444600486614ba4565b6000856fffffffffffffffffffffffffffffffff1611806134625750835b61346d576000613478565b613478600486614ba4565b91509150935093915050565b6000806134918585613e9d565b80549091506bffffffffffffffffffffffff1660006134b08286614199565b83547fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166bffffffffffffffffffffffff82169081178555909150158015613515575082546c0100000000000000000000000090046bffffffffffffffffffffffff16155b156135305761352487876141a5565b600193505050506108ee565b5060009695505050505050565b60008061354a8585613e9d565b80549091506c0100000000000000000000000090046bffffffffffffffffffffffff1660006135798286614199565b83547fffffffffffffffff000000000000000000000000ffffffffffffffffffffffff166c010000000000000000000000006bffffffffffffffffffffffff8316908102919091178555909150158015613515575082546bffffffffffffffffffffffff166135305761352487876141a5565b620100026000838360405160200161361192919091825260e81b602082015260230190565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181528151602092830120835290820192909252016000908120555050565b600a600083613668613a5f565b6136729190614cc9565b905060006136808383614ce6565b905060ff63ffffffff8216116113285760008481526201000960205260409020546601000000000000900460ff1663ffffffff82161015611328576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600160248201527f4a000000000000000000000000000000000000000000000000000000000000006044820152606401610506565b82546000906fffffffffffffffffffffffffffffffff168161373f828661422e565b86549091506137709068ffffffffffffffffff86811691700100000000000000000000000000000000900416613bae565b92506fffffffffffffffffffffffffffffffff8116156138565785547d010000000000000000000000000000000000000000000000000000000000900460ff1615613817576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f4f520000000000000000000000000000000000000000000000000000000000006044820152606401610506565b85547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff821617865561387d565b85547fffff0000000000000000000000000000000000000000000000000000000000001686555b50509392505050565b600080600061389b87600001518787876132e4565b919450925090506138ca876138c16fffffffffffffffffffffffffffffffff8916611a41565b61340790614bf6565b5094509492505050565b60008060008367ffffffffffffffff161180156139095750846080015167ffffffffffffffff168367ffffffffffffffff1611155b156139b957600061393a6fffffffffffffffffffffffffffffffff861667ffffffffffffffff86160260301c612e4f565b90506000816fffffffffffffffffffffffffffffffff16118015613985575085602001516fffffffffffffffffffffffffffffffff16816fffffffffffffffffffffffffffffffff16105b156139b7576000806139978884612cdf565b90925090506139a6828b614ba4565b99506139b2818a614ba4565b985050505b505b509495939450505050565b60006201000e60006139d6858561423a565b8152602001908152602001600020905092915050565b6000660100000000000075ffffffffffffffffffffffffffffffff000000000000603085901b168267ffffffffffffffff851683018281613a2f57613a2f614c34565b049695505050505050565b8082016fffffffffffffffffffffffffffffffff808416908216101561009957600080fd5b60004263ffffffff811115610fa85763ffffffff91505090565b6000806000613a8c848660600151614295565b8551909150613ab4906fffffffffffffffffffffffffffffffff80841691160260401c611e0a565b9250613ac7611e0a828760000151612b8f565b9150509250929050565b61204782612d19836fffffffffffffffffffffffffffffffff16611a41565b6020820151613b11906fffffffffffffffffffffffffffffffff16826142ea565b6fffffffffffffffffffffffffffffffff1660209092019190915250565b600080613b3c8686613e9d565b90508360020b8560020b1315613b7e578054611185907801000000000000000000000000000000000000000000000000900467ffffffffffffffff1684614d09565b547801000000000000000000000000000000000000000000000000900467ffffffffffffffff1695945050505050565b60006002613bbc8184614c85565b68ffffffffffffffffff168468ffffffffffffffffff161115611be357600081613be68587614ca7565b613bf09190614ca7565b905067ffffffffffffffff68ffffffffffffffffff821611613c125780613c1c565b67ffffffffffffffff5b92505050610099565b806fffffffffffffffffffffffffffffffff811671ffffffffffffffffffffffffffffffffffff821614610fa857600080fd5b600080613c658584614357565b600081815262010010602052604090208054919250906fffffffffffffffffffffffffffffffff908116908616811115613cfa57815486908390600090613cbf9084906fffffffffffffffffffffffffffffffff16614bcd565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff160217905550613d2d565b81547fffffffffffffffffffffffffffffffff00000000000000000000000000000000168255613d2a8187614bcd565b93505b5050509392505050565b6fffffffffffffffffffffffffffffffff831615613d6057613d5b84848484613d85565b612cd9565b73ffffffffffffffffffffffffffffffffffffffff8216612cd957612cd98482612013565b73ffffffffffffffffffffffffffffffffffffffff8216613dab57613d5b84848361438c565b612cd9848484614432565b6000613dc28483614357565b6000818152620100106020526040812080549293508592909190613df99084906fffffffffffffffffffffffffffffffff16614ba4565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff16021790555050505050565b73ffffffffffffffffffffffffffffffffffffffff8216611a3c57611a3c8382612013565b73ffffffffffffffffffffffffffffffffffffffff8216613e8057613d5b848483614450565b612cd98285856fffffffffffffffffffffffffffffffff16614475565b60006201000260008484604051602001613ec492919091825260e81b602082015260230190565b604051602081830303815290604052805190602001208152602001908152602001600020905092915050565b6000613efe8260020b6145de565b60ff166001901b90506000613f158360020b6145fa565b60ff166001901b905081620100066000613f2f8787614608565b81526020019081526020016000206000828254179250508190555080620100076000613f5b8787614653565b815260208101919091526040016000208054909117905550505050565b8082016bffffffffffffffffffffffff808416908216101561009957600080fd5b6000816fffffffffffffffffffffffffffffffff16600003613fbc575083611190565b836fffffffffffffffffffffffffffffffff16600003613fdd575081611190565b8268ffffffffffffffffff168568ffffffffffffffffff1603614001575083611190565b600061401786866140128682614ba4565b61467f565b9050600061402a8585614012818a614ba4565b9050614037816001614c85565b614042836001614c85565b6124f49190614c85565b6000808360020b8360020b1361406157600080fd5b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5d892600285900b121561409257600080fd5b620cb14a600284900b13156140a657600080fd5b6140af8461206f565b91506140ba8361206f565b90509250929050565b6000826fffffffffffffffffffffffffffffffff16826fffffffffffffffffffffffffffffffff161115614103576140fc8483856146ca565b90506108ee565b6140fc8484846146ca565b600080826fffffffffffffffffffffffffffffffff16846fffffffffffffffffffffffffffffffff161161414457838303614148565b8284035b905061206685826001614749565b8015611a3c57604083015161417d906fffffffffffffffffffffffffffffffff16836142ea565b6fffffffffffffffffffffffffffffffff166040840152505050565b60006108ee8284614d2a565b60006141b38260020b6145de565b60ff166001901b19905060006141cb8360020b6145fa565b60ff166001901b19905060006141e18585614653565b600081815262010007602052604081208054851690819055919250819003610a5d57600061420f8787614608565b6000908152620100066020526040902080548616905550505050505050565b60006108ee8284614bcd565b6040517fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606084901b166020820152603481018290526000906054015b60405160208183030381529060405280519060200120905092915050565b600066010000000000006fffffffffffffffffffffffffffffffff80851667ffffffffffffffff851683010290603082901c90811115612066576fffffffffffffffffffffffffffffffff9350505050610099565b60008082600f0b121561432157508082016fffffffffffffffffffffffffffffffff8084169082161061431c57600080fd5b610099565b826fffffffffffffffffffffffffffffffff168284019150816fffffffffffffffffffffffffffffffff16101561009957600080fd5b6040805173ffffffffffffffffffffffffffffffffffffffff8085166020830152831691810191909152600090606001614277565b816fffffffffffffffffffffffffffffffff16816fffffffffffffffffffffffffffffffff16101561441a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f45430000000000000000000000000000000000000000000000000000000000006044820152606401610506565b60006144268383614bcd565b9050612cd98482612013565b611a3c818430856fffffffffffffffffffffffffffffffff1661479c565b611a3c8361445e8385614ba4565b6fffffffffffffffffffffffffffffffff16612f37565b6040805173ffffffffffffffffffffffffffffffffffffffff8481166024830152604480830185905283518084039091018152606490920183526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb00000000000000000000000000000000000000000000000000000000179052915160009283929087169161450c9190614d4f565b6000604051808303816000865af19150503d8060008114614549576040519150601f19603f3d011682016040523d82523d6000602084013e61454e565b606091505b50915091508180156145785750805115806145785750808060200190518101906145789190614b53565b611328576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f54460000000000000000000000000000000000000000000000000000000000006044820152606401610506565b60006101006145f08360020b60081d90565b6100999190614d7e565b600061009961010083614c63565b600080600283900b60101d9050838160405160200161463492919091825260f81b602082015260210190565b6040516020818303038152906040528051906020012091505092915050565b600080600283900b60081d9050838160405160200161463492919091825260f01b602082015260220190565b6000816fffffffffffffffffffffffffffffffff16836fffffffffffffffffffffffffffffffff168568ffffffffffffffffff1602816146c1576146c1614c34565b04949350505050565b6000806146d78385614bcd565b905060006146e58685612b8f565b77ffffffffffffffffffffffffffffffffffffffffffffffff1690506000856fffffffffffffffffffffffffffffffff16836fffffffffffffffffffffffffffffffff16836147349190614da0565b61473e9190614db7565b9050613d2a81612ebd565b60006111908261477c5761475d8585612b8f565b77ffffffffffffffffffffffffffffffffffffffffffffffff16612ebd565b6fffffffffffffffffffffffffffffffff8086169085160260401c61475d565b6040805173ffffffffffffffffffffffffffffffffffffffff85811660248301528481166044830152606480830185905283518084039091018152608490920183526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f23b872dd00000000000000000000000000000000000000000000000000000000179052915160009283929088169161483b9190614d4f565b6000604051808303816000865af19150503d8060008114614878576040519150601f19603f3d011682016040523d82523d6000602084013e61487d565b606091505b50915091508180156148a75750805115806148a75750808060200190518101906148a79190614b53565b610a5d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f54460000000000000000000000000000000000000000000000000000000000006044820152606401610506565b73ffffffffffffffffffffffffffffffffffffffff8116811461492f57600080fd5b50565b6000806040838503121561494557600080fd5b82356149508161490d565b9150602083013561ffff8116811461496757600080fd5b809150509250929050565b6000806020838503121561498557600080fd5b823567ffffffffffffffff8082111561499d57600080fd5b818501915085601f8301126149b157600080fd5b8135818111156149c057600080fd5b8660208285010111156149d257600080fd5b60209290920196919550909350505050565b803560ff81168114610fa857600080fd5b8035600281900b8114610fa857600080fd5b80356fffffffffffffffffffffffffffffffff81168114610fa857600080fd5b60008060008060008060008060008060006101608c8e031215614a4957600080fd5b614a528c6149e4565b9a5060208c0135614a628161490d565b995060408c0135614a728161490d565b985060608c01359750614a8760808d016149f5565b9650614a9560a08d016149f5565b9550614aa360c08d01614a07565b9450614ab160e08d01614a07565b9350614ac06101008d01614a07565b9250614acf6101208d016149e4565b91506101408c0135614ae08161490d565b809150509295989b509295989b9093969950565b606081528360608201528385608083013760006080858301810191909152600f93840b60208301529190920b6040830152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0160101919050565b600060208284031215614b6557600080fd5b815180151581146108ee57600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6fffffffffffffffffffffffffffffffff818116838216019080821115611c5657611c56614b75565b6fffffffffffffffffffffffffffffffff828116828216039080821115611c5657611c56614b75565b600081600f0b7fffffffffffffffffffffffffffffffff800000000000000000000000000000008103614c2b57614c2b614b75565b60000392915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b60008260020b80614c7657614c76614c34565b808360020b0791505092915050565b68ffffffffffffffffff818116838216019080821115611c5657611c56614b75565b68ffffffffffffffffff828116828216039080821115611c5657611c56614b75565b63ffffffff828116828216039080821115611c5657611c56614b75565b600063ffffffff80841680614cfd57614cfd614c34565b92169190910492915050565b67ffffffffffffffff828116828216039080821115611c5657611c56614b75565b6bffffffffffffffffffffffff828116828216039080821115611c5657611c56614b75565b6000825160005b81811015614d705760208186018101518583015201614d56565b506000920191825250919050565b60008260010b80614d9157614d91614c34565b808360010b0791505092915050565b808202811582820484141761009957610099614b75565b600082614dc657614dc6614c34565b50049056fea2646970667358221220d2334152cd5a637eae5cc18ec8a2df20696c659ad28634d45efb4c46bc50c46964736f6c63430008130033
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 30 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.