The third version of the protocol tackles the scalability problem of decentralized exchanges. Previous versions of the protocol already did the order matching off-chain, but the settlement was completely on-chain. This still has a high computational and storage cost on-chain.
Protocol 3.0 solves this by moving almost all data off-chain as well as moving all computations for all requests off-chain using zero-knowledge proofs (zkSNARKs).
Data is stored in a Merkle tree using an account model. Each user has a single account with a one-to-one mapping to his Ethereum address. This account can store balances for all tokens the exchange wants to support and also stores all the trading history data for the user. Requests modify the Merkle tree and the state transitions are verified on-chain using proofs.
For efficiency reasons, requests are batched together in blocks. The cost for verifying a proof remains constant no matter the number of requests in a block, but we are limited in how many requests we can include in a block, otherwise, the proof verification on-chain would not be efficient anymore. How many requests we can include in a block depends on the complexity of the request. If multiple requests types would be included in a block there would be some overhead of doing computations that are only necessary for a single request type so we limit blocks to a single request type for now.
We currently support 5 different requests:
- Trade settlement
- On-chain withdrawal
- Off-chain withdrawal
- Off-chain order cancellation
Only the first three are necessary for building a fully functional DEX. The other two provide some extra flexibility for users and exchanges.
Once the DEX operator has committed a block on-chain he also needs to submit the proof for the block within a time limit. The commit of the block and the submitting of the proof is done separately because the proof generation currently takes some time. By allowing the DEX operator to commit the block first without having to wait on the proof generation allows other operators to create new blocks that build upon the state of the latest committed block and also allows the proof generation to be done in parallel. Once the proof is generated the operator can send the proof on-chain so the state transitions done in the block are verified. If the operator fails to submit a valid proof in time he is punished and the state is automatically reverted to a valid state.
Only the Merkle tree root is stored on-chain. This is sufficient for users to prove (using a Merkle proof) that they own a certain amount of tokens on the DEX. Even if the DEX operator doesn’t cooperate anymore, users can withdraw any funds they have in ‘withdrawal mode’.
Users need to be able to create a Merkle proof. For that, they need to have access to the complete Merkle tree, not just the Merkle root. In the best case, this data is made available somewhere by the DEX, but there is no guarantee that this is actually the case. To make sure all data is available for all users this data is sent on-chain (this is done using CALLDATA, not storage, which is cheaper and more future proof). Currently, this still has a high cost on Ethereum (though this will improve in the future) and this limits the maximum throughput that can be achieved. This is why we also support disabling on-chain data availability, DEXs are then free to implement some off-chain solution for this.
To start trading users first need to deposit some funds to the exchange smart contract. This is done using a normal on-chain transaction and the request is added to the on-chain deposit queue. Once the DEX operator includes the deposit request in a block the user can start trading.
A user signs an order a single time and the order contains all the data needed for a DEX to settle the order. The protocol supports
- Maker/taker orders (using the same order)
- Market orders
- Fee payments to the DEX
- Rebate payments from the DEX to an order owner
- Automatic order scaling
- Partial order matching
- Dual-Authoring to prevent the stealing of orders and/or ring settlements
How the orders are matched is fully up to the DEX, but the settlement will respect the wishes of the order creator in all cases.
The DEX creates a ring settlement request by matching two orders and sends the request to the DEX operator who will include the request in a block to receive the fee specified in the request. The operator can then commit the block on-chain.
If the user wants to withdraw their tokens from the exchange, they need to do a withdrawal request. If the user does an on-chain withdrawal request, the DEX needs to include it in a block in a reasonable time. If a certain amount of time has passed since the withdrawal request was done, we first force the operator to process it by only making it possible to commit on-chain withdrawal blocks. If the operator still refuses to process the withdrawal request the exchange goes into ‘withdrawal mode’. ‘Withdrawal mode’ is irreversible and completely shuts down the exchange, the only thing that’s still possible is for the users to withdraw their funds. This ensures that users remain in full control of their money in all cases without having to trust anyone.
For a deep dive on the technical design, please see the Design Doc here.