Liquidations 2.0: Technical Summary

Liquidations 2.0: Technical Summary

Background

Previously, a high-level summary of the design for the new auction and liquidation system was presented for feedback. Now that a significant portion of the code has been implemented and open-sourced (still a work-in-progress, however!), the SC Domain Team would like to invite public feedback at this early stage from all interested stakeholders. The rest of this post will describe the technical features of the new design in normal language, highlighting where it departs from the current system (referred to as “Liquidations 1.2”).

Vault Liquidation

https://github.com/makerdao/dss/blob/liq-2.0/src/dog.sol

In the context of the Maker protocol, a “liquidation” is the seizure of collateral from an insufficiently collateralized Vault, along with the transfer of that Vault’s debt to the protocol. In both the current liquidation contract (the Cat) and the 2.0 version (the Dog), an auction is started immediately to sell the collateral for DAI in an attempt to cancel out the debt now assigned to the protocol. This makes the behavior of the new contract very similar to that of the current one, but there are some important differences, explained below.

Partial versus Total Liquidations

In the current system, in each call to the liquidation function (Cat.bite) transfers a fixed amount of debt (the dunk) from the affected Vault, along with a proportional amount of the Vault’s collateral to the protocol. For example, if 50% of a Vault’s debt is taken, then half of its collateral is taken as well. If the Vault’s debt is less than the dunk, then all debt and collateral is taken. In 2.0, all debt is taken when the liquidation function (Dog.bark) is called, and no analogue of the dunk parameter exists. The reasoning behind this change is that because the new auctions allow partial purchases of collateral, the liquidity available to a participant no longer limits their ability to participate in auctions, so instead the total number of auctions should be minimized.

Limits on DAI Needed to Cover Debt and Fees of Active Auctions

In situations involving large amounts of collateral at auction, the current and new designs modify the behavior described in the previous section. Both liquidations 1.2 and 2.0 implement a limit on the total amount of DAI needed to cover the summed debt and liquidation penalty associated with all active auctions. Whenever the maximum possible addition to this value is less than the amount of debt+fees that would otherwise be sent to auction, a partial liquidation is performed so as not to exceed this amount. In the current system there is only a global limit; in 2.0, in addition to the global limit, there is also a per-collateral limit as well. This ensures that typical trading depth against DAI can be taken into account on a per-collateral basis by those determining risk parameters.

An Open Question

In this design, there is less incentive to liquidate Vaults than in the current system, because there is no inherent advantage obtained by doing so. In contrast, the current auction system grants the account triggering a liquidation the privilege of making the first bid in the resulting auction. It is unclear whether this matters significantly in practice, or whether some stronger incentive should be added (for example, a small DAI reward paid to liquidators). Feedback is particularly appreciated on this matter.

Collateral Auctions

https://github.com/makerdao/dss/blob/liq-2.0/src/clip.sol

As discussed previously, collateral auctions are being changed from English to Dutch style. The current auction functionality is described in the official Maker documentation; here, we will focus on the new Dutch design, and only reference some contrasting points relative to the current design.

Auction Initiation

The auction initiation function (Clipper.kick) performs several actions:

  • increments a counter and assigns a unique numerical id to the new auction
  • inserts the id into a list tracking active auctions
  • creates a structure to record the parameters of the auction; this includes:
    • it’s position in the active auctions list
    • the target amount of DAI to raise from bidders (tab)
    • the amount of collateral available for purchase (lot)
    • the Vault that was liquidated to create this auction (allows return of collateral if not all of it is sold)
    • the timestamp of the auctions creation (as a Unix epoch)
    • the initial price of collateral in the auction (top)

The initial price is set by reading the current price in the corresponding Oracle Security Module (OSM) and multiplying by a configurable percentage (the buf parameter). Note that the current OSM price is between one and two hours delayed relative to the actual market price.

Bidding (Purchasing)

Important Contrasts With Current System

Instant Settlement

