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..fd3fb11 100644 --- a/pkg/processor/transaction/structlog_agg/aggregator.go +++ b/pkg/processor/transaction/structlog_agg/aggregator.go @@ -356,12 +356,17 @@ 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). // -// Formula from int_transaction_call_frame.sql: +// The receiptGas parameter MUST be the post-refund value (what the user pays). +// This is the value from Receipt.GasUsed / ExecutionResult.ReceiptGasUsed. // -// 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) +// 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. +// +// 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 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