rain.orderbook

Docs at https://rainprotocol.github.io/foundry.template

Use as template

forge init -t rainprotocol/foundry.template <projectname>
cd <projectname>
forge install foundry-rs/forge-std

Then update the readme, set the docs url and configure github pages on github repo settings.

For CI deployments, setup all the environment variables and define contracts to deploy in the matrix.

Contents

MinimumOutput

Git Source

Thrown when the minimum output for the sender is not met after the arb.

error MinimumOutput(uint256 minimum, uint256 actual);

Parameters

NameTypeDescription
minimumuint256The minimum output expected by the sender.
actualuint256The actual output that would be received by the sender.

Initializing

Git Source

Thrown when calling functions while the contract is still initializing.

error Initializing();

NonZeroBeforeArbStack

Git Source

Thrown when the stack is not empty after the access control dispatch.

error NonZeroBeforeArbStack();

BadLender

Git Source

Thrown when the lender is not the trusted OrderBook.

error BadLender(address badLender);

Parameters

NameTypeDescription
badLenderaddressThe untrusted lender calling onFlashLoan.

NonZeroBeforeArbInputs

Git Source

Thrown when "before arb" wants inputs that we don't have.

error NonZeroBeforeArbInputs(uint256 inputs);

OrderBookV3ArbOrderTakerConfigV1

Git Source

Config for OrderBookV3ArbOrderTakerConfigV1 to initialize.

struct OrderBookV3ArbOrderTakerConfigV1 {
    address orderBook;
    EvaluableConfigV3 evaluableConfig;
    bytes implementationData;
}

Properties

NameTypeDescription
orderBookaddressThe IOrderBookV3 to use for takeOrders.
evaluableConfigEvaluableConfigV3The config to eval for access control to arb.
implementationDatabytesArbitrary bytes to pass to the implementation in the beforeInitialize hook.

OrderBookV3ArbOrderTaker

Git Source

Inherits: IOrderBookV3ArbOrderTaker, ReentrancyGuard, Initializable, ICloneableV2, DeployerDiscoverableMetaV3, ERC165

State Variables

sOrderBook

IOrderBookV3 public sOrderBook;

sI9rDispatch

EncodedDispatch public sI9rDispatch;

sI9r

IInterpreterV2 public sI9r;

sI9rStore

IInterpreterStoreV1 public sI9rStore;

Functions

constructor

constructor(bytes32 metaHash, DeployerDiscoverableMetaV3ConstructionConfig memory config)
    DeployerDiscoverableMetaV3(metaHash, config);

supportsInterface

function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool);

_beforeInitialize

function _beforeInitialize(bytes memory data) internal virtual;

onlyNotInitializing

Ensure the contract is not initializing.

modifier onlyNotInitializing();

initialize

function initialize(OrderBookV3ArbOrderTakerConfigV1 calldata) external pure returns (bytes32);

initialize

function initialize(bytes memory data) external initializer nonReentrant returns (bytes32);

arb

function arb(TakeOrdersConfigV2 calldata takeOrders, uint256 minimumSenderOutput)
    external
    payable
    nonReentrant
    onlyNotInitializing;

onTakeOrders

Called by OrderBookV3 when takeOrders is called with non-zero data, if it caused a non-zero input amount. I.e. if the order(s) taker received some tokens. Input and output directions are relative to the IOrderBookV3OrderTaker contract. If the order(s) taker had an active debt from a flash loan then that debt will be paid before calculating any input amounts sent. i.e. the debt is deducted from the input amount before this callback is called.

function onTakeOrders(address, address, uint256, uint256, bytes calldata) public virtual override onlyNotInitializing;

Parameters

NameTypeDescription
<none>address
<none>address
<none>uint256
<none>uint256
<none>bytes

Events

Initialize

event Initialize(address sender, OrderBookV3ArbOrderTakerConfigV1 config);

Constants

Git Source

BEFORE_ARB_SOURCE_INDEX

"Before arb" is evaluabled before the arb is executed. Ostensibly this is to allow for access control to the arb, the return values are ignored.

SourceIndexV2 constant BEFORE_ARB_SOURCE_INDEX = SourceIndexV2.wrap(0);

BEFORE_ARB_MIN_OUTPUTS

"Before arb" has no return values.

uint256 constant BEFORE_ARB_MIN_OUTPUTS = 0;

BEFORE_ARB_MAX_OUTPUTS

"Before arb" has no return values.

uint16 constant BEFORE_ARB_MAX_OUTPUTS = 0;

BadInitiator

Git Source

Thrown when the initiator is not the order book.

error BadInitiator(address badInitiator);

Parameters

NameTypeDescription
badInitiatoraddressThe untrusted initiator of the flash loan.

FlashLoanFailed

Git Source

Thrown when the flash loan fails somehow.

error FlashLoanFailed();

SwapFailed

Git Source

Thrown when the swap fails.

error SwapFailed();

NonZeroBeforeArbInputs

Git Source

Thrown when "Before arb" expects inputs.

error NonZeroBeforeArbInputs();

OrderBookV3FlashBorrowerConfigV2

Git Source

Config for OrderBookV3FlashBorrower to initialize.

struct OrderBookV3FlashBorrowerConfigV2 {
    address orderBook;
    EvaluableConfigV3 evaluableConfig;
    bytes implementationData;
}

Properties

NameTypeDescription
orderBookaddressThe IOrderBookV3 contract to arb against.
evaluableConfigEvaluableConfigV3The config to eval for access control to arb.
implementationDatabytesArbitrary bytes to pass to the implementation in the beforeInitialize hook.

OrderBookV3FlashBorrower

Git Source

Inherits: IERC3156FlashBorrower, ICloneableV2, ReentrancyGuard, Initializable, DeployerDiscoverableMetaV3, ERC165

Abstract contract that liq-source specifialized contracts can inherit to provide flash loan based arbitrage against external liquidity sources to fill orderbook orders. For example consider a simple order: input = DAI output = USDT IORatio = 1.01e18 Order amount = 100e18 Assume external liq is offering 102 DAI per USDT so it exceeds the IO ratio but the order itself has no way to interact with the external contract. The OrderBookFlashBorrower can:

  • Flash loan 100 USDT from Orderbook
  • Sell the 100 USDT for 102 DAI on external liq
  • Take the order, giving 101 DAI and paying down 100 USDT loan
  • Keep 1 DAI profit As this contract is expected to be cloned using a minimal proxy there will be many copies of it in the wild. Each copy can be access gated by a Rain expression that is evaluated before anything else happens in arb. There are many reasons why this might be desirable:
  • Regulatory reasons that restrict how an arb bot operator can interact with orders and/or external liq.
  • The arb operator wants to attempt to prevent front running by other bots.
  • The arb operator may prefer a dedicated instance of the contract to make it easier to track profits, etc.

State Variables

sOrderBook

OrderBook contract to lend and arb against.

IOrderBookV3 public sOrderBook;

sI9rDispatch

The encoded dispatch that will run for access control to arb.

EncodedDispatch public sI9rDispatch;

sI9r

The interpreter that will eval access control to arb.

IInterpreterV2 public sI9r;

sI9rStore

The associated store for the interpreter.

IInterpreterStoreV1 public sI9rStore;

Functions

constructor

constructor(bytes32 metaHash, DeployerDiscoverableMetaV3ConstructionConfig memory config)
    DeployerDiscoverableMetaV3(metaHash, config);

supportsInterface

function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool);

_beforeInitialize

Hook called before initialize happens. Inheriting contracts can perform internal state maintenance before any external contract calls are made.

function _beforeInitialize(bytes memory data) internal virtual;

Parameters

NameTypeDescription
databytesArbitrary bytes the child may use to initialize.

initialize

Type hints for the input encoding for the initialize function. Reverts ALWAYS with InitializeSignatureFn as per ICloneableV2.

function initialize(OrderBookV3FlashBorrowerConfigV2 calldata) external pure returns (bytes32);

initialize

function initialize(bytes memory data) external initializer nonReentrant returns (bytes32);

onlyNotInitializing

Ensure the contract is not initializing.

modifier onlyNotInitializing();

_exchange

Hook that inheriting contracts MUST implement in order to achieve anything other than raising the ambient temperature of the room. _exchange is responsible for converting the flash loaned assets into the assets required to fill the orders. Generally this can only be achieved by interacting with an external liquidity source that is offering a better price than the orders require.

function _exchange(TakeOrdersConfigV2 memory takeOrders, bytes memory exchangeData) internal virtual;

Parameters

NameTypeDescription
takeOrdersTakeOrdersConfigV2As per arb.
exchangeDatabytesAs per arb.

onFlashLoan

Receive a flash loan.

function onFlashLoan(address initiator, address, uint256, uint256, bytes calldata data)
    external
    onlyNotInitializing
    returns (bytes32);

Parameters

NameTypeDescription
initiatoraddressThe initiator of the loan.
<none>address
<none>uint256
<none>uint256
databytesArbitrary data structure, intended to contain user-defined parameters.

Returns

NameTypeDescription
<none>bytes32The keccak256 hash of "ERC3156FlashBorrower.onFlashLoan"

arb

Primary function to process arbitrage opportunities. Firstly the access gate is evaluated to ensure the sender is allowed to submit arbitrage. If there is no access control the sender should expect to be front run on the arb for any sufficiently profitable opportunity. This may be desirable in some cases, as the sender may simply want to be clearing the orderbook and they are expecting profit/utility from the orderbook strategies themselves somehow. Secondly the flash loan is taken and the _exchange hook is called to allow the inheriting contract to convert the flash loaned assets into the assets required to fill the orders. Finally the orders are taken and the remaining assets are sent to the sender.

function arb(TakeOrdersConfigV2 calldata takeOrders, uint256 minimumSenderOutput, bytes calldata exchangeData)
    external
    payable
    nonReentrant
    onlyNotInitializing;

Parameters

NameTypeDescription
takeOrdersTakeOrdersConfigV2As per IOrderBookV3.takeOrders.
minimumSenderOutputuint256The minimum output that must be sent to the sender by the end of the arb call. This, in combination with the orderbook's own asset handling, is expected to REPLACE the standard slippage protection that would be provided by a DEX. The sender is expected to calculate absolute values based on prevailing conditions such as gas price and the risk of holding the assets any arb profit is denominated in.
exchangeDatabytesArbitrary bytes that will be passed to _exchange after the flash loan is taken. The inheriting contract is responsible for decoding this data and defining how it controls interactions with the external liquidity. For example, GenericPoolOrderBookV3FlashBorrower uses this data as a literal encoded external call.

Events

Initialize

Emitted when the contract is initialized. Contains the OrderBookFlashBorrowerConfig struct to ensure the type appears in the ABI.

event Initialize(address sender, OrderBookV3FlashBorrowerConfigV2 config);

Constants

Git Source

BEFORE_ARB_SOURCE_INDEX

"Before arb" is evaluated before the flash loan is taken. Ostensibly allows for some kind of access control to the arb.

SourceIndexV2 constant BEFORE_ARB_SOURCE_INDEX = SourceIndexV2.wrap(0);

BEFORE_ARB_MIN_INPUTS

