CIP-11: Rules of the Solver Competition - status quo and an update proposal

Violation in magnitude vs in price

Right now a lot of buy orders (exact output orders) are filled through 0x, as they have an API endpoint for those.

Computing execution paths for exact output orders is actually incredibly awkward. Not all DEXes have methods for exact output orders, especially not if you string multiple pools together, and definitely not across different ecosystems.

What’s really awkward though is handling slippage during the actual swap. Afaik only uni v3 and balancer have a way to execute the multi leg swaps in reverse order. That way you can set the slippage tolerance on the input amount, while always getting the required output and paying the minimum delta on your input to make the swap go through.

For all others cases, you maybe able to compute the required input on the historical block, but when swapping you need to first transfer the full input amount. If you send the exact quoted input amount, and anywhere in the path slippage occurs, you end up with insufficient output, which is not acceptable for a buy order. So the only way to ensure you get enough output AND allow slippage is to always input more than you think is necessary.

To give an example:
suppose you want to execute a buy order trading 1 ETH for 1000 USDC with 0.1% slippage. In practice, you actually swap 1.001 ETH for 1001 USDC (same exchange rate, higher volume). In case you hit the maximum slippage of 0.1% you still end up with the required output amount.

So this is what 0x does for buy orders. A consequence is that those orders create (asymmetric) token conservation violations in the typical case. We quote 1 ETH for 1000 USDC to the user, but most of the time we sell 1.001 ETH for 1001 USDC, so we have a -0.001 ETH and a + 1 USDC violation.

On average this should not affect the slippage penalty as the violations cancel out. However, this is fundamentally different from slippage due to random price movements with expectation 0, which is what we were mostly talking about in the rest of the thread.

I personally always thought allowing solvers to do this is a good thing. We could avoid it by not using aggregators for buy orders and always routing multi hop swaps through the settlement contract after each step. This would lead to worse rates and/or higher gas prices though, so I think it’s throwing out the baby with the bathwater. Buy orders are rare though, so I don’t have a strong opinion on this.

However, I now realize that this same problem pops up in worse form whenever we want to settle a literal COW, which is the whole point of the protocol.

Token conservation for COW batches

Suppose we want to solve a COW batch with two orders involving tokens A and B:

  • o1: selling 10 A for at least 9 B
  • o2: selling 5 B for at least 5 A

As the amounts don’t line up, we need to find an AMM to fill the gap. Due to uniform clearing price rule and global token conservation (see also this post) , the orders will be settled at the same exchange rate as the AMM.

This means that we need to find an AMM interaction swapping x token B for y token A, such that x and y solve these equations:

10*x/y = 5+x
and
5*y/x + y = 10

There is no direct way to express this solution in terms of exact input/output swap of amounts that can be computed from the input. You basically need to iterate over different values of x, compute the returned y and see if it fits. It’s possible, but finding the best y given input x is already a NP-hard problem if you have all the inputs, and the latency of querying swap method on the contract sequentially would be a big issue as well. The only case that is kind of doable is using only baseline liquidity, where we have swap curves that are described by a simple function and you can do everything in memory.

One simple way we could approach the general case would be to find a swap x for y that has slightly too much volume to satisfy the equations. Now we scale it down, while keeping the exchange rate fixed, until the equations are satisfied. The global token conservation will be violated, but the violation cancels out across tokens because we didn’t change the exchange rate. This is somewhat analogous to what happens with 0x buy orders.

Note that there is still an incentive to find a trade that is as close as possible to exact, because in that case you get a better exchange rate at the AMM, and therefore higher surplus (also potentially less gas). But if by the auction deadline the solver has found an interaction with a strictly better exchange rate than the best baseline liquidity, just with slightly too much volume, they can still submit it if we allow for such net-0 violations. This should lead to overall better solutions for the user.

(Disclaimer: I know it is possible to get the value of internal storage for eg Uniswap V3 through GraphQL in bulk and recreate the smart contact logic locally so you can efficiently iterate in memory, as this is something MEV searchers do. I don’t think the development, maintenance and resource cost of doing that for all dexes is anywhere near proportional to the revenue potential for solvers right now though. Also, those kinds of optimizations are more a consequence of the winner takes all dynamic, than actually adding value for anybody.)