From a86a34872ce6f9f0e5b244265a7bb912f5adfa92 Mon Sep 17 00:00:00 2001 From: Matty Evans Date: Fri, 6 Feb 2026 13:04:34 +1000 Subject: [PATCH 1/2] docs: clarify gas semantics for EIP-7778 split between receipt and block gas --- pkg/ethereum/execution/block.go | 6 +++++- pkg/ethereum/execution/structlog.go | 9 +++++++++ pkg/processor/transaction/structlog_agg/aggregator.go | 7 +++++++ .../structlog_agg/transaction_processing.go | 11 +++++++++-- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/pkg/ethereum/execution/block.go b/pkg/ethereum/execution/block.go index e4e839d..5757849 100644 --- a/pkg/ethereum/execution/block.go +++ b/pkg/ethereum/execution/block.go @@ -131,6 +131,10 @@ type Receipt interface { // TxHash returns the transaction hash. TxHash() Hash - // GasUsed returns the gas used by the transaction. + // GasUsed returns the post-refund gas used by the transaction (what the user pays). + // + // EIP-7778 context: This remains post-refund. The EIP-7778 split between receipt gas + // and block gas only affects ExecutionResult at the EVM layer; the Receipt's GasUsed + // field and its derivation from CumulativeGasUsed are unchanged. GasUsed() uint64 } diff --git a/pkg/ethereum/execution/structlog.go b/pkg/ethereum/execution/structlog.go index 5b0c856..9d00e69 100644 --- a/pkg/ethereum/execution/structlog.go +++ b/pkg/ethereum/execution/structlog.go @@ -1,6 +1,15 @@ package execution +// TraceTransaction holds the result of a debug_traceTransaction call. type TraceTransaction struct { + // Gas is the post-refund gas used by this transaction (what the user pays). + // Set by the data source from the execution result or receipt's GasUsed. + // + // EIP-7778 context: After EIP-7778, Ethereum splits gas accounting into: + // - ReceiptGasUsed (post-refund): what the user pays, stored in receipts + // - BlockGasUsed (pre-refund): used for block gas limit accounting + // This field carries the receipt (post-refund) value. The computeIntrinsicGas() + // formula in structlog_agg depends on this being post-refund. Gas uint64 `json:"gas"` Failed bool `json:"failed"` ReturnValue *string `json:"returnValue"` diff --git a/pkg/processor/transaction/structlog_agg/aggregator.go b/pkg/processor/transaction/structlog_agg/aggregator.go index d50ff1f..8e3c7d6 100644 --- a/pkg/processor/transaction/structlog_agg/aggregator.go +++ b/pkg/processor/transaction/structlog_agg/aggregator.go @@ -356,6 +356,13 @@ func mapOpcodeToCallType(op string) string { // computeIntrinsicGas computes the intrinsic gas for a transaction. // This is the gas consumed before EVM execution begins (21000 base + calldata costs). // +// The receiptGas parameter MUST be the post-refund value (what the user pays). +// This is the value from Receipt.GasUsed / ExecutionResult.ReceiptGasUsed. +// +// EIP-7778 context: This formula remains correct after EIP-7778. The EIP splits +// ExecutionResult into ReceiptGasUsed (post-refund) and BlockGasUsed (pre-refund), +// but the receipt gas semantics that this formula depends on are unchanged. +// // Formula from int_transaction_call_frame.sql: // // IF gas_refund >= receipt_gas / 4 THEN diff --git a/pkg/processor/transaction/structlog_agg/transaction_processing.go b/pkg/processor/transaction/structlog_agg/transaction_processing.go index 34a3020..f6f96d2 100644 --- a/pkg/processor/transaction/structlog_agg/transaction_processing.go +++ b/pkg/processor/transaction/structlog_agg/transaction_processing.go @@ -70,6 +70,7 @@ func (p *Processor) ProcessTransaction(ctx context.Context, block execution.Bloc // For simple transfers (no EVM execution), all gas is intrinsic. // This is true for both successful and failed transactions. + // trace.Gas is the post-refund receipt gas — see TraceTransaction.Gas doc. intrinsicGas := trace.Gas rootFrame.IntrinsicGas = &intrinsicGas @@ -158,8 +159,14 @@ func (p *Processor) ProcessTransaction(ctx context.Context, block execution.Bloc aggregator.SetRootTargetAddress(&addr) } - // Get receipt gas for intrinsic gas calculation - // For now, we use trace.Gas as a proxy (TODO: get actual receipt gas from block receipts) + // Get receipt gas for intrinsic gas calculation. + // trace.Gas is the post-refund receipt gas set by the data source (erigone/xatu sets + // this from ExecutionResult.ReceiptGasUsed; RPC mode gets it from receipt.GasUsed). + // + // EIP-7778 context: computeIntrinsicGas() requires the post-refund value because its + // formula accounts for refunds: intrinsic = receiptGas - gasCumulative + gasRefund. + // This is correct both pre- and post-EIP-7778 since ReceiptGasUsed preserves the + // same post-refund semantics that GasUsed had before the split. receiptGas := trace.Gas // Finalize aggregation and get call frame rows From 55926964be588210f84fdbfc5540c887e0282b24 Mon Sep 17 00:00:00 2001 From: Matty Evans Date: Fri, 6 Feb 2026 13:07:23 +1000 Subject: [PATCH 2/2] docs(aggregator.go): update comment to clarify intrinsic gas calculation does not encode EIP-3529 refund cap --- pkg/processor/transaction/structlog_agg/aggregator.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pkg/processor/transaction/structlog_agg/aggregator.go b/pkg/processor/transaction/structlog_agg/aggregator.go index 8e3c7d6..fd3fb11 100644 --- a/pkg/processor/transaction/structlog_agg/aggregator.go +++ b/pkg/processor/transaction/structlog_agg/aggregator.go @@ -363,12 +363,10 @@ func mapOpcodeToCallType(op string) string { // ExecutionResult into ReceiptGasUsed (post-refund) and BlockGasUsed (pre-refund), // but the receipt gas semantics that this formula depends on are unchanged. // -// Formula from int_transaction_call_frame.sql: -// -// IF gas_refund >= receipt_gas / 4 THEN -// intrinsic = receipt_gas * 5 / 4 - gas_cumulative (refund was capped) -// ELSE -// intrinsic = receipt_gas - gas_cumulative + gas_refund (uncapped) +// The computed intrinsic gas value does not encode whether the EVM refund cap +// (EIP-3529: max refund = gasUsed/5) was applied. It is up to the consumer of +// this data to determine whether the cap was hit, using the gas_refund and +// gas_used columns. func computeIntrinsicGas(gasCumulative, gasRefund, receiptGas uint64) uint64 { if receiptGas == 0 { return 0