"Before arb" has no inputs.

uint256 constant BEFORE_ARB_MIN_INPUTS = 0;

BEFORE_ARB_MIN_OUTPUTS

"Before arb" has no outputs.

uint256 constant BEFORE_ARB_MIN_OUTPUTS = 0;

BEFORE_ARB_MAX_OUTPUTS

"Before arb" has no outputs.

uint256 constant BEFORE_ARB_MAX_OUTPUTS = 0;

FlashLenderCallbackFailed

Git Source

Thrown when the onFlashLoan callback returns anything other than ON_FLASH_LOAN_CALLBACK_SUCCESS.

error FlashLenderCallbackFailed(bytes32 result);

Parameters

NameTypeDescription
resultbytes32The value that was returned by onFlashLoan.

OrderBookV3FlashLender

Git Source

Inherits: IERC3156FlashLender

Implements IERC3156FlashLender for OrderBook. Based on the reference implementation by Alberto Cuesta Cañada found at https://eips.ethereum.org/EIPS/eip-3156#flash-loan-reference-implementation

Functions

flashLoan

Initiate a flash loan.

function flashLoan(IERC3156FlashBorrower receiver, address token, uint256 amount, bytes calldata data)
    external
    override
    returns (bool);

Parameters

NameTypeDescription
receiverIERC3156FlashBorrowerThe receiver of the tokens in the loan, and the receiver of the callback.
tokenaddressThe loan currency.
amountuint256The amount of tokens lent.
databytesArbitrary data structure, intended to contain user-defined parameters.

flashFee

The fee to be charged for a given loan.

function flashFee(address, uint256) external pure override returns (uint256);

Parameters

NameTypeDescription
<none>address
<none>uint256

Returns

NameTypeDescription
<none>uint256The amount of token to be charged for the loan, on top of the returned principal.

maxFlashLoan

There's no limit to the size of a flash loan from Orderbook other than the current tokens deposited in Orderbook. If there is an active debt then loans are disabled so the max becomes 0 until after repayment.

The amount of currency available to be lent.

function maxFlashLoan(address token) external view override returns (uint256);

Parameters

NameTypeDescription
tokenaddressThe loan currency.

Returns

NameTypeDescription
<none>uint256The amount of token that can be borrowed.

Constants

Git Source

FLASH_FEE

Flash fee is always 0 for orderbook as there's no entity to take revenue for Orderbook and its more important anyway that flashloans happen to connect external liquidity to live orders via arbitrage.

uint256 constant FLASH_FEE = 0;

Contents

GenericPoolOrderBookV3ArbOrderTaker

Git Source

Inherits: OrderBookV3ArbOrderTaker

Functions

constructor

constructor(DeployerDiscoverableMetaV3ConstructionConfig memory config)
    OrderBookV3ArbOrderTaker(CALLER_META_HASH, config);

onTakeOrders

function onTakeOrders(
    address inputToken,
    address outputToken,
    uint256 inputAmountSent,
    uint256 totalOutputAmount,
    bytes calldata takeOrdersData
) public virtual override;

fallback

Allow receiving gas.

fallback() external onlyNotInitializing;

Constants

Git Source

CALLER_META_HASH

bytes32 constant CALLER_META_HASH = bytes32(0xe1d075e6f17f6706d942759ec359deb7f354ab4ac55e58eda2870c0ab3a89fa5);

GenericPoolOrderBookV3FlashBorrower

Git Source

Inherits: OrderBookV3FlashBorrower

Functions

constructor

constructor(DeployerDiscoverableMetaV3ConstructionConfig memory config)
    OrderBookV3FlashBorrower(CALLER_META_HASH, config);

_exchange

Hook that inheriting contracts MUST implement in order to achieve anything other than raising the ambient temperature of the room. _exchange is responsible for converting the flash loaned assets into the assets required to fill the orders. Generally this can only be achieved by interacting with an external liquidity source that is offering a better price than the orders require.

function _exchange(TakeOrdersConfigV2 memory takeOrders, bytes memory exchangeData) internal virtual override;

Parameters

NameTypeDescription
takeOrdersTakeOrdersConfigV2As per arb.
exchangeDatabytesAs per arb.

fallback

Allow receiving gas.

fallback() external onlyNotInitializing;

Constants

Git Source

CALLER_META_HASH

*Metadata hash for DeployerDiscoverableMetaV1.

  • ABI for GenericPoolOrderBookV3FlashBorrower
  • Interpreter caller metadata V1 for GenericPoolOrderBookV3FlashBorrower*
bytes32 constant CALLER_META_HASH = bytes32(0x52cdec296c4ecb0b2452c9e314df50e1b5d52fef03927934b7426ff6f150f0fe);

ReentrancyGuardReentrantCall

Git Source

This will exist in a future version of Open Zeppelin if their main branch is to be believed.

error ReentrancyGuardReentrantCall();

NotOrderOwner

Git Source

Thrown when the msg.sender modifying an order is not its owner.

error NotOrderOwner(address sender, address owner);

Parameters

NameTypeDescription
senderaddressmsg.sender attempting to modify the order.
owneraddressThe owner of the order.

TokenMismatch

Git Source

Thrown when the input and output tokens don't match, in either direction.

error TokenMismatch(address aliceToken, address bobToken);

Parameters

NameTypeDescription
aliceTokenaddressThe input or output of one order.
bobTokenaddressThe input or output of the other order that doesn't match a.

TokenDecimalsMismatch

Git Source

Thrown when the input and output token decimals don't match, in either direction.

error TokenDecimalsMismatch(uint8 aliceTokenDecimals, uint8 bobTokenDecimals);

Parameters

NameTypeDescription
aliceTokenDecimalsuint8The input or output decimals of one order.
bobTokenDecimalsuint8The input or output decimals of the other order.

MinimumInput

Git Source

Thrown when the minimum input is not met.

error MinimumInput(uint256 minimumInput, uint256 input);

Parameters

NameTypeDescription
minimumInputuint256The minimum input required.
inputuint256The input that was achieved.

SameOwner

Git Source

Thrown when two orders have the same owner during clear.

error SameOwner(address owner);

Parameters

NameTypeDescription
owneraddressThe owner of both orders.

UnsupportedCalculateInputs

Git Source

Thrown when calculate order expression wants inputs.

error UnsupportedCalculateInputs(uint256 inputs);

Parameters

NameTypeDescription
inputsuint256The inputs the expression wants.

UnsupportedCalculateOutputs

Git Source

Thrown when calculate order expression offers too few outputs.

error UnsupportedCalculateOutputs(uint256 outputs);

Parameters

NameTypeDescription
outputsuint256The outputs the expression offers.

UnsupportedHandleInputs

Git Source

Thrown when handle IO expression wants inputs.

error UnsupportedHandleInputs(uint256 inputs);

Parameters

NameTypeDescription
inputsuint256The inputs the expression wants.

OrderIOCalculationV2

Git Source

All information resulting from an order calculation that allows for vault IO to be calculated and applied, then the handle IO entrypoint to be dispatched.

struct OrderIOCalculationV2 {
    OrderV2 order;
    uint256 outputIOIndex;
    Output18Amount outputMax;
    uint256 IORatio;
    uint256[][] context;
    StateNamespace namespace;
    uint256[] kvs;
}

Properties

NameTypeDescription
orderOrderV2
outputIOIndexuint256
outputMaxOutput18AmountThe UNSCALED maximum output calculated by the order expression. WILL BE RESCALED ACCORDING TO TOKEN DECIMALS to an 18 fixed point decimal number for the purpose of calculating actual vault movements. The output max is CAPPED AT THE OUTPUT VAULT BALANCE OF THE ORDER OWNER. The order is guaranteed that the total output of this single clearance cannot exceed this (subject to rescaling). It is up to the order expression to track values over time if the output max is to impose a global limit across many transactions and counterparties.
IORatiouint256The UNSCALED order ratio as input/output from the perspective of the order. As each counterparty's input is the other's output, the IORatio calculated by each order is inverse of its counterparty. IORatio is SCALED ACCORDING TO TOKEN DECIMALS to allow 18 decimal fixed point math over the vault balances. I.e. 1e18 returned from the expression is ALWAYS "one" as ECONOMIC EQUIVALENCE between two tokens, but this will be rescaled according to the decimals of the token. For example, if DAI and USDT have a ratio of 1e18 then in reality 1e12 DAI will move in the vault for every 1 USDT that moves, because DAI has 1e18 decimals per $1 peg and USDT has 1e6 decimals per $1 peg. THE ORDER DEFINES THE DECIMALS for each token, NOT the token itself, because the token MAY NOT report its decimals as per it being optional in the ERC20 specification.
contextuint256[][]The entire 2D context array, initialized from the context passed into the order calculations and then populated with the order calculations and vault IO before being passed back to handle IO entrypoint.
namespaceStateNamespaceThe StateNamespace to be passed to the store for calculate IO state changes.
kvsuint256[]KVs returned from calculate order entrypoint to pass to the store before calling handle IO entrypoint.

Output18Amount

Git Source

type Output18Amount is uint256;

Input18Amount

Git Source

type Input18Amount is uint256;

OrderBook

Git Source

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

NameTypeDescription
owneraddressThe owner of the vault.
tokenaddressThe token the vault is for.
vaultIduint256

Returns

NameTypeDescription
<none>uint256balance 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

NameTypeDescription
orderHashbytes32The hash of the order to check.

Returns

NameTypeDescription
<none>boolexists 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

NameTypeDescription
tokenaddressThe token to deposit.
vaultIduint256The vault ID to deposit under.
amountuint256The 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

NameTypeDescription
tokenaddressThe token to withdraw.
vaultIduint256The vault ID to withdraw from.
targetAmountuint256The 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

NameTypeDescription
configOrderConfigV2All config required to build an Order.

Returns

NameTypeDescription
stateChangedboolTrue 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

NameTypeDescription
orderOrderV2The Order data exactly as it was added.

Returns

NameTypeDescription
stateChangedboolTrue 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

NameTypeDescription
configTakeOrdersConfigV2The 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

NameTypeDescription
totalTakerInputuint256totalInput Total tokens sent to msg.sender, taken from order vaults processed.
totalTakerOutputuint256totalOutput 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

NameTypeDescription
aliceOrderOrderV2
bobOrderOrderV2
clearConfigClearConfigAdditional configuration for the clearance such as how to handle the bounty payment for the msg.sender.
aliceSignedContextSignedContextV1[]Optional signed context that is relevant to A.
bobSignedContextSignedContextV1[]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

NameTypeDescription
orderOrderV2The order to evaluate.
inputIOIndexuint256The index of the input token being calculated for.
outputIOIndexuint256The index of the output token being calculated for.
counterpartyaddressThe counterparty of the order as it is currently being cleared against.
signedContextSignedContextV1[]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

NameTypeDescription
inputuint256The exact token input amount to move into the owner's vault.
outputuint256The exact token output amount to move out of the owner's vault.
orderIOCalculationOrderIOCalculationV2The 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

NameTypeDescription
aliceOrderIOCalculationOrderIOCalculationV2Order calculation for Alice.
bobOrderIOCalculationOrderIOCalculationV2Order calculation for Bob.

