From 9c5462a161b10b9415ce9a9f11d40b8e887a104b Mon Sep 17 00:00:00 2001 From: William Morriss Date: Fri, 2 Oct 2026 20:34:09 -0500 Subject: [PATCH] fix(gas): apply EIP-7623 calldata floor to transactions Assisted-by: Claude:claude-opus-5-5 cast uncrustify --- include/gas.h | 1 + src/evm.c | 30 ++++++++++++++++++++++-------- tst/evm.c | 4 ++-- tst/extcodehash.json | 7 +++++++ tst/quine.json | 2 +- 5 files changed, 33 insertions(+), 11 deletions(-) diff --git a/include/gas.h b/include/gas.h index 5cb5a1c..8bf0efd 100644 --- a/include/gas.h +++ b/include/gas.h @@ -31,6 +31,7 @@ #define G_CALLDATAZERO 4 #define G_CALLDATA 16 #define G_CALLDATANONZERO 12 +#define G_TXDATA_FLOOR 10 #define G_LOG 375 #define G_LOGDATA 8 #define G_LOGTOPIC 375 diff --git a/src/evm.c b/src/evm.c index f8bbf82..b3b47cf 100644 --- a/src/evm.c +++ b/src/evm.c @@ -341,6 +341,18 @@ static inline uint64_t calldataGas(const data_t *calldata) { return gas; } +// EIP-7623: a transaction's gasUsed is at least this +// calldata tokens count zero bytes once and nonzero bytes four times, so calldataGas is G_CALLDATAZERO per token +static inline uint64_t floorGas(const data_t *calldata) { + return G_TX + calldataGas(calldata) / G_CALLDATAZERO * G_TXDATA_FLOOR; +} + +static inline void applyFloor(result_t *result, uint64_t gas, uint64_t minGasUsed) { + if (gas - result->gasRemaining < minGasUsed) { + result->gasRemaining = gas - minGasUsed; + } +} + // In the yellow paper, this is the R function. static inline uint64_t initcodeGas(const data_t *initcode) { return G_INITCODEWORD * ((initcode->size + 31) >> 5); // EIP 3860: 2 gas per word @@ -2486,12 +2498,11 @@ static result_t _evmConstruct(account_t *fromAccount, account_t *to, uint64_t ga context_t *callContext = callstack.next; callContext->gas = gas; if (callstack.next == callstack.bottom) { - callContext->gas -= G_TXCREATE + G_TX; - callContext->gas -= calldataGas(&input); - callContext->gas -= initcodeGas(&input); - if (gas < callContext->gas) { - // underflow indicates insufficient initial gas - fprintf(stderr, "Out of gas while initializing initcode (have %" PRIu64 " need %" PRIu64 ")\n", gas, gas - callContext->gas); + uint64_t intrinsicGas = G_TXCREATE + G_TX + calldataGas(&input) + initcodeGas(&input); + uint64_t minGasUsed = floorGas(&input); + callContext->gas -= intrinsicGas; + if (gas < intrinsicGas || gas < minGasUsed) { + fprintf(stderr, "Out of gas while initializing initcode (have %" PRIu64 " need %" PRIu64 ")\n", gas, intrinsicGas > minGasUsed ? intrinsicGas : minGasUsed); evmRevert(&callContext->stateChanges); result_t result; result.gasRemaining = 0; @@ -2570,6 +2581,7 @@ static result_t _evmConstruct(account_t *fromAccount, account_t *to, uint64_t ga } result.gasRemaining += refund; refundCounter = 0; + applyFloor(&result, gas, floorGas(&input)); } return result; @@ -2602,8 +2614,9 @@ result_t txCall(address_t from, uint64_t gas, address_t to, val_t value, data_t } accessList = accessList->prev; } - if (gas < intrinsicGas) { - fprintf(stderr, "Insufficient intrinsic gas %" PRIu64 " (need %" PRIu64 ")\n", gas, intrinsicGas); + uint64_t minGasUsed = floorGas(&input); + if (gas < intrinsicGas || gas < minGasUsed) { + fprintf(stderr, "Insufficient intrinsic gas %" PRIu64 " (need %" PRIu64 ")\n", gas, intrinsicGas > minGasUsed ? intrinsicGas : minGasUsed); result_t result; result.gasRemaining = 0; clear256(&result.status); @@ -2629,6 +2642,7 @@ result_t txCall(address_t from, uint64_t gas, address_t to, val_t value, data_t } result.gasRemaining += refund; refundCounter = 0; + applyFloor(&result, originalGas, minGasUsed); evmIteration++; blockEnd(); diff --git a/tst/evm.c b/tst/evm.c index 34131f8..fcdd920 100644 --- a/tst/evm.c +++ b/tst/evm.c @@ -1127,7 +1127,7 @@ void test_callEmpty() { }; address_t from = AddressFromHex42("0x4a6f6B9fF1fc974096f9063a45Fd12bD5B928AD1"); address_t to = AddressFromHex42("0x4a6f6B9fF1fc974096f9063a45Fd12bD5B928AD1"); - uint64_t gas = 21432; + uint64_t gas = 22080; val_t value; value[0] = 0; value[1] = 0; @@ -1330,7 +1330,7 @@ void test_extcodecopy() { } assert(examineFirstAccount.gasRemaining == 0); - gas = 21617; + gas = 21920; input.content = locations[3].address - 12; result_t examineSecondAccount = txCall(from, gas, to, value, input, NULL); assert(UPPER(UPPER(examineSecondAccount.status)) == 0); diff --git a/tst/extcodehash.json b/tst/extcodehash.json index 5f92ea2..4c7aa80 100644 --- a/tst/extcodehash.json +++ b/tst/extcodehash.json @@ -33,6 +33,13 @@ "input": "0x0000000000000000000000000000000000000000000000000000000000000001", "gasUsed": "0x53eb", "output": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47042745b147133dc2dae253631d9081ba4c329248e74d1b9f10e38efe1385af484" + }, + { + "name": "tx.origin, calldata floor (sepolia 0xe3d091e1cbcdb866699e9fe5bd0d38223e75238f2ff1434477126dd5013c3aff)", + "from": "0x3b559cf662fdfe485ec216c036d4cee91fbd1d39", + "input": "0x0000000000000000000000003b559cf662fdfe485ec216c036d4cee91fbd1d39", + "gasUsed": "0x55a0", + "output": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47042745b147133dc2dae253631d9081ba4c329248e74d1b9f10e38efe1385af484" } ] } diff --git a/tst/quine.json b/tst/quine.json index 69f31a1..79111f8 100644 --- a/tst/quine.json +++ b/tst/quine.json @@ -10,7 +10,7 @@ }, { "name": "ignores calldata", - "gasUsed": "0x525b", + "gasUsed": "0x52a8", "input": "0xdeadbeef", "output": "0x383d3d39383df3" }