> For the complete documentation index, see [llms.txt](https://docs.megaeth.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.megaeth.com/spec/megaevm/resource-accounting.md).

# Resource Accounting

This page specifies how MegaETH accounts for usage across the four runtime resource dimensions: [compute gas](/spec/reference/glossary.md#compute-gas), data size, KV updates, and state growth. It defines what each dimension tracks, when counters are updated, and how revert behavior affects the counters.

## Motivation

MegaETH enforces multiple runtime resource limits in addition to the transaction gas limit. Those limits are meaningful only if every node computes the same resource usage for the same transaction.

Without a precise accounting specification, different implementations could disagree on questions such as:

* whether reverted subcalls still count toward a resource dimension,
* whether repeated account updates should be counted once or multiple times,
* whether new storage writes and later resets within the same transaction cancel out,
* and whether logs or deployed bytecode should count before or after success is known.

Resource accounting exists to make runtime-limit enforcement deterministic across implementations.

## Specification

The named constants referenced in this section are defined later in [Constants](#constants).

### Overview

MegaETH defines four runtime resource dimensions:

1. [Compute gas](/spec/reference/glossary.md#compute-gas)
2. Data size
3. KV updates
4. State growth

A node MUST track each dimension independently. Runtime limit enforcement for these dimensions is defined in [Multidimensional Resource Limits](/spec/megaevm/resource-limits.md). This page defines only how usage is counted.

### Revert Behavior

Unless explicitly stated otherwise on this page, resource trackers MUST be [call-frame](/spec/reference/glossary.md#call-frame)-aware:

* usage created within a child call frame MUST be discarded if that child frame reverts,
* and usage created within a child call frame MUST be merged into the parent call frame if that child call frame succeeds.

The sole stable exception is [compute gas](/spec/reference/glossary.md#compute-gas), which MUST accumulate globally and MUST NOT be reverted.

<details>

<summary>Rex6 (unstable): creator nonce-bump write booked to the parent frame</summary>

#### Creator Nonce-Bump Frame Attribution

Every `CREATE` / `CREATE2` bumps the creator's nonce, and that account-info write is charged to the data-size and KV-update dimensions.

Pre-Rex6, the charge is recorded in the **child** (created) frame's discardable lane, so it is discarded when the child `CREATE` reverts — even though the creator's nonce bump survives the revert under EVM semantics.

Under Rex6, a node MUST record the creator nonce-bump account-info write (`ACCOUNT_UPDATE_DATA_SIZE` bytes of data size and one KV update) in the **parent** frame's discardable lane, so that it is discarded only when the parent itself reverts, matching the nonce bump's actual revert scope.

</details>

### Compute Gas

#### Definition

A node MUST track compute gas as the cumulative gas consumed by EVM execution, independent of [storage gas](/spec/megaevm/dual-gas-model.md).

#### Included Usage

A node MUST include the following in compute gas usage:

* gas consumed by EVM instruction execution,
* memory expansion costs,
* and precompile costs.

#### Excluded Usage

A node MUST NOT subtract gas refunds from compute gas usage. Refunds affect final gas settlement but do not reduce the tracked compute gas consumed during execution.

#### Revert Behavior

Compute gas usage MUST NOT be reverted when a child call frame reverts. All compute gas spent by all executed call frames contributes to the transaction's total compute gas usage.

#### Enforcement Reference

If `compute_gas_used > effective_compute_gas_limit`, the transaction MUST halt. The effective limit MAY be reduced by [gas detention](/spec/megaevm/gas-detention.md).

#### Contract Creation Code Deposit

For any contract creation (`CREATE`, `CREATE2`, or a contract-creation transaction), the code-deposit compute gas (`code_length × 200`, the standard EVM per-byte code-deposit cost inherited from Ethereum) MUST be recorded atomically with the deployment commit: it is recorded when the deployment's pre-commit success conditions hold, at the same point the EVM charges the code-deposit gas and commits the created contract. A node MUST NOT additionally record this code-deposit compute gas during post-execution compute-gas accounting; double-counting it is prohibited.

### Data Size

#### Definition

A node MUST track data size as the total number of bytes of execution-related data attributable to the transaction.

#### Non-Discardable Data Size

The following contributions MUST be counted at transaction start and MUST NOT be reverted:

| Data Type                 | Size                                                |
| ------------------------- | --------------------------------------------------- |
| Base transaction data     | `BASE_TRANSACTION_DATA_SIZE`                        |
| Calldata                  | `tx.input().len()`                                  |
| Access list               | Sum of encoded entry sizes                          |
| EIP-7702 authorizations   | `AUTHORIZATION_DATA_SIZE × authorization_count`     |
| Caller account update     | `ACCOUNT_UPDATE_DATA_SIZE`                          |
| Authority account updates | `ACCOUNT_UPDATE_DATA_SIZE × authority_update_count` |

#### Discardable Data Size

The following contributions MUST be tracked within call frames and MUST be discarded if the call frame reverts:

| Data Type                        | Size                                | Trigger                                  |
| -------------------------------- | ----------------------------------- | ---------------------------------------- |
| Log topics                       | `LOG_TOPIC_DATA_SIZE × topic_count` | `LOG0`–`LOG4`                            |
| Log data                         | `log_data.len()`                    | `LOG0`–`LOG4`                            |
| SSTORE new write                 | `ACCOUNT_UPDATE_DATA_SIZE`          | `original == present && original != new` |
| SSTORE reset                     | `-ACCOUNT_UPDATE_DATA_SIZE`         | `original != present && original == new` |
| Account update (CALL with value) | `ACCOUNT_UPDATE_DATA_SIZE`          | Balance change on CALL-like operation    |
| Account update (CREATE/CREATE2)  | `ACCOUNT_UPDATE_DATA_SIZE`          | Successful account creation path         |
| Deployed bytecode                | `code.len()`                        | Successful `CREATE` or `CREATE2`         |

#### Account Update Deduplication

Within a single call frame, a node MUST count a given account update at most once for data-size tracking. If the same account is updated multiple times within the same call frame — including the caller account across multiple value-transferring sub-calls or creates — subsequent updates in that call frame MUST NOT add additional `ACCOUNT_UPDATE_DATA_SIZE` bytes.

<details>

<summary>Rex6 (unstable): EIP-7702 authority updates narrowed to applied authorizations (data size)</summary>

#### Applied-Authorization Narrowing for Data Size

Pre-Rex6, a node counts one `ACCOUNT_UPDATE_DATA_SIZE` authority account update for every authorization whose authority address is recoverable, including authorizations that are later skipped by the chain-id, nonce, or code application gates.

Under Rex6, a node MUST count the `ACCOUNT_UPDATE_DATA_SIZE` authority account update only for an *applied* authorization — one that passes all application gates and therefore writes the authority account. A node MUST NOT count a skipped authorization toward `authority_update_count`. The per-record `AUTHORIZATION_DATA_SIZE × authorization_count` contribution is unchanged and still counts every authorization in the list.

When multiple authorizations target the same authority, a node MUST evaluate them sequentially against the authority nonce and MUST count each applied authorization independently.

</details>

<details>

<summary>Rex6 (unstable): Per-log data-size base</summary>

#### Per-Log Base Cost

Pre-Rex6, a log contributes `LOG_TOPIC_DATA_SIZE × topic_count + log_data.len()` to data size; the log address is not counted, so an empty `LOG0` (no topics, no data) contributes zero even though it produces a receipt log entry.

Under Rex6, a node MUST additionally charge a fixed `LOG_BASE_DATA_SIZE` per emitted log for the log address, so each log contributes `LOG_BASE_DATA_SIZE + LOG_TOPIC_DATA_SIZE × topic_count + log_data.len()`.

</details>

<details>

<summary>Rex6 (unstable): value self-transfer counted as one account update (data size)</summary>

#### Value Self-Transfer Deduplication for Data Size

A value-transferring call whose target equals its caller touches a single account, but the per-call accounting records a caller-side and a target-side account update.

Pre-Rex6, both updates are charged, so the one account contributes `2 × ACCOUNT_UPDATE_DATA_SIZE` bytes.

Under Rex6, when a value-transferring call's target equals its caller, a node MUST count the account update once: the target-side `ACCOUNT_UPDATE_DATA_SIZE` charge MUST be suppressed, leaving the caller-side charge (or, at the top level, the transaction-start caller record) as the single charge for the account. Calls with distinct caller and target, and zero-value calls, are unchanged.

</details>

<details>

<summary>Rex6 (unstable): SELFDESTRUCT balance credit to an existing beneficiary (data size)</summary>

#### SELFDESTRUCT Existing-Beneficiary Data Size

Pre-Rex6, a `SELFDESTRUCT` whose balance credit goes to an already-existing beneficiary records no data size, because the credit does not flow through the frame-initialization or caller-deduplication paths; only a `SELFDESTRUCT` that materializes a new beneficiary is charged.

Under Rex6, a node MUST record `ACCOUNT_UPDATE_DATA_SIZE` bytes of data size for a `SELFDESTRUCT` that transfers a **non-zero** balance to an existing account **distinct** from the executing contract. A `SELFDESTRUCT` of a zero-balance contract performs no balance credit and MUST record nothing. A `SELFDESTRUCT` whose target is the executing contract itself is an [EIP-6780](https://eips.ethereum.org/EIPS/eip-6780) balance no-op and MUST record nothing.

</details>

### KV Updates

#### Definition

A node MUST track KV updates as the number of state-modifying key-value updates attributable to the transaction.

#### Non-Discardable KV Updates

The following contributions MUST be counted at transaction scope and MUST NOT be reverted:

| Operation                  | Count                 |
| -------------------------- | --------------------- |
| Transaction caller update  | `1`                   |
| EIP-7702 authority updates | `authorization_count` |

#### Discardable KV Updates

The following contributions MUST be tracked within call frames and MUST be discarded if the call frame reverts:

| Operation        | Count      | Trigger                                                                                |
| ---------------- | ---------- | -------------------------------------------------------------------------------------- |
| SSTORE new write | `+1`       | `original == present && original != new`                                               |
| SSTORE reset     | `-1`       | `original != present && original == new`                                               |
| CREATE/CREATE2   | `1` or `2` | Created account plus caller update if caller not yet counted in the current call frame |
| CALL with value  | `1` or `2` | Callee update plus caller update if caller not yet counted in the current call frame   |

#### Account Update Deduplication

Within a single call frame, a node MUST deduplicate caller account updates for KV-update tracking in the same way it does for data-size tracking. When a CALL with value or CREATE occurs, the caller's update MUST be counted only if it has not already been counted in the current call frame.

<details>

<summary>Rex6 (unstable): EIP-7702 authority updates narrowed to applied authorizations (KV updates)</summary>

#### Applied-Authorization Narrowing for KV Updates

Pre-Rex6, a node counts one authority KV update for every authorization with a recoverable authority, including authorizations that are skipped by the application gates.

Under Rex6, a node MUST count one authority KV update only for each *applied* authorization — one that passes the chain-id, nonce, and code gates and writes the authority account — mirroring the data-size narrowing above. A node MUST NOT count a skipped authorization. When multiple authorizations target the same authority, each applied authorization MUST be counted independently.

</details>

<details>

<summary>Rex6 (unstable): value self-transfer counted as one account update (KV updates)</summary>

#### Value Self-Transfer Deduplication for KV Updates

Under Rex6, when a value-transferring call's target equals its caller, a node MUST count one KV update for the account instead of two, mirroring the data-size deduplication above. Calls with distinct caller and target, and zero-value calls, are unchanged.

</details>

<details>

<summary>Rex6 (unstable): SELFDESTRUCT balance credit to an existing beneficiary (KV updates)</summary>

#### SELFDESTRUCT Existing-Beneficiary KV Update

Under Rex6, a node MUST record one KV update for a `SELFDESTRUCT` that transfers a **non-zero** balance to an existing account **distinct** from the executing contract, mirroring the data-size rule above. No state growth is recorded — the account already exists. A `SELFDESTRUCT` of a zero-balance contract, or to the executing contract itself, MUST record nothing.

</details>

### State Growth

#### Definition

A node MUST track state growth as the net increase in on-chain state caused by new accounts and new storage slots.

#### Storage Slot Growth Rules

For `SSTORE`, a node MUST apply the following state-growth accounting rules:

| Original | Present | New     | Growth |
| -------- | ------- | ------- | ------ |
| `0`      | `0`     | non-`0` | `+1`   |
| `0`      | non-`0` | `0`     | `-1`   |
| `0`      | non-`0` | non-`0` | `0`    |
| non-`0`  | any     | any     | `0`    |

The table above means:

* the first write to a slot that was empty at transaction start MUST increase state growth by `1`,
* clearing such a slot later in the same transaction MUST decrease state growth by `1`,
* rewriting a slot already counted within the transaction MUST NOT change state growth further,
* and slots that were already non-zero at transaction start MUST NOT contribute to state growth.

<details>

<summary>Rex6 (unstable): CREATE state growth only for net-new addresses</summary>

#### Conditional CREATE State Growth

Pre-Rex6, every `CREATE` / `CREATE2` frame records `+1` state growth unconditionally when the frame starts.

Under Rex6, a node MUST record the `+1` state growth for a `CREATE` / `CREATE2` only when the created address is net-new — that is, the account at the derived address is empty under the state-clear rule when the frame starts. Deploying to an address that already exists (for example, an address previously funded with a balance) MUST NOT record state growth, mirroring the existing value-transfer rule that counts only newly materialized accounts.

</details>

#### SELFDESTRUCT Refund

When a same-transaction-created contract is destroyed by `SELFDESTRUCT`, the node MUST apply a state-growth refund. See [SELFDESTRUCT — State Growth Refund](/spec/megaevm/selfdestruct.md#state-growth-refund) for the full specification.

#### Negative Intermediate Values

The state-growth counter MAY become negative during execution. The reported final state growth for limit enforcement MUST be clamped to a minimum of `0`.

<details>

<summary>Rex6 (unstable): post-execution fee-reward account writes</summary>

### Post-Execution Fee-Reward Accounting

After execution, the protocol credits transaction fees to the block beneficiary and the protocol fee vaults (the L1-fee, base-fee, and operator-fee recipients). These writes happen after the per-transaction resource trackers have been finalized, so pre-Rex6 they escape resource accounting entirely.

Under Rex6, for each **distinct** fee-recipient account whose balance the fee-reward step changes, a node MUST record one account-info write — `ACCOUNT_UPDATE_DATA_SIZE` bytes of data size and one KV update — in the transaction-persistent lane. If the write materializes a previously non-existent account (empty before the credit, non-empty after), the node MUST additionally record `+1` state growth.

A fee recipient that coincides with another (for example, a block beneficiary that is also a fee vault) MUST be counted once. This usage is recorded after the transaction's execution result is final: it feeds the transaction's reported usage and the block-level cumulative counters, and it MUST NOT retroactively change the transaction's outcome — a transaction-level limit crossed only by the fee-reward writes does not fail the transaction. Transactions that credit no fees (deposit transactions and sandboxed executions) record nothing in this step. The deposit-mint counterpart of this gap — a deposit materializing its sender — was already accounted in Rex5.

</details>

## Constants

| Constant                     | Value | Description                                                                       |
| ---------------------------- | ----- | --------------------------------------------------------------------------------- |
| `BASE_TRANSACTION_DATA_SIZE` | 110   | Fixed estimate of the RLP-encoded transaction envelope excluding calldata         |
| `AUTHORIZATION_DATA_SIZE`    | 101   | Bytes counted per EIP-7702 authorization                                          |
| `ACCOUNT_UPDATE_DATA_SIZE`   | 40    | Bytes counted for an account update or storage-write record in data-size tracking |
| `LOG_TOPIC_DATA_SIZE`        | 32    | Bytes counted per log topic in data-size tracking                                 |
| `LOG_BASE_DATA_SIZE`         | 32    | Rex6+ per-log base counted for the log address in data-size tracking              |

## Rationale

**Why make most resource dimensions call-frame-aware?** Data size, KV updates, and state growth represent effects that should match the surviving transaction outcome. If a child call frame reverts, its discarded logs, writes, and transient growth should not count toward the final resource totals.

**Why is compute gas the exception?** Compute gas measures work already performed by the node. That work cannot be undone merely because a child call frame reverted. Making compute gas non-revertible prevents implementations from undercounting resource consumption in transactions that repeatedly attempt and revert expensive subcalls.

**Why deduplicate account updates within a call frame?** Repeated writes to the same account within one call frame do not represent distinct independent account objects in state. Deduplication prevents artificial inflation of data-size and KV-update counts from repeated modifications to the same account within a single call frame.

**Why allow negative intermediate state growth?** During execution, a transaction may first create new state and later remove it. Allowing the counter to go negative during intermediate steps keeps the accounting locally composable across nested call frames, while clamping the final reported value prevents negative net state growth from being treated as a meaningful resource credit.

## Security Considerations

**If compute gas were made revertible** (scoped to call frames like data size and KV updates), an attacker could execute and revert expensive subcalls repeatedly within a single transaction, consuming negligible apparent compute gas while imposing real execution cost on nodes.

## Spec History

This page describes the current accounting behavior.

* [Rex4](/spec/network-upgrades/rex4.md) — introduced per-call-frame runtime budgets for all four resource dimensions.
* [Rex5](/spec/network-upgrades/rex5.md) — corrected caller-account update deduplication: pre-Rex5, the caller's `ACCOUNT_UPDATE_DATA_SIZE` (data size) and KV-update count were re-charged on every value-transferring sub-call or create from the same parent frame because the caller was never marked as already counted after the first charge; Rex5 marks the caller after the first charge so subsequent operations from the same parent frame do not re-count the caller account. Rex5 also records contract-creation code-deposit compute gas atomically with the deployment commit instead of during post-execution accounting.
* Rex6 (**unstable**) — narrowed the EIP-7702 authority data-size and KV-update charges from every recoverable authorization to only *applied* authorizations: pre-Rex6, the `ACCOUNT_UPDATE_DATA_SIZE` and KV update were charged for every authorization with a recoverable authority, including ones later skipped by the chain-id, nonce, or code application gates; Rex6 charges them only for authorizations that pass all gates and write the authority account.
* Rex6 (**unstable**) — corrected two `CREATE`-frame accounting errors: the creator nonce-bump account-info write is booked to the parent frame's discardable lane instead of the child's, so it survives a child-`CREATE` revert correctly; and `CREATE` records `+1` state growth only when the created address is net-new instead of unconditionally.
* Rex6 (**unstable**) — counted the account writes performed by op-revm's post-execution `reward_beneficiary` step toward resource accounting: pre-Rex6, fee-recipient writes performed after the `AdditionalLimit` trackers were finalized escaped accounting entirely; Rex6 records one account-info write (data size and KV update) per distinct fee recipient whose balance changes, plus `+1` state growth when the write materializes a previously non-existent account. The deposit-mint half was already closed in Rex5; Rex6 covers the remaining non-deposit fee-credit paths.
* Rex6 (**unstable**) — counted the account-info write of a `SELFDESTRUCT` balance credit to an already-existing beneficiary: pre-Rex6 only a `SELFDESTRUCT` that created a new beneficiary was metered, so a balance credit to an existing beneficiary (which does not flow through the frame-initialization or caller-dedup path) recorded nothing; Rex6 records data size and a KV update — no state growth, since the account already exists — for a non-zero balance credit to an existing *distinct* target, and records nothing for a zero-balance `SELFDESTRUCT` or for a `SELFDESTRUCT` to the executing contract itself (an [EIP-6780](https://eips.ethereum.org/EIPS/eip-6780) balance no-op).
* Rex6 (**unstable**) — added a per-log data-size base: pre-Rex6, an empty `LOG0` contributed zero data size because the log address was not counted; Rex6 charges `LOG_BASE_DATA_SIZE` per log for the address.
* Rex6 (**unstable**) — deduplicated the value self-transfer account-info write: when a value-transferring call's target equals its caller, the caller-side and target-side writes refer to the same account, but pre-Rex6 the data-size (`ACCOUNT_UPDATE_DATA_SIZE`) and KV-update charges were recorded for both, double-counting the one account (an over-count of block-level usage; it never under-charges). Rex6 records the `caller == target` case as a single account-info write. This extends the Rex5 caller-account deduplication above to the self-transfer case.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.megaeth.com/spec/megaevm/resource-accounting.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