Returns

NameTypeDescription
clearStateChangeClearStateChangeThe 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);

Constants

Git Source

ORDER_LIVE

Stored value for a live order. NOT a boolean because storing a boolean is more expensive than storing a uint256.

uint256 constant ORDER_LIVE = 1;

ORDER_DEAD

Stored value for a dead order. 0 is chosen because it is the default value for a mapping, which means all orders are dead unless explicitly made live.

uint256 constant ORDER_DEAD = 0;

CALCULATE_ORDER_ENTRYPOINT

Entrypoint to a calculate the amount and ratio of an order.

SourceIndexV2 constant CALCULATE_ORDER_ENTRYPOINT = SourceIndexV2.wrap(0);

HANDLE_IO_ENTRYPOINT

Entrypoint to handle the final internal vault movements resulting from matching multiple calculated orders.

SourceIndexV2 constant HANDLE_IO_ENTRYPOINT = SourceIndexV2.wrap(1);

CALCULATE_ORDER_MIN_OUTPUTS

Minimum outputs for calculate order are the amount and ratio.

uint256 constant CALCULATE_ORDER_MIN_OUTPUTS = 2;

CALCULATE_ORDER_MAX_OUTPUTS

Maximum outputs for calculate order are the amount and ratio.

uint16 constant CALCULATE_ORDER_MAX_OUTPUTS = 2;

HANDLE_IO_MIN_OUTPUTS

Handle IO has no outputs as it only responds to vault movements.

uint256 constant HANDLE_IO_MIN_OUTPUTS = 0;

HANDLE_IO_MAX_OUTPUTS

Handle IO has no outputs as it only response to vault movements.

uint16 constant HANDLE_IO_MAX_OUTPUTS = 0;

CALLING_CONTEXT_COLUMNS

Orderbook context is actually fairly complex. The calling context column is populated before calculate order, but the remaining columns are only available to handle IO as they depend on the full evaluation of calculuate order, and cross referencing against the same from the counterparty, as well as accounting limits such as current vault balances, etc. The token address and decimals for vault inputs and outputs IS available to the calculate order entrypoint, but not the final vault balances/diff.

uint256 constant CALLING_CONTEXT_COLUMNS = 4;

CONTEXT_BASE_COLUMN

Base context from LibContext.

uint256 constant CONTEXT_BASE_COLUMN = 0;

CONTEXT_CALLING_CONTEXT_COLUMN

Contextual data available to both calculate order and handle IO. The order hash, order owner and order counterparty. IMPORTANT NOTE that the typical base context of an order with the caller will often be an unrelated clearer of the order rather than the owner or counterparty.

uint256 constant CONTEXT_CALLING_CONTEXT_COLUMN = 1;

CONTEXT_CALCULATIONS_COLUMN

Calculations column contains the DECIMAL RESCALED calculations but otherwise provided as-is according to calculate order entrypoint

uint256 constant CONTEXT_CALCULATIONS_COLUMN = 2;

CONTEXT_VAULT_INPUTS_COLUMN

Vault inputs are the literal token amounts and vault balances before and after for the input token from the perspective of the order. MAY be significantly different to the calculated amount due to insufficient vault balances from either the owner or counterparty, etc.

uint256 constant CONTEXT_VAULT_INPUTS_COLUMN = 3;

CONTEXT_VAULT_OUTPUTS_COLUMN

Vault outputs are the same as vault inputs but for the output token from the perspective of the order.

uint256 constant CONTEXT_VAULT_OUTPUTS_COLUMN = 4;

CONTEXT_VAULT_IO_TOKEN

Row of the token address for vault inputs and outputs columns.

uint256 constant CONTEXT_VAULT_IO_TOKEN = 0;

CONTEXT_VAULT_IO_TOKEN_DECIMALS

Row of the token decimals for vault inputs and outputs columns.

uint256 constant CONTEXT_VAULT_IO_TOKEN_DECIMALS = 1;

CONTEXT_VAULT_IO_VAULT_ID

Row of the vault ID for vault inputs and outputs columns.

uint256 constant CONTEXT_VAULT_IO_VAULT_ID = 2;

CONTEXT_VAULT_IO_BALANCE_BEFORE

Row of the vault balance before the order was cleared for vault inputs and outputs columns.

uint256 constant CONTEXT_VAULT_IO_BALANCE_BEFORE = 3;

CONTEXT_VAULT_IO_BALANCE_DIFF

Row of the vault balance difference after the order was cleared for vault inputs and outputs columns. The diff is ALWAYS POSITIVE as it is a uint256 so it must be added to input balances and subtraced from output balances.

uint256 constant CONTEXT_VAULT_IO_BALANCE_DIFF = 4;

CONTEXT_VAULT_IO_ROWS

Length of a vault IO column.

uint256 constant CONTEXT_VAULT_IO_ROWS = 5;

CALLER_META_HASH

Hash of the caller contract metadata for construction.

bytes32 constant CALLER_META_HASH = bytes32(0x4383dd3c5a557f161038940161f880963e1d4a31e8f2da771427b891956ad831);

RouteProcessorOrderBookV3ArbOrderTaker

Git Source

Inherits: OrderBookV3ArbOrderTaker

State Variables

sRouteProcessor

IRouteProcessor public sRouteProcessor;

Functions

constructor

constructor(DeployerDiscoverableMetaV3ConstructionConfig memory config)
    OrderBookV3ArbOrderTaker(CALLER_META_HASH, config);

_beforeInitialize

function _beforeInitialize(bytes memory data) internal virtual override;

onTakeOrders

function onTakeOrders(
    address inputToken,
    address outputToken,
    uint256 inputAmountSent,
    uint256 totalOutputAmount,
    bytes calldata takeOrdersData
) public virtual override;

fallback

Allow receiving gas.

fallback() external onlyNotInitializing;

Constants

Git Source

CALLER_META_HASH

bytes32 constant CALLER_META_HASH = bytes32(0xb5923e408032e4f76298ae26c496091f2d8f16a7c968bbd5ee83bf90b5aca00e);

Contents

Contents

DepositConfig

Git Source

Configuration for a deposit. All deposits are processed by and for msg.sender so the vaults are unambiguous here.

struct DepositConfig {
    address token;
    uint256 vaultId;
    uint256 amount;
}

Properties

NameTypeDescription
tokenaddressThe token to deposit.
vaultIduint256The vault ID for the token to deposit.
amountuint256The amount of the token to deposit.

WithdrawConfig

Git Source

Configuration for a withdrawal. All withdrawals are processed by and for msg.sender so the vaults are unambiguous here.

struct WithdrawConfig {
    address token;
    uint256 vaultId;
    uint256 amount;
}

Properties

NameTypeDescription
tokenaddressThe token to withdraw.
vaultIduint256The vault ID for the token to withdraw.
amountuint256The amount of the token to withdraw.

IO

Git Source

Configuration for a single input or output on an Order.

struct IO {
    address token;
    uint8 decimals;
    uint256 vaultId;
}

Properties

NameTypeDescription
tokenaddressThe token to either send from the owner as an output or receive from the counterparty to the owner as an input. The tokens are not moved during an order, only internal vault balances are updated, until a separate withdraw step.
decimalsuint8The decimals to use for internal scaling calculations for token. This is provided directly in IO to save gas on external lookups and to respect the ERC20 spec that mandates NOT assuming or using the decimals method for onchain calculations. Ostensibly the decimals exists so that all calculate order entrypoints can treat amounts and ratios as 18 decimal fixed point values. Order max amounts MUST be rounded down and IO ratios rounded up to compensate for any loss of precision during decimal rescaling.
vaultIduint256The vault ID that tokens will move into if this is an input or move out from if this is an output.

OrderConfig

Git Source

Config the order owner may provide to define their order. The msg.sender that adds an order cannot modify the owner nor bypass the integrity check of the expression deployer that they specify. However they MAY specify a deployer with a corrupt integrity check, so counterparties and clearers MUST check the DISpair of the order and avoid untrusted pairings.

struct OrderConfig {
    IO[] validInputs;
    IO[] validOutputs;
    EvaluableConfig evaluableConfig;
    bytes meta;
}

Properties

NameTypeDescription
validInputsIO[]As per validInputs on the Order.
validOutputsIO[]As per validOutputs on the Order.
evaluableConfigEvaluableConfigStandard EvaluableConfig used to produce the Evaluable on the order.
metabytesArbitrary bytes that will NOT be used in the order evaluation but MUST be emitted as a Rain MetaV1 when the order is placed so can be used by offchain processes.

Order

Git Source

Defines a fully deployed order ready to evaluate by Orderbook.

struct Order {
    address owner;
    bool handleIO;
    Evaluable evaluable;
    IO[] validInputs;
    IO[] validOutputs;
}

Properties

NameTypeDescription
owneraddressThe owner of the order is the msg.sender that added the order.
handleIObooltrue if there is a "handle IO" entrypoint to run. If false the order book MAY skip calling the interpreter to save gas.
evaluableEvaluableStandard Evaluable with entrypoints for both "calculate order" and "handle IO". The latter MAY be empty bytes, in which case it will be skipped at runtime to save gas.
validInputsIO[]A list of input tokens that are economically equivalent for the purpose of processing this order. Inputs are relative to the order so these tokens will be sent to the owners vault.
validOutputsIO[]A list of output tokens that are economically equivalent for the purpose of processing this order. Outputs are relative to the order so these tokens will be sent from the owners vault.

TakeOrdersConfig

Git Source

Config for a list of orders to take sequentially as part of a takeOrders call.

struct TakeOrdersConfig {
    address output;
    address input;
    uint256 minimumInput;
    uint256 maximumInput;
    uint256 maximumIORatio;
    TakeOrderConfig[] orders;
}

Properties

NameTypeDescription
outputaddressOutput token from the perspective of the order taker.
inputaddressInput token from the perspective of the order taker.
minimumInputuint256Minimum input from the perspective of the order taker.
maximumInputuint256Maximum input from the perspective of the order taker.
maximumIORatiouint256Maximum IO ratio as calculated by the order being taken. The input is from the perspective of the order so higher ratio means worse deal for the order taker.
ordersTakeOrderConfig[]Ordered list of orders that will be taken until the limit is hit. Takers are expected to prioritise orders that appear to be offering better deals i.e. lower IO ratios. This prioritisation and sorting MUST happen offchain, e.g. via. some simulator.

TakeOrderConfig

Git Source

Config for an individual take order from the overall list of orders in a call to takeOrders.

struct TakeOrderConfig {
    Order order;
    uint256 inputIOIndex;
    uint256 outputIOIndex;
    SignedContext[] signedContext;
}

Properties

NameTypeDescription
orderOrderThe order being taken this iteration.
inputIOIndexuint256The index of the input token in order to match with the take order output.
outputIOIndexuint256The index of the output token in order to match with the take order input.
signedContextSignedContext[]Optional additional signed context relevant to the taken order.

ClearConfig

Git Source

Additional config to a clear that allows two orders to be fully matched to a specific token moment. Also defines the bounty for the clearer.

