> ## Documentation Index
> Fetch the complete documentation index at: https://docs.monad.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Reserve Balance

## Introduction

The **Reserve Balance** mechanism is a set of light constraints - at consensus time on which
transactions can be **included**, and at execution time on which transactions **don't revert** - which
allow Monad to simultaneously support [asynchronous execution](/monad-arch/consensus/asynchronous-execution)
and [EIP-7702](/developer-essentials/eip-7702).

The Reserve Balance mechanism is designed to preserve safety under asynchronous execution
without interfering with normal usage patterns. **Most users and developers need not worry
about the Reserve Balance constraints**, however we provide the details here for those
encountering corner cases.

## Summary

Asynchronous execution means that nodes achieve consensus on a block proposal prior to executing
the transactions in that block. Execution is required to be completed in the next `k` (delay
factor) blocks. (Currently `k=3`. *NOTE*: In the context of discussing [block states](/monad-arch/consensus/block-states)
and [asynchronous execution](/monad-arch/consensus/asynchronous-execution), letter `D` is
usually used to denote the same parameter.)

Because consensus operates on a `k`-block delayed view of the global state, it is necessary
to adjust the consensus and execution rules slightly to allow consensus to safely build
and validate blocks that include only transactions whose gas costs can be paid for.

Monad introduces the **Reserve Balance** mechanism to allow consensus and execution
to collaborate across a multi-block lag to ensure that all EOAs must have enough MON
in their account to pay for gas for any transaction included in the blockchain.

<Info title="Inflight transaction">
  Throughout this document, an **inflight transaction** refers to a transaction that has
  been included in a block less than `k` blocks ago.
</Info>

Here is a very brief summary of the rules:

* From the perspective of a particular EOA, MON spent from that EOA in the course of a transaction
  is partitioned into two parts: **gas spend** and **value spend**.
  * In the case where the EOA was the sender:
    * **gas spend** is `gas_price * gas_limit`;
    * **value spend** is the `value` parameter on that transaction.
  * In the case where the EOA wasn't the sender (where they previously delegated via EIP-7702 and some other
    EOA submitted a transaction which called this EOA):
    * **gas spend** is 0;
    * **value spend** is whatever MON is sent out during the course of executing this EOA's code.
* Let `user_reserve_balance = 10 MON`
* *Execution time*: during execution, transactions revert due to **value spend** when that
  account's ending balance (before refunds) dips below `user_reserve_balance`, except in certain circumstances described below.
* *Consensus time*: For each account, consensus has a budget for the **gas spend** for all inflight
  transactions; this budget is `user_reserve_balance` (or the account's balance from the lagged
  execution state, whichever is lower). The budget is further reduced if the first inflight
  transaction earned the exception mentioned above, by that transaction's **value spend**.
  When performing block validity checks for block `n`, consensus checks that the budget is not exceeded.