Unlike the current English auctions, in which which DAI bids are placed, with a participant’s capital remaining locked until they are outbid or until the auction terminates, Dutch auctions settle instantly. They do so according to a price calculated from the initial price and the time elapsed since the auction began. Price versus time curves are discussed more later. The lack of a lock-up period mitigates much of the price volatility risk for auction participants and allows for faster capital recycling.

Partial Settlement

Also unlike the current auctions, Dutch auctions allow for only a portion of an auction’s assigned collateral to be purchased during a bidding action (discussed earlier in the section on partial versus total liquidations). This means there is no minimum DAI liquidity requirement for the sale of collateral on a per-participant basis.

Flash Lending of Collateral

This feature, enabled by instant settlement, eliminates any capital requirement for bidders (excepting gas)—in the sense that even a participant with zero DAI (and nothing to trade for DAI) could still purchase from an auction by directing the sale of the auction’s collateral into other protocols in exchange for DAI. Thus, all DAI liquidity available across DeFi can be used by any participant to purchase collateral, subject only to gas requirements. The exact mechanics are discussed below, but essentially a participant needs to specify a contract (which conforms to a particular interface), and calldata to supply to it, and the auction contract will automatically invoke whatever logic is in the external contract.

Detailed Overview of a Purchase of Collateral

The purchasing function (Clipper.take) behaves as follows:

The caller supplies five arguments:

  • the numerical id of the auction to bid on
  • the maximum amount of collateral to buy (amt)—a purchase behaves like a limit order
  • the maximum acceptable price in DAI per unit collateral (max)
  • address that will receive the collateral and have its (internal to the protocol) DAI balance debited (who)
  • an arbitrary bytestring (if provided, the address who is assumed to be a contract with an interface as described below, to which this data is passed)

Once a call has been made, several initial checks are performed:

  • a reentrancy check to ensure the function is not being recursively invoked
  • that the auction id corresponds to a valid auction
  • that the auction does not need to be reset, either due to having experienced to large a percentage decrease in price, or having existed for too long of a time duration
  • that the caller’s specified maximum price is at least the current auction price

Then, the amount of collateral to attempt to purchase is computed as the minimum of the collateral left in the auction (lot) and the caller’s specified quantity (amt)—the resulting value is the slice. This value is then multiplied by the current price of the auction to compute the DAI owed in exchange (owe). If owe exceeds the DAI collection target of the auction (tab), then owe is adjusted to be equal to tab, and slice is set to tab / price (i.e. the auction will not sell more collateral than is needed to cover debt+fees from the liquidated Vault).

Once the collateral to buy and corresponding DAI owed are determined, a check is done to ensure that the remaining DAI collection target either exceeds a minimum amount or is zero; if not, the function reverts. Callers should be aware of this possibility and account for it when possible.

Next, collateral is transferred (within the protocol’s accounting module) to the who address provided by the caller. If the caller provided a bytestring with greater than zero length, an external call is made to the who address, assuming it exposes a function, follow Solidity conventions, with the following signature:

clipperCall(uint256 /*owe*/, uint256 /*slice*/, bytes calldata)

The first argument is the DAI owed (as a 45 decimal digit fixed-precision integer, or rad), the second argument is the collateral being purchased (as an 18 decimal digit fixed-precision integer, or wad), regardless of the precision of the external token contract), and the last argument is identical to what bytestring the caller originally supplied to the purchase function. As mentioned earlier, a locking mechanism prevents reentry into the purchase function during this external call for security reasons.

After the external call completes (or immediately following the transfer of collateral, if no external call was executed), DAI is transferred (internally, within the core accounting module) from who to the protocol.

Lastly, various values are updated to record the changed state of the auction: the DAI needed to cover debt and fees for outstanding auctions, and outstanding auctions of the given collateral type, are reduced (via a callback to the liquidator contract) is reduced by owe, and the tab (DAI collection target) and lot (collateral for sale) of the auction are adjusted as well. If all collateral has been drained from an auction, all its data is cleared and it is removed from the active auctions list. If collateral remains, but the DAI collection target has been reached, the same is done and excess collateral is returned to the liquidated Vault.

Price as a Function of Time

https://github.com/makerdao/dss/blob/liq-2.0/src/abaci.sol