struct ClearConfig {
    uint256 aliceInputIOIndex;
    uint256 aliceOutputIOIndex;
    uint256 bobInputIOIndex;
    uint256 bobOutputIOIndex;
    uint256 aliceBountyVaultId;
    uint256 bobBountyVaultId;
}

Properties

NameTypeDescription
aliceInputIOIndexuint256The index of the input token in order A.
aliceOutputIOIndexuint256The index of the output token in order A.
bobInputIOIndexuint256The index of the input token in order B.
bobOutputIOIndexuint256The index of the output token in order B.
aliceBountyVaultIduint256The vault ID that the bounty from order A should move to for the clearer.
bobBountyVaultIduint256The vault ID that the bounty from order B should move to for the clearer.

ClearStateChange

Git Source

Summary of the vault state changes due to clearing an order. NOT the state changes sent to the interpreter store, these are the LOCAL CHANGES in vault balances. Note that the difference in inputs/outputs overall between the counterparties is the bounty paid to the entity that cleared the order.

struct ClearStateChange {
    uint256 aliceOutput;
    uint256 bobOutput;
    uint256 aliceInput;
    uint256 bobInput;
}

Properties

NameTypeDescription
aliceOutputuint256Amount of counterparty A's output token that moved out of their vault.
bobOutputuint256Amount of counterparty B's output token that moved out of their vault.
aliceInputuint256Amount of counterparty A's input token that moved into their vault.
bobInputuint256Amount of counterparty B's input token that moved into their vault.

IOrderBookV1

Git Source

Inherits: IERC3156FlashLender, IInterpreterCallerV1

An orderbook that deploys strategies represented as interpreter expressions rather than individual orders. The order book contract itself behaves similarly to an ERC4626 vault but with much more fine grained control over how tokens are allocated and moved internally by their owners, and without any concept of "shares". Token owners MAY deposit and withdraw their tokens under arbitrary vault IDs on a per-token basis, then define orders that specify how tokens move between vaults according to an expression. The expression returns a maximum amount and a token input/output ratio from the perpective of the order. When two expressions intersect, as in their ratios are the inverse of each other, then tokens can move between vaults. For example, consider order A with input TKNA and output TKNB with a constant ratio of 100:1. This order in isolation has no ability to move tokens. If an order B appears with input TKNB and output TKNA and a ratio of 1:100 then this is a perfect match with order A. In this case 100 TKNA will move from order B to order A and 1 TKNB will move from order A to order B. IO ratios are always specified as input:output and are 18 decimal fixed point values. The maximum amount that can be moved in the current clearance is also set by the order expression as an 18 decimal fixed point value. Typically orders will not clear when their match is exactly 1:1 as the clearer needs to pay gas to process the match. Each order will get exactly the ratio it calculates when it does clear so if there is overlap in the ratios then the clearer keeps the difference. In our above example, consider order B asking a ratio of 1:110 instead of 1:100. In this case 100 TKNA will move from order B to order A and 10 TKNA will move to the clearer's vault and 1 TKNB will move from order A to order B. In the case of fixed prices this is not very interesting as order B could more simply take order A directly for cheaper rather than involving a third party. Indeed, Orderbook supports a direct "take orders" method that works similar to a "market buy". In the case of dynamic expression based ratios, it allows both order A and order B to clear non-interactively according to their strategy, trading off active management, dealing with front-running, MEV, etc. for zero-gas and exact-ratio clearance. The general invariant for clearing and take orders is:

ratioA = InputA / OutputA
ratioB = InputB / OutputB
ratioA * ratioB = ( InputA * InputB ) / ( OutputA * OutputB )
OutputA >= InputB
OutputB >= InputA
∴ ratioA * ratioB <= 1

Orderbook is IERC3156FlashLender compliant with a 0 fee flash loan implementation to allow external liquidity from other onchain DEXes to match against orderbook expressions. All deposited tokens across all vaults are available for flashloan, the flashloan MAY BE REPAID BY CALLING TAKE ORDER such that Orderbook's liability to its vaults is decreased by an incoming trade from the flashloan borrower. See ZeroExOrderBookFlashBorrower for an example of how this works in practise. Orderbook supports many to many input/output token relationship, for example some order can specify an array of stables it would be willing to accept in return for some ETH. This removes the need for a combinatorial explosion of order strategies between like assets but introduces the issue of token decimal handling. End users understand that "one" USDT is roughly equal to "one" DAI, but onchain this is incorrect by 12 orders of magnitude. This is because "one" DAI is 1e18 tokens and "one" USDT is 1e6 tokens. The orderbook is allowing orders to deploy expressions that define economic equivalence but this doesn't map 1:1 with numeric equivalence in a many to many setup behind token decimal convensions. The solution is to require that end users who place orders provide the decimals of each token they include in their valid IO lists, and to calculate all amounts and ratios in their expressions as though they were 18 decimal fixed point values. Orderbook will then automatically rescale the expression values before applying the final vault movements. If an order provides the "wrong" decimal values for some token then it will simply calculate its own ratios and amounts incorrectly which will either lead to no matching orders or a very bad trade for the order owner. There is no way that misrepresenting decimals can attack some other order by a counterparty. Orderbook DOES NOT read decimals from tokens onchain because A. this would be gas for an external call to a cold token contract and B. the ERC20 standard specifically states NOT to read decimals from the interface onchain. Token amounts and ratios returned by calculate order MUST be 18 decimal fixed point values. Token amounts input to handle IO MUST be the exact absolute values that move between the vaults, i.e. NOT rescaled to 18 decimals. The author of the handle IO expression MUST use the token decimals and amounts to rescale themselves if they want that logic, notably the expression author will need to specify the desired rounding behaviour in the rescaling process. When two orders clear there are NO TOKEN MOVEMENTS, only internal vault balances are updated from the input and output vaults. Typically this results in less gas per clear than calling external token transfers and also avoids issues with reentrancy, allowances, external balances etc. This also means that REBASING TOKENS AND TOKENS WITH DYNAMIC BALANCE ARE NOT SUPPORTED. Orderbook ONLY WORKS IF TOKEN BALANCES ARE 1:1 WITH ADDITION/SUBTRACTION PER VAULT MOVEMENT. Dust due to rounding errors always favours the order. Output max is rounded down and IO ratios are rounded up. Input and output amounts are always converted to absolute values before applying to vault balances such that orderbook always retains fully collateralised inventory of underlying token balances to support withdrawals, with the caveat that dynamic token balanes are not supported. When an order clears it is NOT removed. Orders remain active until the owner deactivates them. This is gas efficient as order owners MAY deposit more tokens in a vault with an order against it many times and the order strategy will continue to be clearable according to its expression. As vault IDs are uint256 values there are effectively infinite possible vaults for any token so there is no limit to how many active orders any address can have at one time. This also allows orders to be daisy chained arbitrarily where output vaults for some order are the input vaults for some other order. Expression storage is namespaced by order owner, so gets and sets are unique to each onchain address. Order owners MUST TAKE CARE not to override their storage sets globally across all their orders, which they can do most simply by hashing the order hash into their get/set keys inside the expression. This gives maximum flexibility for shared state across orders without allowing order owners to attack and overwrite values stored by orders placed by their counterparty. Note that each order specifies its own interpreter and deployer so the owner is responsible for not corrupting their own calculations with bad interpreters. This also means the Orderbook MUST assume the interpreter, and notably the interpreter's store, is malicious and guard against reentrancy etc. As Orderbook supports any expression that can run on any IInterpreterV1 and counterparties are available to the order, order strategies are free to implement KYC/membership, tracking, distributions, stock, buybacks, etc. etc.

Functions

vaultBalance

Get the current balance of a vault for a given owner, token and vault ID.

function vaultBalance(address owner, address token, uint256 id) external view returns (uint256 balance);

Parameters

NameTypeDescription
owneraddressThe owner of the vault.
tokenaddressThe token the vault is for.
iduint256The vault ID to read.

Returns

NameTypeDescription
balanceuint256The current balance of the vault.

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.

function deposit(DepositConfig calldata config) external;

Parameters

NameTypeDescription
configDepositConfigAll config for the 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.

function withdraw(WithdrawConfig calldata config) external;

Parameters

NameTypeDescription
configWithdrawConfigAll config required to withdraw. Notably if the amount is less than the current vault balance then the vault will be cleared to 0 rather than the withdraw transaction reverting.

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.

function addOrder(OrderConfig calldata config) external;

Parameters

NameTypeDescription
configOrderConfigAll config required to build an Order.

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(Order calldata order) external;

Parameters

NameTypeDescription
orderOrderThe Order data exactly as it was added.

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(TakeOrdersConfig calldata config) external returns (uint256 totalInput, uint256 totalOutput);

Parameters

NameTypeDescription
configTakeOrdersConfigThe 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

NameTypeDescription
totalInputuint256Total tokens sent to msg.sender, taken from order vaults processed.
totalOutputuint256Total 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(
    Order memory alice,
    Order memory bob,
    ClearConfig calldata clearConfig,
    SignedContext[] memory aliceSignedContext,
    SignedContext[] memory bobSignedContext
) external;

Parameters

NameTypeDescription
aliceOrderSome order to clear.
bobOrderAnother order to clear.
clearConfigClearConfigAdditional configuration for the clearance such as how to handle the bounty payment for the msg.sender.
aliceSignedContextSignedContext[]Optional signed context that is relevant to A.
bobSignedContextSignedContext[]Optional signed context that is relevant to B.

Events

Deposit

Some tokens have been deposited to a vault.

event Deposit(address sender, DepositConfig config);

Parameters

NameTypeDescription
senderaddressmsg.sender depositing tokens. Delegated deposits are NOT supported.
configDepositConfigAll config sent to the deposit call.

Withdraw

Some tokens have been withdrawn from a vault.

event Withdraw(address sender, WithdrawConfig config, uint256 amount);

Parameters

NameTypeDescription
senderaddressmsg.sender withdrawing tokens. Delegated withdrawals are NOT supported.
configWithdrawConfigAll config sent to the withdraw call.
amountuint256The amount of tokens withdrawn, can be less than the config amount if the vault does not have the funds available to cover the config amount. For example an active order might move tokens before the withdraw completes.

AddOrder

An order has been added to the orderbook. The order is permanently and always active according to its expression until/unless it is removed.

event AddOrder(address sender, IExpressionDeployerV3 expressionDeployer, Order order, uint256 orderHash);

Parameters

NameTypeDescription
senderaddressmsg.sender adding the order and is owner of the order.
expressionDeployerIExpressionDeployerV3The expression deployer that ran the integrity check for this order. This is NOT included in the Order itself but is important for offchain processes to ignore untrusted deployers before interacting with them.
orderOrderThe newly added order. MUST be handed back as-is when clearing orders and contains derived information in addition to the order config that was provided by the order owner.
orderHashuint256The hash of the order as it is recorded onchain. Only the hash is stored in Orderbook storage to avoid paying gas to store the entire order.

RemoveOrder

An order has been removed from the orderbook. This effectively deactivates it. Orders can be added again after removal.

event RemoveOrder(address sender, Order order, uint256 orderHash);

Parameters

