Introduction
This book is the documentation for the Linear multicollateral upgrade.
Overview
This page explains what the multicollateral upgrade is and why it's needed.
This page only discusses the upgrade from a high-level perspective. See the following pages for detailed changes.
Background
Historically, the Linear Finance platform only accepts its native currency LINA as collateral for minting lUSD, the currency used in the Linear Exchange for synthetic asset trading. This limits the supply of lUSD and thus causes issues:
Friction onboarding traders
While the platform offers "unlimited liquidity" for trading, it's only for the conversion between different synthetic asset types, not for traders entering/exiting the platform (i.e. acquiring/liquidating lUSD).
Traders buy/sell lUSD through third party exchanges, and an abundance of lUSD supply is needed to ensure stable prices and low slippages.
Insufficient lUSD for liquidations
Debt liquidation (not to be confused with perpetual position liquidation) requires lUSD to be burnt. The larger the lUSD supply is, the easier it is for liquidators to acquire lUSD and perform liquidations in a timely manner, which is critical to the health of the platform.
On the contrary, the lack of lUSD supply forces its price up, making liquidations unprofitable. Liquidators would no longer be incentivized to perform liquidations, and bad debt might be created.
What's changed
Simply put, the multicollateral upgrade changes the platform to accept collateral tokens other than LINA. The lUSD token minted remains to be the same one as before.
ℹ️ Note
There's only one
lUSDtoken. All collateral tokens would mint the same fungiblelUSDtoken. This means thatlUSDminted from one collateral token can be used to repay debt incurred under another collateral token.
The support for multiple collateral tokens is conceptually similar to "subaccounts". Each such "subaccount" under the same user is completely isolated from others. The unique key to identify a debt position changes from (user_address) before the upgrade to (user_address, collateral_token) after the upgrade.
ℹ️ Note
The system treats different debt positions under the same user in isolation. It's thus possible for a user to have a healthy debt position in one collateral token, and a liquidatable position under another.
The upgrade only affects the building aspect of the platform, and the trading side is completely unaffected. The contracts and dapps powering Linear Exchange only care about the lUSD token out of the entire minting system, and the fact that only one lUSD token exists hasn't changed with the upgrade.
Contract changes
⚠️ Warning
While the contract changes contained in this upgrade have been carefully tested, they haven't been audited by a third party. Make sure you review/audit the upgrade before using it in production.
All contract changes are done in Linear-finance/linear. The upgrade is submitted as the dev/multi_collateral branch.
For any modified contracts, the modifications are all made in a backward-compatible, upgrade-safe manner, as this upgrade needs to be deployed to production.
ℹ️ Note
Mock/interface files are excluded from this page.
ℹ️ Note
This page only documents the source code changes for each file modified/added. The actual deployment steps (i.e. what contracts to upgrade; what new contracts to deploy) are documented separately.
Notable changes
Architecture change
Originally, only one instance of each of the following contracts is deployed:
LnBuildBurnSystemLnCollateralSystemLnDebtSystemLnLiquidationLnRewardSystem
With the upgrade, these 5 contracts will be deployed for each supported collateral token. A new contract DebtDistribution has been added to govern the different collateral tokens.
ℹ️ Note
Each set of the 5 collateral-specific contracts is not linked to, nor aware of the existence of, the set of contracts of any other collateral token. The new
DebtDistributioncontract is necessary as each collateral needs to know how much (in terms of proportion) of the whole debt pool it's entitled to.
Reduced usage of registry pattern
The Linear smart contract system was originally designed to use the registry pattern heavily (with LnAddressCache), with the LnAssetSystem serving as the contract address registry. However, newer contracts (e.g. LnLiquidation) stopped using the pattern. Right before the multicollateral upgrade, these contracts still used this pattern:
LnBuildBurnSystemLnCollateralSystemLnDebtSystemLnAssetUpgradeableLnExchangeSystem
Out of the 5 contracts, these 3 contracts used the pattern in a way that's incompatible with the multicollateral upgrade:
LnBuildBurnSystemLnCollateralSystemLnDebtSystem
ℹ️ Note
What makes them incompatible is that these 3 contracts assume that only a single instance of the collateral-specific contracts is deployed, which is no longer true. See the architecture change section for details.
As such, the use of the registry pattern in these 3 contracts has been removed and replaced with setting the addresses via the initializer function, or via specific setter functions if circular dependency is involved.
ℹ️ Note
The initializer function is only run on proxy contract deployment and not on contract upgrades. This means the new address setting behavior would only be effective for newly-deployed collateral-specific contracts, and not for
LINA-specific contracts.But this is fine as
LINA-specific contracts already had the addresses configured via the registry pattern. It would be as if the new initializer has been run on them.
Code change of SafeDecimalMath
Two new functions have been added to the SafeDecimalMath library. Since SafeDecimalMath is a dynamically-linked library, a new instance would need to be deployed and all new dependant contracts to be linked to the new instance.
ℹ️ Note
Writing library contracts with an external API, and thus requiring explicit linking (like the
SafeDecimalMathwe have) is now widely considered an anti-pattern.However, the multicollateral upgrade leaves this questionable design unchanged to minimize the scope of changes. A follow-up on this shall be made to migrate away from external libraries.
Per-file changes
ℹ️ Note
This section only summarizes the major changes for each relevant file, and does not replace actually reading the source code.
DebtDistribution.sol
This is a new contract for tracking how much (in terms of proportion) debt each collateral token is entitled to. See the architecture change section for details.
LnBuildBurnSystem.sol
Main changes to this contract include:
- Removed the registry pattern for setting contract addresses. See this section for details.
- Removed the debt changes calculation logic, and changed to delegate to
LnDebtSysteminstead. - Changed to use collateral-specific config keys for reading config values.
LnCollateralSystem.sol
This contract has been largely rewritten (with backward compatibility preserved).
The original codebase assumed that all collateral types will be aggregated into a single USD amount, which is now considered impractical, as different collaterals have different risk characteristics. The new design is to have each LnCollateralSystem contract handle only a single collateral token.
LnDebtSystem.sol
This contract has been largely rewritten (with backward compatibility preserved).
The original implementation is essentially a simple key-value store, accepting already-computed latest values from LnBuildBurnSystem. With this upgrade, LnDebtSystem now handles and encapsulates debt changes calculations, and exposes increaseDebt() and decreaseDebt() instead.
LnLiquidation.sol
Only minimal changes were made to this contract:
- Changed to use collateral-specific config keys for reading config values.
- Added the ability to handle collateral tokens that are not 18 decimals in precision.
SafeDecimalMath.sol
Two functions have been added to allow handling collateral tokens that are not 18 decimals in precision:
multiplyDecimalWith()divideDecimalWith()
utilities/ConfigHelper.sol
This library provides helper functions for computing config key values based on the collateral token used. This is needed as different collateral tokens have different settings.
ℹ️ Note
Unlike
SafeDecimalMath, this contract does not expose a public API, and is thus statically linked to dependant contracts. You don't need to deploy this library itself.
Service changes
This page documents the changes made to off-chain services supporting the Linear Finance platform.
ℹ️ Note
Most code changes were made assuming this list of collateral tokens. Due to legacy reasons (mainly the lack of proper templating setup), some repositories hard-code the list of collateral tokens.
Therefore, if you plan to deploy a different list of collateral tokens, you will need to make further changes.
Repositories changed
Changes for each repository are submitted under a new dev/multi_collateral branch.
The section only lists the repositories being updated. Check out the patch branches for the actual code changes.
Subgraphs
ℹ️ Note
The patch submitted for
linear-subgraphsis based on commit0d76432, as the now-latest commit218f16awasn't available at the time the patch was authored. The new team needs to port the patch upon the latest commit.
Workers
Docker services
Merging and releasing
All the code changes are submitted on a dev/multi_collateral branch under each affected repository. They're not submitted to master as they're considered unreviewed. Once reviewed and approved by the team, the changes shall be merged into master and have Docker images built for them.
ℹ️ Note
Strictly speaking, Docker images are not needed for workers to be deployed to production, but they're still needed for running the development environment.
Even prior to the upgrade, GitHub Actions workflows have already been configured on all the affected repositories to automatically build Docker images upon the creation of new Git tags with format v*.*.*. Therefore, once merged, the team only needs to tag the new merged master branches to have the images built.
As for the actual version used in v*.*.*, any version greater than the existing Git tags can be used and is up to the team. However, it's recommended to use follow the new versions assumed by the dev environment changes.
ℹ️ Note
If versions other than those assumed in dev environment changes are used, you would need to modify the versions referenced in
Linear-finance/linear-devto have a functional development environment.
Web app changes
This page documents the changes made to web apps of the Linear Finance platform.
As noted in this section. The upgrade only affects the building aspect of the protocol. No changes were made to other web apps.
ℹ️ Note
Most code changes were made assuming this list of collateral tokens. Due to legacy reasons (mainly the lack of proper templating setup), some repositories hard-code the list of collateral tokens.
Therefore, if you plan to deploy a different list of collateral tokens, you will need to make further changes.
Repositories changed
Changes for each repository are submitted under a new dev/multi_collateral branch.
The section only lists the repositories being updated. Check out the patch branches for the actual code changes:
Dev environment changes
To faciliate testing, the Linear development environment has been updated as part of the upgrade.
Mock collateral tokens
The updated development environment assumes the use of the following collateral tokens:
| Name | Symbol | Decimals |
|---|---|---|
| Linear Token | LINA | 18 |
| Wrapped BNB | WBNB | 18 |
| BTCB Token | BTCB | 18 |
| Ethereum Token | ETH | 18 |
ℹ️ Note
These tokens actually exist in the Binance Smart Chain network, and they actually match the list of collateral tokens assumed for production deployment.
ℹ️ Note
The upgrade only supports ERC20 tokens, and not the native network currency
BNB. Therefore, to supportBNBas collateral, the wrapped tokenWBNBmust be used instead.It's technically possible for the system to internally handle the conversion between
BNBandWBNB, in a way similar to how Uniswap handlesETHswaps on the router level. However, this is out of scope for this upgrade itself.
Environment initialization
A patch to Linear-finance/linear-dev-cli has been submitted as a new dev/multi_collateral branch. Similar to the service changes, a new Docker image has not been built for it yet. When building, it's recommended to use a tag expected by the Docker Compose stack.
Docker Compose stack
Linear-finance/linear-dev has been updated (also in the dev/multi_collateral branch) to make the upgrade available in the development environment.
It's important to note that new Docker images referenced by the upgrade do not actually exist yet. Rather, they are expected to be built once other parts of the upgrade are merged and released (see this section and this session). Therefore, if you want to test the stack before those images are built from GitHub Actions, you'll need to manually build them locally.
Deployment
This page documents the steps for actually deploying the multicollateral upgrade to production.
⚠️ Warning
Make sure you really understand the deployment process before applying it to production, especially for the contracts part.
💡 Tips
The patch for Linear-finance/linear-dev-cli demonstrates how each of the additional collateral token is handled. It could be a useful source of reference.
💡 Tips
It might be helpful to practice/test by deploying a development environment with a version or Linear-finance/linear-dev prior to the upgrade, and then upgrading it.
Assumed collateral configuration
| Name | Symbol | Decimals |
|---|---|---|
| Linear Token | LINA | 18 |
| Wrapped BNB | WBNB | 18 |
| BTCB Token | BTCB | 18 |
| Ethereum Token | ETH | 18 |
ℹ️ Note
Code changes to services and web apps are required if you plan to deploy different collateral tokens.
Deploying contract changes
ℹ️ Note
This section only documents the deployment steps. See the contract changes page for details.
Deploying new SafeDecimalMath instance
Due to changes in SafeDecimalMath, a new instance of the library needs to be deployed. Once deployed, the address of this new instance should be used in any subsequent step where any contract using the libarary is upgraded/deployed.
Deploying the DebtDistribution contracts
Apart from turning some contracts from singleton deployments into being collateral-specific, the upgrade also adds a new contract DebtDistribution. This contract needs to be deployed first, as the collatera-specific contracts rely on it to function.
Once deployed, take note of its address as it will be needed for the subsequent steps.
Deciding on Collateral ID
For each collateral token (including LINA), you must decide on a Collateral ID to use. This Collateral ID could be different from the token symbol itself, but it's recommended to simply use the symbol. The Collateral ID will be needed when activating the tokens.
An example of deriving the Collateral ID from the string value "LINA" using ethers.js's formatBytes32String function:
const collateralId = formatBytes32String("LINA");
Upgrading LINA contracts
This step involves upgrading existing collateral-specific contracts for LINA to make them multi-collateral-aware.
Specifically, the following contracts need to be upgraded:
LnBuildBurnSystemLnCollateralSystemLnDebtSystemLnLiquidation
Once upgraded, activate LINA as collateral by sending these two transactions:
- Calling
addCollateral()onDebtDistributionwithLINA'sLnDebtSystemaddress and its Collateral ID to register the token. - Calling
setDebtDistribution()onLINA'sLnDebtSystemcontract withDebtDistribution's address to link the two contracts.
ℹ️ Note
With the multi-collateral system, each token must be activated to become usable as collateral. This includes
LINA. So it's important for the activation step to be performed in a timely manner to limit downtime.
ℹ️ Note
Once this step is done, the system would have techinically already enabled multi-collateral support. It's just that
LINAis the only activated collateral token.It's therefore a good opportunity to pause here and test that everything still works as expected before proceeding.
Deploying new collateral contracts
ℹ️ Note
Some contract constructor fields require addresses of contracts that are not yet deployed. While it's technically possible to pre-compute those addresses before deployment, it might be easier to simply feed the constructor with mock addresses first (e.g.
0x0000000000000000000000000000000000000001) and configure them later via setters once all other contracts are deployed.
For each collateral token:
- Deploy
LnDebtSystem. - Deploy
LnBuildBurnSystem, leaving the_collaterSysand_liquidationarguments as mocked addresses. - Deploy
LnCollateralSystem, leaving the_mRewardLockerand_liquidationarguments as mocked addresses. - Deploy
LnLiquidation, leaving the_lnRewardLockerargument as mocked address.
⚠️ Warning
The address for
LnRewardLockeris deliberately left out (and not filled back in later) forLnCollateralSystemandLnLiquidation, as only the native collateral token (i.e.LINA) can be locked as rewards. Incorrectly feedingLINA'sLnRewardLockeraddress to these contracts will result in system misbehaviors.
-
Deploy
LnRewardSystem, usingLINA'sLnRewardLocker's address for the_rewardLockerAddressargument. -
Configure the collateral token by setting the following values on
LnConfig:COLLATERALID_BuildRatioCOLLATERALID_LiqRatioCOLLATERALID_LiqMarkerRewardCOLLATERALID_LiqLiquidatorRewardCOLLATERALID_LiqDelay
where
COLLATERALIDmust be replaced with the actual Collateral ID chosen. For example, if the chosen Collateral ID isBTCB, then the first key would beBTCB_BuildRatio. -
Assign the following roles on
LnAccessControl:ISSUE_ASSETforLnBuildBurnSystemBURN_ASSETforLnBuildBurnSystemLnDebtSystemforLnBuildBurnSystemLOCK_REWARDforLnRewardSystem
-
Fill some of the addresses left out during deployment:
- Call
setCollateralSystemAddress()onLnBuildBurnSystem - Call
setLiquidationAddress()onLnBuildBurnSystem - Call
setLiquidationAddress()onLnCollateralSystem
- Call
-
Call
updateTokenInfo()onLnCollateralSystemwith the token's Collateral ID and address. -
Set up
LnOracleRouterto handle price queries for the Collateral ID, if the Collateral ID doesn't already map to a valid oracle setting.
Activating new collateral tokens
⚠️ Warning
Once this step is done, users can already start using the new collateral tokens (even if the web apps haven't been updated). Old subgraphs are not aware of the upgrade and would serve inaccurate data if anyone actually uses the new tokens.
To avoid this, consider deploying and synchronizing the new subgraphs first before proceeding with this step. This is especially true considering that some subgraphs take a long time to synchronize.
For each collateral token, follow the same steps taken to activate LINA:
- Calling
addCollateral()onDebtDistributionwith the collateral token'sLnDebtSystemaddress and its Collateral ID to register the token. - Calling
setDebtDistribution()on the collateral token'sLnDebtSystemcontract withDebtDistribution's address to link the two contracts.
Upgrading subgraphs
See this subgraphs section for the list of changed subgraphs.
To deploy new subgraphs, first make changes to Linear-finance/graph-registry to bump subgraph versions and update environment variables.
Then, deploy the updated subgraphs and wait for the synchronization to finish. After that, update Linear-finance/graph-proxy-config to start routing traffic to the new subgraphs.
💡 Tips
See the patch in Linear-finance/linear-dev for subgraph deployment changes.
Upgrading services
See the workers section and Docker services section for the list of changed services.
💡 Tips
See the patch in Linear-finance/linear-dev for service deployment changes.
Upgrading web apps
The final step is upgrading the web apps to enable end users to interact with the new collateral tokens. See this page for a list of changed web apps.