SIP-185: Debt Shares

Author
StatusSC_Review_Pending
TypeGovernance
ImplementorTBD
ReleaseTBD
Discussions-ToSynthetix Discord
Created2021-09-28

Simple Summary

Track staker’s issued debt amount by issuing debt shares when minting or burning sUSD debt.

Abstract

This SIP proposes the implementation of a debt shares token mechanism that allows the percentage of a staker’s debt to be calculated based on their number of shares. A staker’s percentage of the debt pool is their balance of debt shares divided by the total supply of all outstanding debt shares. Minting and burning sUSD debt updates the balance of debt shares issued.

Motivation

Issuing sUSD debt on Synthetix exposes stakers to fluctuations in the debt pool when synths are exchanged. In order to accurately track the debt owed by a staker, we need to track the percentage of the debt pool each staker has minted using the debt ledger. The debt shares mechanism will replace the debt ledger by issuing or burning a number of share tokens when sUSD is minted or burned and a staker’s debt percentage would be their balance of tokens divided by the total supply of debt shares. Using debt shares instead of the debt ledger mechanism also enables fees and rewards to be distributed using the staking rewards model where rewards are earned based on the staker’s balance of debt shares.

In order to support migration of the debt pool to L2, the debt shares will allow the migration of staker’s debt across to L2 by transferring their balance of shares to L2 and vice versa.

Specification

Overview

When issuing sUSD, the amount of debt shares issued is based on the amount of sUSD being minted. The debt shares represent the percentage of the debt pool they will owe after minting. The price of each debt share is calculated based on the total supply of sUSD issued by all stakers and the current debt pool size. Debt shares enables L1 debt and staked SNX to migrate to L2 by sharing a combined debt shares supply.

Example:

  • Alice mints 100 sUSD, gets issued with 100 debt shares
  • Bob mints 100 sUSD, gets issued with 100 debt shares
  • Both Alice and Bob each have 50% of the debt shares (100 / 200 shares)

When the total debt pool value fluctuates, the shares will be used to calculate how much debt the minter owes. For example, if the debt pool now doubles to 400 sUSD, based on the above scenario:

  • Alice who has 50% (100 shares), will have 200 sUSD debt
  • Alice who has 50% (100 shares), will have 200 sUSD debt

Burning sUSD reduces the number of debt shares issued against a staker and the number of shares burnt is calculated with the total debt pool value. Continuing with the example above,

Example:

  • Alice now burns 100 sUSD, which burns (100 / 400) * 200 shares = 50 shares
  • Alice would have 50 shares after her burn, ⅓ of the debt pool.
  • Alice’s remaining debt will be (50 / 150 shares) * 300 = 100 sUSD

Rationale

This mechanism has several major improvements and benefits:

  • Provides a way to track staker’s debt using a token standard / mechanism.
  • Simplifies the debt ledger by using a balance and total supply of debt shares issued.
  • Enables staking rewards to be distributed fairly using the StakingRewards contract that is ERC20 token based.
  • Prevents fee period snapshotting exploits as rewards can be fairly distributed based on staker’s debt shares.
  • Enables migration of stakers debt from L1 -> L2 and bridging the SNX staked + debt in one transaction.

Technical Specification

The debt shares issued and burned when minting sUSD or burning sUSD is calculated based on the formula:

\( shares = total shares * \frac{sUSD amount}{total debt pool}\)

where total debt pool is the sUSD value of the debt pool calculated when the staker is minting or burning to issue or burn the right number of shares.

Different scenarios of minting and burning sUSD and the debt shares issued is covered in this sheet

To calculate the debt percentage of a staker and their sUSD debt owed would be simple as:

\(debt percentage = \frac{balance (user)}{total supply (debt shares)}\)

This avoids the need to calculate the debt percentage of each staker and storing the debt percentage number in the debt ledger.

The amount of sUSD debt can be calculated as debt percentage * total debt pool.

FeePool and rewards

Phase 1 Fee Pool upgrades

Currently the FeePoolState stores the staker's issuance record on minting and burning. The debt shares will be used to record the staker's balance of shares after minting or burning and when the FeePool.closePeriod() is executed, it will store the total supply of debt shares. Using the balance of shares for each staker, they can claim their proportion of the sUSD and SNX rewards for the fee period as is.

The debt ledger will be deprecated and the issuer will append the issuance data to the Fee Pool based on the debt shares contract.

Migration to Debt Shares

On L1, there are currently 7000-8000 active stakers who's debt shares will be imported into the new Debt Share contract. The Total Supply of the debt shares to be minted will be 1,000,000 debt shares and the number of shares minted for each staker is based on their current debt percentage read from the debt ledger contract. The precision of the debt shares will be 18 decimal places.

For example, a staker with a debt percentage of 10% would be minted 1,000,000 * 10% = 100,000 shares to represent their share of the debt pool. All stakers' debt shares will be calculated before the initial minting of their debt shares.

The estimated transaction costs of the migration is 6M gas per 1000 addresses imported into the debt shares contract.

An example of the Debt Shares contract with the import function

contract DebtShares {

    uint256 public totalSupply;
    mapping(address => uint256) public balances;

    function importAddresses(address[] calldata accounts, uint256[] calldata amounts) public onlyOwner onlySetup virtual {
        for (uint i = 0; i < accounts.length; i++) {
            mint(accounts[i], amounts[i]);
        }
    }

    function mint(address account, uint256 amount) public virtual {
        require(account != address(0), "ERC20: mint to the zero address");
        totalSupply += amount;
        balances[account] += amount;
        emit Transfer(address(0), account, amount);
}

    event Transfer(address indexed from, address indexed to, uint256 value);
}

Test Cases

Configurable Values (Via SCCP)

Copyright and related rights waived via CC0.