NameTypeDescription
senderaddressmsg.sender removing the order and is owner of the order.
orderOrderThe removed order.
orderHashuint256The hash of the removed order.

TakeOrder

Some order has been taken by msg.sender. This is the same as them placing inverse orders then immediately clearing them all, but costs less gas and is more convenient and reliable. Analogous to a market buy against the specified orders. Each order that is matched within a the takeOrders loop emits its own individual event.

event TakeOrder(address sender, TakeOrderConfig config, uint256 input, uint256 output);

Parameters

NameTypeDescription
senderaddressmsg.sender taking the orders.
configTakeOrderConfigAll config defining the orders to attempt to take.
inputuint256The input amount from the perspective of sender.
outputuint256The output amount from the perspective of sender.

OrderNotFound

Emitted when attempting to match an order that either never existed or was removed. An event rather than an error so that we allow attempting many orders in a loop and NOT rollback on "best effort" basis to clear.

event OrderNotFound(address sender, address owner, uint256 orderHash);

Parameters

NameTypeDescription
senderaddressmsg.sender clearing the order that wasn't found.
owneraddressOwner of the order that was not found.
orderHashuint256Hash of the order that was not found.

OrderZeroAmount

Emitted when an order evaluates to a zero amount. An event rather than an error so that we allow attempting many orders in a loop and NOT rollback on a "best effort" basis to clear.

event OrderZeroAmount(address sender, address owner, uint256 orderHash);

Parameters

NameTypeDescription
senderaddressmsg.sender clearing the order that had a 0 amount.
owneraddressOwner of the order that evaluated to a 0 amount.
orderHashuint256Hash of the order that evaluated to a 0 amount.

OrderExceedsMaxRatio

Emitted when an order evaluates to a ratio exceeding the counterparty's maximum limit. An error rather than an error so that we allow attempting many orders in a loop and NOT rollback on a "best effort" basis to clear.

event OrderExceedsMaxRatio(address sender, address owner, uint256 orderHash);

Parameters

NameTypeDescription
senderaddressmsg.sender clearing the order that had an excess ratio.
owneraddressOwner of the order that had an excess ratio.
orderHashuint256Hash of the order that had an excess ratio.

Clear

Emitted before two orders clear. Covers both orders and includes all the state before anything is calculated.

event Clear(address sender, Order alice, Order bob, ClearConfig clearConfig);

Parameters

NameTypeDescription
senderaddressmsg.sender clearing both orders.
aliceOrderOne of the orders.
bobOrderThe other order.
clearConfigClearConfigAdditional config required to process the clearance.

AfterClear

Emitted after two orders clear. Includes all final state changes in the vault balances, including the clearer's vaults.

event AfterClear(address sender, ClearStateChange clearStateChange);

Parameters

NameTypeDescription
senderaddressmsg.sender clearing the order.
clearStateChangeClearStateChangeThe final vault state changes from the clearance.

DepositConfig

Git Source

Configuration for a deposit. All deposits are processed by and for msg.sender so the vaults are unambiguous here.

struct DepositConfig {
    address token;
    uint256 vaultId;
    uint256 amount;
}

Properties

NameTypeDescription
tokenaddressThe token to deposit.
vaultIduint256The vault ID for the token to deposit.
amountuint256The amount of the token to deposit.

WithdrawConfig

Git Source

Configuration for a withdrawal. All withdrawals are processed by and for msg.sender so the vaults are unambiguous here.

struct WithdrawConfig {
    address token;
    uint256 vaultId;
    uint256 amount;
}

Properties

NameTypeDescription
tokenaddressThe token to withdraw.
vaultIduint256The vault ID for the token to withdraw.
amountuint256The amount of the token to withdraw.

IO

Git Source

Configuration for a single input or output on an Order.

struct IO {
    address token;
    uint8 decimals;
    uint256 vaultId;
}

Properties

NameTypeDescription
tokenaddressThe token to either send from the owner as an output or receive from the counterparty to the owner as an input. The tokens are not moved during an order, only internal vault balances are updated, until a separate withdraw step.
decimalsuint8The decimals to use for internal scaling calculations for token. This is provided directly in IO to save gas on external lookups and to respect the ERC20 spec that mandates NOT assuming or using the decimals method for onchain calculations. Ostensibly the decimals exists so that all calculate order entrypoints can treat amounts and ratios as 18 decimal fixed point values. Order max amounts MUST be rounded down and IO ratios rounded up to compensate for any loss of precision during decimal rescaling.
vaultIduint256The vault ID that tokens will move into if this is an input or move out from if this is an output.

OrderConfig

Git Source

Config the order owner may provide to define their order. The msg.sender that adds an order cannot modify the owner nor bypass the integrity check of the expression deployer that they specify. However they MAY specify a deployer with a corrupt integrity check, so counterparties and clearers MUST check the DISpair of the order and avoid untrusted pairings.

struct OrderConfig {
    IO[] validInputs;
    IO[] validOutputs;
    EvaluableConfig evaluableConfig;
    bytes meta;
}

Properties

NameTypeDescription
validInputsIO[]As per validInputs on the Order.
validOutputsIO[]As per validOutputs on the Order.
evaluableConfigEvaluableConfigStandard EvaluableConfig used to produce the Evaluable on the order.
metabytesArbitrary bytes that will NOT be used in the order evaluation but MUST be emitted as a Rain MetaV1 when the order is placed so can be used by offchain processes.

Order

Git Source

Defines a fully deployed order ready to evaluate by Orderbook.

struct Order {
    address owner;
    bool handleIO;
    Evaluable evaluable;
    IO[] validInputs;
    IO[] validOutputs;
}

Properties

NameTypeDescription
owneraddressThe owner of the order is the msg.sender that added the order.
handleIObooltrue if there is a "handle IO" entrypoint to run. If false the order book MAY skip calling the interpreter to save gas.
evaluableEvaluableStandard Evaluable with entrypoints for both "calculate order" and "handle IO". The latter MAY be empty bytes, in which case it will be skipped at runtime to save gas.
validInputsIO[]A list of input tokens that are economically equivalent for the purpose of processing this order. Inputs are relative to the order so these tokens will be sent to the owners vault.
validOutputsIO[]A list of output tokens that are economically equivalent for the purpose of processing this order. Outputs are relative to the order so these tokens will be sent from the owners vault.

TakeOrdersConfig

Git Source

Config for a list of orders to take sequentially as part of a takeOrders call.

struct TakeOrdersConfig {
    address output;
    address input;
    uint256 minimumInput;
    uint256 maximumInput;
    uint256 maximumIORatio;
    TakeOrderConfig[] orders;
}

Properties

NameTypeDescription
outputaddressOutput token from the perspective of the order taker.
inputaddressInput token from the perspective of the order taker.
minimumInputuint256Minimum input from the perspective of the order taker.
maximumInputuint256Maximum input from the perspective of the order taker.
maximumIORatiouint256Maximum IO ratio as calculated by the order being taken. The input is from the perspective of the order so higher ratio means worse deal for the order taker.
ordersTakeOrderConfig[]Ordered list of orders that will be taken until the limit is hit. Takers are expected to prioritise orders that appear to be offering better deals i.e. lower IO ratios. This prioritisation and sorting MUST happen offchain, e.g. via. some simulator.

TakeOrderConfig

Git Source

Config for an individual take order from the overall list of orders in a call to takeOrders.

struct TakeOrderConfig {
    Order order;
    uint256 inputIOIndex;
    uint256 outputIOIndex;
    SignedContextV1[] signedContext;
}

Properties

NameTypeDescription
orderOrderThe order being taken this iteration.
inputIOIndexuint256The index of the input token in order to match with the take order output.
outputIOIndexuint256The index of the output token in order to match with the take order input.
signedContextSignedContextV1[]Optional additional signed context relevant to the taken order.

ClearConfig

Git Source

Additional config to a clear that allows two orders to be fully matched to a specific token moment. Also defines the bounty for the clearer.

struct ClearConfig {
    uint256 aliceInputIOIndex;
    uint256 aliceOutputIOIndex;
    uint256 bobInputIOIndex;
    uint256 bobOutputIOIndex;
    uint256 aliceBountyVaultId;
    uint256 bobBountyVaultId;
}

Properties

NameTypeDescription
aliceInputIOIndexuint256The index of the input token in order A.
aliceOutputIOIndexuint256The index of the output token in order A.
bobInputIOIndexuint256The index of the input token in order B.
bobOutputIOIndexuint256The index of the output token in order B.
aliceBountyVaultIduint256The vault ID that the bounty from order A should move to for the clearer.
bobBountyVaultIduint256The vault ID that the bounty from order B should move to for the clearer.

ClearStateChange

Git Source

Summary of the vault state changes due to clearing an order. NOT the state changes sent to the interpreter store, these are the LOCAL CHANGES in vault balances. Note that the difference in inputs/outputs overall between the counterparties is the bounty paid to the entity that cleared the order.

struct ClearStateChange {
    uint256 aliceOutput;
    uint256 bobOutput;
    uint256 aliceInput;
    uint256 bobInput;
}

Properties

NameTypeDescription
aliceOutputuint256Amount of counterparty A's output token that moved out of their vault.
bobOutputuint256Amount of counterparty B's output token that moved out of their vault.
aliceInputuint256Amount of counterparty A's input token that moved into their vault.
bobInputuint256Amount of counterparty B's input token that moved into their vault.

IOrderBookV2

Git Source

Inherits: IERC3156FlashLender, IInterpreterCallerV2

An orderbook that deploys strategies represented as interpreter expressions rather than individual orders. The order book contract itself behaves similarly to an ERC4626 vault but with much more fine grained control over how tokens are allocated and moved internally by their owners, and without any concept of "shares". Token owners MAY deposit and withdraw their tokens under arbitrary vault IDs on a per-token basis, then define orders that specify how tokens move between vaults according to an expression. The expression returns a maximum amount and a token input/output ratio from the perpective of the order. When two expressions intersect, as in their ratios are the inverse of each other, then tokens can move between vaults. For example, consider order A with input TKNA and output TKNB with a constant ratio of 100:1. This order in isolation has no ability to move tokens. If an order B appears with input TKNB and output TKNA and a ratio of 1:100 then this is a perfect match with order A. In this case 100 TKNA will move from order B to order A and 1 TKNB will move from order A to order B. IO ratios are always specified as input:output and are 18 decimal fixed point values. The maximum amount that can be moved in the current clearance is also set by the order expression as an 18 decimal fixed point value. Typically orders will not clear when their match is exactly 1:1 as the clearer needs to pay gas to process the match. Each order will get exactly the ratio it calculates when it does clear so if there is overlap in the ratios then the clearer keeps the difference. In our above example, consider order B asking a ratio of 1:110 instead of 1:100. In this case 100 TKNA will move from order B to order A and 10 TKNA will move to the clearer's vault and 1 TKNB will move from order A to order B. In the case of fixed prices this is not very interesting as order B could more simply take order A directly for cheaper rather than involving a third party. Indeed, Orderbook supports a direct "take orders" method that works similar to a "market buy". In the case of dynamic expression based ratios, it allows both order A and order B to clear non-interactively according to their strategy, trading off active management, dealing with front-running, MEV, etc. for zero-gas and exact-ratio clearance. The general invariant for clearing and take orders is:

