Liquity v2
Smart Contract Security Assessment
August 28, 2024
SUMMARY
ABSTRACT
Dedaub was commissioned to perform a security audit of the Liquity v2 (BOLD) protocol, focusing on both the smart contract code and the overall logic of the system. The codebase was of high quality, with extensive comments and a detailed README that thoroughly describes all the aspects of the protocol. Additionally, the protocol is supported by an extensive test suite.
BACKGROUND
Liquity v2 is a decentralized lending protocol that mints a stablecoin called BOLD. The core mechanics are the same as in Liquity v1: users deposit collateral, and the protocol mints BOLD tokens. As long as a user's collateral ratio stays above a certain threshold, they remain safe from liquidation. However, if their collateral ratio drops below this threshold, anyone can trigger the liquidation process. The debt and collateral from a liquidated trove are absorbed by the stability pool, with any excess distributed proportionally to other troves based on their collateral. BOLD is redeemable at its face value of $1. It’s important to note that while Liquity v2 shares similarities with v1, the two protocols are entirely independent.
Key Differences and New Features in Liquity v2:
- Multi-Collateral Support: Liquity v2 introduces support for multiple collateral types, including wrapped ETH and various LSTs. Each collateral type forms its own branch with a separate set of troves and a dedicated stability pool.
- Interest Rate Mechanism: Instead of paying a one-time fee when opening a trove, users in Liquity v2 select an annual interest rate, which they will pay over time. The interest is split between stability pool depositors and other liquidity providers, such as AMM LPs for pools containing BOLD. The percentage of interest allocated to the stability pool is fixed at deployment and cannot be changed.
- Redemption Process: When redeeming BOLD tokens, users cannot choose which collateral they will receive. Instead, redemptions are distributed across the various collateral branches in proportion to their unbacked BOLD (i.e., total BOLD minted by the branch minus the amount of BOLD held in the branch's stability pool). Troves within each branch are ordered by their annual interest rate, with redemptions first targeting troves with lower interest rates. To avoid being targeted by redemptions, trove owners must adjust their interest rates. The protocol simplifies this process by allowing batch interest delegation. Any user can open an interest manager, charge a fee, and let trove owners join the manager to have their interest rates managed and updated.
SETTING & CAVEATS
This audit report covers the contracts of the at-the-time private repository https://github.com/liquity/bold, branch dev,
of the Liquity v2 protocol at commit 2a859733eff540aae2996d13b06a9c5d334e7616
.
2 auditors worked on the codebase for 4 weeks on the following contracts:
- ActivePool.sol
- AddressesRegistry.sol
- BoldToken.sol
- BorrowerOperations.sol
- CollateralRegistry.sol
- CollSurplusPool.sol
- DefaultPool.sol
- Dependencies/
- AddRemoveManagers.sol
- AggregatorV3Interface.sol
- Constants.sol
- IOsTokenVaultController.sol
- IRETHToken.sol
- IStaderOracle.sol
- LiquityBase.sol
- LiquityMath.sol
- Ownable.sol
- GasPool.sol
- HintHelpers.sol
- MultiTroveGetter.sol
- PriceFeeds/
- CompositePriceFeed.sol
- ETHXPriceFeed.sol
- MainnetPriceFeedBase.sol
- OSETHPriceFeed.sol
- RETHPriceFeed.sol
- WETHPriceFeed.sol
- WSTETHPriceFeed.sol
- SortedTroves.sol
- StabilityPool.sol
- TroveManager.sol
- TroveNFT.sol
- Types/
- BatchId.sol
- LatestBatchData.sol
- LatestTroveData.sol
- TroveChange.sol
- TroveId.sol
- Zappers/
- GasCompZapper.sol
- WETHZapper.sol
The audit’s main target is security threats, i.e., what the community understanding would likely call "hacking", rather than the regular use of the protocol. Functional correctness (i.e. issues in "regular use") is a secondary consideration. Typically it can only be covered if we are provided with unambiguous (i.e. full-detail) specifications of what is the expected, correct behavior. In terms of functional correctness, we often trusted the code’s calculations and interactions, in the absence of any other specification. Functional correctness relative to low-level calculations (including units, scaling and quantities returned from external protocols) is generally most effectively done through thorough testing rather than human auditing.
In this audit, we identified several accounting bugs, most of which are related to the batch interest delegation feature. Additionally, the Liquity team, who conducted an internal review and testing of the code in parallel with this audit, discovered a few more bugs (although these bugs are not included in this report, we have reviewed the fixes). The relatively high number of accounting bugs and the complexity of the batch delegation feature suggest that further testing and potentially a second round of auditing are necessary before deployment.
PROTOCOL-LEVEL CONSIDERATIONS
The new version of Liquity supports multiple collaterals, with each collateral associated with its own branch, with its own set of troves and stability pool. While certain risks are isolated within each branch—such as liquidations being contained to the branch’s stability pool and redistributions affecting only its troves—not all risks are similarly contained.
We are particularly concerned about the potential impact on other branches if one is shut down due to low collateralization ( TCR < SCR
) caused by a significant decrease in the collateral asset’s price. In such a scenario, it’s possible that the total collateral of the affected branch may not cover its total debt, leading to bad debt.
Even if the total collateral value across all branches exceeds the total remaining BOLD, theoretically ensuring that BOLD is fully backed, in practice, not all BOLD might be redeemable. This could trigger BOLD holders to rush for redemptions, leading to a decrease in BOLD’s market price and potentially causing it to lose its peg.
The protocol relies on Chainlink oracles to obtain the price for the collateral. If an oracle call fails, or returns an invalid (zero) value or stale price, the protocol immediately initiates the shutdown procedure. While we understand that it is challenging to determine on-chain whether an oracle failure is temporary or permanent, the current approach of instantly shutting down the branch is overly aggressive. A failed oracle call could be due to a temporary issue, such as an error during an update to the Chainlink contracts, which may be resolved shortly. Instead of an immediate shutdown, the protocol could monitor for multiple consecutive failed oracle calls over a set period and temporarily freeze the branch during this time. This would allow the system to handle temporary disruptions more gracefully.
VULNERABILITIES & FUNCTIONAL ISSUES
This section details issues affecting the functionality of the contract. Dedaub generally categorizes issues according to the following severities, but may also take other considerations into account such as impact or difficulty in exploitation:
- User or system funds can be lost when third-party systems misbehave.
- DoS, under specific conditions.
- Part of the functionality becomes unusable due to a programming error.
- Breaking important system invariants but without apparent consequences.
- Buggy functionality for trusted users where a workaround exists.
- Security issues which may manifest when the system evolves.
Issue resolution includes “dismissed” or “acknowledged” but no action taken, by the client, or “resolved”, per the auditors.