This proposal introduces a new opcode that allows contracts to inspect the transaction outcomes on-chain. This opcode will allow contract developers to define assertions for state changes that can be enforced on-chain. These can protect Ethereum users by restricting the behavior of the smart contracts they are interacting with.
Motivation
The total value of crypto assets that have been stolen to date exceeds the yearly GDP of a medium-sized nation. This level of loss and waste is indefensible and has a long list of negative consequences for everyone around the world.
The ability of an average user or a Wallet application to find, collect, review, and analyze the EVM code the transaction will execute is very limited.
This leaves the users with no mechanism to enforce any restrictions on what the transaction actually does once it is signed. This leads users to perform de-facto blind signing every time they interact with Ethereum, exposing themselves to significant risks.
By providing the Wallets and dApps with the ability to observe and restrict the possible outcomes of a transaction, we create a tool that users can apply to reduce their risk levels.
Specification
Constants
Name
Value
TXTRACE_GAS_COST
TBD
EVENTDATACOPY_GAS_COST
TBD
Transaction Trace Opcode
We introduce a new TXTRACE opcode.
It can be used to retrieve the full state diff of the current transaction up to this point.
It accepts a (param, index) inputs similar to the FRAMEPARAM opcode from EIP-8141.
The available parameters are listed in the table below.
param
in2
Return value
0x00
must be 0
balances_changed - the total number of changed balances
0x01
must be 0
slots_changed - the total number of changes storage slots
0x02
must be 0
contracts_deployed - the total number of newly deployed contracts
0x03
index in balances_changed
change_address - the address of the account with balance change
0x04
index in balances_changed
balance_before - the balance of the address at the start of the transaction
0x05
index in balances_changed
balance_after - the balance of the address as of this TXTRACE call
0x06
index in slots_changed
change_address - the address of the account with storage change
0x07
index in slots_changed
slot_key - the storage slot key that was changed
0x08
index in slots_changed
slot_value_before - the value of the slot at the start of the transaction
0x09
index in slots_changed
slot_value_after - the value of the slot as of this TXTRACE call
0x0A
index in contracts_deployed
deployed_address - the address of the newly deployed contract
0x0B
index in contracts_deployed
codehash_after - the codehash of the newly deployed contract
0x0C
must be 0
events_count - the total number of emitted events
0x0D
index in events_count
events_address - the address of the contract that emitted the event
0x0E
index in events_count
event_topic_count - the number of topics of the event (0–4)
0x0F
index in events_count
event_topic0 - the first topic of the event; exceptional halt if no topic
0x10
index in events_count
event_topic1 - the second topic of the event
0x11
index in events_count
event_topic2 - the third topic of the event
0x12
index in events_count
event_topic3 - the fourth topic of the event
0x13
index in events_count
event_data_len - the byte length of the event’s non-indexed data
0x14
must be 0
gas_pre_charge - the total amount deducted from the gas payer
0x15
must be 0
gas_payer_address - the address charged the gas pre-charge
For transactions with blobs attached, the gas_pre_charge parameter includes the blob fees as gas_pre_charge = gas_limit × gas_price + blob_count × GAS_PER_BLOB × blob_base_fee.
The gas_payer_address is normally a tx.origin but may be dependent on a future transaction type details.
For EIP-8141 transactions, the target of the APPROVE_PAYMENT frame may replace the transaction sender as the gas payer.
State Difference Semantics
The before values reflect the transaction prestate values recorded before the start of entire transaction’s execution, before any state writes made in relation to this transaction. The after values reflect the current state as of the TXTRACE opcode call. Intermediary writes between transaction start and the TXTRACE call are not observable separately.
An address will appear in balances_changed when its balance at the time of the TXTRACE call differs from its balance at transaction start. This includes the gas fee pre-charge applied to the gas payer address. Callers computing the net ETH transferred to or from an address can look up the gas payer via gas_payer_address (param 0x15) and subtract gas_pre_charge (param 0x14) from that address’s balance delta.
Results Ordering
Balance and storage slot changes returned by the TXTRACE opcode are enumerated in ascending order sorted by the affected address as a numerical uint160 value.
Storage changes within a single address are sorted by the storage slot key as a numerical uint256 value.
Events are enumerated in the order they were emitted during transaction execution, matching their global log index within the transaction.
EVENTDATACOPY opcode
This opcode copies event data into memory. The gas cost matches CALLDATACOPY, i.e. the operation has a fixed cost of 3 and a variable cost that accounts for the memory expansion and copying.
Stack
Stack
Value
top - 0
event_index
top - 1
memOffset
top - 2
dataOffset
top - 3
length
No stack output value is produced.
Behavior
The operation semantics match CALLDATACOPY, copying length bytes from the event’s non-indexed data, starting at the given byte dataOffset, into a memory region starting at memOffset.
If event_index >= events_count, an exceptional halt occurs.
If dataOffset + length exceeds the event’s data length, an exceptional halt occurs.
Rationale
Selection Parameter Design
The TXTRACE opcode follows the same (param, index) two-argument pattern used by FRAMEPARAM in EIP-8141. This keeps the interface consistent and avoids introducing a separate opcode for every piece of trace information.
Enumeration instead of lookup
The TXTRACE opcode exposes transaction outcomes through index-based access over the full set of observable state changes, but it does not provide a mechanism to look up a balance or storage change for a specific address or slot key.
Doing so would require introducing a separate “state diff lookup opcode”, which would spare contracts from performing a linear search over enumerated results — a common operation in post-transaction assertions.
A lookup opcode is deliberately excluded from this proposal for the following reasons.
Redundant querying mechanism
While the TXTRACE opcode is sufficient for contracts to gain access to any storage change within a transaction, a lookup mechanism has no way to enumerate all changes and would still rely on TXTRACE to list all observed state diffs when expressing any “negative conditions” (i.e. “only these changes happened”). Relying solely on TXTRACE for both lookup and enumeration is feasible and avoids introducing a second, partially overlapping querying mechanism.
Implementation overhead
A direct-access storage opcode would require EVM implementations to expose a lookup interface from an (address, slot) key tuple to the storage diff entry.
A topic-based lookup opcode would require clients to maintain a per-topic index over the transaction log and expose it within the EVM.
These introduce extra complexity to client implementations of this proposal and represent non-trivial operations.
TXTRACE’s enumeration model is simpler; it relies only on the ordered state-diff data that every EVM client already tracks during execution.
High-level language syntax support
For the overwhelming majority of assertion use cases, a compiler can provide a high-level abstraction that internally searches the enumerated TXTRACE results with minimal overhead.
Even in the worst case, the gas cost of a compiler-generated linear search over TXTRACE results is negligible relative to the gas cost of any state-changing operation.
Typical transaction assertion costs are negligible
Balance and storage results are returned in sorted order, so a single entry lookup by address or slot key can use binary search.
Events are in emission order and require a linear scan.
At the 16,777,216 gas transaction limit specified by EIP-7825, the entry counts and corresponding scan costs, assuming TXTRACE_GAS_COST = 100 gas comparable to warm memory access, are the following:
Category
Typical transaction entries
Max possible entries (estimated)
Max possible lookup cost for single entry
Typical tx lookup cost for single entry
Max cost of a full iteration of state diff by assertion script
Typical tx cost
Balance changes
~15
1,380
log₂(1,380) × 100 = 1,100
log₂(15) × 100 = 400
1,380 × 100 = 138,000
15 × 100 = 1,500
Storage slots
~100
3,200
log₂(3,200) × 100 = 1,200
log₂(100) × 100 = 700
3,200 × 100 = 320,000
100 × 100 = 10,000
Events
~40
42,600
42,600 × 100 = 4,260,000
40 × 100 = 4,000
42,600 × 100 = 4,260,000
40 × 100 = 4,000
Total
15,500
Most assertion scripts are expected to enumerate the full set of allowed state changes and will not require a binary search.
Binary search is relevant only for sparse, targeted checks against a small subset of entries.
The values given for a “typical transaction” attempt to reflect state changes we might expect from some complex DeFi transaction.
Use with Frame Transactions
When used within an EIP-8141 frame transaction, placing the assertion logic in the last frame ensures the diff is final and the assertion can reason about the full transaction outcome.
Per-contract Usage
Individual contracts can use the TXTRACE opcode to inspect the state changes made internally, using a pattern similar to “reentrancy guard” modifier for their external functions.
Individual Topic Access
EVM events carry 0–4 topics, each a 32-byte word. Topic 0 is conventionally the event signature hash; topics 1–3 carry indexed parameters. Assertion contracts that verify which specific token was transferred, which address was approved, or which identifier was involved need to inspect these indexed values directly.
Accessing a topic slot at or beyond event_topic_count causes an exceptional halt, consistent with out-of-bounds behavior for all other indexed params.
EVENTDATACOPY as a Companion Opcode
Event non-indexed data is variable-length and cannot be returned as a single 32-byte stack word. A memory-copy opcode with the same semantics as CALLDATACOPY is the idiomatic EVM approach for variable-length data access.
Gas Pre-Charge Parameter
The gas pre-charge (gas_limit × gas_price) is deducted at transaction start and appears in the gas payer’s balance_after, making it hard to isolate actual ETH transfers. The pre-charge is also provisional: a refund for unused gas is issued after execution, so the bundled figure is not the final cost.
Exposing gas_pre_charge directly lets callers subtract it with a single opcode call. It covers all gas-related deductions including the blob fee for EIP-4844 transactions, so the same subtraction isolates pure ETH transfers regardless of transaction type. gas_payer_address completes the picture: in EIP-8141 transactions the gas payer may be a separate paymaster rather than the sender, and no existing opcode exposes that address. Together the two parameters let assertion contracts identify the right balances_changed entry and apply the subtraction uniformly across all transaction types.
Deterministic Enumeration
State changes use address-sorted order because the state diff model collapses all intermediate writes into a single entry per (address, slot). Sequence of execution does not define a deterministic order for the collapsed state diff, as the same slot may be written multiple times across interleaved reentrant calls, yet produce exactly one entry. Sorting by address and slot key ensures a canonical, deterministic enumeration independent of execution flow.
Events can use emission order because each event is a distinct, non-collapsed entity with a canonical position corresponding to its log index. Assertion contracts that verify cross-contract event sequencing require this ordering.
Backwards Compatibility
TXTRACE and EVENTDATACOPY occupy previously unused opcode slots. No changes are made to existing opcodes, transaction types, or precompiles, so existing contracts and tooling are unaffected.
Security Considerations
Insufficiently Restrictive Assertions
The main risk is a false sense of security: an assertion contract that checks too little may mislead users into believing a transaction is safe when it is not.
Wallets and dApps that build on TXTRACE must ensure their assertion logic covers all relevant state changes for the protected operation. It is critical that the ecosystem treats incomplete assertions as no better than no assertion at all.
Assertion Gas Exhaustion
Assertion contracts that enumerate TXTRACE results may run out of gas.
As stated previously, a transaction can produce up to ~42,600 events in a transaction in the current Ethereum configuration.
Asserting over them will require a significant amount of gas in the worst-case.
Assertion contracts should defend against assertion gas related issues by reading the total entry counts and ensuring these are below a safe limit.
The framework layer calling the assertion must forward a gas stipend proportional to the entry counts it expects to process.
An assertion that runs out of gas before completing its enumeration loop has not verified the full outcome.
Any framework built on TXTRACE must ensure that assertion OOG is treated as an explicit assertion revert.