ratioA = InputA / OutputA
ratioB = InputB / OutputB
ratioA * ratioB = ( InputA * InputB ) / ( OutputA * OutputB )
OutputA >= InputB
OutputB >= InputA
∴ ratioA * ratioB <= 1

Orderbook is IERC3156FlashLender compliant with a 0 fee flash loan implementation to allow external liquidity from other onchain DEXes to match against orderbook expressions. All deposited tokens across all vaults are available for flashloan, the flashloan MAY BE REPAID BY CALLING TAKE ORDER such that Orderbook's liability to its vaults is decreased by an incoming trade from the flashloan borrower. See ZeroExOrderBookFlashBorrower for an example of how this works in practise. Orderbook supports many to many input/output token relationship, for example some order can specify an array of stables it would be willing to accept in return for some ETH. This removes the need for a combinatorial explosion of order strategies between like assets but introduces the issue of token decimal handling. End users understand that "one" USDT is roughly equal to "one" DAI, but onchain this is incorrect by 12 orders of magnitude. This is because "one" DAI is 1e18 tokens and "one" USDT is 1e6 tokens. The orderbook is allowing orders to deploy expressions that define economic equivalence but this doesn't map 1:1 with numeric equivalence in a many to many setup behind token decimal convensions. The solution is to require that end users who place orders provide the decimals of each token they include in their valid IO lists, and to calculate all amounts and ratios in their expressions as though they were 18 decimal fixed point values. Orderbook will then automatically rescale the expression values before applying the final vault movements. If an order provides the "wrong" decimal values for some token then it will simply calculate its own ratios and amounts incorrectly which will either lead to no matching orders or a very bad trade for the order owner. There is no way that misrepresenting decimals can attack some other order by a counterparty. Orderbook DOES NOT read decimals from tokens onchain because A. this would be gas for an external call to a cold token contract and B. the ERC20 standard specifically states NOT to read decimals from the interface onchain. Token amounts and ratios returned by calculate order MUST be 18 decimal fixed point values. Token amounts input to handle IO MUST be the exact absolute values that move between the vaults, i.e. NOT rescaled to 18 decimals. The author of the handle IO expression MUST use the token decimals and amounts to rescale themselves if they want that logic, notably the expression author will need to specify the desired rounding behaviour in the rescaling process. When two orders clear there are NO TOKEN MOVEMENTS, only internal vault balances are updated from the input and output vaults. Typically this results in less gas per clear than calling external token transfers and also avoids issues with reentrancy, allowances, external balances etc. This also means that REBASING TOKENS AND TOKENS WITH DYNAMIC BALANCE ARE NOT SUPPORTED. Orderbook ONLY WORKS IF TOKEN BALANCES ARE 1:1 WITH ADDITION/SUBTRACTION PER VAULT MOVEMENT. Dust due to rounding errors always favours the order. Output max is rounded down and IO ratios are rounded up. Input and output amounts are always converted to absolute values before applying to vault balances such that orderbook always retains fully collateralised inventory of underlying token balances to support withdrawals, with the caveat that dynamic token balanes are not supported. When an order clears it is NOT removed. Orders remain active until the owner deactivates them. This is gas efficient as order owners MAY deposit more tokens in a vault with an order against it many times and the order strategy will continue to be clearable according to its expression. As vault IDs are uint256 values there are effectively infinite possible vaults for any token so there is no limit to how many active orders any address can have at one time. This also allows orders to be daisy chained arbitrarily where output vaults for some order are the input vaults for some other order. Expression storage is namespaced by order owner, so gets and sets are unique to each onchain address. Order owners MUST TAKE CARE not to override their storage sets globally across all their orders, which they can do most simply by hashing the order hash into their get/set keys inside the expression. This gives maximum flexibility for shared state across orders without allowing order owners to attack and overwrite values stored by orders placed by their counterparty. Note that each order specifies its own interpreter and deployer so the owner is responsible for not corrupting their own calculations with bad interpreters. This also means the Orderbook MUST assume the interpreter, and notably the interpreter's store, is malicious and guard against reentrancy etc. As Orderbook supports any expression that can run on any IInterpreterV1 and counterparties are available to the order, order strategies are free to implement KYC/membership, tracking, distributions, stock, buybacks, etc. etc.

Functions

vaultBalance

Get the current balance of a vault for a given owner, token and vault ID.

function vaultBalance(address owner, address token, uint256 id) external view returns (uint256 balance);

Parameters

NameTypeDescription
owneraddressThe owner of the vault.
tokenaddressThe token the vault is for.
iduint256The vault ID to read.

Returns

NameTypeDescription
balanceuint256The current balance of the vault.

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.

function deposit(DepositConfig calldata config) external;

Parameters

NameTypeDescription
configDepositConfigAll config for the 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.

function withdraw(WithdrawConfig calldata config) external;

Parameters

NameTypeDescription
configWithdrawConfigAll config required to withdraw. Notably if the amount is less than the current vault balance then the vault will be cleared to 0 rather than the withdraw transaction reverting.

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.

function addOrder(OrderConfig calldata config) external;

Parameters

NameTypeDescription
configOrderConfigAll config required to build an Order.

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(Order calldata order) external;

Parameters

NameTypeDescription
orderOrderThe Order data exactly as it was added.

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(TakeOrdersConfig calldata config) external returns (uint256 totalInput, uint256 totalOutput);

Parameters

NameTypeDescription
configTakeOrdersConfigThe 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

NameTypeDescription
totalInputuint256Total tokens sent to msg.sender, taken from order vaults processed.
totalOutputuint256Total 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(
    Order memory alice,
    Order memory bob,
    ClearConfig calldata clearConfig,
    SignedContextV1[] memory aliceSignedContext,
    SignedContextV1[] memory bobSignedContext
) external;

Parameters

NameTypeDescription
aliceOrderSome order to clear.
bobOrderAnother order to clear.
clearConfigClearConfigAdditional configuration for the clearance such as how to handle the bounty payment for the msg.sender.
aliceSignedContextSignedContextV1[]Optional signed context that is relevant to A.
bobSignedContextSignedContextV1[]Optional signed context that is relevant to B.

Events

Deposit

Some tokens have been deposited to a vault.

event Deposit(address sender, DepositConfig config);

Parameters

NameTypeDescription
senderaddressmsg.sender depositing tokens. Delegated deposits are NOT supported.
configDepositConfigAll config sent to the deposit call.

Withdraw

Some tokens have been withdrawn from a vault.

event Withdraw(address sender, WithdrawConfig config, uint256 amount);

Parameters

NameTypeDescription
senderaddressmsg.sender withdrawing tokens. Delegated withdrawals are NOT supported.
configWithdrawConfigAll config sent to the withdraw call.
amountuint256The amount of tokens withdrawn, can be less than the config amount if the vault does not have the funds available to cover the config amount. For example an active order might move tokens before the withdraw completes.

AddOrder

An order has been added to the orderbook. The order is permanently and always active according to its expression until/unless it is removed.

event AddOrder(address sender, IExpressionDeployerV2 expressionDeployer, Order order, uint256 orderHash);

Parameters

NameTypeDescription
senderaddressmsg.sender adding the order and is owner of the order.
expressionDeployerIExpressionDeployerV2The expression deployer that ran the integrity check for this order. This is NOT included in the Order itself but is important for offchain processes to ignore untrusted deployers before interacting with them.
orderOrderThe newly added order. MUST be handed back as-is when clearing orders and contains derived information in addition to the order config that was provided by the order owner.
orderHashuint256The hash of the order as it is recorded onchain. Only the hash is stored in Orderbook storage to avoid paying gas to store the entire order.

RemoveOrder

An order has been removed from the orderbook. This effectively deactivates it. Orders can be added again after removal.

event RemoveOrder(address sender, Order order, uint256 orderHash);

Parameters

NameTypeDescription
senderaddressmsg.sender removing the order and is owner of the order.
orderOrderThe removed order.
orderHashuint256The hash of the removed order.

TakeOrder

Some order has been taken by msg.sender. This is the same as them placing inverse orders then immediately clearing them all, but costs less gas and is more convenient and reliable. Analogous to a market buy against the specified orders. Each order that is matched within a the takeOrders loop emits its own individual event.

event TakeOrder(address sender, TakeOrderConfig config, uint256 input, uint256 output);

Parameters

NameTypeDescription
senderaddressmsg.sender taking the orders.
configTakeOrderConfigAll config defining the orders to attempt to take.
inputuint256The input amount from the perspective of sender.
outputuint256The output amount from the perspective of sender.

OrderNotFound

Emitted when attempting to match an order that either never existed or was removed. An event rather than an error so that we allow attempting many orders in a loop and NOT rollback on "best effort" basis to clear.

event OrderNotFound(address sender, address owner, uint256 orderHash);

Parameters

NameTypeDescription
senderaddressmsg.sender clearing the order that wasn't found.
owneraddressOwner of the order that was not found.
orderHashuint256Hash of the order that was not found.

OrderZeroAmount

Emitted when an order evaluates to a zero amount. An event rather than an error so that we allow attempting many orders in a loop and NOT rollback on a "best effort" basis to clear.

event OrderZeroAmount(address sender, address owner, uint256 orderHash);

Parameters

NameTypeDescription
senderaddressmsg.sender clearing the order that had a 0 amount.
owneraddressOwner of the order that evaluated to a 0 amount.
orderHashuint256Hash of the order that evaluated to a 0 amount.

OrderExceedsMaxRatio

Emitted when an order evaluates to a ratio exceeding the counterparty's maximum limit. An error rather than an error so that we allow attempting many orders in a loop and NOT rollback on a "best effort" basis to clear.

event OrderExceedsMaxRatio(address sender, address owner, uint256 orderHash);

Parameters

NameTypeDescription
senderaddressmsg.sender clearing the order that had an excess ratio.
owneraddressOwner of the order that had an excess ratio.
orderHashuint256Hash of the order that had an excess ratio.

Clear

Emitted before two orders clear. Covers both orders and includes all the state before anything is calculated.

event Clear(address sender, Order alice, Order bob, ClearConfig clearConfig);

Parameters

NameTypeDescription
senderaddressmsg.sender clearing both orders.
aliceOrderOne of the orders.
bobOrderThe other order.
clearConfigClearConfigAdditional config required to process the clearance.

AfterClear

Emitted after two orders clear. Includes all final state changes in the vault balances, including the clearer's vaults.

event AfterClear(address sender, ClearStateChange clearStateChange);

Parameters

NameTypeDescription
senderaddressmsg.sender clearing the order.
clearStateChangeClearStateChangeThe final vault state changes from the clearance.

Contents

IERC3156FlashBorrower

Git Source

Functions

onFlashLoan

Receive a flash loan.

function onFlashLoan(address initiator, address token, uint256 amount, uint256 fee, bytes calldata data)
    external
    returns (bytes32);

Parameters

