Modern DEXes,
how they're made: CoW Protocol

Author: Sergey Boogerwooger, Dmirty Zakharov
Security researchers at MixBytes
Introduction
CoW Protocol (and CoW Swap, which is built on it) belongs to the "solvers" category of DEXes.

Conceptually, solvers represent a middle ground between CEXes and DEXes, incorporating order books like CEXes but outsourcing order matching to independent competing entities while finalizing trades on-chain, akin to DEXes.

CoW Protocol enables not only simple orders but also programmable orders, which can incorporate additional logic while maintaining the same workflow for solvers. This allows traders to develop flexible trading strategies, with a highly responsive execution layer provided by solvers, free from on-chain restrictions. In addition, the design of CoW Swap allows avoiding risks associated with MEV and offers very attractive slippage restrictions.
Solvers in CoW Protocol, with access to users' trades, can potentially be malicious, exploiting trades by stealing fees or reordering. Thus, CoW Protocol supports only an approved list of solvers, requiring each to post a significant bond, which can be slashed if any misconduct is detected by CoW DAO. For good behavior, the CoW Protocol rewards solvers with COW tokens, incentivizing them to find the best solutions for users' trades.
This is an undoubtedly interesting project, so let's delve into how it's made!
Higher-Level Design
The higher-level trade flow in CoW Swap operates as follows: users sign "intents" to exchange specific amounts of tokens and add them to an off-chain order book. Solvers then process these orders, seeking combinations of trades with maximum surplus, and publish their "batches" in an auction to select the best one. The best batch is then submitted on-chain in a smart contract, settling all trades in this "best" batch.

This off-chain matching of users' orders and one-time trade settlement avoids direct payment of fees to liquidity providers and prevents MEV manipulations with trade orders. Additionally, solvers can use their liquidity to "support" their batches, potentially leading to better pricing for some swaps. Another important feature is the ability for users to trade without paying native transaction fees, as they don't need to interact directly with the blockchain. Solvers can instead collect native token fees in user-provided tokens.

On-chain settlement of individual trades by users is inefficient because it requires paying a native transaction fee for each one. To address this, CoW Swap employs "batch" settlements, which process multiple trades in a single transaction. Such batches are necessary because it's uncommon to have an "ideal" match of exact swap amounts, even when there are many different orders. For instance, if a user wants to buy 100 USDT, a solver might partially use an order from another user selling 200 USDT, or combine two orders selling 30 + 70 USDT, or use an intermediate trade through a third token, or even involve 3+ users' orders to complete a complex "round" swap. These scenarios are well explained here. A real-life example of solving such swaps can be found here.

A critical concept in CoW Protocol for making these batches effective is the "Uniform Clearing Price." All trades within a batch for the same token1/token2 pair use this same clearing price; otherwise, solving and proving the best batch effectively wouldn't be possible. In some instances, this aspect can even improve trades. For example, when a uniform clearing price in a particular order within a batch is better than a "wild" price, it makes the entire group of orders more profitable. We will revisit this concept multiple times in this article.
In CoW Protocol, orders can vary and implement different types of logic, such as:

  • A simple order (amount + price)
  • A limit order (amount + price + expiration date), which can be held until the target price is reached
  • TWAP order - a large order split into smaller limit orders to allow sequential execution with tighter slippage tolerance for each segment
  • Programmatic order - the most powerful type, allowing any logic implementation by checking on-chain conditions like prices, thresholds, and timing
  • Milkman order - executable at a price given by an external oracle at swap time
  • CoW Hooks - pre- and post-swap hooks allowing orders to perform actions before and after execution, such as unstaking/staking assets or signing token approvals
These order types are further detailed in this section of the documentation. All the above types allow CoW Protocol significant flexibility, enabling traders to implement diverse strategies.

Solvers that process batches of orders must compete to present the optimal solution. To determine the best solution, a maximization function is used, as described here. This function works with sets of orders, sequentially accepting amounts of "in/out" tokens (x and −y), swap price π, and subtracting order fees. To standardize this measure across different tokens, the function's non-negative result is expressed in "common numéraire" (e.g., in ETH), whose price is determined by an oracle. Each solver presents its solution, which has a quality defined as:

Here, U(o) is the surplus function for each order, f(o) is the fee for each order, and p is the fee unit price (ensuring the entire quality result is measured in units of "common numéraire").

After selecting the best solver, it is crucial to reward them to incentivize not just winning the auction but finding optimal solutions for users. The winning solver's reward is expressed as:

where observedQuality is the quality of the presented "best" solution (observed at settlement), referenceQuality is the quality of the second-best solution, and the cap() function restricts the payment using predefined upper and lower bounds (cl, cu) = (0.010, 0.012) ETH. The rewards logic is detailed here, mentioning that in some cases (when the difference between the best and second-best solutions is large) solvers may present an "underreported quality," but this behavior requires significant effort from the solver and is thus acceptable under the current scheme. This scheme also accounts for potential slippage when solvers use their liquidity through external AMM swaps. This aspect, too, is described here.
CoW AMM
An additional critical component in CoW Protocol is the CoW AMM. To fulfill orders, solvers should perform swaps using external AMMs, like Uniswap or Balancer. However, such swaps are susceptible to MEV and arbitrage, while maintaining user surplus is essential for solvers. Moreover, the "Uniform Clearing Price," mentioned earlier, applies to the entire order batch for the same token pair, facilitating optimal order batch solutions. Thus, CoW AMM was designed to support transaction batches instead of single swaps performed at a uniform price. The paper discussing this AMM design is here.