<Info>
  See also the formal definition in the
  [Monad Initial Spec](https://category-labs.github.io/category-research/monad-initial-spec-proposal.pdf)
  proposal from Category Labs.
</Info>

## Parameters

| Parameter              | Value  |
| ---------------------- | ------ |
| `user_reserve_balance` | 10 MON |

## Why is reserve balance needed?

Monad has asynchronous execution: consensus is allowed to progress with building and
validating blocks without waiting for execution to catch up. Specifically, proposing
and validating consensus block `n` only requires knowledge of the state obtained after
applying block `n-k`.

While asynchronous execution has performance benefits, it introduces a novel challenge:
how is consensus supposed to know the validity of a block if it does not have the latest
state?

Let’s illustrate this challenge with an example (for our examples, we will use `k = 3`):

Consensus is validating block 4, which contains a transaction `t` from Alice with the
relevant fields as:

```
sender=Alice, to=Bob, value=100, gas=1
```

Consensus only has the state that was obtained by executing block 1:

```
block=1, balances={Alice: 110}
```

If consensus simply accepts block 4 as valid because Alice appears to have enough
balance, it risks a safety failure. For instance, Alice may have already spent her
balance in transaction `t’` in block 2. This creates a denial-of-service (DoS)
vector, as Alice could cause consensus to include many transactions for free.

## First attempt at a solution

One idea is for the consensus client to statically inspect transactions in blocks 2
and later, checking if Alice has spent any value in her transactions. This would let
consensus reject block 4 as invalid if any transaction before `t` (such as `t'`) in
blocks 2, 3, or 4 originates from Alice and spends some value or gas.

While this is a fine solution on the face of it, it suffers from two shortcomings:

1. Suppose, as part of smart contract execution in blocks 2 or 3, Alice received a lot
   of currency. She would have had enough balance to pay for transaction `t` despite
   `t'` existing, if only we had the latest state. So, rejecting transactions based
   solely on static checks is overly restrictive.

2. It is not only restrictive, it is also not safe with EIP-7702. With EIP-7702, Alice
   could have her account delegated to a smart contract, which can transfer out currency
   from Alice’s account in a way that is not statically inspectable by consensus.
   Concretely in our example, Alice does not need to send a transaction like `t'` from
   her account in order to spend currency from her account, if her account is delegated.
   A spend could potentially be triggered by a transaction submitted by anyone else.
   So our static check would not succeed and it may be unsafe to accept block 4 as valid
   even if we don’t see any other transaction from Alice in blocks 2, 3 and 4.

## Reserve balance as the solution

### Simple version

Intuitively, the core idea of reserve balance is as follows: if consensus and execution
agree ahead of time that, for each EOA, execution will prevent the ending balance from
dropping below a certain pre-determined threshold known to consensus (`user_reserve_balance`),
up to the sender's **gas spend** allowance, then consensus can safely include a series of transactions whose running
**gas spend** stays below `user_reserve_balance`, without knowing the latest state and
without being vulnerable to the DoS vector described above.

This concept can be generalized as follows:

* *Execution time*: after execution (before gas refunds), a reserve-balance check is applied to
  the ending balance. For a non-sender account, the ending balance must not be lower than
  `min(balance at transaction start, user_reserve_balance)`. For the sender, the ending balance may be lower
  by at most the transaction's **gas spend** (unless the emptying exception below applies).
  Excessive intermediate debits during execution are allowed as long as the ending balance is sufficient.
* *Consensus time*: For each account, consensus has a budget for the **gas spend** for all inflight
  transactions; this budget is `user_reserve_balance` (or the account's balance from the lagged
  execution state, whichever is lower).
  When performing block validity checks for block `n`, consensus checks that the budget is not exceeded.

In Monad, `user_reserve_balance` is currently set to `10 MON` for each EOA.

The above rule is sufficient to ensure all transactions included in consensus can be paid
for, thus solving our problem. However, it has a drawback, which is that EOAs can't spend
all of their MON, and EOAs with balances below `user_reserve_balance` won't be able to send
any successful transactions.

For instance, the following behaviors might be desired, but are currently blocked
by the above rule (with `user_reserve_balance` set to `10 MON`):

* Alice has a balance of 5 MON and wants to send 4.99 MON to Bob (plus pay 0.01 MON
  in gas)
* Alice has a balance of 20 MON and wants to swap 18 MON into a memecoin (plus pay
  0.01 MON in gas)

To address this, we add some additional conditions under which transactions are allowed.

### Addressing the drawback

First let's define an "emptying transaction":

<Info title="Emptying Transaction">
  A transaction is an **"emptying transaction"** iff

  * the sender is undelegated.
  * the sender has not sent any other transaction within the past `k` blocks.
  * nobody has sent a delegation or undelegation request for the sender within the past `k` blocks, including in this transaction.

  Such transactions are eligible for the emptying exception (see rules below).
</Info>

Notice that if a user account is **not** EIP-7702-delegated, then consensus can simply
inspect transactions statically in order to estimate the lowest a user’s balance can
possibly go. (This is because an undelegated user’s account can only be debited due to value
transfers and gas fees specified in the transaction data).

Therefore, we add the following exception to the reversion rule:

3. *Execution policy*: for each **undelegated** account sender:
   * *if* a transaction is an emptying transaction
   * *then* allow that transaction to proceed anyway (an "emptying transaction").
4. *Consensus policy*: for each **undelegated** account sender:
   * *if* a transaction is an emptying transaction
   * *then* statically inspect that transaction's total MON needs (i.e. `gas_bid * gas_limit + value`),
     and take into account the fact that execution will still allow this transaction through. This
     means that for any subsequent transactions in the next `k` blocks, the reserve balance that
     consensus is working with will be lower by `value`.

This rule lets execution allow consistently-undelegated accounts to dip below the reserve balance
once every `k` blocks. Since `k` blocks is 1.2 seconds, this policy should allow most small
accounts to still interact with the blockchain normally.
Note that an account is not eligible for the exception criteria if there was recent (within `k` blocks) undelegation request for the account even if it was already undelegated at that time.

See [Full specification](#full-specification) for details.

The additional policy allows both of the examples mentioned at the end of the previous section,
as long as they are the first transaction sent by the sender in `k` blocks.

## Discussion

### EIP-7702-delegated accounts

If an EOA is not EIP-7702-delegated, then the reserve balance is rarely invasive, even
if the EOA's balance is very low, because most transactions are the first transaction in `k=3` blocks
sent by that EOA.

But what if the EOA *is* EIP-7702-delegated? What is the impact?

* The only transactions that will revert are ones in which the balance of an EIP-7702-delegated EOA **dips below** 10 MON.
  * *'Dips below'* means *'decrements **and** drops below'*.
  * Transactions where the EOA's balance ends above or at 10 MON are fine.
  * Transactions where the EOA's balance is unchanged or increases are fine.
  * **Only** transactions that decrement the balance **and** result in a final balance
    below 10 MON are reverted.
  * Delegated EOAs cannot use the emptying exception described above.

For example, many sponsored gas workflows have users set up EIP-7702-delegated EOAs that
don't interact with MON at all (i.e. they start out empty, and only receive MON involuntarily).
Under a typical such workflow, a sponsor submits a transaction that calls into the EOA as
smart contract. This workflow works fine in Monad; it's fine for the EOA in question to
have a zero or very small balance.

The only case where a sponsored gas workflow will be blocked is if the EOA's balance is
(say) 6 MON, and the sponsored transaction calls code that tries to transfer (say) 1 MON out of the EOA.

### Transactions that are included but revert

Because of the Reserve Balance rules, you may see transactions *included* in the chain whose
execution *reverts*, such as transactions trying to transfer out more MON than are in the account
balance.

These transactions are still *valid* transactions that pay for gas, but the
*result* of these transactions is nothing except for gas being decremented from the sender.
They are included because at the time of consensus, the proposer cannot be sure that the account
isn't going to receive more MON from someone else, and the sender has the budget to pay for gas.

Ethereum includes many transactions whose execution reverts, so this is not a protocol difference.
However, in practice, Ethereum block builders may screen out transactions with insufficient balance
to process a transfer out, so this behavior may be different from what you're accustomed to seeing.

## Full specification

See the
[reserve balance spec](https://category-labs.github.io/category-research/monad-initial-spec-proposal.pdf)
for the formal set of Reserve Balance rules.

Algorithms 1 and 2 implement this check for consensus and execution, respectively.

Algorithm 3 implements the mechanism to detect the dipping into the reserve balance
(Algorithm 2 uses Algorithm 3 to revert transactions that dip).

Algorithm 4 specifies the criteria for emptying transactions:

* The sender account must be undelegated in the prior `k` blocks. This is checked
  statically by verifying the account was undelegated in a known state in the past
  `k` blocks, and there have been no delegation or undelegation requests in the last
  `k` blocks (this can be inspected statically).
* There must not be another transaction from the same sender in the prior k blocks.

Here is a quick summary of the reserve balance rules at consensus time:

#### If the account is not delegated and there are no inflight transactions

If the account is not delegated, and there are no previous inflight transactions, then consensus
checks that the gas fee for this transaction is less than the balance from the lagged state.

$$
\text{gas\_fees}(\text{tx}) \leq \text{balance}
$$

#### If the account is not delegated and has one emptying inflight transaction

If the account is not delegated, and there is one previous inflight transaction, then
consensus has to take into account the inflight transaction's total MON expenditures
(including `value`):

$$
\text{let adjusted\_balance} = \text{balance} - (\text{first\_tx.value} + \text{gas\_fees}(\text{first\_tx}))
$$

$$
\text{let } \text{reserve} = \min(\text{user\_reserve\_balance}(t.\text{sender}), \text{adjusted\_balance}) \quad
$$

A new transaction can only be included if the sum of all inflight transactions' gas fees
(excluding the first one) is less than the reserve:

$$
\sum_{tx \in I[1:]} \text{gas\_fees}(tx) \leq \text{reserve}
$$

#### All other cases

The reserve is equal to minimum of systemwide reserve balance (`10 MON`) or the
account's balance at block `n - k`:

$$
\text{reserve} = \min(\text{user\_reserve\_balance}(t.\text{sender}), \text{balance})
$$

A new transaction can only be included if the sum of all inflight transactions' gas fees
is less than the reserve:

$$
\sum_{tx \in I} \text{gas\_fees}(tx) \leq \text{reserve}
$$

## Adjusting the reserve balance

The reserve balance is currently the same for every account (`10 MON`).
In a future version, the protocol could allow users, through a stateful precompile, to
customize their reserve balance.

## Coq proofs

The safety of the reserve balance specification has been formally proved in Coq.

The full proofs documentation is available
[here](https://category-labs.github.io/category-research/reserve-balance-coq-proofs).

The consensus check is formalized in Coq as `consensusAcceptableTxs`. The predicate,
`consensusAcceptableTxs s ltx`, defines the criteria for the consensus module to accept
the list of transactions `ltx` on top of state `s`.

The proof shows that `consensusAcceptableTxs s ltx` implies that when the execution
module executes all the transactions in ltx one by one on top of s, none of them will
fail due to having insufficient balance to cover gas fees. The proof is by induction
on the list `ltx`: one can think of this as doing natural induction on the length of `ltx`.
The proof in the inductive step involves unfolding the definitions of the consensus
and execution checks and considering all the cases. In each case, the estimates of
effective reserve balance in consensus checks is shown to be conservative with respect
to what happens in execution.

## Additional examples

To test your understanding, here are some examples along with the expected outcome.
Each example is independent.

In the following examples, we use `start_block = 2`, meaning the initial balances
and reserves are after block 1. We also specify the reserve balance parameter for
each example, although it is a constant system wide parameter.

For each transaction, the expected result is indicated by a code:

* **2**: Successfully executed
* **1**: Included but reverted during execution (due to reserve balance dip)
* **0**: Excluded by consensus

### Example 1: Basic transaction inclusion

Initial state:

```
Alice: balance = 100, reserve = 10
Bob: balance = 5, reserve = 10
```

Transactions:

```
Block 2: [
  Alice: send 1 MON, fee 0.05 — Expected: 2
  Bob: send 2 MON, fee 0.05 — Expected: 2
]
```

Final balances:

```
Alice: 98.95
Bob: 2.95
```

### Example 2: Low reserve balance but high balance

Initial state:

```
Alice: balance = 100, reserve = 1
```

Transactions:

```
Block 2: [
  Alice: send 3 MON, fee 2 — Expected: 2 (emptying transaction)
  Alice: send 3 MON, fee 2 — Expected: 0 (excluded)
]
```

Final balance:

```
Alice: 95.0
```

### Example 3: Multi-block, low reserve but high balance

Initial state:

```
Alice: balance = 100, reserve = 1
```

Transactions:

```
Block 2: [
  Alice: send 3 MON, fee 2 — Expected: 2
]

Block 5: [
  Alice: send 3 MON, fee 2 — Expected: 2
]
```

Final balance:

```
Alice: 90.0
```

### Example 4: Comprehensive

Initial state:

```
Alice: balance = 100, reserve = 1
```

Transactions:

```
Block 2: [
  Alice: send 99 MON, fee 0.1 — Expected: 2 (large emptying transaction)
]

Block 3: [
  Alice: send 0.5 MON, fee 0.99 — Expected: 0 (excluded)
]

Block 4: [
  Alice: send 0.8 MON, fee 0.1 — Expected: 1 (included but reverted)
]

Block 5: [
  Alice: send 0 MON, fee 0.9 — Expected: 0 (excluded)
  Alice: send 5 MON, fee 0.1 — Expected: 1 (included but reverted)
  Alice: send 5 MON, fee 0.8 — Expected: 0 (excluded)
]
```

Final balance:

```
Alice: 0.70
```

### Example 5: Edge case — zero value transactions

Initial state:

```
Alice: balance = 2, reserve = 1
```

Transactions:

```
Block 2: [
  Alice: send 0 MON, fee 0.5 — Expected: 2
  Alice: send 0 MON, fee 0.6 — Expected: 2
  Alice: send 0 MON, fee 0.5 — Expected: 0 (exceeds reserve)
]
```

Final balance:

```
Alice: 0.9
```

### Example 6: Reserve balance boundary

Initial state:

```
Alice: balance = 10, reserve = 2
```

Transactions:

```
Block 2: [
  Alice: send 1 MON, fee 2 — Expected: 2 (matches reserve)
  Alice: send 0 MON, fee 0.01 — Expected: 2
]
```

Final balance:

```
Alice: 6.99
```

### Example 7: Account delegated in the interim

Initial state:

```
Alice: balance = 15, reserve = 10
```

Transactions:

```
Block 1: []

Block 2: [
  Alice is delegated
  Bob on behalf of Alice: send 5 MON, fee 0 — Expected: 2 (executed)
  Alice is undelegated
]

Block 3: [
  Alice: send 3 MON, fee 0.1 — Expected: 1 (included but reverted)
]

Block 4: []

Block 5: [
  Alice: send 0 MON, fee 8 - Expected: 2 (executed)
]
```

Final balance:

```
Alice: 1.9
```

**NOTE**: If execution would not have reverted transaction in `Block 3` (by checking delegation
status in prior `k` blocks), consensus would include transaction in `Block 5` which would later run
out of MON for the fee.

Also, consider `Block 2` is empty instead. Transaction in `Block 3` is then an emptying transaction,
which proceeds with execution, but transaction in `Block 5` is excluded as not having enough reserve
for fee.

### Example 8: Account receives MON before second inflight tx

Initial state:

```
Alice: balance = 15, reserve = 10
```

Transactions:

```
Block 1: []

Block 2: [
  Alice: send 6 MON, fee 0.1 - Expected: 2 (executed, emptying tx)
]

Block 3: [
  Alice receives 6 MON from a smart contract 
  Alice: send 7 MON, fee 0.1 — Expected: 1 (included but reverted, cannot be 2nd emptying so soon)
]

Block 4: []

Block 5: [
  Alice: send 0 MON, fee 8 - Expected: 2 (executed)
]
```

Final balance:

```
Alice: 6.8
```

**NOTE**: If execution would not have reverted the 2nd transaction in `Block 3` (from Alice; by
checking existence of emptying transactions in prior `k` blocks), consensus would include
transaction in `Block 5` which would later run out of MON for the fee.

Also, consider `Block 2` is empty instead. Transaction in `Block 3` (from Alice) is then an emptying
transaction, which proceeds with execution, but transaction in `Block 5` is excluded as one
potentially not having enough reserve for fee - consensus doesn't see Alice being credited in
`Block 3`.