NameTypeDescription
initiatoraddressThe initiator of the loan.
tokenaddressThe loan currency.
amountuint256The amount of tokens lent.
feeuint256The additional amount of tokens to repay.
databytesArbitrary data structure, intended to contain user-defined parameters.

Returns

NameTypeDescription
<none>bytes32The keccak256 hash of "ERC3156FlashBorrower.onFlashLoan"

Constants

Git Source

ON_FLASH_LOAN_CALLBACK_SUCCESS

The ERC3156 spec mandates this hash be returned by onFlashLoan if it succeeds.

bytes32 constant ON_FLASH_LOAN_CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");

IERC3156FlashLender

Git Source

Functions

maxFlashLoan

The amount of currency available to be lent.

function maxFlashLoan(address token) external view returns (uint256);

Parameters

NameTypeDescription
tokenaddressThe loan currency.

Returns

NameTypeDescription
<none>uint256The amount of token that can be borrowed.

flashFee

The fee to be charged for a given loan.

function flashFee(address token, uint256 amount) external view returns (uint256);

Parameters

NameTypeDescription
tokenaddressThe loan currency.
amountuint256The amount of tokens lent.

Returns

NameTypeDescription
<none>uint256The amount of token to be charged for the loan, on top of the returned principal.

flashLoan

Initiate a flash loan.

function flashLoan(IERC3156FlashBorrower receiver, address token, uint256 amount, bytes calldata data)
    external
    returns (bool);

Parameters

NameTypeDescription
receiverIERC3156FlashBorrowerThe receiver of the tokens in the loan, and the receiver of the callback.
tokenaddressThe loan currency.
amountuint256The amount of tokens lent.
databytesArbitrary data structure, intended to contain user-defined parameters.

Contents

NoOrders

Git Source

Import unmodified structures from older versions of IOrderBook. Thrown when take orders is called with no orders.

error NoOrders();

ZeroMaximumInput

Git Source

Thrown when take orders is called with a zero maximum input.

error ZeroMaximumInput();

OrderConfigV2

Git Source

Config the order owner may provide to define their order. The msg.sender that adds an order cannot modify the owner nor bypass the integrity check of the expression deployer that they specify. However they MAY specify a deployer with a corrupt integrity check, so counterparties and clearers MUST check the DISpair of the order and avoid untrusted pairings.

struct OrderConfigV2 {
    IO[] validInputs;
    IO[] validOutputs;
    EvaluableConfigV3 evaluableConfig;
    bytes meta;
}

Properties

NameTypeDescription
validInputsIO[]As per validInputs on the Order.
validOutputsIO[]As per validOutputs on the Order.
evaluableConfigEvaluableConfigV3Standard EvaluableConfig used to produce the Evaluable on the order.
metabytesArbitrary bytes that will NOT be used in the order evaluation but MUST be emitted as a Rain MetaV1 when the order is placed so can be used by offchain processes.

TakeOrderConfigV2

Git Source

Config for an individual take order from the overall list of orders in a call to takeOrders.

struct TakeOrderConfigV2 {
    OrderV2 order;
    uint256 inputIOIndex;
    uint256 outputIOIndex;
    SignedContextV1[] signedContext;
}

Properties

NameTypeDescription
orderOrderV2The order being taken this iteration.
inputIOIndexuint256The index of the input token in order to match with the take order output.
outputIOIndexuint256The index of the output token in order to match with the take order input.
signedContextSignedContextV1[]Optional additional signed context relevant to the taken order.

OrderV2

Git Source

Defines a fully deployed order ready to evaluate by Orderbook. Identical to Order except for the newer EvaluableV2.

struct OrderV2 {
    address owner;
    bool handleIO;
    EvaluableV2 evaluable;
    IO[] validInputs;
    IO[] validOutputs;
}

Properties

NameTypeDescription
owneraddressThe owner of the order is the msg.sender that added the order.
handleIObooltrue if there is a "handle IO" entrypoint to run. If false the order book MAY skip calling the interpreter to save gas.
evaluableEvaluableV2Standard EvaluableV2 with entrypoints for both "calculate order" and "handle IO". The latter MAY be empty bytes, in which case it will be skipped at runtime to save gas.
validInputsIO[]A list of input tokens that are economically equivalent for the purpose of processing this order. Inputs are relative to the order so these tokens will be sent to the owners vault.
validOutputsIO[]A list of output tokens that are economically equivalent for the purpose of processing this order. Outputs are relative to the order so these tokens will be sent from the owners vault.

TakeOrdersConfigV2

Git Source

Config for a list of orders to take sequentially as part of a takeOrders call.

struct TakeOrdersConfigV2 {
    uint256 minimumInput;
    uint256 maximumInput;
    uint256 maximumIORatio;
    TakeOrderConfigV2[] orders;
    bytes data;
}

Properties

NameTypeDescription
minimumInputuint256Minimum input from the perspective of the order taker.
maximumInputuint256Maximum input from the perspective of the order taker.
maximumIORatiouint256Maximum IO ratio as calculated by the order being taken. The input is from the perspective of the order so higher ratio means worse deal for the order taker.
ordersTakeOrderConfigV2[]Ordered list of orders that will be taken until the limit is hit. Takers are expected to prioritise orders that appear to be offering better deals i.e. lower IO ratios. This prioritisation and sorting MUST happen offchain, e.g. via. some simulator.
databytesIf nonzero length, triggers onTakeOrders on the caller of takeOrders with this data. This allows the caller to perform arbitrary onchain actions between receiving their input tokens, before having to send their output tokens.

IOrderBookV3

Git Source

Inherits: IERC3156FlashLender, IInterpreterCallerV2

An orderbook that deploys strategies represented as interpreter expressions rather than individual orders. The order book contract itself behaves similarly to an ERC4626 vault but with much more fine grained control over how tokens are allocated and moved internally by their owners, and without any concept of "shares". Token owners MAY deposit and withdraw their tokens under arbitrary vault IDs on a per-token basis, then define orders that specify how tokens move between vaults according to an expression. The expression returns a maximum amount and a token input/output ratio from the perpective of the order. When two expressions intersect, as in their ratios are the inverse of each other, then tokens can move between vaults. For example, consider order A with input TKNA and output TKNB with a constant ratio of 100:1. This order in isolation has no ability to move tokens. If an order B appears with input TKNB and output TKNA and a ratio of 1:100 then this is a perfect match with order A. In this case 100 TKNA will move from order B to order A and 1 TKNB will move from order A to order B. IO ratios are always specified as input:output and are 18 decimal fixed point values. The maximum amount that can be moved in the current clearance is also set by the order expression as an 18 decimal fixed point value. Typically orders will not clear when their match is exactly 1:1 as the clearer needs to pay gas to process the match. Each order will get exactly the ratio it calculates when it does clear so if there is overlap in the ratios then the clearer keeps the difference. In our above example, consider order B asking a ratio of 1:110 instead of 1:100. In this case 100 TKNA will move from order B to order A and 10 TKNA will move to the clearer's vault and 1 TKNB will move from order A to order B. In the case of fixed prices this is not very interesting as order B could more simply take order A directly for cheaper rather than involving a third party. Indeed, Orderbook supports a direct "take orders" method that works similar to a "market buy". In the case of dynamic expression based ratios, it allows both order A and order B to clear non-interactively according to their strategy, trading off active management, dealing with front-running, MEV, etc. for zero-gas and exact-ratio clearance. The general invariant for clearing and take orders is:

ratioA = InputA / OutputA
ratioB = InputB / OutputB
ratioA * ratioB = ( InputA * InputB ) / ( OutputA * OutputB )
OutputA >= InputB
OutputB >= InputA
∴ ratioA * ratioB <= 1

Orderbook is IERC3156FlashLender compliant with a 0 fee flash loan implementation to allow external liquidity from other onchain DEXes to match against orderbook expressions. All deposited tokens across all vaults are available for flashloan, the flashloan MAY BE REPAID BY CALLING TAKE ORDER such that Orderbook's liability to its vaults is decreased by an incoming trade from the flashloan borrower. See ZeroExOrderBookFlashBorrower for an example of how this works in practise. Orderbook supports many to many input/output token relationship, for example some order can specify an array of stables it would be willing to accept in return for some ETH. This removes the need for a combinatorial explosion of order strategies between like assets but introduces the issue of token decimal handling. End users understand that "one" USDT is roughly equal to "one" DAI, but onchain this is incorrect by 12 orders of magnitude. This is because "one" DAI is 1e18 tokens and "one" USDT is 1e6 tokens. The orderbook is allowing orders to deploy expressions that define economic equivalence but this doesn't map 1:1 with numeric equivalence in a many to many setup behind token decimal convensions. The solution is to require that end users who place orders provide the decimals of each token they include in their valid IO lists, and to calculate all amounts and ratios in their expressions as though they were 18 decimal fixed point values. Orderbook will then automatically rescale the expression values before applying the final vault movements. If an order provides the "wrong" decimal values for some token then it will simply calculate its own ratios and amounts incorrectly which will either lead to no matching orders or a very bad trade for the order owner. There is no way that misrepresenting decimals can attack some other order by a counterparty. Orderbook DOES NOT read decimals from tokens onchain because A. this would be gas for an external call to a cold token contract and B. the ERC20 standard specifically states NOT to read decimals from the interface onchain. Token amounts and ratios returned by calculate order MUST be 18 decimal fixed point values. Token amounts input to handle IO MUST be the exact absolute values that move between the vaults, i.e. NOT rescaled to 18 decimals. The author of the handle IO expression MUST use the token decimals and amounts to rescale themselves if they want that logic, notably the expression author will need to specify the desired rounding behaviour in the rescaling process. When two orders clear there are NO TOKEN MOVEMENTS, only internal vault balances are updated from the input and output vaults. Typically this results in less gas per clear than calling external token transfers and also avoids issues with reentrancy, allowances, external balances etc. This also means that REBASING TOKENS AND TOKENS WITH DYNAMIC BALANCE ARE NOT SUPPORTED. Orderbook ONLY WORKS IF TOKEN BALANCES ARE 1:1 WITH ADDITION/SUBTRACTION PER VAULT MOVEMENT. Dust due to rounding errors always favours the order. Output max is rounded down and IO ratios are rounded up. Input and output amounts are always converted to absolute values before applying to vault balances such that orderbook always retains fully collateralised inventory of underlying token balances to support withdrawals, with the caveat that dynamic token balanes are not supported. When an order clears it is NOT removed. Orders remain active until the owner deactivates them. This is gas efficient as order owners MAY deposit more tokens in a vault with an order against it many times and the order strategy will continue to be clearable according to its expression. As vault IDs are uint256 values there are effectively infinite possible vaults for any token so there is no limit to how many active orders any address can have at one time. This also allows orders to be daisy chained arbitrarily where output vaults for some order are the input vaults for some other order. Expression storage is namespaced by order owner, so gets and sets are unique to each onchain address. Order owners MUST TAKE CARE not to override their storage sets globally across all their orders, which they can do most simply by hashing the order hash into their get/set keys inside the expression. This gives maximum flexibility for shared state across orders without allowing order owners to attack and overwrite values stored by orders placed by their counterparty. Note that each order specifies its own interpreter and deployer so the owner is responsible for not corrupting their own calculations with bad interpreters. This also means the Orderbook MUST assume the interpreter, and notably the interpreter's store, is malicious and guard against reentrancy etc. As Orderbook supports any expression that can run on any IInterpreterV1 and counterparties are available to the order, order strategies are free to implement KYC/membership, tracking, distributions, stock, buybacks, etc. etc. Main differences between IOrderBookV2 and IOderBookV3:

  • Most structs are now primitives to save gas.
  • Order hash is bytes32.
  • deposit and withdraw MUST revert if the amount is zero.
  • adding an order MUST revert if there is no calculation entrypoint.
  • adding an order MUST revert if there is no handle IO entrypoint.
  • adding an order MUST revert if there are no inputs.
  • adding an order MUST revert if there are no outputs.
  • adding and removing orders MUST return a boolean indicating if the state changed.
  • new orderExists method.