The CoW AMM repository (developed in partnership with Balancer) is here. We will not delve deeply into CoW AMM implementation, but significant integration points with the CoW Protocol include:


Having such a "committing" AMM enables solvers, in many cases, to settle trades in CoW AMM instead of using Uniswap, Balancer, or 0x swap pools. This approach simplifies achieving a uniform price for the batch, facilitating all described schemes, where user orders are partially filled or combined to deliver the best surplus for users while providing protection from frontrunning.
Now, let's proceed to the CoW Protocol implementation.
Core
The codebase of CoW Protocol (formerly Gnosis Protocol) can be found at the contracts repository.
Let's start with the GPv2Order.Data struct, which holds order data, including:

  • Addresses and amounts of tokens being swapped and the receiver.
  • uint32 order expiry time.
  • Additional bytes32 appData for external apps working with the order. This field contains an IPFS hash of JSON containing metadata, which can include referral addresses, external IDs, and other options.
  • Fee amount for the order.
  • bool partiallyFillable flag, indicating if the order can be partially filled.
  • bytes32 kind of order, which is a keccak hash of strings "buy" or "sell". This allows EIP-712 compatible wallets to display a descriptive string for this field.
  • bytes32 values (sell/buy)TokenBalance indicating the "origin" of buy/sell token balances: direct ERC20 balance of user, approval for the Vault (will be desribed below) for user, or an internal balance of the Vault itself. Hashes are used similarly to the kind of order.

Next, we discuss the key core contract of the CoW Protocol, which processes users' signed orders, making them available for solvers. The contract is GPv2Settlement.sol. It contains three additional contract addresses:

  • Authenticator - responsible for authenticating solvers (only whitelisted solvers can interact with the protocol).
  • Vault - a Balancer V2 Vault contract. It facilitates Balancer V2 swaps by holding users' tokens via approvals and allows the CoW Protocol to use Vault balances for gas-efficient swaps in Balancer V2 pools.
  • VaultRelayer - direct use of Balancer V2 Vault with GPv2Settlement is not safe, because approves from Vault to Settlement allows a malicious solver to drain user's funds from the Vault. So, an additional relayer is required to manage the necessary approvals to prevent unauthorized drains.