Price versus time curves are specified through an interface that treats price at the current time as a function of the initial price of an auction and the time at which it was initiated. How to determine the most effective price curve for a given collateral is still an active area of research; some initial options (linear and stairstep exponential) have been implemented for research purposes. Other candidates besides these include a piecewise linear curve and a piecewise exponential curve. Feedback or suggestions are particularly welcome in this area.

Resetting an Auction

As mentioned above, auctions can reach a defunct state that requires resetting for two reasons:

  • too much time has elapsed since the auction started (controlled by the tail governance parameter)
  • the ratio of the current price to the initial price has fallen below a certain level (specified by the cusp governance parameter).

The reset function, when called, first ensures that at least one of these conditions holds. Then it adjusts the starting time of the auction to the current time, and sets the starting price in exactly the same way as is done in the initialization function (i.e. the current OSM price increased percentage-wise by the buf parameter). This process will repeat until all collateral has been sold; contrast this behavior with the current auctions, which reset until at least one bid is received.

Stakeholder Meeting

There will be a community stakeholder meeting on Wednesday, October 14, 2020 17:00:00 UTC. If you’re a community stakeholder interested in the outcome of LIQ-2.0 and have questions, comments, or concerns, please bring them to this meeting.

14 Likes

Can keepers bid with Dai generated from a new CDP within the same transaction? I.e. open a CDP, deposit collateral, draw Dai, use the Dai to bid in an auction, atomically?

1 Like

Yes, but depending on what keepers want to do, this won’t be necessary. There is also the flash mint module MIP25 that would allow for the flash mining of DAI to cover an auction. But most importantly, one can flash loan the collateral from the auction to obtain DAI on any market including putting collateral together with a little of the keeper’s collateral to make a Vault and payback the DAI. It’s almost more appropriate to ask, what can’t LIQ-2.0 do?

4 Likes

Can it do FLAPs and FLOPs? It’s named liquidations 2.0, so I assume not. But wanted to clarify. Also you provided a good opening :smiley:

2 Likes

Re rewards for triggering liquidations: I strongly support this. I ran a keeper for a while and noticed that there was a very small number of addresses (i.e 2 or 3) that were triggering liquidations as it required running software with much more overhead. Furthermore, gas cost was high and didn’t provide any real advantage for winning the auction- so very few people had any reason to do it and I assumed that the foundation was running these bots. I think if we don’t incentivize with a small reward, it could become a security liability for the protocol and we should cover the gas costs and small payment for running the software.

5 Likes

Any rough estimates for the gas cost of kick() for Liquidations 2.0.

1 Like

It’s just collateral auctions for now, as that’s the highest priority. If the model proves itself, surplus and debt auctions are a natural extension.

3 Likes

We haven’t done gas profiling yet, but I’d expect that the cost is on the same order of magnitude as that of the current system (due to having a similar number of storage operations). Note that we may add an incentive of some sort for kicking.

2 Likes

If this is relatively easy and safe to implement, then we should implement it with the initial incentive set to zero DAI which is adjustable by governance. If auctions are not triggered in a timely manner, then we can increase the incentive until they are.

Why is there a minimum amount which causes the call to revert?

I had some thoughts about this. I was imagining an inverse S-curve with an initial starting point ~50% (maybe overkill, 25% or 33% might be plenty) above the oracle price for volatile assets. I spent a little time modelling this so it’s easier to understand what I’m talking about:

  • This would mean that the auction starts at above the reported price, it hopefully reduces losses in cases where we see a decrease then a subsequent increase.
  • However we decrease quickly at the start in case the actual price is already lower than the reported OSM price.
  • At that point we decrease fairly linearly until ~50% of the value, at which point we give up on getting a reasonable price and get whatever we can.
  • X axis is time normalized from 0-1.
  • Y axis is auction price in percentage terms of the OSM price at auction start (assumed to start at 150%)

Going to note that this is pretty speculative, and may just be worse than something simple like a linear or exponentially decreasing curve. Someone should definitely think about it a lot more about it before adopting this (or any other curve.)

