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 lUSD token. All collateral tokens would mint the same fungible lUSD token. This means that lUSD minted 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:

  • LnBuildBurnSystem
  • LnCollateralSystem
  • LnDebtSystem
  • LnLiquidation
  • LnRewardSystem

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 DebtDistribution contract 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:

  • LnBuildBurnSystem
  • LnCollateralSystem
  • LnDebtSystem
  • LnAssetUpgradeable
  • LnExchangeSystem

Out of the 5 contracts, these 3 contracts used the pattern in a way that's incompatible with the multicollateral upgrade:

  • LnBuildBurnSystem
  • LnCollateralSystem
  • LnDebtSystem

ℹ️ 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 SafeDecimalMath we 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 LnDebtSystem instead.
  • 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-subgraphs is based on commit 0d76432, as the now-latest commit 218f16a wasn'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-dev to 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:

NameSymbolDecimals
Linear TokenLINA18
Wrapped BNBWBNB18
BTCB TokenBTCB18
Ethereum TokenETH18

ℹ️ 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 support BNB as collateral, the wrapped token WBNB must be used instead.

It's technically possible for the system to internally handle the conversion between BNB and WBNB, in a way similar to how Uniswap handles ETH swaps 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

NameSymbolDecimals
Linear TokenLINA18
Wrapped BNBWBNB18
BTCB TokenBTCB18
Ethereum TokenETH18

ℹ️ 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:

  • LnBuildBurnSystem
  • LnCollateralSystem
  • LnDebtSystem
  • LnLiquidation

Once upgraded, activate LINA as collateral by sending these two transactions:

  1. Calling addCollateral() on DebtDistribution with LINA's LnDebtSystem address and its Collateral ID to register the token.
  2. Calling setDebtDistribution() on LINA's LnDebtSystem contract with DebtDistribution'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 LINA is 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:

  1. Deploy LnDebtSystem.
  2. Deploy LnBuildBurnSystem, leaving the _collaterSys and _liquidation arguments as mocked addresses.
  3. Deploy LnCollateralSystem, leaving the _mRewardLocker and _liquidation arguments as mocked addresses.
  4. Deploy LnLiquidation, leaving the _lnRewardLocker argument as mocked address.

⚠️ Warning

The address for LnRewardLocker is deliberately left out (and not filled back in later) for LnCollateralSystem and LnLiquidation, as only the native collateral token (i.e. LINA) can be locked as rewards. Incorrectly feeding LINA's LnRewardLocker address to these contracts will result in system misbehaviors.

  1. Deploy LnRewardSystem, using LINA's LnRewardLocker's address for the _rewardLockerAddress argument.

  2. Configure the collateral token by setting the following values on LnConfig:

    • COLLATERALID_BuildRatio
    • COLLATERALID_LiqRatio
    • COLLATERALID_LiqMarkerReward
    • COLLATERALID_LiqLiquidatorReward
    • COLLATERALID_LiqDelay

    where COLLATERALID must be replaced with the actual Collateral ID chosen. For example, if the chosen Collateral ID is BTCB, then the first key would be BTCB_BuildRatio.

  3. Assign the following roles on LnAccessControl:

    • ISSUE_ASSET for LnBuildBurnSystem
    • BURN_ASSET for LnBuildBurnSystem
    • LnDebtSystem for LnBuildBurnSystem
    • LOCK_REWARD for LnRewardSystem
  4. Fill some of the addresses left out during deployment:

    • Call setCollateralSystemAddress() on LnBuildBurnSystem
    • Call setLiquidationAddress() on LnBuildBurnSystem
    • Call setLiquidationAddress() on LnCollateralSystem
  5. Call updateTokenInfo() on LnCollateralSystem with the token's Collateral ID and address.

  6. Set up LnOracleRouter to 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:

  1. Calling addCollateral() on DebtDistribution with the collateral token's LnDebtSystem address and its Collateral ID to register the token.
  2. Calling setDebtDistribution() on the collateral token's LnDebtSystem contract with DebtDistribution'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.