Substrate Deep Dive: Imbalances

This article kicks off a series of technical Substrate articles that I have planned. I will be discussing various specifics of Substrate in these articles that will help developers building on top of Substrate understand Substrate better. The topic for this post is Substrate’s Imbalances.

Background

The balances module of Substrate’s FRAME runtime maintains a total issuance variable. This variable is the sum of all native currency funds on the chain. In the case of Polymesh, it is the total amount of POLYX.

Total issuance includes all of the native currency that is on a Substrate chain including the staked and locked funds.

For the purposes of this deep dive, I will be calling the native Substrate currency Omni coin and the Substrate chain Knight chain from hereon.

What are Imbalances?

At the end of every transaction, the total issuance is always equal to the total amount of Omni coin on the Knight chain. However, during transaction processing, these two values can intermittently differ. Throughout this article, I will refer to the variable in the balances module as total issuance, and the actual sum of all Omni coin, total supply.

An Imbalance represents the difference between total issuance stored in the variable in the balances module and the actual total supply of Omni coin. An imbalance can be created when Omni coin is being burned or minted. An Imbalance can also be created by changing someone’s balance without changing total issuance. Imbalance helps us maintain balance in all things.

In Substrate, burning or minting Omni coin does not affect anyone’s balance. It only changes the total issuance and returns an imbalance. The imbalance can then be “handled” by the system in many ways. The system may increase/decrease multiple user’s balances by using that imbalance or simply reverse the change in total issuance. For example, when someone is charged the transaction fee, their balance is reduced but total issuance is not changed and that gives us an imbalance. The system then splits that imbalance to deposit some Omni coin into the treasury’s account and some in the validator’s account. Once the deposit is complete, the sum of all balances again becomes equal to total issuance.

In general, TotalSupply = TotalIssuance+Imbalance

Image for post

Types of Imbalances

There are two types of Imbalance in Substrate:

Positive Imbalance

Positive Imbalance means that the total supply of Omni coin is more than the total issuance stored in the balances module. If you burn Omni coin (decrease total issuance without changing anyone’s balance) or deposit Omni coin into someone’s account (increase someone’s balance without increasing total issuance), you will get Positive Imbalance.

TotalSupply = TotalIssuance + PositiveImbalance

Negative Imbalance

Negative Imbalance means that the total supply of Omni coin is less than the total issuance stored in the balances module. If you mint Omni coin (increase total issuance without changing anyone’s balance) or withdraw Omni coin from someone’s account (decrease someone’s balance without increasing total issuance), you will get Negative Imbalance.

TotalSupply = TotalIssuance — NegativeImbalance

Since Imbalances are unsigned numbers, Negative Imbalance is also stored as a positive number. The negative of the Negative Imbalance is represented in the formula and how it is handled.

Handling Imbalances

It is not always required to handle Imbalances manually. Imbalances implement the drop trait. In Rust, the drop trait is essentially what we call destructor in C++. It is a piece of code that is run when a variable goes out of scope. Substrate, by default, increases total issuance when Positive Imbalance is dropped and decreases total issuance when Negative Imbalance is dropped so that total issuance becomes equal to total supply once again. Essentially, dropping an Imbalance will either mint or burn Omni coin to reverse the cause of the Imbalance.

This is why, if you just need to increase/decrease the total issuance, you do not need to handle the Imbalance. For example, if you want to burn someone’s balance, you should call the withdraw function and just ignore the Imbalance. i.e. you just do let _= withdraw();. Under the hood, the withdraw function will generate a Negative Imbalance and return it to you. An important thing to note is that at this moment, the total issuance is not equal to the total supply in the system. Once _ goes out of scope (usually when you exit the current code block), the drop function in the Negative Imbalance will be called and the total issuance will get reduced to make it equal to the total supply.

You can also manually call the drop() function of an Imbalance. The other functions that an Imbalance has are:

/// The zero imbalance. Can be destroyed with `drop_zero`.
fn zero() -> Self;

/// Drop an instance cleanly. Only works if its `self.value()` is zero.
fn drop_zero(self) -> Result<(), Self>;

/// Consume `self` and return two independent instances; the first
/// is guaranteed to be at most `amount` and the second will be the remainder.
fn split(self, amount: Balance) -> (Self, Self);

/// Consume `self` and an `other` to return a new instance that combines
/// both.
fn merge(self, other: Self) -> Self;

/// Consume `self` and maybe an `other` to return a new instance that combines
/// both.
fn maybe_merge(self, other: Option<Self>) -> Self {
    if let Some(o) = other {
        self.merge(o)
    } else {
        self
    }
}

/// Consume an `other` to mutate `self` into a new instance that combines
/// both.
fn subsume(&mut self, other: Self);

/// Maybe consume an `other` to mutate `self` into a new instance that combines
/// both.
fn maybe_subsume(&mut self, other: Option<Self>) {
    if let Some(o) = other {
        self.subsume(o)
    }
}

/// Consume self and along with an opposite counterpart to return
/// a combined result.
///
/// Returns `Ok` along with a new instance of `Self` if this instance has a
/// greater value than the `other`. Otherwise returns `Err` with an instance of
/// the `Opposite`. In both cases the value represents the combination of `self`
/// and `other`.
fn offset(self, other: Self::Opposite) -> Result<Self, Self::Opposite>;

/// The raw value of self.
fn peek(&self) -> Balance;

Sometimes, you need to use these functions to handle Imbalance and sometimes it is more efficient to handle Imbalance manually rather than dropping them.

For example, let’s say you want to split a transaction fee into 3 parts. You’d first withdraw the fee from the user’s account and get a Negative Imbalance. You will then split the fees and deposit it wherever you want to. Depositing the fees will return you Positive Imbalances. You can then call the offset function of the Negative Imbalance with these Positive Imbalances to reduce the Negative Imbalance. If the fee was divisible by 3, the Negative Imbalance will automatically become 0 and dropping it will be a no-op. If the fee was not divisible by 3 then dropping the remaining negative fee will decrease the total issuance to account for it.

If you were to drop every Imbalance instead of using the offset function, it will still work but you’d be modifying the total issuance variable four times. By using the offset function, you only need to modify the total issuance variable one time. If the Imbalance was to be split among more people like when splitting rewards between thousands of nominators, using the offset function is even more beneficial.

Final Thoughts

An Imbalance represents the difference between total issuance stored in the variable in the balances module and the actual total supply of the native currency in Substrate. Positive and Negative Imbalances are a bit confusing but you can remember them with the help of this general formula, TotalSupply = TotalIssuance + Imbalance.

Also, it’s easy to remember that Positive Imbalance will increase total issuance when dropped and Negative Imbalance will decrease total issuance when dropped.

I hope this article clears up your doubts about Imbalances in Substrate. If you want to learn more about the Polymesh blockchain, read our whitepaper. Please feel free to reach out if you need any help. Cheers!

I first published this article on https://blog.polymath.network/substrate-deep-dive-imbalances-8dfa89cc1d1

Leave a Comment

Your email address will not be published. Required fields are marked *