An essential component is filledAmount, which records the fulfillment status of orders by storing the "filled" amount for each UID. A "fully filled" amount indicates that the order has been completed or canceled.
Orders Settling
We now transition to the final on-chain operations of the CoW protocol, leaving off-chain aspects to subsequent article parts. The final solution settlement (a batch of optimal trades from CoW's backend) is executed by the settle() function, which includes:

  • Performing initial external interactions ("before" settlement).
  • Computing and validating target "in/out" amounts for all orders.
  • Transferring "in" amounts from accounts.
  • Executing intermediate interactions ("during" settlement) between "tokens in"/"tokens out".
  • Transferring "out" tokens to accounts.
  • Executing final interactions ("after" settlement).

We'll explore these procedures, starting with the computation of trade executions by computeTradeExecutions(). This function iteratively calls computeTradeExecution() for each trade to verify expiry times, buy/sell prices, and check if orders are overfilled. If valid, it returns the required "in"/"out" amounts.

The CoW Protocol integrates with Balancer V2, utilizing its vaults to manage token balances. This integration can replace external transfers with operations involving internal users' and solvers' balances in Balancer V2 vaults, eliminating additional approvals and leveraging Balancer V2 liquidity for order fulfillment. As a result, token operations in the CoW Protocol can involve either direct ERC20 transfers from users and solvers or interactions with Balancer V2 vaults.

The "in" transfers are executed by transferFromAccounts in GPv2Transfer.sol, offering two ways to handle token balances: direct usage of ERC20 transferFrom() or assembling an array of operations for single recipients and passing it to the Balancer V2 vault. Token operations in the Balancer vault can be explored here, highlighting different types of internal balance operations.

The "out" transfers in the settle() function use transferToAccounts(), which functions similarly but allows direct vault operations without relayer control. Direct ETH or ERC20 transfers proceed with sending the required amounts, while for vault operations, a pack of operations is prepared and executed.

Next, we discuss the executeInteractions() executed before, during, and after order settlement. The GPv2VaultRelayer handles allowances from users, making interactions risky. To mitigate this, external operations begin with a security check denying interaction with GPv2VaultRelayer. The execution itself involves simple calls to external contracts, where successful return status ensures operation success (let's recall, that an external call revert is not propagated to a caller).

The GPv2Settlement contract also provides a swap() function for direct swaps in Balancer pools. This creates a swap order "in-place" and calls the batchSwapWithFee() from the GPv2VaultRelayer, aiding solvers in acquiring necessary liquidity to fulfill user orders.
Orders Placing
As previously mentioned, users' trades are sent to an off-chain API, making them available for solvers where auctions are conducted. The core backend for this is in the CoW Protocol Services repository. Here we cover just a few parts for better clarity.

Part of the CoW Protocol Services APIs related to the orderbook is in api.rs or in the documentation.

Firstly, posting of an order, outlined in post_order.rs, includes several verifications, such as user's signature verification and balance checks. Order cancellation also necessitates user signatures to prevent unauthorized cancellations.

Once verified, the order is added to a database. CoW uses PostgreSQL, which SQL schema and its updates can be found here.

Other areas related to order statuses, auction information, etc., are not directly within this article's scope, thus we'll proceed to the solver aspect.
Solvers
The solver working on the current batch auction receives a pack of orders and should provide the best quality solution (see this link). The example code of the solver is provided in the solver crate.

Let's examine the example solver, which works with multiple orders, presented in multi_order_solver.rs. Here we see the function solve(). It launches a loop that adds additional context to orders (e.g., available reserves on Uniswap) and tries to find a valid solution. If a solution is not found, it removes the "worse" order from the pack and tries again.

The solver then chooses the best solution by precalculating the results of swaps, including fees, and returns the final execution plan.

This execution plan includes giving allowances, a sequence of swaps in different DEXes to produce the best outcome, and other operations. Examples of the execution plans for Uniswap V2/V3, 0x, and Balancer V2 are provided. An example of the solution sent by the solver can be found on this page (examine the /solve request).

Next is the auction itself, implemented in the AuctionProcessor. The Auction entity includes the block that this auction is valid for, the block of the last settlement, a pack of orders, and a pack of reference prices of used tokens.

First, let's focus on the prioritization of orders using the comparators based on prices, creation time, and owner of the order (solver's orders are prioritized to allow them to provide better solutions). In the same procedure, additional CoW AMM orders are injected (the creation of cow_amm_order can be examined here). Additionally, orders are filtered to ensure there are sufficient token amounts to perform the trades.

Next is the scoring of settlements, which is implemented here and follows the rules (at the time this article was written) described in CIP38. The main change is the exclusion of gas costs from the scoring of solutions, removing a complicated gas-related part that is not directly connected with solution effectiveness. This scoring calculates the difference between planned and actual amounts (see here and here).

Then comes the competition. Incoming items are gathered asynchronously, and the scoring is then applied to each presented solution. The best solution is then selected. An important part then involves the simulation of the winning solution at each new block until the deadline to ensure it works before submitting to a blockchain. This simulation is performed here.
Implementation Details
Let's highlight some important code aspects.

Much of the CoW Protocol's functionality is built on off-chain signatures, requiring support for multiple signing schemes. This code might be useful for your projects, so it's worth examining the different signature recovery functions in this module.

Access controls in the CoW Protocol are straightforward and implemented here. There are two main roles: owner and manager, where the manager is responsible for managing the list of solvers.

The external interaction, performed in executeInteractions() in settlement, is made using assembly to avoid unnecessary copying of return data not used by the protocol. Another assembly usage for external interactions can be seen in implementations of safeTransfer() and safeTransferFrom().

The CoW AMM uses TSTORE/TLOAD instructions for commit locks. We recommend using this relatively new method for tasks requiring persistent storage during a single transaction instead of expensive storage.
Conclusion
CoW Swap, built using the CoW Protocol, is a significant project in the DeFi space, fully demonstrating the "solvers" approach to DeFi swaps. The notable aspect of CoW Swap is its "win-win" strategy for users and solvers: the greater the surplus for a user, the greater the surplus for a solver. While it resembles traditional order books on CEXes, it brings decentralization, allowing anyone to participate in order matching.

Key points of CoW Swap design:

  • Pre-signed user orders are placed off-chain.
  • User order packs participate in competitions among solvers to find the best solution for the trade pack.
  • The final, "best" settlement of trades is executed on-chain, transferring target amounts directly to users.
  • All trades in one settlement with particular token pairs are performed at the same uniform price.
  • Trades can involve multiple external DEXes, such as Uniswap V2/V3, Balancer V2, 0x, CoW AMM.
  • CoW Swap can use its own AMM with "commit batches" functionality, avoiding frontrunning and MEV.
  • Solvers are selected by CoW DAO, placing security bonds that can be slashed for misbehavior.

CoW Swap is excellent for traders who lack liquidity, do not want to maintain their infrastructure for settling trades, face MEV problems, and wish to focus solely on finding the most profitable swap strategies.

See you in our next articles!
  • Who is MixBytes?
    MixBytes is a team of expert blockchain auditors and security researchers specializing in providing comprehensive smart contract audits and technical advisory services for EVM-compatible and Substrate-based projects. Join us on X to stay up-to-date with the latest industry trends and insights.
  • Disclaimer
    The information contained in this Website is for educational and informational purposes only and shall not be understood or construed as financial or investment advice.
Other posts