Implementing Liquidations using CoW Protocol

This post tries to give a little bit more background on how liquidations (e.g. for lending protocols) work today, why they can’t be trivially converted to leverage CoW Protocol’s MEV protection power, and what a native integration may look like.
A short argument for why CoW based liquidations are superior over existing MEV based auctions can be found in this tweet thread.

Liquidations today

Usually, liquidations can be triggered permissionlessly as soon as the loan-to-collateral ratio drops below a healthy threshold and the position is “under water”.
The party “kicking” the auction usually has an economic benefit (e.g. being able to recover the collateral at discount). In the presence of competition, this economic benefit is mostly forwarded in the form of MEV to block proposers.

In it’s simplest form a lending protocol’s liquidation code may look something like:

function liquidate(
    uint256 position_id, 
    address liquidator, 
    bytes memory data
) external nonReentrant {
    ERC20 collateral;
    ERC20 debt;
    uint256 collateral_amount;
    uint256 debt_amount;
    (collateral, collateral_amount, debt, debt_amount) = find_unhealthy(position_id);
    
    // Advance the collateral
    collateral.safeTransfer(liquidator, collateral_amount);
    
    // Execute whatever the liquidator wants to perform liquidation
    liquidator.call(data);
    
    // Ensure debt is recovered from the liquidator
    debt.safeTransferFrom(liquidator, address(this), debt_amount)
}

The fight over the “caller context”

Note, how liquidate calls the liquidator and then synchronously (ie without returning from the function) ensures that sufficient debt is recovered?
This means liquidate holds on to the caller context and the liquidation has to happen atomically within that context.

A sequence diagram could look like this:

Most importantly, the lending protocol doesn’t allow the transfer of collateral and the recovery of the debt to happen in two different calls.
This makes sense as otherwise the atomicity of the two operations couldn’t be guaranteed (and someone could potentially take the collateral without repaying the debt).

Enter CoW Protocol Settlements

CoW Protocol on the other hand, splits the collection of trade amounts, the execution of on-chain interactions to convert “ins” for “outs”, and finally transferring the out amounts back to the trader (from the trader’s perspective) into three different contexts.

A sequence diagram of a simple single order CoW settlement (ERC1271) could look like this:

Note, how this model isn’t compatible with the traditional liquidation logic?
The lending protocol expects to receive the debt (transferOut) before or in the same call as it sends out the collateral (transferIn), yet CoW Protocol requires the trade to yield its context in between before the payouts can happen (to allow for arbitrary interactions and potentially other traders to transfer in their tokens to form a CoW).

The only solution we found so far to work around this in the exisiting architecture, is to wrap the entire settlement into a flashloan so that the settlement contract can “advance” the debt repayment:

This effectively yields the caller context to an even higher order entity (an external flashloan provider) which nests the entire settlement and the liquidation (as a pre-hook) inside it.

The main disadvantage that this architecture brings is its complexity. Solvers need to:

  1. Be aware that certain orders with upfront “capital requirements” exist.
  2. Understand how to borrow this capital.
  3. Accommodate multiple orders (such as two orders creating a CoW) that may potentially create nested flashloans (also with different providers) within one another.
  4. Carefully ensure that loans are repaid by the traders at the end of the batch.
    Moreover, this flow requires significantly more transfers and storage based book-keeping and is thus gas in-efficient.

While this approach is still theoretically possible, there is a much simpler and more elegant way to natively integrate liquidations into CoW Protocol…

CoW tailored liquidation logic

The reason that lending protocols want to stay in control of the caller context is to ensure that the debt is repaid atomically in the same transaction.

While performing all steps atomically is the only secure way to achieve this if arbitrary addresses are allowed to kick the liquidation, it is actually not required if the liquidation is triggered via a CoW Protocol smart order (using ERC1271).
CoW Protocol’s immutable smart contract passes a commitment to the order paramaters (ie. buy and sell amounts) to the verifying smart contract as part of the signature verification step and, once authorized, guarantees that the trader will receive at least as much of the buy token as specified in the order. Otherwise the whole settlement will revert!

This means that as long as the liquidation protocol verifies that the maximum sell amount (collateral) covers the minimum guaranteed buy amount (debt) it can “accept” the trade and rest assured that it will receive the required proceeds to make the position whole. The solver competition will ensure that, if market price permit, the protocol will even receive more than the limit price (surplus).

A CoW Protocol adjusted ERC1271 liquidation logic in its simplest form could look like:

