Liquity is a decentralized protocol that allows Ether holders to obtain maximum liquidity against
their collateral without paying interest. After locking up ETH as collateral in a smart contract and
creating an individual position called a "trove", the user can get instant liquidity by minting LUSD,
a USD-pegged stablecoin. Each trove is required to be collateralized at a minimum of 110%. Any
owner of LUSD can redeem their stablecoins for the underlying collateral at any time. The redemption
mechanism along with algorithmically adjusted fees guarantee a minimum stablecoin value of USD 1.
An unprecedented liquidation mechanism based on incentivized stability deposits and a redistribution
cycle from riskier to safer troves provides stability at a much lower collateral ratio than current
systems. Stability is maintained via economically-driven user interactions and arbitrage, rather
than by active governance or monetary interventions.
The protocol has built-in incentives that encourage both early adoption and the operation of
multiple front ends, enhancing decentralization.
More information
Visit liquity.org to find out more and join the discussion.
Liquity is a collateralized debt platform. Users can lock up Ether, and issue stablecoin tokens (LUSD) to their own Ethereum address, and subsequently transfer those tokens to any other Ethereum address. The individual collateralized debt positions are called Troves.
The stablecoin tokens are economically geared towards maintaining value of 1 LUSD = $1 USD, due to the following properties:
The system is designed to always be over-collateralized - the dollar value of the locked Ether exceeds the dollar value of the issued stablecoins
The stablecoins are fully redeemable - users can always swap $x worth of LUSD for $x worth of ETH (minus fees), directly with the system.
The system algorithmically controls the generation of LUSD through a variable issuance fee.
After opening a Trove with some Ether, users may issue ("borrow") tokens such that the collateralization ratio of their Trove remains above 110%. A user with $1000 worth of ETH in a Trove can issue up to 909.09 LUSD.
The tokens are freely exchangeable - anyone with an Ethereum address can send or receive LUSD tokens, whether they have an open Trove or not. The tokens are burned upon repayment of a Trove's debt.
The Liquity system regularly updates the ETH:USD price via a decentralized data feed. When a Trove falls below a minimum collateralization ratio (MCR) of 110%, it is considered under-collateralized, and is vulnerable to liquidation.
Liquidation and the Stability Pool
Liquity utilizes a two-step liquidation mechanism in the following order of priority:
Offset under-collateralized Troves against the Stability Pool containing LUSD tokens
Redistribute under-collateralized Troves to other borrowers if the Stability Pool is emptied
Liquity primarily uses the LUSD tokens in its Stability Pool to absorb the under-collateralized debt, i.e. to repay the liquidated borrower's liability.
Any user may deposit LUSD tokens to the Stability Pool. This allows them to earn the collateral from the liquidated Trove. When a liquidation occurs, the liquidated debt is cancelled with the same amount of LUSD in the Pool (which is burned as a result), and the liquidated Ether is proportionally distributed to depositors.
Stability Pool depositors can expect to earn net gains from liquidations, as in most cases, the value of the liquidated Ether will be greater than the value of the cancelled debt (since a liquidated Trove will likely have an ICR just slightly below 110%).
If the liquidated debt is higher than the amount of LUSD in the Stability Pool, the system tries to cancel as much debt as possible with the tokens in the Stability Pool, and then redistributes the remaining liquidated collateral and debt across all active Troves.
Anyone may call the public liquidateTroves() function, which will check for under-collateralized Troves, and liquidate them. Alternatively they can call batchLiquidateTroves() with a custom list of trove addresses to attempt to liquidate.
Liquidation gas costs
Currently, mass liquidations performed via the above functions cost 60-65k gas per trove. Thus the system can liquidate up to a maximum of 95-105 troves in a single transaction.
Liquidation Logic
The precise behavior of liquidations depends on the ICR of the Trove being liquidated and global system conditions: the total collateralization ratio (TCR) of the system, the size of the Stability Pool, etc.
Here is the liquidation logic for a single Trove in Normal Mode and Recovery Mode. SP.LUSD represents the LUSD in the Stability Pool.
Liquidations in Normal Mode: TCR >= 150%
Condition
Liquidation behavior
ICR < MCR & SP.LUSD >= trove.debt
LUSD in the StabilityPool equal to the Trove's debt is offset with the Trove's debt. The Trove's ETH collateral is shared between depositors.
ICR < MCR & SP.LUSD < trove.debt
The total StabilityPool LUSD is offset with an equal amount of debt from the Trove. A fraction of the Trove's collateral (equal to the ratio of its offset debt to its entire debt) is shared between depositors. The remaining debt and collateral (minus ETH gas compensation) is redistributed to active Troves
ICR < MCR & SP.LUSD = 0
Redistribute all debt and collateral (minus ETH gas compensation) to active Troves.
ICR >= MCR
Do nothing.
Liquidations in Recovery Mode: TCR < 150%
Condition
Liquidation behavior
ICR <=100%
Redistribute all debt and collateral (minus ETH gas compensation) to active Troves.
100% < ICR < MCR & SP.LUSD > trove.debt
LUSD in the StabilityPool equal to the Trove's debt is offset with the Trove's debt. The Trove's ETH collateral (minus ETH gas compensation) is shared between depsitors.
100% < ICR < MCR & SP.LUSD < trove.debt
The total StabilityPool LUSD is offset with an equal amount of debt from the Trove. A fraction of the Trove's collateral (equal to the ratio of its offset debt to its entire debt) is shared between depositors. The remaining debt and collateral (minus ETH gas compensation) is redistributed to active troves
MCR <= ICR < TCR & SP.LUSD >= trove.debt
The Pool LUSD is offset with an equal amount of debt from the Trove. A fraction of ETH collateral with dollar value equal to 1.1 * debt is shared between depositors. Nothing is redistributed to other active Troves. Since it's ICR was > 1.1, the Trove has a collateral remainder, which is sent to the CollSurplusPool and is claimable by the borrower. The Trove is closed.
MCR <= ICR < TCR & SP.LUSD < trove.debt
Do nothing.
ICR >= TCR
Do nothing.
Gains From Liquidations
Stability Pool depositors gain Ether over time, as liquidated debt is cancelled with their deposit. When they withdraw all or part of their deposited tokens, or top up their deposit, the system sends them their accumulated ETH gains.
Similarly, a Trove's accumulated gains from liquidations are automatically applied to the Trove when the owner performs any operation - e.g. adding/withdrawing collateral, or issuing/repaying LUSD.
LUSD Token Redemption
Any LUSD holder (whether or not they have an active Trove) may redeem their LUSD directly with the system. Their LUSD is exchanged for ETH, at face value: redeeming x LUSD tokens returns $x worth of ETH (minus a redemption fee).
When LUSD is redeemed for ETH, the system cancels the LUSD with debt from Troves, and the ETH is drawn from their collateral.
In order to fulfill the redemption request, Troves are redeemed from in ascending order of their collateralization ratio.
A redemption sequence of n steps will fully redeem from up to n-1 Troves, and, and partially redeems from up to 1 Trove, which is always the last Trove in the redemption sequence.
Redemptions are blocked when TCR < 110% (there is no need to restrict ICR < TCR). At that TCR redemptions would likely be unprofitable, as LUSD is probably trading above $1 if the system has crashed that badly, but it could be a way for an attacker with a lot of LUSD to lower the TCR even further.
Note that redemptions are disabled during the first 14 days of operation since deployment of the Liquity protocol to protect the monetary system in its infancy.
Partial redemption
Most redemption transactions will include a partial redemption, since the amount redeemed is unlikely to perfectly match the total debt of a series of Troves.
The partially redeemed Trove is re-inserted into the sorted list of Troves, and remains active, with reduced collateral and debt.
Full redemption
A Trove is defined as “fully redeemed from” when the redemption has caused (debt-200) of its debt to absorb (debt-200) LUSD. Then, its 200 LUSD Liquidation Reserve is cancelled with its remaining 200 debt: the Liquidation Reserve is burned from the gas address, and the 200 debt is zero’d.
Before closing, we must handle the Trove’s collateral surplus: that is, the excess ETH collateral remaining after redemption, due to its initial over-collateralization.
This collateral surplus is sent to the CollSurplusPool, and the borrower can reclaim it later. The Trove is then fully closed.
Redemptions create a price floor
Economically, the redemption mechanism creates a hard price floor for LUSD, ensuring that the market price stays at or near to $1 USD.
Recovery Mode
Recovery Mode kicks in when the total collateralization ratio (TCR) of the system falls below 150%.
During Recovery Mode, liquidation conditions are relaxed, and the system blocks borrower transactions that would further decrease the TCR. New LUSD may only be issued by adjusting existing Troves in a way that improves their ICR, or by opening a new Trove with an ICR of >=150%. In general, if an existing Trove's adjustment reduces its ICR, the transaction is only executed if the resulting TCR is above 150%
Recovery Mode is structured to incentivize borrowers to behave in ways that promptly raise the TCR back above 150%, and to incentivize LUSD holders to replenish the Stability Pool.
Economically, Recovery Mode is designed to encourage collateral top-ups and debt repayments, and also itself acts as a self-negating deterrent: the possibility of it occurring actually guides the system away from ever reaching it.
Project Structure
Directories
papers - Whitepaper and math papers: a proof of Liquity's trove order invariant, and a derivation of the scalable Stability Pool staking formula
packages/dev-frontend/ - Liquity Developer UI: a fully functional React app used for interfacing with the smart contracts during development
packages/fuzzer/ - A very simple, purpose-built tool based on Liquity middleware for randomly interacting with the system
packages/lib-base/ - Common interfaces and classes shared by the other lib- packages
packages/lib-ethers/ - Ethers-based middleware that can read Liquity state and send transactions
packages/lib-react/ - Components and hooks that React-based apps can use to view Liquity contract state
packages/lib-subgraph/ - Apollo Client-based middleware backed by the Liquity subgraph that can read Liquity state
packages/providers/ - Subclassed Ethers providers used by the frontend
packages/subgraph/ - Subgraph for querying Liquity state as well as historical data like transaction history
packages/contracts/ - The backend development folder, contains the Hardhat project, contracts and tests
packages/contracts/contracts/ - The core back end smart contracts written in Solidity
packages/contracts/test/ - JS test suite for the system. Tests run in Mocha/Chai
packages/contracts/tests/ - Python test suite for the system. Tests run in Brownie
packages/contracts/gasTest/ - Non-assertive tests that return gas costs for Liquity operations under various scenarios
packages/contracts/fuzzTests/ - Echidna tests, and naive "random operation" tests
packages/contracts/migrations/ - contains Hardhat script for deploying the smart contracts to the blockchain
packages/contracts/utils/ - external Hardhat and node scripts - deployment helpers, gas calculators, etc
Backend development is done in the Hardhat framework, and allows Liquity to be deployed on the Hardhat EVM network for fast compilation and test execution.
Branches
As of 18/01/2021, the current working branch is main. master is out of date.
LQTY Token Architecture
The Liquity system incorporates a secondary token, LQTY. This token entitles the holder to a share of the system revenue generated by redemption fees and issuance fees.
To earn a share of system fees, the LQTY holder must stake their LQTY in a staking contract.
Liquity also issues LQTY to Stability Providers, in a continous time-based manner.
The LQTY contracts consist of:
LQTYStaking.sol - the staking contract, containing stake and unstake functionality for LQTY holders. This contract receives ETH fees from redemptions, and LUSD fees from new debt issuance.
CommunityIssuance.sol - This contract handles the issuance of LQTY tokens to Stability Providers as a function of time. It is controlled by the StabilityPool. Upon system launch, the CommunityIssuance automatically receives 32 million LQTY - the “community issuance” supply. The contract steadily issues these LQTY tokens to the Stability Providers over time.
LQTYToken.sol - This is the LQTY ERC20 contract. It has a hard cap supply of 100 million, and during the first year, restricts transfers from the Liquity admin address, a regular Ethereum address controlled by the project company Liquity AG. Note that the Liquity admin address has no extra privileges and does not retain any control over the Liquity protocol once deployed.
LQTY Lockup contracts and token vesting
Some LQTY is reserved for team members and partners, and is locked up for one year upon system launch. Additionally, some team members receive LQTY vested on a monthly basis, which during the first year, is transferred directly to their lockup contract.
In the first year after launch:
All team members and partners are unable to access their locked up LQTY tokens
The Liquity admin address may transfer tokens only to verified lockup contracts with an unlock date at least one year after system deployment
Also, separate LQTY allocations are made at deployent to an EOA that will hold an amount of LQTY for bug bounties/hackathons and to a Uniswap LP reward contract. Aside from these allocations, the only LQTY made freely available in this first year is the LQTY that is publically issued to Stability Providers via the CommunityIssuance contract.
Lockup Implementation and admin transfer restriction
A LockupContractFactory is used to deploy LockupContracts in the first year. During the first year, the LQTYToken checks that any transfer from the Liquity admin address is to a valid LockupContract that is registered in and was deployed through the LockupContractFactory.
Launch sequence and vesting process
Deploy LQTY Contracts
Liquity admin deploys LockupContractFactory
Liquity admin deploys CommunityIssuance
Liquity admin deploys LQTYStaking
Liquity admin creates a Pool in Uniswap for LUSD/ETH and deploys Unipool (LP rewards contract), which knows the address of the Pool
Liquity admin deploys LQTYToken, which upon deployment:
Stores the CommunityIssuance and LockupContractFactory addresses
Mints LQTY tokens to CommunityIssuance, the Liquity admin address, the Unipool LP rewards address, and the bug bounty address
Liquity admin sets LQTYToken address in LockupContractFactory, CommunityIssuance, LQTYStaking, and Unipool
Deploy and fund Lockup Contracts
Liquity admin tells LockupContractFactory to deploy a LockupContract for each beneficiary, with an unlockTime set to exactly one year after system deployment
Liquity admin transfers LQTY to each LockupContract, according to their entitlement
Deploy Liquity Core
Liquity admin deploys the Liquity core system
Liquity admin connects Liquity core system internally (with setters)
Liquity admin connects LQTYStaking to Liquity core contracts and LQTYToken
Liquity admin connects CommunityIssuance to Liquity core contracts and LQTYToken
During one year lockup period
Liquity admin periodically transfers newly vested tokens to team & partners’ LockupContracts, as per their vesting schedules
Liquity admin may only transfer LQTY to LockupContracts
Anyone may deploy new LockupContracts via the Factory, setting any unlockTime that is >= 1 year from system deployment
Upon end of one year lockup period
All beneficiaries may withdraw their entire entitlements
Liquity admin address restriction on LQTY transfers is automatically lifted, and Liquity admin may now transfer LQTY to any address
Anyone may deploy new LockupContracts via the Factory, setting any unlockTime in the future
Post-lockup period
Liquity admin periodically transfers newly vested tokens to team & partners, directly to their individual addresses, or to a fresh lockup contract if required.
NOTE: In the final architecture, a multi-sig contract will be used to move LQTY Tokens, rather than the single Liquity admin EOA. It will be deployed at the start of the sequence, and have its address recorded in LQTYToken in step 4, and receive LQTY tokens. It will be used to move LQTY in step 7, and during & after the lockup period. The Liquity admin EOA will only be used for deployment of contracts in steps 1-4 and 9.
The current code does not utilize a multi-sig. It implements the launch architecture outlined above.
Additionally, a LP staking contract will receive the initial LP staking reward allowance, rather than an EOA. It will be used to hold and issue LQTY to users who stake LP tokens that correspond to certain pools on DEXs.
Core System Architecture
The core Liquity system consists of several smart contracts, which are deployable to the Ethereum blockchain.
All application logic and data is contained in these contracts - there is no need for a separate database or back end logic running on a web server. In effect, the Ethereum network is itself the Liquity back end. As such, all balances and contract data are public.
The system has no admin key or human governance. Once deployed, it is fully automated, decentralized and no user holds any special privileges in or control over the system.
The three main contracts - BorrowerOperations.sol, TroveManager.sol and StabilityPool.sol - hold the user-facing public functions, and contain most of the internal system logic. Together they control Trove state updates and movements of Ether and LUSD tokens around the system.
Core Smart Contracts
BorrowerOperations.sol - contains the basic operations by which borrowers interact with their Trove: Trove creation, ETH top-up / withdrawal, stablecoin issuance and repayment. It also sends issuance fees to the LQTYStaking contract. BorrowerOperations functions call in to TroveManager, telling it to update Trove state, where necessary. BorrowerOperations functions also call in to the various Pools, telling them to move Ether/Tokens between Pools or between Pool <> user, where necessary.
TroveManager.sol - contains functionality for liquidations and redemptions. It sends redemption fees to the LQTYStaking contract. Also contains the state of each Trove - i.e. a record of the Trove’s collateral and debt. TroveManager does not hold value (i.e. Ether / other tokens). TroveManager functions call in to the various Pools to tell them to move Ether/tokens between Pools, where necessary.
LiquityBase.sol - Both TroveManager and BorrowerOperations inherit from the parent contract LiquityBase, which contains global constants and some common functions.
StabilityPool.sol - contains functionality for Stability Pool operations: making deposits, and withdrawing compounded deposits and accumulated ETH and LQTY gains. Holds the LUSD Stability Pool deposits, and the ETH gains for depositors, from liquidations.
LUSDToken.sol - the stablecoin token contract, which implements the ERC20 fungible token standard in conjunction with EIP-2612 and a mechanism that blocks (accidental) transfers to addresses like the StabilityPool and address(0) that are not supposed to receive funds through direct transfers. The contract mints, burns and transfers LUSD tokens.
SortedTroves.sol - a doubly linked list that stores addresses of Trove owners, sorted by their individual collateralization ratio (ICR). It inserts and re-inserts Troves at the correct position, based on their ICR.
PriceFeed.sol - Contains functionality for obtaining the current ETH:USD price, which the system uses for calculating collateralization ratios.
HintHelpers.sol - Helper contract, containing the read-only functionality for calculation of accurate hints to be supplied to borrower operations and redemptions.
Data and Value Silo Contracts
Along with StabilityPool.sol, these contracts hold Ether and/or tokens for their respective parts of the system, and contain minimal logic:
ActivePool.sol - holds the total Ether balance and records the total stablecoin debt of the active Troves.
DefaultPool.sol - holds the total Ether balance and records the total stablecoin debt of the liquidated Troves that are pending redistribution to active Troves. If a Trove has pending ether/debt “rewards” in the DefaultPool, then they will be applied to the Trove when it next undergoes a borrower operation, a redemption, or a liquidation.
CollSurplusPool.sol - holds the ETH surplus from Troves that have been fully redeemed from as well as from Troves with an ICR > MCR that were liquidated in Recovery Mode. Sends the surplus back to the owning borrower, when told to do so by BorrowerOperations.sol.
GasPool.sol - holds the total LUSD liquidation reserves. LUSD is moved into the GasPool when a Trove is opened, and moved out when a Trove is liquidated or closed.
Contract Interfaces
ITroveManager.sol, IPool.sol etc. These provide specification for a contract’s functions, without implementation. They are similar to interfaces in Java or C#.
PriceFeed and Oracle
Liquity functions that require the most current ETH:USD price data fetch the price dynamically, as needed, via the core PriceFeed.sol contract using the Chainlink ETH:USD reference contract as its primary and Tellor's ETH:USD price feed as its secondary (fallback) data source. PriceFeed is stateful, i.e. it records the last good price that may come from either of the two sources based on the contract's current state.
The fallback logic distinguishes 3 different failure modes for Chainlink and 2 failure modes for Tellor:
Frozen (for both oracles): last price update more than 4 hours ago
Broken (for both oracles): response call reverted, invalid timeStamp that is either 0 or in the future, or reported price is non-positive (Chainlink) or zero (Tellor). Chainlink is considered broken if either the response for the latest round or the response for the round before the latest fails one of these conditions.
PriceChangeAboveMax (Chainlink only): higher than 50% deviation between two consecutive price updates
There is also a return condition bothOraclesLiveAndUnbrokenAndSimilarPrice which is a function returning true if both oracles are live and not broken, and the percentual difference between the two reported prices is below 5%.
The current PriceFeed.sol contract has an external fetchPrice() function that is called by core Liquity functions which require a current ETH:USD price. fetchPrice() calls each oracle's proxy, asserts on the responses, and converts returned prices to 18 digits.
PriceFeed Logic
The PriceFeed contract fetches the current price and previous price from Chainlink and changes its state (called Status) based on certain conditions.
Initial PriceFeed state:chainlinkWorking. The initial system state that is maintained as long as Chainlink is working properly, i.e. neither broken nor frozen nor exceeding the maximum price change threshold between two consecutive rounds. PriceFeed then obeys the logic found in this table: