Liquidations 2.0: Technical Summary
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”).
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.
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.
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 (
- the amount of collateral available for purchase (
- 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 (
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.
Important Contrasts With Current System
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.
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 (
- address that will receive the collateral and have its (internal to the protocol) DAI balance debited (
- an arbitrary bytestring (if provided, the address
whois 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 exceeds the DAI collection target of the auction (
owe is adjusted to be equal to
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
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
- the ratio of the current price to the initial price has fallen below a certain level (specified by the
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.
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.