function isValidSignature(
  bytes32 hash,
  bytes calldata signature
) external view returns (bytes4) {
    Order memory data;
    uint256 position_id;
    (data, position_id) = abi.decode(signature, (Order, uint256));
    
    // Check that the order parameters are correct
    require(hash == data.hash(CowDomainSeparator));
    
    // Perform original liquidation checks
    ERC20 collateral;
    ERC20 debt;
    uint256 collateral_amount;
    uint256 debt_amount;
    (collateral, collateral_amount, debt, debt_amount) = find_unhealthy(position_id);
    
    require(order.buy_token == debt);
    require(order.sell_token == collateral);
    require(order.sell_amount <= collateral_amount);
    require(order.buy_amount >= debt_amount);
    
    // If we get to this stage we know we will at least 
    // receive debt_amount if we send out collateral_amount, 
    // so we can accept the trade.
    return ERC1271_MAGIC_VALUE;
}

It is of course possible to combine the traditional liquidation model with the proposed approach either based on time (e.g. allow CoW Swap liquidations only for the first n blocks the loan is under water) or based on the price of the collateral (fallback to traditional model if price drops x% below liquidation threshold).

Book keeping using CoW Hooks Any book-keeping to account or split the extra proceeds can happen in pre- and post-interactions (aka hooks). Note that unlike the transfer of proceeds, the settlement contract itself does not guarantee pre- and post-hooks get executed atomically with the trade. However, solvers can get slashed up to $500k in case they violate this assumption. Moreover, in particular pre-hooks can still be enforced on-chain (by checking their execution happened inside `isValidSignature`) and post-hooks can either be made permissionlessly executable by other actors and/or enforced in subsequent pre-hooks.

Conclusion

Liquidations create a lot of MEV, which can be mitigated by using CoW Protocol’s batch auctions and treating them as regular stop-loss triggered limit orders subject to uniform price clearing and surplus maximisation.

Current lending protocols however rely on liquidation mechanisms that assume disbursement of collateral and repayment of debt happening atomically in the same “call”. This model is incompatible with CoW Protocol’s settlement flow which assumes transferIn and transferOut of trade amounts happens sequentially in separate calls.

While a complicated architecture to combine the two models via a superordinate flashloan context is possible, it would be much simpler and equally safe for lending protocols to allow repayment of debt to happen asynchronously via ERC1271 based “smart orders”. Trading amounts can be verified during signature validation and are guaranteed by CoW Protocol’s immutable settlement contract code to be sent out to the recipient as part of the settlement. Any additional book-keeping can happen in pre/post trade hooks.

4 Likes

Great to see research like this.

It is asking a lot for protocol designers to trust a 3rd party system to faithfully execute their liquidation transactions. The cost of failure is so high in case of a bug, possibly the complete loss of all user assets. To gain the trust needed, the external protocol should ideally be as simple as possible and will need time to develop the lindy effect.

I have long been a proponent of redesigning our existing liquidation protocols to use some type of auction mechanism. I explained some of the reasons back in this 2019 critique of the new Compound protocol:

2019 Compound v2 liquidation critique

Advantages of auctions include:

  • Reduced loss to MEV extraction: instead of excess liquidation incentive going to MEV, it can be retained by the protocol or its users.
  • Flexibility to respond to extreme market conditions: an auction implies a dynamic, “just enough” incentive that can adapt to market conditions we haven’t seen before.

The main objections in 2019 are still relevant today:

  • An auction, especially a dutch auction, takes time. During that time, the health of at-risk positions can degrade further, possibly leading to protocol losses that would have been avoided with an instant liquidation.
  • An auction is, by definition, more complex. This adds risk, both in implementation bugs and unrecognized defects in mechanism design. Also, the behavior of a complex system is more difficult to analyze, model, and predict.

But still, I’d love to see more experimentation along these lines. The Euler proejct is a good example.

1 Like

Right. It is suggesting to use a 3rd party system: CoW Protocol’s settlement contract!

The proposition is simple and easy to evaluate, not only because the contract’s code is fully public, but also because it is deployed in production for more than two years, and has processed $28b worth of trading volume.

You mentioned “faithfully execute their liquidations”, but IMO this has very little to do with faith, and more to do with smart contract code that gives onchain guarantees for the defined min_out.

As far as lindy effect goes, CoW Protocol’s settlement contract paired with a batch auction mechanism and a set of more than 15 experienced solvers is a great candidate for serving the need of highly efficient liquidation mechanism.

1 Like

Great write-up!

Agree that dutch auctions add their own risk in this case. CoW Protocol’s auction is a single round-trip auction, so it wouldn’t take a lot of time (if speed is they main worry, we could add a period after which the old liquidation mechanism kicks in).
Also, the liquidation can use a very generous limit price. Solvers compete to maximise the amount recovered and therefore find the “correct” liquidation discount in a single round of the auction.