Transaction Hash:
Block:
17673558 at Jul-11-2023 11:13:35 PM +UTC
Transaction Fee:
0.00157088 ETH
$3.17
Gas Used:
98,180 Gas / 16 Gwei
Emitted Events:
| 237 |
FiatTokenProxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x000000000000000000000000a5364aa9e7f081920a5e67d5f70442727113ba2b, 0x000000000000000000000000aaaaaaaaa24eeeb8d57d431224f73832bc34f688, 00000000000000000000000000000000000000000000000000000000122dee40 )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
|
0x95222290...5CC4BAfe5
Miner
| (beaverbuild) | 4.244415780684018448 Eth | 4.244591508236151528 Eth | 0.00017572755213308 | |
| 0xA0b86991...E3606eB48 | |||||
| 0xa5364aA9...27113Ba2b |
0.570057956230274903 Eth
Nonce: 12
|
0.405749700067476693 Eth
Nonce: 13
| 0.16430825616279821 | ||
| 0xAaAaAAAa...2bC34f688 | (Ambient Finance: Croc Swap Dex) | 1,122.093774456206749695 Eth | 1,122.256511832369547905 Eth | 0.16273737616279821 |
Execution Trace
ETH 0.16793826358288147
Ambient Finance: Croc Swap Dex.a15112f9( )
ETH 0.16793826358288147
WarmPath.userCmd( input=0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A0B86991C6218B36C1D19D4A2E9EB0CE3606EB4800000000000000000000000000000000000000000000000000000000000001A40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000122DEE400000000000000000000000000000000000000000000058E82679CD9800000000000000000000000000000000000000000000000000005B9D82E1FC1C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ) => ( baseFlow=162737376162798210, quoteFlow=305000000 )- ETH 0.00520088742008326
0xa5364aa9e7f081920a5e67d5f70442727113ba2b.CALL( ) FiatTokenProxy.23b872dd( )
-
FiatTokenV2_1.transferFrom( from=0xa5364aA9E7F081920a5e67d5F70442727113Ba2b, to=0xAaAaAAAaA24eEeb8d57D431224f73832bC34f688, value=305000000 ) => ( True )
-
- ETH 0.00520088742008326
File 1 of 3: FiatTokenProxy
File 2 of 3: WarmPath
File 3 of 3: FiatTokenV2_1
pragma solidity ^0.4.24;
// File: zos-lib/contracts/upgradeability/Proxy.sol
/**
* @title Proxy
* @dev Implements delegation of calls to other contracts, with proper
* forwarding of return values and bubbling of failures.
* It defines a fallback function that delegates all calls to the address
* returned by the abstract _implementation() internal function.
*/
contract Proxy {
/**
* @dev Fallback function.
* Implemented entirely in `_fallback`.
*/
function () payable external {
_fallback();
}
/**
* @return The Address of the implementation.
*/
function _implementation() internal view returns (address);
/**
* @dev Delegates execution to an implementation contract.
* This is a low level function that doesn't return to its internal call site.
* It will return to the external caller whatever the implementation returns.
* @param implementation Address to delegate.
*/
function _delegate(address implementation) internal {
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0, 0, calldatasize)
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(gas, implementation, 0, calldatasize, 0, 0)
// Copy the returned data.
returndatacopy(0, 0, returndatasize)
switch result
// delegatecall returns 0 on error.
case 0 { revert(0, returndatasize) }
default { return(0, returndatasize) }
}
}
/**
* @dev Function that is run as the first thing in the fallback function.
* Can be redefined in derived contracts to add functionality.
* Redefinitions must call super._willFallback().
*/
function _willFallback() internal {
}
/**
* @dev fallback implementation.
* Extracted to enable manual triggering.
*/
function _fallback() internal {
_willFallback();
_delegate(_implementation());
}
}
// File: openzeppelin-solidity/contracts/AddressUtils.sol
/**
* Utility library of inline functions on addresses
*/
library AddressUtils {
/**
* Returns whether the target address is a contract
* @dev This function will return false if invoked during the constructor of a contract,
* as the code is not actually created until after the constructor finishes.
* @param addr address to check
* @return whether the target address is a contract
*/
function isContract(address addr) internal view returns (bool) {
uint256 size;
// XXX Currently there is no better way to check if there is a contract in an address
// than to check the size of the code at that address.
// See https://ethereum.stackexchange.com/a/14016/36603
// for more details about how this works.
// TODO Check this again before the Serenity release, because all addresses will be
// contracts then.
// solium-disable-next-line security/no-inline-assembly
assembly { size := extcodesize(addr) }
return size > 0;
}
}
// File: zos-lib/contracts/upgradeability/UpgradeabilityProxy.sol
/**
* @title UpgradeabilityProxy
* @dev This contract implements a proxy that allows to change the
* implementation address to which it will delegate.
* Such a change is called an implementation upgrade.
*/
contract UpgradeabilityProxy is Proxy {
/**
* @dev Emitted when the implementation is upgraded.
* @param implementation Address of the new implementation.
*/
event Upgraded(address implementation);
/**
* @dev Storage slot with the address of the current implementation.
* This is the keccak-256 hash of "org.zeppelinos.proxy.implementation", and is
* validated in the constructor.
*/
bytes32 private constant IMPLEMENTATION_SLOT = 0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3;
/**
* @dev Contract constructor.
* @param _implementation Address of the initial implementation.
*/
constructor(address _implementation) public {
assert(IMPLEMENTATION_SLOT == keccak256("org.zeppelinos.proxy.implementation"));
_setImplementation(_implementation);
}
/**
* @dev Returns the current implementation.
* @return Address of the current implementation
*/
function _implementation() internal view returns (address impl) {
bytes32 slot = IMPLEMENTATION_SLOT;
assembly {
impl := sload(slot)
}
}
/**
* @dev Upgrades the proxy to a new implementation.
* @param newImplementation Address of the new implementation.
*/
function _upgradeTo(address newImplementation) internal {
_setImplementation(newImplementation);
emit Upgraded(newImplementation);
}
/**
* @dev Sets the implementation address of the proxy.
* @param newImplementation Address of the new implementation.
*/
function _setImplementation(address newImplementation) private {
require(AddressUtils.isContract(newImplementation), "Cannot set a proxy implementation to a non-contract address");
bytes32 slot = IMPLEMENTATION_SLOT;
assembly {
sstore(slot, newImplementation)
}
}
}
// File: zos-lib/contracts/upgradeability/AdminUpgradeabilityProxy.sol
/**
* @title AdminUpgradeabilityProxy
* @dev This contract combines an upgradeability proxy with an authorization
* mechanism for administrative tasks.
* All external functions in this contract must be guarded by the
* `ifAdmin` modifier. See ethereum/solidity#3864 for a Solidity
* feature proposal that would enable this to be done automatically.
*/
contract AdminUpgradeabilityProxy is UpgradeabilityProxy {
/**
* @dev Emitted when the administration has been transferred.
* @param previousAdmin Address of the previous admin.
* @param newAdmin Address of the new admin.
*/
event AdminChanged(address previousAdmin, address newAdmin);
/**
* @dev Storage slot with the admin of the contract.
* This is the keccak-256 hash of "org.zeppelinos.proxy.admin", and is
* validated in the constructor.
*/
bytes32 private constant ADMIN_SLOT = 0x10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b;
/**
* @dev Modifier to check whether the `msg.sender` is the admin.
* If it is, it will run the function. Otherwise, it will delegate the call
* to the implementation.
*/
modifier ifAdmin() {
if (msg.sender == _admin()) {
_;
} else {
_fallback();
}
}
/**
* Contract constructor.
* It sets the `msg.sender` as the proxy administrator.
* @param _implementation address of the initial implementation.
*/
constructor(address _implementation) UpgradeabilityProxy(_implementation) public {
assert(ADMIN_SLOT == keccak256("org.zeppelinos.proxy.admin"));
_setAdmin(msg.sender);
}
/**
* @return The address of the proxy admin.
*/
function admin() external view ifAdmin returns (address) {
return _admin();
}
/**
* @return The address of the implementation.
*/
function implementation() external view ifAdmin returns (address) {
return _implementation();
}
/**
* @dev Changes the admin of the proxy.
* Only the current admin can call this function.
* @param newAdmin Address to transfer proxy administration to.
*/
function changeAdmin(address newAdmin) external ifAdmin {
require(newAdmin != address(0), "Cannot change the admin of a proxy to the zero address");
emit AdminChanged(_admin(), newAdmin);
_setAdmin(newAdmin);
}
/**
* @dev Upgrade the backing implementation of the proxy.
* Only the admin can call this function.
* @param newImplementation Address of the new implementation.
*/
function upgradeTo(address newImplementation) external ifAdmin {
_upgradeTo(newImplementation);
}
/**
* @dev Upgrade the backing implementation of the proxy and call a function
* on the new implementation.
* This is useful to initialize the proxied contract.
* @param newImplementation Address of the new implementation.
* @param data Data to send as msg.data in the low level call.
* It should include the signature and the parameters of the function to be
* called, as described in
* https://solidity.readthedocs.io/en/develop/abi-spec.html#function-selector-and-argument-encoding.
*/
function upgradeToAndCall(address newImplementation, bytes data) payable external ifAdmin {
_upgradeTo(newImplementation);
require(address(this).call.value(msg.value)(data));
}
/**
* @return The admin slot.
*/
function _admin() internal view returns (address adm) {
bytes32 slot = ADMIN_SLOT;
assembly {
adm := sload(slot)
}
}
/**
* @dev Sets the address of the proxy admin.
* @param newAdmin Address of the new proxy admin.
*/
function _setAdmin(address newAdmin) internal {
bytes32 slot = ADMIN_SLOT;
assembly {
sstore(slot, newAdmin)
}
}
/**
* @dev Only fall back when the sender is not the admin.
*/
function _willFallback() internal {
require(msg.sender != _admin(), "Cannot call fallback function from the proxy admin");
super._willFallback();
}
}
// File: contracts/FiatTokenProxy.sol
/**
* Copyright CENTRE SECZ 2018
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is furnished to
* do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
pragma solidity ^0.4.24;
/**
* @title FiatTokenProxy
* @dev This contract proxies FiatToken calls and enables FiatToken upgrades
*/
contract FiatTokenProxy is AdminUpgradeabilityProxy {
constructor(address _implementation) public AdminUpgradeabilityProxy(_implementation) {
}
}File 2 of 3: WarmPath
// 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';
/* @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);
}
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;
/* @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, uint64 mileage) external returns (bool);
function withdrawCrocLiq (address sender, bytes32 poolHash,
int24 lowerTick, int24 upperTick,
uint128 liq, uint64 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 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));
}
}
/* @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;
}
}
}
// 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_);
(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);
}
/* @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) {
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 {
(paidBase, paidQuote, qtyLeft) = curve.rollFlow
(realFlows, inBaseQty, isBuy, swapQty);
assertFlowEndStable(curve, qtyLeft, isBuy, limitPrice);
}
}
/* 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;
if (feeGlobal != lvl.feeOdometer_) {
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);
}
/* @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); }
}
/* @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;
}
}
/* @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) {
// 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) {
(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;
}
// Since take rate is represented in 1/256, this represents a maximum possible take
// rate of 50%.
uint8 MAX_TAKE_RATE = 128;
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 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 (RangePosition storage) {
return positions_[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, uint64 feeMileage)
internal returns (uint64) {
RangePosition 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 (RangePosition storage pos,
uint128 burnLiq, uint64 feeMileage) internal returns
(uint64 rewards) {
uint128 liq = pos.liquidity_;
uint128 nextLiq = LiquidityMath.minusDelta(liq, burnLiq);
rewards = feeMileage.deltaRewardsRate(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, uint64 feeMileage)
internal returns (uint128 rewards) {
RangePosition storage pos = lookupPosition(owner, poolIdx, lowerTick, upperTick);
uint64 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.deltaRewardsRate(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 {
RangePosition 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, uint64 feeMileage) internal {
RangePosition 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.
}
/* @notice Increments a range order position with the amount of liquidity being
* burned. If necessary blends a weighted average rewards mileage with the
* previous position. */
function incrementPosLiq (RangePosition storage pos, uint128 liqAdd,
uint64 feeMileage) private {
uint128 liq = pos.liquidity_;
uint64 oldMileage;
if (liq > 0) {
oldMileage = pos.feeMileage_;
} else {
oldMileage = 0;
}
uint128 liqNext = liq.addLiq(liqAdd);
uint64 mileage = feeMileage.blendMileage(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 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_;
}
/* @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 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;
// 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 = 2;
uint16 constant COLD_PROXY_IDX = 3;
uint16 constant LONG_PROXY_IDX = 4;
uint16 constant MICRO_PROXY_IDX = 5;
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;
}
// 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) {
uint64 feeMileage = addBookLiq(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) {
uint64 feeMileage = removeBookLiq(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, uint64 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, uint64 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, uint64 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, uint64 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) {
uint64 feeMileage = clockFeeOdometer(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 = true;
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 {
\taddress constant CONSOLE_ADDRESS = address(0x000000000000000000636F6e736F6c652e6c6f67);
\tfunction _sendLogPayload(bytes memory payload) private view {
\t\tuint256 payloadLength = payload.length;
\t\taddress consoleAddress = CONSOLE_ADDRESS;
\t\tassembly {
\t\t\tlet payloadStart := add(payload, 32)
\t\t\tlet r := staticcall(gas(), consoleAddress, payloadStart, payloadLength, 0, 0)
\t\t}
\t}
\tfunction log() internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log()"));
\t}
\tfunction logInt(int256 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(int256)", p0));
\t}
\tfunction logUint(uint256 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256)", p0));
\t}
\tfunction logString(string memory p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string)", p0));
\t}
\tfunction logBool(bool p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool)", p0));
\t}
\tfunction logAddress(address p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address)", p0));
\t}
\tfunction logBytes(bytes memory p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes)", p0));
\t}
\tfunction logBytes1(bytes1 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes1)", p0));
\t}
\tfunction logBytes2(bytes2 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes2)", p0));
\t}
\tfunction logBytes3(bytes3 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes3)", p0));
\t}
\tfunction logBytes4(bytes4 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes4)", p0));
\t}
\tfunction logBytes5(bytes5 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes5)", p0));
\t}
\tfunction logBytes6(bytes6 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes6)", p0));
\t}
\tfunction logBytes7(bytes7 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes7)", p0));
\t}
\tfunction logBytes8(bytes8 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes8)", p0));
\t}
\tfunction logBytes9(bytes9 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes9)", p0));
\t}
\tfunction logBytes10(bytes10 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes10)", p0));
\t}
\tfunction logBytes11(bytes11 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes11)", p0));
\t}
\tfunction logBytes12(bytes12 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes12)", p0));
\t}
\tfunction logBytes13(bytes13 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes13)", p0));
\t}
\tfunction logBytes14(bytes14 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes14)", p0));
\t}
\tfunction logBytes15(bytes15 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes15)", p0));
\t}
\tfunction logBytes16(bytes16 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes16)", p0));
\t}
\tfunction logBytes17(bytes17 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes17)", p0));
\t}
\tfunction logBytes18(bytes18 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes18)", p0));
\t}
\tfunction logBytes19(bytes19 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes19)", p0));
\t}
\tfunction logBytes20(bytes20 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes20)", p0));
\t}
\tfunction logBytes21(bytes21 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes21)", p0));
\t}
\tfunction logBytes22(bytes22 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes22)", p0));
\t}
\tfunction logBytes23(bytes23 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes23)", p0));
\t}
\tfunction logBytes24(bytes24 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes24)", p0));
\t}
\tfunction logBytes25(bytes25 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes25)", p0));
\t}
\tfunction logBytes26(bytes26 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes26)", p0));
\t}
\tfunction logBytes27(bytes27 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes27)", p0));
\t}
\tfunction logBytes28(bytes28 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes28)", p0));
\t}
\tfunction logBytes29(bytes29 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes29)", p0));
\t}
\tfunction logBytes30(bytes30 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes30)", p0));
\t}
\tfunction logBytes31(bytes31 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes31)", p0));
\t}
\tfunction logBytes32(bytes32 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bytes32)", p0));
\t}
\tfunction log(uint256 p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256)", p0));
\t}
\tfunction log(string memory p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string)", p0));
\t}
\tfunction log(bool p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool)", p0));
\t}
\tfunction log(address p0) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address)", p0));
\t}
\tfunction log(uint256 p0, uint256 p1) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256)", p0, p1));
\t}
\tfunction log(uint256 p0, string memory p1) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,string)", p0, p1));
\t}
\tfunction log(uint256 p0, bool p1) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,bool)", p0, p1));
\t}
\tfunction log(uint256 p0, address p1) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,address)", p0, p1));
\t}
\tfunction log(string memory p0, uint256 p1) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,uint256)", p0, p1));
\t}
\tfunction log(string memory p0, string memory p1) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,string)", p0, p1));
\t}
\tfunction log(string memory p0, bool p1) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,bool)", p0, p1));
\t}
\tfunction log(string memory p0, address p1) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,address)", p0, p1));
\t}
\tfunction log(bool p0, uint256 p1) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,uint256)", p0, p1));
\t}
\tfunction log(bool p0, string memory p1) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,string)", p0, p1));
\t}
\tfunction log(bool p0, bool p1) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,bool)", p0, p1));
\t}
\tfunction log(bool p0, address p1) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,address)", p0, p1));
\t}
\tfunction log(address p0, uint256 p1) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,uint256)", p0, p1));
\t}
\tfunction log(address p0, string memory p1) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,string)", p0, p1));
\t}
\tfunction log(address p0, bool p1) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,bool)", p0, p1));
\t}
\tfunction log(address p0, address p1) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,address)", p0, p1));
\t}
\tfunction log(uint256 p0, uint256 p1, uint256 p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256)", p0, p1, p2));
\t}
\tfunction log(uint256 p0, uint256 p1, string memory p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string)", p0, p1, p2));
\t}
\tfunction log(uint256 p0, uint256 p1, bool p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool)", p0, p1, p2));
\t}
\tfunction log(uint256 p0, uint256 p1, address p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address)", p0, p1, p2));
\t}
\tfunction log(uint256 p0, string memory p1, uint256 p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256)", p0, p1, p2));
\t}
\tfunction log(uint256 p0, string memory p1, string memory p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,string,string)", p0, p1, p2));
\t}
\tfunction log(uint256 p0, string memory p1, bool p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool)", p0, p1, p2));
\t}
\tfunction log(uint256 p0, string memory p1, address p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,string,address)", p0, p1, p2));
\t}
\tfunction log(uint256 p0, bool p1, uint256 p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256)", p0, p1, p2));
\t}
\tfunction log(uint256 p0, bool p1, string memory p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string)", p0, p1, p2));
\t}
\tfunction log(uint256 p0, bool p1, bool p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool)", p0, p1, p2));
\t}
\tfunction log(uint256 p0, bool p1, address p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address)", p0, p1, p2));
\t}
\tfunction log(uint256 p0, address p1, uint256 p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256)", p0, p1, p2));
\t}
\tfunction log(uint256 p0, address p1, string memory p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,address,string)", p0, p1, p2));
\t}
\tfunction log(uint256 p0, address p1, bool p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool)", p0, p1, p2));
\t}
\tfunction log(uint256 p0, address p1, address p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,address,address)", p0, p1, p2));
\t}
\tfunction log(string memory p0, uint256 p1, uint256 p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256)", p0, p1, p2));
\t}
\tfunction log(string memory p0, uint256 p1, string memory p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,uint256,string)", p0, p1, p2));
\t}
\tfunction log(string memory p0, uint256 p1, bool p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool)", p0, p1, p2));
\t}
\tfunction log(string memory p0, uint256 p1, address p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,uint256,address)", p0, p1, p2));
\t}
\tfunction log(string memory p0, string memory p1, uint256 p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,string,uint256)", p0, p1, p2));
\t}
\tfunction log(string memory p0, string memory p1, string memory p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,string,string)", p0, p1, p2));
\t}
\tfunction log(string memory p0, string memory p1, bool p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,string,bool)", p0, p1, p2));
\t}
\tfunction log(string memory p0, string memory p1, address p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,string,address)", p0, p1, p2));
\t}
\tfunction log(string memory p0, bool p1, uint256 p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256)", p0, p1, p2));
\t}
\tfunction log(string memory p0, bool p1, string memory p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,bool,string)", p0, p1, p2));
\t}
\tfunction log(string memory p0, bool p1, bool p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,bool,bool)", p0, p1, p2));
\t}
\tfunction log(string memory p0, bool p1, address p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,bool,address)", p0, p1, p2));
\t}
\tfunction log(string memory p0, address p1, uint256 p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,address,uint256)", p0, p1, p2));
\t}
\tfunction log(string memory p0, address p1, string memory p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,address,string)", p0, p1, p2));
\t}
\tfunction log(string memory p0, address p1, bool p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,address,bool)", p0, p1, p2));
\t}
\tfunction log(string memory p0, address p1, address p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,address,address)", p0, p1, p2));
\t}
\tfunction log(bool p0, uint256 p1, uint256 p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256)", p0, p1, p2));
\t}
\tfunction log(bool p0, uint256 p1, string memory p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string)", p0, p1, p2));
\t}
\tfunction log(bool p0, uint256 p1, bool p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool)", p0, p1, p2));
\t}
\tfunction log(bool p0, uint256 p1, address p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address)", p0, p1, p2));
\t}
\tfunction log(bool p0, string memory p1, uint256 p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256)", p0, p1, p2));
\t}
\tfunction log(bool p0, string memory p1, string memory p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,string,string)", p0, p1, p2));
\t}
\tfunction log(bool p0, string memory p1, bool p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,string,bool)", p0, p1, p2));
\t}
\tfunction log(bool p0, string memory p1, address p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,string,address)", p0, p1, p2));
\t}
\tfunction log(bool p0, bool p1, uint256 p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256)", p0, p1, p2));
\t}
\tfunction log(bool p0, bool p1, string memory p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,bool,string)", p0, p1, p2));
\t}
\tfunction log(bool p0, bool p1, bool p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool)", p0, p1, p2));
\t}
\tfunction log(bool p0, bool p1, address p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,bool,address)", p0, p1, p2));
\t}
\tfunction log(bool p0, address p1, uint256 p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256)", p0, p1, p2));
\t}
\tfunction log(bool p0, address p1, string memory p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,address,string)", p0, p1, p2));
\t}
\tfunction log(bool p0, address p1, bool p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,address,bool)", p0, p1, p2));
\t}
\tfunction log(bool p0, address p1, address p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,address,address)", p0, p1, p2));
\t}
\tfunction log(address p0, uint256 p1, uint256 p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256)", p0, p1, p2));
\t}
\tfunction log(address p0, uint256 p1, string memory p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,uint256,string)", p0, p1, p2));
\t}
\tfunction log(address p0, uint256 p1, bool p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool)", p0, p1, p2));
\t}
\tfunction log(address p0, uint256 p1, address p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,uint256,address)", p0, p1, p2));
\t}
\tfunction log(address p0, string memory p1, uint256 p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,string,uint256)", p0, p1, p2));
\t}
\tfunction log(address p0, string memory p1, string memory p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,string,string)", p0, p1, p2));
\t}
\tfunction log(address p0, string memory p1, bool p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,string,bool)", p0, p1, p2));
\t}
\tfunction log(address p0, string memory p1, address p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,string,address)", p0, p1, p2));
\t}
\tfunction log(address p0, bool p1, uint256 p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256)", p0, p1, p2));
\t}
\tfunction log(address p0, bool p1, string memory p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,bool,string)", p0, p1, p2));
\t}
\tfunction log(address p0, bool p1, bool p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,bool,bool)", p0, p1, p2));
\t}
\tfunction log(address p0, bool p1, address p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,bool,address)", p0, p1, p2));
\t}
\tfunction log(address p0, address p1, uint256 p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,address,uint256)", p0, p1, p2));
\t}
\tfunction log(address p0, address p1, string memory p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,address,string)", p0, p1, p2));
\t}
\tfunction log(address p0, address p1, bool p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,address,bool)", p0, p1, p2));
\t}
\tfunction log(address p0, address p1, address p2) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,address,address)", p0, p1, p2));
\t}
\tfunction log(uint256 p0, uint256 p1, uint256 p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, uint256 p1, uint256 p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,string)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, uint256 p1, uint256 p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,bool)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, uint256 p1, uint256 p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,address)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, uint256 p1, string memory p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, uint256 p1, string memory p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,string)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, uint256 p1, string memory p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,bool)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, uint256 p1, string memory p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,address)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, uint256 p1, bool p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, uint256 p1, bool p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,string)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, uint256 p1, bool p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,bool)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, uint256 p1, bool p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,address)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, uint256 p1, address p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, uint256 p1, address p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,string)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, uint256 p1, address p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,bool)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, uint256 p1, address p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,address)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, string memory p1, uint256 p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, string memory p1, uint256 p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,string)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, string memory p1, uint256 p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,bool)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, string memory p1, uint256 p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,address)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, string memory p1, string memory p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, string memory p1, string memory p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,string)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, string memory p1, string memory p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,bool)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, string memory p1, string memory p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,address)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, string memory p1, bool p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, string memory p1, bool p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,string)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, string memory p1, bool p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,bool)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, string memory p1, bool p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,address)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, string memory p1, address p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, string memory p1, address p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,string)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, string memory p1, address p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,bool)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, string memory p1, address p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,address)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, bool p1, uint256 p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, bool p1, uint256 p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,string)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, bool p1, uint256 p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,bool)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, bool p1, uint256 p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,address)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, bool p1, string memory p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, bool p1, string memory p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,string)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, bool p1, string memory p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,bool)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, bool p1, string memory p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,address)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, bool p1, bool p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, bool p1, bool p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,string)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, bool p1, bool p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,bool)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, bool p1, bool p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,address)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, bool p1, address p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, bool p1, address p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,string)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, bool p1, address p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,bool)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, bool p1, address p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,address)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, address p1, uint256 p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, address p1, uint256 p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,string)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, address p1, uint256 p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,bool)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, address p1, uint256 p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,address)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, address p1, string memory p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, address p1, string memory p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,string)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, address p1, string memory p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,bool)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, address p1, string memory p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,address)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, address p1, bool p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, address p1, bool p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,string)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, address p1, bool p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,bool)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, address p1, bool p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,address)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, address p1, address p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, address p1, address p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,string)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, address p1, address p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,bool)", p0, p1, p2, p3));
\t}
\tfunction log(uint256 p0, address p1, address p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,address)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, uint256 p1, uint256 p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, uint256 p1, uint256 p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,string)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, uint256 p1, uint256 p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,bool)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, uint256 p1, uint256 p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,address)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, uint256 p1, string memory p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, uint256 p1, string memory p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,string)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, uint256 p1, string memory p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,bool)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, uint256 p1, string memory p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,address)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, uint256 p1, bool p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, uint256 p1, bool p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,string)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, uint256 p1, bool p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,bool)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, uint256 p1, bool p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,address)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, uint256 p1, address p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, uint256 p1, address p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,string)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, uint256 p1, address p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,bool)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, uint256 p1, address p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,address)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, string memory p1, uint256 p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, string memory p1, uint256 p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,string)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, string memory p1, uint256 p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,bool)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, string memory p1, uint256 p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,address)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, string memory p1, string memory p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,string,string,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, string memory p1, string memory p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,string,string,string)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, string memory p1, string memory p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,string,string,bool)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, string memory p1, string memory p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,string,string,address)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, string memory p1, bool p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,string,bool,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, string memory p1, bool p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,string,bool,string)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, string memory p1, bool p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,string,bool,bool)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, string memory p1, bool p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,string,bool,address)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, string memory p1, address p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,string,address,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, string memory p1, address p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,string,address,string)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, string memory p1, address p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,string,address,bool)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, string memory p1, address p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,string,address,address)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, bool p1, uint256 p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, bool p1, uint256 p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,string)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, bool p1, uint256 p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,bool)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, bool p1, uint256 p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,address)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, bool p1, string memory p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,bool,string,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, bool p1, string memory p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,bool,string,string)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, bool p1, string memory p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,bool,string,bool)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, bool p1, string memory p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,bool,string,address)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, bool p1, bool p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, bool p1, bool p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,string)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, bool p1, bool p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,bool)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, bool p1, bool p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,address)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, bool p1, address p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,bool,address,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, bool p1, address p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,bool,address,string)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, bool p1, address p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,bool,address,bool)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, bool p1, address p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,bool,address,address)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, address p1, uint256 p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, address p1, uint256 p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,string)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, address p1, uint256 p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,bool)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, address p1, uint256 p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,address)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, address p1, string memory p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,address,string,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, address p1, string memory p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,address,string,string)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, address p1, string memory p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,address,string,bool)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, address p1, string memory p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,address,string,address)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, address p1, bool p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,address,bool,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, address p1, bool p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,address,bool,string)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, address p1, bool p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,address,bool,bool)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, address p1, bool p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,address,bool,address)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, address p1, address p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,address,address,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, address p1, address p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,address,address,string)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, address p1, address p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,address,address,bool)", p0, p1, p2, p3));
\t}
\tfunction log(string memory p0, address p1, address p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(string,address,address,address)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, uint256 p1, uint256 p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, uint256 p1, uint256 p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,string)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, uint256 p1, uint256 p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,bool)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, uint256 p1, uint256 p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,address)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, uint256 p1, string memory p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, uint256 p1, string memory p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,string)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, uint256 p1, string memory p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,bool)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, uint256 p1, string memory p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,address)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, uint256 p1, bool p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, uint256 p1, bool p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,string)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, uint256 p1, bool p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,bool)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, uint256 p1, bool p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,address)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, uint256 p1, address p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, uint256 p1, address p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,string)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, uint256 p1, address p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,bool)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, uint256 p1, address p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,address)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, string memory p1, uint256 p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, string memory p1, uint256 p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,string)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, string memory p1, uint256 p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,bool)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, string memory p1, uint256 p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,address)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, string memory p1, string memory p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,string,string,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, string memory p1, string memory p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,string,string,string)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, string memory p1, string memory p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,string,string,bool)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, string memory p1, string memory p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,string,string,address)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, string memory p1, bool p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, string memory p1, bool p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,string)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, string memory p1, bool p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,bool)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, string memory p1, bool p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,address)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, string memory p1, address p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,string,address,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, string memory p1, address p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,string,address,string)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, string memory p1, address p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,string,address,bool)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, string memory p1, address p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,string,address,address)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, bool p1, uint256 p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, bool p1, uint256 p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,string)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, bool p1, uint256 p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,bool)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, bool p1, uint256 p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,address)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, bool p1, string memory p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, bool p1, string memory p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,string)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, bool p1, string memory p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,bool)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, bool p1, string memory p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,address)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, bool p1, bool p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, bool p1, bool p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,string)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, bool p1, bool p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,bool)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, bool p1, bool p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,address)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, bool p1, address p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, bool p1, address p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,string)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, bool p1, address p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,bool)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, bool p1, address p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,address)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, address p1, uint256 p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, address p1, uint256 p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,string)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, address p1, uint256 p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,bool)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, address p1, uint256 p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,address)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, address p1, string memory p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,address,string,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, address p1, string memory p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,address,string,string)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, address p1, string memory p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,address,string,bool)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, address p1, string memory p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,address,string,address)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, address p1, bool p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, address p1, bool p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,string)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, address p1, bool p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,bool)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, address p1, bool p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,address)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, address p1, address p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,address,address,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, address p1, address p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,address,address,string)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, address p1, address p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,address,address,bool)", p0, p1, p2, p3));
\t}
\tfunction log(bool p0, address p1, address p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(bool,address,address,address)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, uint256 p1, uint256 p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, uint256 p1, uint256 p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,string)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, uint256 p1, uint256 p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,bool)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, uint256 p1, uint256 p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,address)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, uint256 p1, string memory p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, uint256 p1, string memory p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,string)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, uint256 p1, string memory p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,bool)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, uint256 p1, string memory p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,address)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, uint256 p1, bool p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, uint256 p1, bool p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,string)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, uint256 p1, bool p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,bool)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, uint256 p1, bool p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,address)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, uint256 p1, address p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, uint256 p1, address p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,string)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, uint256 p1, address p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,bool)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, uint256 p1, address p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,address)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, string memory p1, uint256 p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, string memory p1, uint256 p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,string)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, string memory p1, uint256 p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,bool)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, string memory p1, uint256 p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,address)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, string memory p1, string memory p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,string,string,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, string memory p1, string memory p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,string,string,string)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, string memory p1, string memory p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,string,string,bool)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, string memory p1, string memory p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,string,string,address)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, string memory p1, bool p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,string,bool,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, string memory p1, bool p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,string,bool,string)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, string memory p1, bool p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,string,bool,bool)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, string memory p1, bool p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,string,bool,address)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, string memory p1, address p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,string,address,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, string memory p1, address p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,string,address,string)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, string memory p1, address p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,string,address,bool)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, string memory p1, address p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,string,address,address)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, bool p1, uint256 p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, bool p1, uint256 p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,string)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, bool p1, uint256 p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,bool)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, bool p1, uint256 p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,address)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, bool p1, string memory p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,bool,string,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, bool p1, string memory p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,bool,string,string)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, bool p1, string memory p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,bool,string,bool)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, bool p1, string memory p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,bool,string,address)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, bool p1, bool p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, bool p1, bool p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,string)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, bool p1, bool p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,bool)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, bool p1, bool p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,address)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, bool p1, address p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,bool,address,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, bool p1, address p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,bool,address,string)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, bool p1, address p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,bool,address,bool)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, bool p1, address p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,bool,address,address)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, address p1, uint256 p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, address p1, uint256 p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,string)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, address p1, uint256 p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,bool)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, address p1, uint256 p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,address)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, address p1, string memory p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,address,string,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, address p1, string memory p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,address,string,string)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, address p1, string memory p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,address,string,bool)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, address p1, string memory p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,address,string,address)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, address p1, bool p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,address,bool,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, address p1, bool p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,address,bool,string)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, address p1, bool p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,address,bool,bool)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, address p1, bool p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,address,bool,address)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, address p1, address p2, uint256 p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,address,address,uint256)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, address p1, address p2, string memory p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,address,address,string)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, address p1, address p2, bool p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,address,address,bool)", p0, p1, p2, p3));
\t}
\tfunction log(address p0, address p1, address p2, address p3) internal view {
\t\t_sendLogPayload(abi.encodeWithSignature("log(address,address,address,address)", p0, p1, p2, p3));
\t}
}
File 3 of 3: FiatTokenV2_1
// File: @openzeppelin/contracts/math/SafeMath.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
}
// File: @openzeppelin/contracts/token/ERC20/IERC20.sol
pragma solidity ^0.6.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount)
external
returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender)
external
view
returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
}
// File: contracts/v1/AbstractFiatTokenV1.sol
/**
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
abstract contract AbstractFiatTokenV1 is IERC20 {
function _approve(
address owner,
address spender,
uint256 value
) internal virtual;
function _transfer(
address from,
address to,
uint256 value
) internal virtual;
}
// File: contracts/v1/Ownable.sol
/**
* Copyright (c) 2018 zOS Global Limited.
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
/**
* @notice The Ownable contract has an owner address, and provides basic
* authorization control functions
* @dev Forked from https://github.com/OpenZeppelin/openzeppelin-labs/blob/3887ab77b8adafba4a26ace002f3a684c1a3388b/upgradeability_ownership/contracts/ownership/Ownable.sol
* Modifications:
* 1. Consolidate OwnableStorage into this contract (7/13/18)
* 2. Reformat, conform to Solidity 0.6 syntax, and add error messages (5/13/20)
* 3. Make public functions external (5/27/20)
*/
contract Ownable {
// Owner of the contract
address private _owner;
/**
* @dev Event to show ownership has been transferred
* @param previousOwner representing the address of the previous owner
* @param newOwner representing the address of the new owner
*/
event OwnershipTransferred(address previousOwner, address newOwner);
/**
* @dev The constructor sets the original owner of the contract to the sender account.
*/
constructor() public {
setOwner(msg.sender);
}
/**
* @dev Tells the address of the owner
* @return the address of the owner
*/
function owner() external view returns (address) {
return _owner;
}
/**
* @dev Sets a new owner address
*/
function setOwner(address newOwner) internal {
_owner = newOwner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == _owner, "Ownable: caller is not the owner");
_;
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) external onlyOwner {
require(
newOwner != address(0),
"Ownable: new owner is the zero address"
);
emit OwnershipTransferred(_owner, newOwner);
setOwner(newOwner);
}
}
// File: contracts/v1/Pausable.sol
/**
* Copyright (c) 2016 Smart Contract Solutions, Inc.
* Copyright (c) 2018-2020 CENTRE SECZ0
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
/**
* @notice Base contract which allows children to implement an emergency stop
* mechanism
* @dev Forked from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/feb665136c0dae9912e08397c1a21c4af3651ef3/contracts/lifecycle/Pausable.sol
* Modifications:
* 1. Added pauser role, switched pause/unpause to be onlyPauser (6/14/2018)
* 2. Removed whenNotPause/whenPaused from pause/unpause (6/14/2018)
* 3. Removed whenPaused (6/14/2018)
* 4. Switches ownable library to use ZeppelinOS (7/12/18)
* 5. Remove constructor (7/13/18)
* 6. Reformat, conform to Solidity 0.6 syntax and add error messages (5/13/20)
* 7. Make public functions external (5/27/20)
*/
contract Pausable is Ownable {
event Pause();
event Unpause();
event PauserChanged(address indexed newAddress);
address public pauser;
bool public paused = false;
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*/
modifier whenNotPaused() {
require(!paused, "Pausable: paused");
_;
}
/**
* @dev throws if called by any account other than the pauser
*/
modifier onlyPauser() {
require(msg.sender == pauser, "Pausable: caller is not the pauser");
_;
}
/**
* @dev called by the owner to pause, triggers stopped state
*/
function pause() external onlyPauser {
paused = true;
emit Pause();
}
/**
* @dev called by the owner to unpause, returns to normal state
*/
function unpause() external onlyPauser {
paused = false;
emit Unpause();
}
/**
* @dev update the pauser role
*/
function updatePauser(address _newPauser) external onlyOwner {
require(
_newPauser != address(0),
"Pausable: new pauser is the zero address"
);
pauser = _newPauser;
emit PauserChanged(pauser);
}
}
// File: contracts/v1/Blacklistable.sol
/**
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
/**
* @title Blacklistable Token
* @dev Allows accounts to be blacklisted by a "blacklister" role
*/
contract Blacklistable is Ownable {
address public blacklister;
mapping(address => bool) internal blacklisted;
event Blacklisted(address indexed _account);
event UnBlacklisted(address indexed _account);
event BlacklisterChanged(address indexed newBlacklister);
/**
* @dev Throws if called by any account other than the blacklister
*/
modifier onlyBlacklister() {
require(
msg.sender == blacklister,
"Blacklistable: caller is not the blacklister"
);
_;
}
/**
* @dev Throws if argument account is blacklisted
* @param _account The address to check
*/
modifier notBlacklisted(address _account) {
require(
!blacklisted[_account],
"Blacklistable: account is blacklisted"
);
_;
}
/**
* @dev Checks if account is blacklisted
* @param _account The address to check
*/
function isBlacklisted(address _account) external view returns (bool) {
return blacklisted[_account];
}
/**
* @dev Adds account to blacklist
* @param _account The address to blacklist
*/
function blacklist(address _account) external onlyBlacklister {
blacklisted[_account] = true;
emit Blacklisted(_account);
}
/**
* @dev Removes account from blacklist
* @param _account The address to remove from the blacklist
*/
function unBlacklist(address _account) external onlyBlacklister {
blacklisted[_account] = false;
emit UnBlacklisted(_account);
}
function updateBlacklister(address _newBlacklister) external onlyOwner {
require(
_newBlacklister != address(0),
"Blacklistable: new blacklister is the zero address"
);
blacklister = _newBlacklister;
emit BlacklisterChanged(blacklister);
}
}
// File: contracts/v1/FiatTokenV1.sol
/**
*
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
/**
* @title FiatToken
* @dev ERC20 Token backed by fiat reserves
*/
contract FiatTokenV1 is AbstractFiatTokenV1, Ownable, Pausable, Blacklistable {
using SafeMath for uint256;
string public name;
string public symbol;
uint8 public decimals;
string public currency;
address public masterMinter;
bool internal initialized;
mapping(address => uint256) internal balances;
mapping(address => mapping(address => uint256)) internal allowed;
uint256 internal totalSupply_ = 0;
mapping(address => bool) internal minters;
mapping(address => uint256) internal minterAllowed;
event Mint(address indexed minter, address indexed to, uint256 amount);
event Burn(address indexed burner, uint256 amount);
event MinterConfigured(address indexed minter, uint256 minterAllowedAmount);
event MinterRemoved(address indexed oldMinter);
event MasterMinterChanged(address indexed newMasterMinter);
function initialize(
string memory tokenName,
string memory tokenSymbol,
string memory tokenCurrency,
uint8 tokenDecimals,
address newMasterMinter,
address newPauser,
address newBlacklister,
address newOwner
) public {
require(!initialized, "FiatToken: contract is already initialized");
require(
newMasterMinter != address(0),
"FiatToken: new masterMinter is the zero address"
);
require(
newPauser != address(0),
"FiatToken: new pauser is the zero address"
);
require(
newBlacklister != address(0),
"FiatToken: new blacklister is the zero address"
);
require(
newOwner != address(0),
"FiatToken: new owner is the zero address"
);
name = tokenName;
symbol = tokenSymbol;
currency = tokenCurrency;
decimals = tokenDecimals;
masterMinter = newMasterMinter;
pauser = newPauser;
blacklister = newBlacklister;
setOwner(newOwner);
initialized = true;
}
/**
* @dev Throws if called by any account other than a minter
*/
modifier onlyMinters() {
require(minters[msg.sender], "FiatToken: caller is not a minter");
_;
}
/**
* @dev Function to mint tokens
* @param _to The address that will receive the minted tokens.
* @param _amount The amount of tokens to mint. Must be less than or equal
* to the minterAllowance of the caller.
* @return A boolean that indicates if the operation was successful.
*/
function mint(address _to, uint256 _amount)
external
whenNotPaused
onlyMinters
notBlacklisted(msg.sender)
notBlacklisted(_to)
returns (bool)
{
require(_to != address(0), "FiatToken: mint to the zero address");
require(_amount > 0, "FiatToken: mint amount not greater than 0");
uint256 mintingAllowedAmount = minterAllowed[msg.sender];
require(
_amount <= mintingAllowedAmount,
"FiatToken: mint amount exceeds minterAllowance"
);
totalSupply_ = totalSupply_.add(_amount);
balances[_to] = balances[_to].add(_amount);
minterAllowed[msg.sender] = mintingAllowedAmount.sub(_amount);
emit Mint(msg.sender, _to, _amount);
emit Transfer(address(0), _to, _amount);
return true;
}
/**
* @dev Throws if called by any account other than the masterMinter
*/
modifier onlyMasterMinter() {
require(
msg.sender == masterMinter,
"FiatToken: caller is not the masterMinter"
);
_;
}
/**
* @dev Get minter allowance for an account
* @param minter The address of the minter
*/
function minterAllowance(address minter) external view returns (uint256) {
return minterAllowed[minter];
}
/**
* @dev Checks if account is a minter
* @param account The address to check
*/
function isMinter(address account) external view returns (bool) {
return minters[account];
}
/**
* @notice Amount of remaining tokens spender is allowed to transfer on
* behalf of the token owner
* @param owner Token owner's address
* @param spender Spender's address
* @return Allowance amount
*/
function allowance(address owner, address spender)
external
override
view
returns (uint256)
{
return allowed[owner][spender];
}
/**
* @dev Get totalSupply of token
*/
function totalSupply() external override view returns (uint256) {
return totalSupply_;
}
/**
* @dev Get token balance of an account
* @param account address The account
*/
function balanceOf(address account)
external
override
view
returns (uint256)
{
return balances[account];
}
/**
* @notice Set spender's allowance over the caller's tokens to be a given
* value.
* @param spender Spender's address
* @param value Allowance amount
* @return True if successful
*/
function approve(address spender, uint256 value)
external
override
whenNotPaused
notBlacklisted(msg.sender)
notBlacklisted(spender)
returns (bool)
{
_approve(msg.sender, spender, value);
return true;
}
/**
* @dev Internal function to set allowance
* @param owner Token owner's address
* @param spender Spender's address
* @param value Allowance amount
*/
function _approve(
address owner,
address spender,
uint256 value
) internal override {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
allowed[owner][spender] = value;
emit Approval(owner, spender, value);
}
/**
* @notice Transfer tokens by spending allowance
* @param from Payer's address
* @param to Payee's address
* @param value Transfer amount
* @return True if successful
*/
function transferFrom(
address from,
address to,
uint256 value
)
external
override
whenNotPaused
notBlacklisted(msg.sender)
notBlacklisted(from)
notBlacklisted(to)
returns (bool)
{
require(
value <= allowed[from][msg.sender],
"ERC20: transfer amount exceeds allowance"
);
_transfer(from, to, value);
allowed[from][msg.sender] = allowed[from][msg.sender].sub(value);
return true;
}
/**
* @notice Transfer tokens from the caller
* @param to Payee's address
* @param value Transfer amount
* @return True if successful
*/
function transfer(address to, uint256 value)
external
override
whenNotPaused
notBlacklisted(msg.sender)
notBlacklisted(to)
returns (bool)
{
_transfer(msg.sender, to, value);
return true;
}
/**
* @notice Internal function to process transfers
* @param from Payer's address
* @param to Payee's address
* @param value Transfer amount
*/
function _transfer(
address from,
address to,
uint256 value
) internal override {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
require(
value <= balances[from],
"ERC20: transfer amount exceeds balance"
);
balances[from] = balances[from].sub(value);
balances[to] = balances[to].add(value);
emit Transfer(from, to, value);
}
/**
* @dev Function to add/update a new minter
* @param minter The address of the minter
* @param minterAllowedAmount The minting amount allowed for the minter
* @return True if the operation was successful.
*/
function configureMinter(address minter, uint256 minterAllowedAmount)
external
whenNotPaused
onlyMasterMinter
returns (bool)
{
minters[minter] = true;
minterAllowed[minter] = minterAllowedAmount;
emit MinterConfigured(minter, minterAllowedAmount);
return true;
}
/**
* @dev Function to remove a minter
* @param minter The address of the minter to remove
* @return True if the operation was successful.
*/
function removeMinter(address minter)
external
onlyMasterMinter
returns (bool)
{
minters[minter] = false;
minterAllowed[minter] = 0;
emit MinterRemoved(minter);
return true;
}
/**
* @dev allows a minter to burn some of its own tokens
* Validates that caller is a minter and that sender is not blacklisted
* amount is less than or equal to the minter's account balance
* @param _amount uint256 the amount of tokens to be burned
*/
function burn(uint256 _amount)
external
whenNotPaused
onlyMinters
notBlacklisted(msg.sender)
{
uint256 balance = balances[msg.sender];
require(_amount > 0, "FiatToken: burn amount not greater than 0");
require(balance >= _amount, "FiatToken: burn amount exceeds balance");
totalSupply_ = totalSupply_.sub(_amount);
balances[msg.sender] = balance.sub(_amount);
emit Burn(msg.sender, _amount);
emit Transfer(msg.sender, address(0), _amount);
}
function updateMasterMinter(address _newMasterMinter) external onlyOwner {
require(
_newMasterMinter != address(0),
"FiatToken: new masterMinter is the zero address"
);
masterMinter = _newMasterMinter;
emit MasterMinterChanged(masterMinter);
}
}
// File: @openzeppelin/contracts/utils/Address.sol
pragma solidity ^0.6.2;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// According to EIP-1052, 0x0 is the value returned for not-yet created accounts
// and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
// for accounts without code, i.e. `keccak256('')`
bytes32 codehash;
bytes32 accountHash
= 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
// solhint-disable-next-line no-inline-assembly
assembly {
codehash := extcodehash(account)
}
return (codehash != accountHash && codehash != 0x0);
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(
address(this).balance >= amount,
"Address: insufficient balance"
);
// solhint-disable-next-line avoid-low-level-calls, avoid-call-value
(bool success, ) = recipient.call{ value: amount }("");
require(
success,
"Address: unable to send value, recipient may have reverted"
);
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain`call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data)
internal
returns (bytes memory)
{
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return _functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return
functionCallWithValue(
target,
data,
value,
"Address: low-level call with value failed"
);
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(
address(this).balance >= value,
"Address: insufficient balance for call"
);
return _functionCallWithValue(target, data, value, errorMessage);
}
function _functionCallWithValue(
address target,
bytes memory data,
uint256 weiValue,
string memory errorMessage
) private returns (bytes memory) {
require(isContract(target), "Address: call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.call{
value: weiValue
}(data);
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
// solhint-disable-next-line no-inline-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// File: @openzeppelin/contracts/token/ERC20/SafeERC20.sol
pragma solidity ^0.6.0;
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using SafeMath for uint256;
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(
token,
abi.encodeWithSelector(token.transfer.selector, to, value)
);
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(
token,
abi.encodeWithSelector(token.transferFrom.selector, from, to, value)
);
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
// solhint-disable-next-line max-line-length
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(
token,
abi.encodeWithSelector(token.approve.selector, spender, value)
);
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender).add(
value
);
_callOptionalReturn(
token,
abi.encodeWithSelector(
token.approve.selector,
spender,
newAllowance
)
);
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender).sub(
value,
"SafeERC20: decreased allowance below zero"
);
_callOptionalReturn(
token,
abi.encodeWithSelector(
token.approve.selector,
spender,
newAllowance
)
);
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(
data,
"SafeERC20: low-level call failed"
);
if (returndata.length > 0) {
// Return data is optional
// solhint-disable-next-line max-line-length
require(
abi.decode(returndata, (bool)),
"SafeERC20: ERC20 operation did not succeed"
);
}
}
}
// File: contracts/v1.1/Rescuable.sol
/**
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
contract Rescuable is Ownable {
using SafeERC20 for IERC20;
address private _rescuer;
event RescuerChanged(address indexed newRescuer);
/**
* @notice Returns current rescuer
* @return Rescuer's address
*/
function rescuer() external view returns (address) {
return _rescuer;
}
/**
* @notice Revert if called by any account other than the rescuer.
*/
modifier onlyRescuer() {
require(msg.sender == _rescuer, "Rescuable: caller is not the rescuer");
_;
}
/**
* @notice Rescue ERC20 tokens locked up in this contract.
* @param tokenContract ERC20 token contract address
* @param to Recipient address
* @param amount Amount to withdraw
*/
function rescueERC20(
IERC20 tokenContract,
address to,
uint256 amount
) external onlyRescuer {
tokenContract.safeTransfer(to, amount);
}
/**
* @notice Assign the rescuer role to a given address.
* @param newRescuer New rescuer's address
*/
function updateRescuer(address newRescuer) external onlyOwner {
require(
newRescuer != address(0),
"Rescuable: new rescuer is the zero address"
);
_rescuer = newRescuer;
emit RescuerChanged(newRescuer);
}
}
// File: contracts/v1.1/FiatTokenV1_1.sol
/**
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
/**
* @title FiatTokenV1_1
* @dev ERC20 Token backed by fiat reserves
*/
contract FiatTokenV1_1 is FiatTokenV1, Rescuable {
}
// File: contracts/v2/AbstractFiatTokenV2.sol
/**
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
abstract contract AbstractFiatTokenV2 is AbstractFiatTokenV1 {
function _increaseAllowance(
address owner,
address spender,
uint256 increment
) internal virtual;
function _decreaseAllowance(
address owner,
address spender,
uint256 decrement
) internal virtual;
}
// File: contracts/util/ECRecover.sol
/**
* Copyright (c) 2016-2019 zOS Global Limited
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
/**
* @title ECRecover
* @notice A library that provides a safe ECDSA recovery function
*/
library ECRecover {
/**
* @notice Recover signer's address from a signed message
* @dev Adapted from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/65e4ffde586ec89af3b7e9140bdc9235d1254853/contracts/cryptography/ECDSA.sol
* Modifications: Accept v, r, and s as separate arguments
* @param digest Keccak-256 hash digest of the signed message
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
* @return Signer address
*/
function recover(
bytes32 digest,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (
uint256(s) >
0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0
) {
revert("ECRecover: invalid signature 's' value");
}
if (v != 27 && v != 28) {
revert("ECRecover: invalid signature 'v' value");
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(digest, v, r, s);
require(signer != address(0), "ECRecover: invalid signature");
return signer;
}
}
// File: contracts/util/EIP712.sol
/**
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
/**
* @title EIP712
* @notice A library that provides EIP712 helper functions
*/
library EIP712 {
/**
* @notice Make EIP712 domain separator
* @param name Contract name
* @param version Contract version
* @return Domain separator
*/
function makeDomainSeparator(string memory name, string memory version)
internal
view
returns (bytes32)
{
uint256 chainId;
assembly {
chainId := chainid()
}
return
keccak256(
abi.encode(
// keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f,
keccak256(bytes(name)),
keccak256(bytes(version)),
chainId,
address(this)
)
);
}
/**
* @notice Recover signer's address from a EIP712 signature
* @param domainSeparator Domain separator
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
* @param typeHashAndData Type hash concatenated with data
* @return Signer's address
*/
function recover(
bytes32 domainSeparator,
uint8 v,
bytes32 r,
bytes32 s,
bytes memory typeHashAndData
) internal pure returns (address) {
bytes32 digest = keccak256(
abi.encodePacked(
"\x19\x01",
domainSeparator,
keccak256(typeHashAndData)
)
);
return ECRecover.recover(digest, v, r, s);
}
}
// File: contracts/v2/EIP712Domain.sol
/**
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
/**
* @title EIP712 Domain
*/
contract EIP712Domain {
/**
* @dev EIP712 Domain Separator
*/
bytes32 public DOMAIN_SEPARATOR;
}
// File: contracts/v2/EIP3009.sol
/**
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
/**
* @title EIP-3009
* @notice Provide internal implementation for gas-abstracted transfers
* @dev Contracts that inherit from this must wrap these with publicly
* accessible functions, optionally adding modifiers where necessary
*/
abstract contract EIP3009 is AbstractFiatTokenV2, EIP712Domain {
// keccak256("TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)")
bytes32
public constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH = 0x7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267;
// keccak256("ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)")
bytes32
public constant RECEIVE_WITH_AUTHORIZATION_TYPEHASH = 0xd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de8;
// keccak256("CancelAuthorization(address authorizer,bytes32 nonce)")
bytes32
public constant CANCEL_AUTHORIZATION_TYPEHASH = 0x158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a1597429;
/**
* @dev authorizer address => nonce => bool (true if nonce is used)
*/
mapping(address => mapping(bytes32 => bool)) private _authorizationStates;
event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce);
event AuthorizationCanceled(
address indexed authorizer,
bytes32 indexed nonce
);
/**
* @notice Returns the state of an authorization
* @dev Nonces are randomly generated 32-byte data unique to the
* authorizer's address
* @param authorizer Authorizer's address
* @param nonce Nonce of the authorization
* @return True if the nonce is used
*/
function authorizationState(address authorizer, bytes32 nonce)
external
view
returns (bool)
{
return _authorizationStates[authorizer][nonce];
}
/**
* @notice Execute a transfer with a signed authorization
* @param from Payer's address (Authorizer)
* @param to Payee's address
* @param value Amount to be transferred
* @param validAfter The time after which this is valid (unix time)
* @param validBefore The time before which this is valid (unix time)
* @param nonce Unique nonce
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
*/
function _transferWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) internal {
_requireValidAuthorization(from, nonce, validAfter, validBefore);
bytes memory data = abi.encode(
TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
from,
to,
value,
validAfter,
validBefore,
nonce
);
require(
EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == from,
"FiatTokenV2: invalid signature"
);
_markAuthorizationAsUsed(from, nonce);
_transfer(from, to, value);
}
/**
* @notice Receive a transfer with a signed authorization from the payer
* @dev This has an additional check to ensure that the payee's address
* matches the caller of this function to prevent front-running attacks.
* @param from Payer's address (Authorizer)
* @param to Payee's address
* @param value Amount to be transferred
* @param validAfter The time after which this is valid (unix time)
* @param validBefore The time before which this is valid (unix time)
* @param nonce Unique nonce
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
*/
function _receiveWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) internal {
require(to == msg.sender, "FiatTokenV2: caller must be the payee");
_requireValidAuthorization(from, nonce, validAfter, validBefore);
bytes memory data = abi.encode(
RECEIVE_WITH_AUTHORIZATION_TYPEHASH,
from,
to,
value,
validAfter,
validBefore,
nonce
);
require(
EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == from,
"FiatTokenV2: invalid signature"
);
_markAuthorizationAsUsed(from, nonce);
_transfer(from, to, value);
}
/**
* @notice Attempt to cancel an authorization
* @param authorizer Authorizer's address
* @param nonce Nonce of the authorization
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
*/
function _cancelAuthorization(
address authorizer,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) internal {
_requireUnusedAuthorization(authorizer, nonce);
bytes memory data = abi.encode(
CANCEL_AUTHORIZATION_TYPEHASH,
authorizer,
nonce
);
require(
EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == authorizer,
"FiatTokenV2: invalid signature"
);
_authorizationStates[authorizer][nonce] = true;
emit AuthorizationCanceled(authorizer, nonce);
}
/**
* @notice Check that an authorization is unused
* @param authorizer Authorizer's address
* @param nonce Nonce of the authorization
*/
function _requireUnusedAuthorization(address authorizer, bytes32 nonce)
private
view
{
require(
!_authorizationStates[authorizer][nonce],
"FiatTokenV2: authorization is used or canceled"
);
}
/**
* @notice Check that authorization is valid
* @param authorizer Authorizer's address
* @param nonce Nonce of the authorization
* @param validAfter The time after which this is valid (unix time)
* @param validBefore The time before which this is valid (unix time)
*/
function _requireValidAuthorization(
address authorizer,
bytes32 nonce,
uint256 validAfter,
uint256 validBefore
) private view {
require(
now > validAfter,
"FiatTokenV2: authorization is not yet valid"
);
require(now < validBefore, "FiatTokenV2: authorization is expired");
_requireUnusedAuthorization(authorizer, nonce);
}
/**
* @notice Mark an authorization as used
* @param authorizer Authorizer's address
* @param nonce Nonce of the authorization
*/
function _markAuthorizationAsUsed(address authorizer, bytes32 nonce)
private
{
_authorizationStates[authorizer][nonce] = true;
emit AuthorizationUsed(authorizer, nonce);
}
}
// File: contracts/v2/EIP2612.sol
/**
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
/**
* @title EIP-2612
* @notice Provide internal implementation for gas-abstracted approvals
*/
abstract contract EIP2612 is AbstractFiatTokenV2, EIP712Domain {
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")
bytes32
public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
mapping(address => uint256) private _permitNonces;
/**
* @notice Nonces for permit
* @param owner Token owner's address (Authorizer)
* @return Next nonce
*/
function nonces(address owner) external view returns (uint256) {
return _permitNonces[owner];
}
/**
* @notice Verify a signed approval permit and execute if valid
* @param owner Token owner's address (Authorizer)
* @param spender Spender's address
* @param value Amount of allowance
* @param deadline The time at which this expires (unix time)
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
*/
function _permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
require(deadline >= now, "FiatTokenV2: permit is expired");
bytes memory data = abi.encode(
PERMIT_TYPEHASH,
owner,
spender,
value,
_permitNonces[owner]++,
deadline
);
require(
EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == owner,
"EIP2612: invalid signature"
);
_approve(owner, spender, value);
}
}
// File: contracts/v2/FiatTokenV2.sol
/**
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
/**
* @title FiatToken V2
* @notice ERC20 Token backed by fiat reserves, version 2
*/
contract FiatTokenV2 is FiatTokenV1_1, EIP3009, EIP2612 {
uint8 internal _initializedVersion;
/**
* @notice Initialize v2
* @param newName New token name
*/
function initializeV2(string calldata newName) external {
// solhint-disable-next-line reason-string
require(initialized && _initializedVersion == 0);
name = newName;
DOMAIN_SEPARATOR = EIP712.makeDomainSeparator(newName, "2");
_initializedVersion = 1;
}
/**
* @notice Increase the allowance by a given increment
* @param spender Spender's address
* @param increment Amount of increase in allowance
* @return True if successful
*/
function increaseAllowance(address spender, uint256 increment)
external
whenNotPaused
notBlacklisted(msg.sender)
notBlacklisted(spender)
returns (bool)
{
_increaseAllowance(msg.sender, spender, increment);
return true;
}
/**
* @notice Decrease the allowance by a given decrement
* @param spender Spender's address
* @param decrement Amount of decrease in allowance
* @return True if successful
*/
function decreaseAllowance(address spender, uint256 decrement)
external
whenNotPaused
notBlacklisted(msg.sender)
notBlacklisted(spender)
returns (bool)
{
_decreaseAllowance(msg.sender, spender, decrement);
return true;
}
/**
* @notice Execute a transfer with a signed authorization
* @param from Payer's address (Authorizer)
* @param to Payee's address
* @param value Amount to be transferred
* @param validAfter The time after which this is valid (unix time)
* @param validBefore The time before which this is valid (unix time)
* @param nonce Unique nonce
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
*/
function transferWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) external whenNotPaused notBlacklisted(from) notBlacklisted(to) {
_transferWithAuthorization(
from,
to,
value,
validAfter,
validBefore,
nonce,
v,
r,
s
);
}
/**
* @notice Receive a transfer with a signed authorization from the payer
* @dev This has an additional check to ensure that the payee's address
* matches the caller of this function to prevent front-running attacks.
* @param from Payer's address (Authorizer)
* @param to Payee's address
* @param value Amount to be transferred
* @param validAfter The time after which this is valid (unix time)
* @param validBefore The time before which this is valid (unix time)
* @param nonce Unique nonce
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
*/
function receiveWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) external whenNotPaused notBlacklisted(from) notBlacklisted(to) {
_receiveWithAuthorization(
from,
to,
value,
validAfter,
validBefore,
nonce,
v,
r,
s
);
}
/**
* @notice Attempt to cancel an authorization
* @dev Works only if the authorization is not yet used.
* @param authorizer Authorizer's address
* @param nonce Nonce of the authorization
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
*/
function cancelAuthorization(
address authorizer,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) external whenNotPaused {
_cancelAuthorization(authorizer, nonce, v, r, s);
}
/**
* @notice Update allowance with a signed permit
* @param owner Token owner's address (Authorizer)
* @param spender Spender's address
* @param value Amount of allowance
* @param deadline Expiration time, seconds since the epoch
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external whenNotPaused notBlacklisted(owner) notBlacklisted(spender) {
_permit(owner, spender, value, deadline, v, r, s);
}
/**
* @notice Internal function to increase the allowance by a given increment
* @param owner Token owner's address
* @param spender Spender's address
* @param increment Amount of increase
*/
function _increaseAllowance(
address owner,
address spender,
uint256 increment
) internal override {
_approve(owner, spender, allowed[owner][spender].add(increment));
}
/**
* @notice Internal function to decrease the allowance by a given decrement
* @param owner Token owner's address
* @param spender Spender's address
* @param decrement Amount of decrease
*/
function _decreaseAllowance(
address owner,
address spender,
uint256 decrement
) internal override {
_approve(
owner,
spender,
allowed[owner][spender].sub(
decrement,
"ERC20: decreased allowance below zero"
)
);
}
}
// File: contracts/v2/FiatTokenV2_1.sol
/**
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
// solhint-disable func-name-mixedcase
/**
* @title FiatToken V2.1
* @notice ERC20 Token backed by fiat reserves, version 2.1
*/
contract FiatTokenV2_1 is FiatTokenV2 {
/**
* @notice Initialize v2.1
* @param lostAndFound The address to which the locked funds are sent
*/
function initializeV2_1(address lostAndFound) external {
// solhint-disable-next-line reason-string
require(_initializedVersion == 1);
uint256 lockedAmount = balances[address(this)];
if (lockedAmount > 0) {
_transfer(address(this), lostAndFound, lockedAmount);
}
blacklisted[address(this)] = true;
_initializedVersion = 2;
}
/**
* @notice Version string for the EIP712 domain separator
* @return Version string
*/
function version() external view returns (string memory) {
return "2";
}
}