Running the canyon upgrade.

Rebasing balances

Account state trie changes

In order to update the balances of every account as ETH yield comes in, Blast represents ETH balances in terms of shares. The desired yield mode is stored in Flags, and the Fixed, Shares, and Remainder fields store the necessary data to support each mode.

  // upstream                            // blast
  type StateAccount struct {             type StateAccount struct {
      Nonce    uint64                        Nonce     uint64
      Root     common.Hash                   Root      common.Hash
      CodeHash []byte                        CodeHash  []byte
-     Balance  *big.Int                +     Flags     uint8
                                       +     Fixed     *big.Int
                                       +     Shares    *big.Int
                                       +     Remainder *big.Int
  }                                      }

Modifying the type of the state trie is a significant change that affects test cases, chain genesis, and balance merkle proofs. However, from the perspective of a single transaction, there’s no difference. The balance opcode behaves identically, and smart contracts never need to consider their account’s shares.

In order to remain EVM compatible, Blast’s share system is designed to maintain wei-level precision. Accounts can transfer a precise amount of wei, even if their balances are rebasing and the recipient’s balance is/is not rebasing. Other share-based rebasing implementations do not necessarily have this property, which is essential for maintaining EVM compatibility.

Instead of adding new opcodes to handle transactions that require modifying these new fields (e.g. changing an account’s yield mode), we instead place that logic in a precompile. This is to avoid complicating the development toolchain since there’s no compiler modifications needed to use new precompiles.

Yield mode: automatic

In this mode, balances increase automatically as yield gets reported to the L2. The formula is:

balance = account.shares * sharePrice + account.remainder

Generally, in this mode, account.remainder will be a small dusty value to enable wei-level precision. It should adhere to account.remainder < sharePrice because otherwise, account.shares would be higher.

Yield mode: claimable

If the yield mode is claimable, then the balance is just:

balance   = account.fixed
claimable = account.shares * sharePrice + account.remainder - account.fixed

The claimable value refers to yield that this account can claim that accumulates separately from the account’s balance.

Yield mode: disabled

If the yield mode is disabled, then the balance is static and there’s no claimable yield.

balance   = account.fixed
claimable = 0

Global share price state