0xProject / ZEIPs

0x Improvement Proposals

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Atomic order matching

abandeali1 opened this issue · comments

Motivation:
Currently, order matching of limit orders is supported through use of batchFillOrders and batchFillOrKillOrders. While the net result of filling a pair of opposing orders with these functions is the same as an atomic match, a taker is still required to have sufficient funds to fill the first order in order to complete the transaction. This adds an unnecessary cost of capital and is especially problematic when matching very large orders. A taker without the necessary funds to fill the initial order would be required to break down the match into smaller batches, which is also an inefficient use of gas.

Proposed Solution:
Add a matchOrders function that will atomically fill valid crossing orders without requiring capital upfront. This will lower the barriers to entry of running a centralized matching engine and of arbitraging across exchanges. The requirements of this function would be:

  • Takes 2 orders as input parameters
  • orderA.makerToken == orderB.takerToken
  • orderA.takerToken == orderB.makerToken
  • msg.sender is a valid taker for both orders
  • Prices of both orders cross each other
  • Makers of both orders receive amounts specified by orders
  • msg.sender keeps the difference

Implementation:
TODO

This looks good, but I would suggest:

  • orderA.makerTokenAmount >= orderB.takerTokenAmount
  • orderB.makerTokenAmount >= orderA.takerTokenAmount
  • takes an amount as argument corresponding to the amount of orderA.makerToken to trade, where amount <= orderA.remainingMakerTokenAmount

This would allow orders with differing amounts but overlapping prices to be matched.

Example: orderA offers 10 REP for 1 WETH, and orderB offers .5 WETH for 4.5 REP. Anyone could take these two, send them to the exchange contract which would cause:

  • 4.5 REP A -> B
  • .5 WETH B -> A
  • .5 REP A -> Matcher (msg.sender)

This way both makers get to execute the trades they had offered, and the matcher gets to keep the spread.

How will fees operate in the matchOrders function?

In a central matching model takerFee as implemented does not make much sense since the central relayer is the msg.sender and taker always. If using the makerFee field exclusively, signing the makerFee into the matching engine designated taker's order on the fly could create race conditions. Prior to v2 the relayer can partial fill the matching engine designated taker's order at their discretion to replicate a taker fee and side-step the signed order parameters.

In the open order book model the atomic matching mechanism is ideal for arbitrageurs to perform cross-relayer and spread arbitrage without the need of large upfront capital in every asset. Open order book relayers want to incentivize these arbitrageurs to maintenance order books and close up the arbitrage opportunities. If the takerFee is pushed on to the msg.sender there is a higher economic barrier since the arbitrageur will need to consider:

  • excess received from order match (any asset) > gas (ETH) + orderA.takerFee (ZRX) + orderB.takerFee (ZRX)

Dealing with so many variables in various assets exposes the arbitrageurs to a lot of volatility risk across assets with every filled match. It is preferable that the msg.sender only needs to consider the price of gas in their calculation to incentivize filling tighter arbitrage opportunities.

A naive solution could be designating the makerOrder and takerOrder as parameters to matchOrders and enforcing respective fees. The problem with this approach is it introduces ambiguity to the order signer about if they will end up paying the makerFee or the takerFee when their order gets filled.

@ctebbe I agree using the makerFee for the taker's order is a bit awkward. ZEIP #18 allows for what you are talking about because there is a clear distinction between the maker and taker of an order (the matcher would be sender and doesn't pay fees).

I think in the final implementation of matchOrders, the matcher will have to pay the takerFee, though. It's not much different than on centralized exchanged, where arbitrageurs still have to take fees into account. Anything else seems like it could be gamed. There's no reason that a relayer should end up with partial fees only because an order was matched rather than filled directly.

@abandeali1 good point on relayer expectations re: fees. I am thinking through a way to keep matchOrders as frictionless and fair as possible for arbitrageurs on the open order book model since they are the target user of this function and necessary for order book maintenance.

Arbitrageurs (like opportunistic takers) don't want exposure to ZRX volatility risk or management, so is there a plan to implement a generic batch function such that fee abstraction can be effectively used here? ie. the arbitrageur batches a fillOrder in front of matchOrders to acquire the needed ZRX to pay fees in a single transaction.