Functions

vaultBalance

Get the current balance of a vault for a given owner, token and vault ID.

function vaultBalance(address owner, address token, uint256 id) external view returns (uint256 balance);

Parameters

NameTypeDescription
owneraddressThe owner of the vault.
tokenaddressThe token the vault is for.
iduint256The vault ID to read.

Returns

NameTypeDescription
balanceuint256The current balance of the vault.

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;

Parameters

NameTypeDescription
tokenaddressThe token to deposit.
vaultIduint256The vault ID to deposit under.
amountuint256The 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;

Parameters

NameTypeDescription
tokenaddressThe token to withdraw.
vaultIduint256The vault ID to withdraw from.
targetAmountuint256The 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 returns (bool stateChanged);

Parameters

NameTypeDescription
configOrderConfigV2All config required to build an Order.

Returns

NameTypeDescription
stateChangedboolTrue if the order was added, false if it already existed.

orderExists

Returns true if the order exists, false otherwise.

function orderExists(bytes32 orderHash) external view returns (bool exists);

Parameters

NameTypeDescription
orderHashbytes32The hash of the order to check.

Returns

NameTypeDescription
existsboolTrue if the order exists, false otherwise.

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 returns (bool stateChanged);

Parameters

NameTypeDescription
orderOrderV2The Order data exactly as it was added.

Returns

NameTypeDescription
stateChangedboolTrue 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 returns (uint256 totalInput, uint256 totalOutput);

Parameters

NameTypeDescription
configTakeOrdersConfigV2The 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

NameTypeDescription
totalInputuint256Total tokens sent to msg.sender, taken from order vaults processed.
totalOutputuint256Total 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 alice,
    OrderV2 memory bob,
    ClearConfig calldata clearConfig,
    SignedContextV1[] memory aliceSignedContext,
    SignedContextV1[] memory bobSignedContext
) external;

Parameters

NameTypeDescription
aliceOrderV2Some order to clear.
bobOrderV2Another order to clear.
clearConfigClearConfigAdditional configuration for the clearance such as how to handle the bounty payment for the msg.sender.
aliceSignedContextSignedContextV1[]Optional signed context that is relevant to A.
bobSignedContextSignedContextV1[]Optional signed context that is relevant to B.

Events

Deposit

Some tokens have been deposited to a vault.

event Deposit(address sender, address token, uint256 vaultId, uint256 amount);

Parameters

NameTypeDescription
senderaddressmsg.sender depositing tokens. Delegated deposits are NOT supported.
tokenaddressThe token being deposited.
vaultIduint256The vault ID the tokens are being deposited under.
amountuint256The amount of tokens deposited.

Withdraw

Some tokens have been withdrawn from a vault.

event Withdraw(address sender, address token, uint256 vaultId, uint256 targetAmount, uint256 amount);

Parameters

NameTypeDescription
senderaddressmsg.sender withdrawing tokens. Delegated withdrawals are NOT supported.
tokenaddressThe token being withdrawn.
vaultIduint256The vault ID the tokens are being withdrawn from.
targetAmountuint256The amount of tokens requested to withdraw.
amountuint256The amount of tokens withdrawn, can be less than the target amount if the vault does not have the funds available to cover the target amount. For example an active order might move tokens before the withdraw completes.

AddOrder

An order has been added to the orderbook. The order is permanently and always active according to its expression until/unless it is removed.

event AddOrder(address sender, IExpressionDeployerV3 expressionDeployer, OrderV2 order, bytes32 orderHash);

Parameters

NameTypeDescription
senderaddressmsg.sender adding the order and is owner of the order.
expressionDeployerIExpressionDeployerV3The expression deployer that ran the integrity check for this order. This is NOT included in the Order itself but is important for offchain processes to ignore untrusted deployers before interacting with them.
orderOrderV2The newly added order. MUST be handed back as-is when clearing orders and contains derived information in addition to the order config that was provided by the order owner.
orderHashbytes32The hash of the order as it is recorded onchain. Only the hash is stored in Orderbook storage to avoid paying gas to store the entire order.

RemoveOrder

An order has been removed from the orderbook. This effectively deactivates it. Orders can be added again after removal.

event RemoveOrder(address sender, OrderV2 order, bytes32 orderHash);

Parameters

NameTypeDescription
senderaddressmsg.sender removing the order and is owner of the order.
orderOrderV2The removed order.
orderHashbytes32The hash of the removed order.

TakeOrder

Some order has been taken by msg.sender. This is the same as them placing inverse orders then immediately clearing them all, but costs less gas and is more convenient and reliable. Analogous to a market buy against the specified orders. Each order that is matched within a the takeOrders loop emits its own individual event.

event TakeOrder(address sender, TakeOrderConfigV2 config, uint256 input, uint256 output);

Parameters

NameTypeDescription
senderaddressmsg.sender taking the orders.
configTakeOrderConfigV2All config defining the orders to attempt to take.
inputuint256The input amount from the perspective of sender.
outputuint256The output amount from the perspective of sender.

OrderNotFound

Emitted when attempting to match an order that either never existed or was removed. An event rather than an error so that we allow attempting many orders in a loop and NOT rollback on "best effort" basis to clear.

event OrderNotFound(address sender, address owner, bytes32 orderHash);

Parameters

NameTypeDescription
senderaddressmsg.sender clearing the order that wasn't found.
owneraddressOwner of the order that was not found.
orderHashbytes32Hash of the order that was not found.

OrderZeroAmount

Emitted when an order evaluates to a zero amount. An event rather than an error so that we allow attempting many orders in a loop and NOT rollback on a "best effort" basis to clear.

event OrderZeroAmount(address sender, address owner, bytes32 orderHash);

Parameters

NameTypeDescription
senderaddressmsg.sender clearing the order that had a 0 amount.
owneraddressOwner of the order that evaluated to a 0 amount.
orderHashbytes32Hash of the order that evaluated to a 0 amount.

OrderExceedsMaxRatio

Emitted when an order evaluates to a ratio exceeding the counterparty's maximum limit. An error rather than an error so that we allow attempting many orders in a loop and NOT rollback on a "best effort" basis to clear.

event OrderExceedsMaxRatio(address sender, address owner, bytes32 orderHash);

Parameters

NameTypeDescription
senderaddressmsg.sender clearing the order that had an excess ratio.
owneraddressOwner of the order that had an excess ratio.
orderHashbytes32Hash of the order that had an excess ratio.

Clear

Emitted before two orders clear. Covers both orders and includes all the state before anything is calculated.

event Clear(address sender, OrderV2 alice, OrderV2 bob, ClearConfig clearConfig);

Parameters

NameTypeDescription
senderaddressmsg.sender clearing both orders.
aliceOrderV2One of the orders.
bobOrderV2The other order.
clearConfigClearConfigAdditional config required to process the clearance.

AfterClear

Emitted after two orders clear. Includes all final state changes in the vault balances, including the clearer's vaults.

event AfterClear(address sender, ClearStateChange clearStateChange);

Parameters

NameTypeDescription
senderaddressmsg.sender clearing the order.
clearStateChangeClearStateChangeThe final vault state changes from the clearance.

Errors

ZeroDepositAmount

MUST be thrown by deposit if the amount is zero.

error ZeroDepositAmount(address sender, address token, uint256 vaultId);

Parameters

NameTypeDescription
senderaddressmsg.sender depositing tokens.
tokenaddressThe token being deposited.
vaultIduint256The vault ID the tokens are being deposited under.

ZeroWithdrawTargetAmount

MUST be thrown by withdraw if the amount requested to withdraw is zero. The withdrawal MAY still not move any tokens if the vault balance is zero, or the withdrawal is used to repay a flash loan.

error ZeroWithdrawTargetAmount(address sender, address token, uint256 vaultId);

Parameters

NameTypeDescription
senderaddressmsg.sender withdrawing tokens.
tokenaddressThe token being withdrawn.
vaultIduint256The vault ID the tokens are being withdrawn from.

OrderNoSources

MUST be thrown by addOrder if the order has no associated calculation.

error OrderNoSources();

OrderNoHandleIO

MUST be thrown by addOrder if the order has no associated handle IO.

error OrderNoHandleIO();

OrderNoInputs

MUST be thrown by addOrder if the order has no inputs.

error OrderNoInputs();

OrderNoOutputs

MUST be thrown by addOrder if the order has no outputs.

error OrderNoOutputs();

IOrderBookV3ArbOrderTaker

Git Source

Inherits: IOrderBookV3OrderTaker

Functions

arb

function arb(TakeOrdersConfigV2 calldata takeOrders, uint256 minimumSenderOutput) external payable;

IOrderBookV3OrderTaker

Git Source

Functions

onTakeOrders

Called by OrderBookV3 when takeOrders is called with non-zero data, if it caused a non-zero input amount. I.e. if the order(s) taker received some tokens. Input and output directions are relative to the IOrderBookV3OrderTaker contract. If the order(s) taker had an active debt from a flash loan then that debt will be paid before calculating any input amounts sent. i.e. the debt is deducted from the input amount before this callback is called.

function onTakeOrders(
    address inputToken,
    address outputToken,
    uint256 inputAmountSent,
    uint256 totalOutputAmount,
    bytes calldata takeOrdersData
) external;

Parameters

NameTypeDescription
inputTokenaddressThe token that was sent to IOrderBookV3OrderTaker.
outputTokenaddressThe token that IOrderBookV3 will attempt to pull from IOrderBookV3OrderTaker after this callback returns.
inputAmountSentuint256The amount of inputToken that was sent to IOrderBookV3OrderTaker.
totalOutputAmountuint256The total amount of outputToken that IOrderBookV3 will attempt to pull from IOrderBookV3OrderTaker after this callback returns.
takeOrdersDatabytesThe data passed to takeOrders by the caller.

Contents

LibOrder

Git Source

Consistent handling of OrderV2 for where it matters w.r.t. determinism and security.

Functions

hash

Hashes OrderV2 in a secure and deterministic way. Uses abi.encode rather than abi.encodePacked to guard against potential collisions where many inputs encode to the same output bytes.

function hash(OrderV2 memory order) internal pure returns (bytes32);

Parameters

NameTypeDescription
orderOrderV2The order to hash.

Returns

NameTypeDescription
<none>bytes32The hash of order.