OrderBook
Inherits: IOrderBookV3, ReentrancyGuard, Multicall, OrderBookV3FlashLender, DeployerDiscoverableMetaV3
State Variables
sOrders
All hashes of all active orders. There's nothing interesting in the value it's just nonzero if the order is live. The key is the hash of the order. Removing an order sets the value back to zero so it is identical to the order never existing. The order hash includes its owner so there's no need to build a multi level mapping, each order hash MUST uniquely identify the order globally. order hash => order is live
mapping(bytes32 orderHash => uint256 liveness) internal sOrders;
sVaultBalances
Vault balances are stored in a mapping of owner => token => vault ID
This gives 1:1 parity with the IOrderBookV1 interface but keeping the
sFoo naming convention for storage variables.
mapping(address owner => mapping(address token => mapping(uint256 vaultId => uint256 balance))) internal sVaultBalances;
Functions
constructor
Initializes the orderbook upon construction for compatibility with Open Zeppelin upgradeable contracts. Orderbook itself does NOT support factory deployments as each order is a unique expression deployment rather than needing to wrap up expressions with proxies.
constructor(DeployerDiscoverableMetaV3ConstructionConfig memory config)
DeployerDiscoverableMetaV3(CALLER_META_HASH, config);
vaultBalance
Get the current balance of a vault for a given owner, token and vault ID.
function vaultBalance(address owner, address token, uint256 vaultId) external view override returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
owner | address | The owner of the vault. |
token | address | The token the vault is for. |
vaultId | uint256 |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | balance The current balance of the vault. |
orderExists
Returns true if the order exists, false otherwise.
function orderExists(bytes32 orderHash) external view override returns (bool);
Parameters
| Name | Type | Description |
|---|---|---|
orderHash | bytes32 | The hash of the order to check. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | exists True if the order exists, false otherwise. |
deposit
msg.sender deposits tokens according to config. The config specifies
the vault to deposit tokens under. Delegated depositing is NOT supported.
Depositing DOES NOT mint shares (unlike ERC4626) so the overall vaulted
experience is much simpler as there is always a 1:1 relationship between
deposited assets and vault balances globally and individually. This
mitigates rounding/dust issues, speculative behaviour on derived assets,
possible regulatory issues re: whether a vault share is a security, code
bloat on the vault, complex mint/deposit/withdraw/redeem 4-way logic,
the need for preview functions, etc. etc.
At the same time, allowing vault IDs to be specified by the depositor
allows much more granular and direct control over token movements within
Orderbook than either ERC4626 vault shares or mere contract-level ERC20
allowances can facilitate.
Vault IDs are namespaced by the token address so there is no risk of
collision between tokens. For example, vault ID 0 for token A is
completely different to vault ID 0 for token B.
0 amount deposits are unsupported as underlying token contracts
handle 0 value transfers differently and this would be a source of
confusion. The order book MUST revert with ZeroDepositAmount if the
amount is zero.
function deposit(address token, uint256 vaultId, uint256 amount) external nonReentrant;
Parameters
| Name | Type | Description |
|---|---|---|
token | address | The token to deposit. |
vaultId | uint256 | The vault ID to deposit under. |
amount | uint256 | The amount of tokens to deposit. |
withdraw
Allows the sender to withdraw any tokens from their own vaults. If the withrawer has an active flash loan debt denominated in the same token being withdrawn then Orderbook will merely reduce the debt and NOT send the amount of tokens repaid to the flashloan debt. MUST revert if the amount requested to withdraw is zero. The withdrawal MAY still not move any tokens (without revert) if the vault balance is zero, or the withdrawal is used to repay a flash loan, or due to any other internal accounting.
function withdraw(address token, uint256 vaultId, uint256 targetAmount) external nonReentrant;
Parameters
| Name | Type | Description |
|---|---|---|
token | address | The token to withdraw. |
vaultId | uint256 | The vault ID to withdraw from. |
targetAmount | uint256 | The amount of tokens to attempt to withdraw. MAY result in fewer tokens withdrawn if the vault balance is lower than the target amount. MAY NOT be zero, the order book MUST revert with ZeroWithdrawTargetAmount if the amount is zero. |
addOrder
Given an order config, deploys the expression and builds the full Order
for the config, then records it as an active order. Delegated adding an
order is NOT supported. The msg.sender that adds an order is ALWAYS
the owner and all resulting vault movements are their own.
MUST revert with OrderNoSources if the order has no associated
calculation and OrderNoHandleIO if the order has no handle IO
entrypoint. The calculation MUST return at least two values from
evaluation, the maximum amount and the IO ratio. The handle IO entrypoint
SHOULD return zero values from evaluation. Either MAY revert during
evaluation on the interpreter, which MUST prevent the order from
clearing.
MUST revert with OrderNoInputs if the order has no inputs.
MUST revert with OrderNoOutputs if the order has no outputs.
If the order already exists, the order book MUST NOT change state, which
includes not emitting an event. Instead it MUST return false. If the
order book modifies state it MUST emit an AddOrder event and return
true.
function addOrder(OrderConfigV2 calldata config) external nonReentrant returns (bool stateChanged);
Parameters
| Name | Type | Description |
|---|---|---|
config | OrderConfigV2 | All config required to build an Order. |
Returns
| Name | Type | Description |
|---|---|---|
stateChanged | bool | True if the order was added, false if it already existed. |
removeOrder
Order owner can remove their own orders. Delegated order removal is NOT supported and will revert. Removing an order multiple times or removing an order that never existed are valid, the event will be emitted and the transaction will complete with that order hash definitely, redundantly not live.
function removeOrder(OrderV2 calldata order) external nonReentrant returns (bool stateChanged);
Parameters
| Name | Type | Description |
|---|---|---|
order | OrderV2 | The Order data exactly as it was added. |
Returns
| Name | Type | Description |
|---|---|---|
stateChanged | bool | True if the order was removed, false if it did not exist. |
takeOrders
Allows msg.sender to attempt to fill a list of orders in sequence
without needing to place their own order and clear them. This works like
a market buy but against a specific set of orders. Every order will
looped over and calculated individually then filled maximally until the
request input is reached for the msg.sender. The msg.sender is
responsible for selecting the best orders at the time according to their
criteria and MAY specify a maximum IO ratio to guard against an order
spiking the ratio beyond what the msg.sender expected and is
comfortable with. As orders may be removed and calculate their ratios
dynamically, all issues fulfilling an order other than misconfiguration
by the msg.sender are no-ops and DO NOT revert the transaction. This
allows the msg.sender to optimistically provide a list of orders that
they aren't sure will completely fill at a good price, and fallback to
more reliable orders further down their list. Misconfiguration such as
token mismatches are errors that revert as this is known and static at
all times to the msg.sender so MUST be provided correctly. msg.sender
MAY specify a minimum input that MUST be reached across all orders in the
list, otherwise the transaction will revert, this MAY be set to zero.
Exactly like withdraw, if there is an active flash loan for msg.sender
they will have their outstanding loan reduced by the final input amount
preferentially before sending any tokens. Notably this allows arb bots
implemented as flash loan borrowers to connect orders against external
liquidity directly by paying back the loan with a takeOrders call and
outputting the result of the external trade.
Rounding errors always favour the order never the msg.sender.
function takeOrders(TakeOrdersConfigV2 calldata config)
external
nonReentrant
returns (uint256 totalTakerInput, uint256 totalTakerOutput);
Parameters
| Name | Type | Description |
|---|---|---|
config | TakeOrdersConfigV2 | The constraints and list of orders to take, orders are processed sequentially in order as provided, there is NO ATTEMPT onchain to predict/filter/sort these orders other than evaluating them as provided. Inputs and outputs are from the perspective of msg.sender except for values specified by the orders themselves which are the from the perspective of that order. |
Returns
| Name | Type | Description |
|---|---|---|
totalTakerInput | uint256 | totalInput Total tokens sent to msg.sender, taken from order vaults processed. |
totalTakerOutput | uint256 | totalOutput Total tokens taken from msg.sender and distributed between vaults. |
clear
Allows msg.sender to match two live orders placed earlier by
non-interactive parties and claim a bounty in the process. The clearer is
free to select any two live orders on the order book for matching and as
long as they have compatible tokens, ratios and amounts, the orders will
clear. Clearing the orders DOES NOT remove them from the orderbook, they
remain live until explicitly removed by their owner. Even if the input
vault balances are completely emptied, the orders remain live until
removed. This allows order owners to deploy a strategy over a long period
of time and periodically top up the input vaults. Clearing two orders
from the same owner is disallowed.
Any mismatch in the ratios between the two orders will cause either more
inputs than there are available outputs (transaction will revert) or less
inputs than there are available outputs. In the latter case the excess
outputs are given to the msg.sender of clear, to the vaults they
specify in the clear config. This not only incentivises "automatic" clear
calls for both alice and bob, but incentivises prioritising greater
ratio differences with a larger bounty. The second point is important
because it implicitly prioritises orders that are further from the
current market price, thus putting constant increasing pressure on the
entire system the further it drifts from the norm, no matter how esoteric
the individual order expressions and sizings might be.
All else equal there are several factors that would impact how reliably
some order clears relative to the wider market, such as:
- Bounties are effectively percentages of cleared amounts so larger orders have larger bounties and cover gas costs more easily
- High gas on the network means that orders are harder to clear profitably so the negative spread of the ratios will need to be larger
- Complex and stateful expressions cost more gas to evalulate so the negative spread will need to be larger
- Erratic behavior of the order owner could reduce the willingness of third parties to interact if it could result in wasted gas due to orders suddently being removed before clearance etc.
- Dynamic and highly volatile words used in the expression could be ignored or low priority by clearers who want to be sure that they can accurately predict the ratios that they include in their clearance
- Geopolitical issues such as sanctions and regulatory restrictions could cause issues for certain owners and clearers
function clear(
OrderV2 memory aliceOrder,
OrderV2 memory bobOrder,
ClearConfig calldata clearConfig,
SignedContextV1[] memory aliceSignedContext,
SignedContextV1[] memory bobSignedContext
) external nonReentrant;
Parameters
| Name | Type | Description |
|---|---|---|
aliceOrder | OrderV2 | |
bobOrder | OrderV2 | |
clearConfig | ClearConfig | Additional configuration for the clearance such as how to handle the bounty payment for the msg.sender. |
aliceSignedContext | SignedContextV1[] | Optional signed context that is relevant to A. |
bobSignedContext | SignedContextV1[] | Optional signed context that is relevant to B. |
calculateOrderIO
Main entrypoint into an order calculates the amount and IO ratio. Both are always treated as 18 decimal fixed point values and then rescaled according to the order's definition of each token's actual fixed point decimals.
function calculateOrderIO(
OrderV2 memory order,
uint256 inputIOIndex,
uint256 outputIOIndex,
address counterparty,
SignedContextV1[] memory signedContext
) internal view returns (OrderIOCalculationV2 memory);
Parameters
| Name | Type | Description |
|---|---|---|
order | OrderV2 | The order to evaluate. |
inputIOIndex | uint256 | The index of the input token being calculated for. |
outputIOIndex | uint256 | The index of the output token being calculated for. |
counterparty | address | The counterparty of the order as it is currently being cleared against. |
signedContext | SignedContextV1[] | Any signed context provided by the clearer/taker that the order may need for its calculations. |
recordVaultIO
Given an order, final input and output amounts and the IO calculation
verbatim from _calculateOrderIO, dispatch the handle IO entrypoint if
it exists and update the order owner's vault balances.
function recordVaultIO(uint256 input, uint256 output, OrderIOCalculationV2 memory orderIOCalculation) internal;
Parameters
| Name | Type | Description |
|---|---|---|
input | uint256 | The exact token input amount to move into the owner's vault. |
output | uint256 | The exact token output amount to move out of the owner's vault. |
orderIOCalculation | OrderIOCalculationV2 | The verbatim order IO calculation returned by _calculateOrderIO. |
handleIO
function handleIO(OrderIOCalculationV2 memory orderIOCalculation) internal;
calculateClearStateChange
Calculates the clear state change given both order calculations for order alice and order bob. The input of each is their output multiplied by their IO ratio and the output of each is the smaller of their maximum output and the counterparty IO * max output.
function calculateClearStateChange(
OrderIOCalculationV2 memory aliceOrderIOCalculation,
OrderIOCalculationV2 memory bobOrderIOCalculation
) internal pure returns (ClearStateChange memory clearStateChange);
Parameters
| Name | Type | Description |
|---|---|---|
aliceOrderIOCalculation | OrderIOCalculationV2 | Order calculation for Alice. |
bobOrderIOCalculation | OrderIOCalculationV2 | Order calculation for Bob. |
Returns
| Name | Type | Description |
|---|---|---|
clearStateChange | ClearStateChange | The clear state change with absolute inputs and outputs for Alice and Bob. |
calculateClearStateAlice
function calculateClearStateAlice(
OrderIOCalculationV2 memory aliceOrderIOCalculation,
OrderIOCalculationV2 memory bobOrderIOCalculation
) internal pure returns (uint256 aliceInput, uint256 aliceOutput);
_calculateOrderDispatch
function _calculateOrderDispatch(address expression_) internal pure returns (EncodedDispatch);
_handleIODispatch
function _handleIODispatch(address expression_) internal pure returns (EncodedDispatch);