Do auctions that are valid to be reset still count towards the litter? Are tail and cusp set per-collateral?


I note there is nothing specifically about stablecoin collateral here. How do you imagine that would fit into this system?

Could we set a flat ‘curve’ with no tail or cusp parameters and hold them in liquidation indefinitely? What would the downsides of doing this be?

2 Likes

What are the plans regarding end.sol? do you plan to deploy a new end contract? Will end.cat point to dog?
We are a project that going live next week and rely on end.cat.ilks().chop to read the liquidation penalty, so we would really like to no your plans about that.
It seems that skip function at end would break if you just change cat to dog, but it is not clear if it is really needed.

Will appreciate an answer on your intent soon, as our system is not upgradable, and rely on the end contract to handle changes in makerdao configurations.

1 Like

Regarding the incentives to liquidators, working on keeper bots in the past, and currently working on a project that aims to solve the incentive problem for liquidators (including over makerdao), my view is that without giving a quantifiable incentives, liquidators will only build simple liquidation systems, that will work only if market conditions are convenient. We already work with few keepers and you can read their thoughts here.
We guide them on the makerdao integration, and from the feedback we get, they would never have spend that effort without any soft guarantee on a potential profit.
Without having keepers building non trivial systems, you end up with a stop-loss order mechanism, where liquidations are just being dumped on the market. Which is better than nothing, but does not give rise to a strong backstop.

A problem with blockchain protocols that quantifiable incentives will end up going to the miners, and this is a serious issue and the project i work on tries to prevents it. So I cannot think of a simple change in the dog implementation to solve it.

2 Likes

This is one of the last pieces being hammered out, but the high-level is that yes, we’ll need to replace the End. We should have more information within a couple days.

3 Likes

If it is not too late, would you consider one of the following requests:

  1. having a getChop(bytes32 ilk) in the dog and/or end contracts. Or even better, in some persistent contract that the dao will regularly maintain.
  2. have the address of the end contract in some persistent contract.
  3. Or more practically have ways to identify contracts in a trust-less way. E.g., the dog function will have isDog() boolean that returns true. And then projects that integrate could assume that if contract X returns true for isDog AND vat.wards(X) == 1, then this is the dog contract.

Regarding the planned change, would appreciate a spoiler on whether the new end will have a dog() function.
Our strategy to fetch chop would be:

  1. if a contract X has cat() function and vat.wards(x)==1, then return X.cat().ilks().chop.
  2. else, if a contract X has dog() then return it with dog.
1 Like

To the point about trustless identification of contracts: there’s an on-chain contract registry in the works, probably coming out within a few weeks (cc @BrianMcMakerDAO). As for chop, I guess the difficulty is that since it’s a struct member, it’s not necessarily so straightforward to read it without knowing exactly the code? I don’t think it hurts anything to have a getChop (preferably on the liquidation contract), so we can add that.

I’d say with very high probability the new End will have a dog() function. The only way I see it not having that is if we decide to re-use the Cat name (current thinking is that since the new liquidator contract is somewhat specialized to the new auctions, it should have a different name to avoid confusion). There should be a PR for this within the next 2 weeks.

2 Likes

Call is happening now: [Agenda/Discussion] LIQ-2.0 Stakeholder Discussion - Wednesday, October 14, 2020 17:00:00 UTC

2 Likes

pinging to ask if there is more info about that.
Also @BrianMcMakerDAO if there is new info/timelines for the registry.

Not quite yet; @wil is working on it.

Is there anywhere to follow along with developments for this? Changing the End contract would mean that Yield Protocol would not be able to shutdown in the event of an Emergency Shutdown.

3 Likes

I’d say that at the moment, following the forums probably is the best way. There’s a lot of noise here though, so if you depend on the Maker protocol in a big way, it’s probably prudent to get in touch with someone involved w/development directly and explain exactly what your dependency is, what things you’d ideally like to see, etc. I think a lesson here is that for future upgrades we should make the design public sooner and overcommunicate potentially breaking changes. We may also need to think about using upgradability patterns that preserve addresses, although I don’t know that we could ever promise complete ABI immutability.

4 Likes