diff --git a/chain/params.go b/chain/params.go index ea6d9a502..676d1aa62 100644 --- a/chain/params.go +++ b/chain/params.go @@ -84,6 +84,8 @@ const ( Petersburg = "petersburg" Istanbul = "istanbul" London = "london" + Paris = "paris" + Shanghai = "shanghai" EIP150 = "EIP150" EIP158 = "EIP158" EIP155 = "EIP155" @@ -123,6 +125,8 @@ func (f *Forks) At(block uint64) ForksInTime { Petersburg: f.IsActive(Petersburg, block), Istanbul: f.IsActive(Istanbul, block), London: f.IsActive(London, block), + Paris: f.IsActive(Paris, block), + Shanghai: f.IsActive(Shanghai, block), EIP150: f.IsActive(EIP150, block), EIP158: f.IsActive(EIP158, block), EIP155: f.IsActive(EIP155, block), @@ -177,6 +181,8 @@ type ForksInTime struct { Petersburg, Istanbul, London, + Paris, + Shanghai, EIP150, EIP158, EIP155, @@ -197,6 +203,8 @@ var AllForksEnabled = &Forks{ Petersburg: NewFork(0), Istanbul: NewFork(0), London: NewFork(0), + Paris: NewFork(0), + Shanghai: NewFork(0), QuorumCalcAlignment: NewFork(0), TxHashWithType: NewFork(0), LondonFix: NewFork(0), diff --git a/jsonrpc/eth_endpoint.go b/jsonrpc/eth_endpoint.go index 547e27eca..675b7e487 100644 --- a/jsonrpc/eth_endpoint.go +++ b/jsonrpc/eth_endpoint.go @@ -531,7 +531,7 @@ func (e *Eth) EstimateGas(arg *txnArgs, rawNum *BlockNumber) (interface{}, error if transaction.IsValueTransfer() { // if it is a simple value transfer or a contract creation, // we already know what is the transaction gas cost, no need to apply transaction - gasCost, err := state.TransactionGasCost(transaction, forksInTime.Homestead, forksInTime.Istanbul) + gasCost, err := state.TransactionGasCost(transaction, forksInTime.Homestead, forksInTime.Istanbul, forksInTime.Shanghai) if err != nil { return nil, err } diff --git a/state/executor.go b/state/executor.go index 962993227..5e4f67fed 100644 --- a/state/executor.go +++ b/state/executor.go @@ -24,6 +24,10 @@ const ( SpuriousDragonMaxCodeSize = 24576 TxPoolMaxInitCodeSize = 2 * SpuriousDragonMaxCodeSize + // EIP-3860: Limit and meter initcode + EIP3860MaxInitCodeSize = 49152 // 48KB limit for initcode + EIP3860InitCodeWordGas uint64 = 2 // 2 gas per 32-byte word for initcode + TxGas uint64 = 21000 // Per transaction not creating a contract TxGasContractCreation uint64 = 53000 // Per transaction that creates a contract ) @@ -584,7 +588,7 @@ func (t *Transition) apply(msg *types.Transaction) (*runtime.ExecutionResult, er } // 4. there is no overflow when calculating intrinsic gas - intrinsicGasCost, err := TransactionGasCost(msg, t.config.Homestead, t.config.Istanbul) + intrinsicGasCost, err := TransactionGasCost(msg, t.config.Homestead, t.config.Istanbul, t.config.Shanghai) if err != nil { return nil, NewTransitionApplicationError(err, false) } @@ -1009,6 +1013,19 @@ func (t *Transition) GetBlockHash(number int64) (res types.Hash) { return t.getHash(uint64(number)) //nolint:gosec } +func (t *Transition) GetPrevBlockHash() types.Hash { + // Get the previous block's hash (current block number - 1) + prevBlockNumber := t.ctx.Number - 1 + + // Safety check: ensure we don't have negative block numbers + if prevBlockNumber < 0 { + // Return zero hash for genesis block (block 0) + return types.ZeroHash + } + + return t.getHash(uint64(prevBlockNumber)) +} + func (t *Transition) EmitLog(addr types.Address, topics []types.Hash, data []byte) { t.state.EmitLog(addr, topics, data) } @@ -1112,7 +1129,7 @@ func (t *Transition) GetRefund() uint64 { return t.state.GetRefund() } -func TransactionGasCost(msg *types.Transaction, isHomestead, isIstanbul bool) (uint64, error) { +func TransactionGasCost(msg *types.Transaction, isHomestead, isIstanbul, isShanghai bool) (uint64, error) { cost := uint64(0) // Contract creation is only paid on the homestead fork @@ -1124,32 +1141,51 @@ func TransactionGasCost(msg *types.Transaction, isHomestead, isIstanbul bool) (u payload := msg.Input if len(payload) > 0 { - zeros := uint64(0) + // EIP-3860: Limit and meter initcode for Shanghai fork + if msg.IsContractCreation() && isShanghai { + // Check initcode size limit + if len(payload) > EIP3860MaxInitCodeSize { + return 0, runtime.ErrMaxCodeSizeExceeded + } + + // Calculate gas cost for initcode: 2 gas per 32-byte word + wordCount := (uint64(len(payload)) + 31) / 32 + initcodeGasCost := wordCount * EIP3860InitCodeWordGas - for i := 0; i < len(payload); i++ { - if payload[i] == 0 { - zeros++ + if (math.MaxUint64-cost)/EIP3860InitCodeWordGas < wordCount { + return 0, ErrIntrinsicGasOverflow } - } - nonZeros := uint64(len(payload)) - zeros - nonZeroCost := uint64(68) + cost += initcodeGasCost + } else { + // Pre-Shanghai gas calculation for non-initcode data + zeros := uint64(0) - if isIstanbul { - nonZeroCost = 16 - } + for i := 0; i < len(payload); i++ { + if payload[i] == 0 { + zeros++ + } + } - if (math.MaxUint64-cost)/nonZeroCost < nonZeros { - return 0, ErrIntrinsicGasOverflow - } + nonZeros := uint64(len(payload)) - zeros + nonZeroCost := uint64(68) - cost += nonZeros * nonZeroCost + if isIstanbul { + nonZeroCost = 16 + } - if (math.MaxUint64-cost)/4 < zeros { - return 0, ErrIntrinsicGasOverflow - } + if (math.MaxUint64-cost)/nonZeroCost < nonZeros { + return 0, ErrIntrinsicGasOverflow + } + + cost += nonZeros * nonZeroCost - cost += zeros * 4 + if (math.MaxUint64-cost)/4 < zeros { + return 0, ErrIntrinsicGasOverflow + } + + cost += zeros * 4 + } } return cost, nil diff --git a/state/executor_eip3860_test.go b/state/executor_eip3860_test.go new file mode 100644 index 000000000..92e1dfc38 --- /dev/null +++ b/state/executor_eip3860_test.go @@ -0,0 +1,418 @@ +package state + +import ( + "testing" + + "github.com/0xPolygon/polygon-edge/types" + "github.com/stretchr/testify/assert" +) + +// TestEIP3860_InitcodeSizeLimits tests the initcode size limits as per EIP-3860 +func TestEIP3860_InitcodeSizeLimits(t *testing.T) { + tests := []struct { + name string + initcodeSize int + isShanghai bool + expectedResult bool // true = should succeed, false = should fail + description string + }{ + // EIP-3860: Maximum initcode size is 49152 bytes (48KB) + { + name: "EIP3860_AtLimit_49152Bytes", + initcodeSize: 49152, + isShanghai: true, + expectedResult: true, + description: "Initcode exactly at 48KB limit should succeed", + }, + { + name: "EIP3860_OverLimit_49153Bytes", + initcodeSize: 49153, + isShanghai: true, + expectedResult: false, + description: "Initcode over 48KB limit should fail", + }, + { + name: "EIP3860_UnderLimit_49151Bytes", + initcodeSize: 49151, + isShanghai: true, + expectedResult: true, + description: "Initcode just under 48KB limit should succeed", + }, + { + name: "EIP3860_ZeroBytes", + initcodeSize: 0, + isShanghai: true, + expectedResult: true, + description: "Zero byte initcode should succeed", + }, + { + name: "EIP3860_OneByte", + initcodeSize: 1, + isShanghai: true, + expectedResult: true, + description: "One byte initcode should succeed", + }, + { + name: "EIP3860_32Bytes", + initcodeSize: 32, + isShanghai: true, + expectedResult: true, + description: "32 bytes initcode should succeed", + }, + { + name: "EIP3860_33Bytes", + initcodeSize: 33, + isShanghai: true, + expectedResult: true, + description: "33 bytes initcode should succeed", + }, + { + name: "EIP3860_1000Bytes", + initcodeSize: 1000, + isShanghai: true, + expectedResult: true, + description: "1000 bytes initcode should succeed", + }, + { + name: "EIP3860_10000Bytes", + initcodeSize: 10000, + isShanghai: true, + expectedResult: true, + description: "10000 bytes initcode should succeed", + }, + { + name: "EIP3860_40000Bytes", + initcodeSize: 40000, + isShanghai: true, + expectedResult: true, + description: "40000 bytes initcode should succeed", + }, + { + name: "EIP3860_49151Bytes", + initcodeSize: 49151, + isShanghai: true, + expectedResult: true, + description: "49151 bytes initcode should succeed", + }, + { + name: "EIP3860_49152Bytes", + initcodeSize: 49152, + isShanghai: true, + expectedResult: true, + description: "49152 bytes initcode should succeed", + }, + { + name: "EIP3860_49153Bytes", + initcodeSize: 49153, + isShanghai: true, + expectedResult: false, + description: "49153 bytes initcode should fail", + }, + { + name: "EIP3860_50000Bytes", + initcodeSize: 50000, + isShanghai: true, + expectedResult: false, + description: "50000 bytes initcode should fail", + }, + { + name: "EIP3860_60000Bytes", + initcodeSize: 60000, + isShanghai: true, + expectedResult: false, + description: "60000 bytes initcode should fail", + }, + // Pre-Shanghai behavior (should not have size limits) + { + name: "PreShanghai_OverLimit_60000Bytes", + initcodeSize: 60000, + isShanghai: false, + expectedResult: true, + description: "Pre-Shanghai: large initcode should succeed", + }, + { + name: "PreShanghai_AtLimit_49152Bytes", + initcodeSize: 49152, + isShanghai: false, + expectedResult: true, + description: "Pre-Shanghai: 48KB initcode should succeed", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create transaction with specified initcode size + tx := &types.Transaction{ + Input: make([]byte, tt.initcodeSize), + To: nil, // Contract creation + } + + // Calculate gas cost + gasCost, err := TransactionGasCost(tx, true, true, tt.isShanghai) + + if tt.expectedResult { + assert.NoError(t, err, tt.description) + assert.Greater(t, gasCost, uint64(0), "Gas cost should be positive") + t.Logf("✅ %s: Gas Cost = %d", tt.description, gasCost) + } else { + assert.Error(t, err, tt.description) + assert.Equal(t, uint64(0), gasCost, "Gas cost should be 0 when transaction fails") + t.Logf("❌ %s: Expected failure, got error: %v", tt.description, err) + } + }) + } +} + +// TestEIP3860_GasMetering tests the gas metering for initcode as per EIP-3860 +func TestEIP3860_GasMetering(t *testing.T) { + tests := []struct { + name string + initcodeSize int + isShanghai bool + expectedGas uint64 + description string + }{ + // EIP-3860: 2 gas per 32-byte word for initcode + { + name: "EIP3860_ZeroBytes", + initcodeSize: 0, + isShanghai: true, + expectedGas: TxGasContractCreation, // Only base cost + description: "Zero bytes initcode should only have base gas cost", + }, + { + name: "EIP3860_OneByte", + initcodeSize: 1, + isShanghai: true, + expectedGas: TxGasContractCreation + 2, // Base + 1 word * 2 gas + description: "One byte initcode should have base cost + 2 gas", + }, + { + name: "EIP3860_32Bytes", + initcodeSize: 32, + isShanghai: true, + expectedGas: TxGasContractCreation + 2, // Base + 1 word * 2 gas + description: "32 bytes initcode should have base cost + 2 gas", + }, + { + name: "EIP3860_33Bytes", + initcodeSize: 33, + isShanghai: true, + expectedGas: TxGasContractCreation + 4, // Base + 2 words * 2 gas + description: "33 bytes initcode should have base cost + 4 gas", + }, + { + name: "EIP3860_64Bytes", + initcodeSize: 64, + isShanghai: true, + expectedGas: TxGasContractCreation + 4, // Base + 2 words * 2 gas + description: "64 bytes initcode should have base cost + 4 gas", + }, + { + name: "EIP3860_1000Bytes", + initcodeSize: 1000, + isShanghai: true, + expectedGas: TxGasContractCreation + 64, // Base + 32 words * 2 gas + description: "1000 bytes initcode should have base cost + 64 gas", + }, + { + name: "EIP3860_49152Bytes", + initcodeSize: 49152, + isShanghai: true, + expectedGas: TxGasContractCreation + 3072, // Base + 1536 words * 2 gas + description: "49152 bytes initcode should have base cost + 3072 gas", + }, + // Pre-Shanghai gas calculation (should use old method) + { + name: "PreShanghai_1000Bytes", + initcodeSize: 1000, + isShanghai: false, + expectedGas: TxGasContractCreation + 4000, // Base + 1000 * 4 gas (all zero bytes) + description: "Pre-Shanghai: 1000 bytes should use old gas calculation", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create transaction with specified initcode size + tx := &types.Transaction{ + Input: make([]byte, tt.initcodeSize), + To: nil, // Contract creation + } + + // Calculate gas cost + gasCost, err := TransactionGasCost(tx, true, true, tt.isShanghai) + + assert.NoError(t, err, tt.description) + assert.Equal(t, tt.expectedGas, gasCost, tt.description) + t.Logf("✅ %s: Expected = %d, Actual = %d", tt.description, tt.expectedGas, gasCost) + }) + } +} + +// TestEIP3860_RegularTransactions tests that regular transactions are not affected by EIP-3860 +func TestEIP3860_RegularTransactions(t *testing.T) { + tests := []struct { + name string + dataSize int + isShanghai bool + expectedGas uint64 + description string + }{ + { + name: "RegularTx_ZeroBytes_PreShanghai", + dataSize: 0, + isShanghai: false, + expectedGas: TxGas, + description: "Pre-Shanghai: Regular tx with zero bytes should have base gas cost", + }, + { + name: "RegularTx_ZeroBytes_Shanghai", + dataSize: 0, + isShanghai: true, + expectedGas: TxGas, + description: "Shanghai: Regular tx with zero bytes should have base gas cost", + }, + { + name: "RegularTx_1000Bytes_PreShanghai", + dataSize: 1000, + isShanghai: false, + expectedGas: TxGas + 4000, // Base + 1000 * 4 gas (all zero bytes) + description: "Pre-Shanghai: Regular tx with 1000 bytes should use old gas calculation", + }, + { + name: "RegularTx_1000Bytes_Shanghai", + dataSize: 1000, + isShanghai: true, + expectedGas: TxGas + 4000, // Base + 1000 * 4 gas (all zero bytes, unchanged) + description: "Shanghai: Regular tx with 1000 bytes should use old gas calculation (unchanged)", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create regular transaction (not contract creation) + tx := &types.Transaction{ + Input: make([]byte, tt.dataSize), + To: &types.Address{}, // Regular transaction + } + + // Calculate gas cost + gasCost, err := TransactionGasCost(tx, true, true, tt.isShanghai) + + assert.NoError(t, err, tt.description) + assert.Equal(t, tt.expectedGas, gasCost, tt.description) + t.Logf("✅ %s: Expected = %d, Actual = %d", tt.description, tt.expectedGas, gasCost) + }) + } +} + +// TestEIP3860_WordCalculation tests the word calculation logic for EIP-3860 +func TestEIP3860_WordCalculation(t *testing.T) { + tests := []struct { + size int + expected uint64 + description string + }{ + {0, 0, "0 bytes = 0 words"}, + {1, 1, "1 byte = 1 word"}, + {31, 1, "31 bytes = 1 word"}, + {32, 1, "32 bytes = 1 word"}, + {33, 2, "33 bytes = 2 words"}, + {64, 2, "64 bytes = 2 words"}, + {100, 4, "100 bytes = 4 words"}, + {1000, 32, "1000 bytes = 32 words"}, + {49152, 1536, "48KB = 1536 words"}, + {49151, 1536, "49151 bytes = 1536 words"}, + {49153, 1537, "49153 bytes = 1537 words"}, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + wordCount := (uint64(tt.size) + 31) / 32 + assert.Equal(t, tt.expected, wordCount, tt.description) + }) + } +} + +// TestEIP3860_Constants tests the EIP-3860 constants +func TestEIP3860_Constants(t *testing.T) { + assert.Equal(t, 49152, EIP3860MaxInitCodeSize, "EIP3860MaxInitCodeSize should be 48KB (49152 bytes)") + assert.Equal(t, uint64(2), EIP3860InitCodeWordGas, "EIP3860InitCodeWordGas should be 2 gas per word") +} + +// TestEIP3860_GasOverflow tests gas overflow scenarios +func TestEIP3860_GasOverflow(t *testing.T) { + // Test with maximum possible initcode size that doesn't overflow + maxSafeSize := 49152 // 48KB limit + + tx := &types.Transaction{ + Input: make([]byte, maxSafeSize), + To: nil, // Contract creation + } + + gasCost, err := TransactionGasCost(tx, true, true, true) + assert.NoError(t, err, "Maximum safe initcode size should not cause overflow") + assert.Greater(t, gasCost, uint64(0), "Gas cost should be positive") + + // Verify the calculation: base cost + word count * 2 gas + expectedWordCount := (uint64(maxSafeSize) + 31) / 32 + expectedGas := TxGasContractCreation + (expectedWordCount * EIP3860InitCodeWordGas) + assert.Equal(t, expectedGas, gasCost, "Gas calculation should match expected formula") +} + +// TestEIP3860_BoundaryConditions tests boundary conditions around the 48KB limit +func TestEIP3860_BoundaryConditions(t *testing.T) { + boundaryTests := []struct { + name string + size int + shouldSucceed bool + }{ + {"Boundary_49150", 49150, true}, + {"Boundary_49151", 49151, true}, + {"Boundary_49152", 49152, true}, // At limit + {"Boundary_49153", 49153, false}, // Over limit + {"Boundary_49154", 49154, false}, + {"Boundary_49155", 49155, false}, + } + + for _, tt := range boundaryTests { + t.Run(tt.name, func(t *testing.T) { + tx := &types.Transaction{ + Input: make([]byte, tt.size), + To: nil, // Contract creation + } + + gasCost, err := TransactionGasCost(tx, true, true, true) + + if tt.shouldSucceed { + assert.NoError(t, err, "Should succeed at boundary") + assert.Greater(t, gasCost, uint64(0), "Should have positive gas cost") + } else { + assert.Error(t, err, "Should fail over boundary") + assert.Equal(t, uint64(0), gasCost, "Should have zero gas cost when failing") + } + }) + } +} + +// TestEIP3860_ForkActivation tests that EIP-3860 only activates with Shanghai fork +func TestEIP3860_ForkActivation(t *testing.T) { + // Test large initcode that should fail with Shanghai but succeed without + largeSize := 60000 // Well over the 48KB limit + + tx := &types.Transaction{ + Input: make([]byte, largeSize), + To: nil, // Contract creation + } + + // Pre-Shanghai: should succeed (no size limits) + gasCostPre, errPre := TransactionGasCost(tx, true, true, false) + assert.NoError(t, errPre, "Pre-Shanghai: Large initcode should succeed") + assert.Greater(t, gasCostPre, uint64(0), "Pre-Shanghai: Should have positive gas cost") + + // Shanghai: should fail (size limits apply) + gasCostPost, errPost := TransactionGasCost(tx, true, true, true) + assert.Error(t, errPost, "Shanghai: Large initcode should fail") + assert.Equal(t, uint64(0), gasCostPost, "Shanghai: Should have zero gas cost when failing") +} diff --git a/state/runtime/evm/dispatch_table.go b/state/runtime/evm/dispatch_table.go index e8afeae0f..3c3761343 100644 --- a/state/runtime/evm/dispatch_table.go +++ b/state/runtime/evm/dispatch_table.go @@ -38,6 +38,7 @@ func init() { register(SMOD, handler{opSMod, 2, 5}) register(EXP, handler{opExp, 2, 10}) + register(PUSH0, handler{opPush0, 0, 2}) registerRange(PUSH1, PUSH32, opPush, 3) registerRange(DUP1, DUP16, opDup, 3) registerRange(SWAP1, SWAP16, opSwap, 3) @@ -118,7 +119,7 @@ func init() { // block information register(BLOCKHASH, handler{opBlockHash, 1, 20}) - register(COINBASE, handler{opCoinbase, 0, 2}) + register(COINBASE, handler{opCoinbase, 0, 0}) // Gas cost handled in instruction for EIP-3651 register(TIMESTAMP, handler{opTimestamp, 0, 2}) register(NUMBER, handler{opNumber, 0, 2}) register(DIFFICULTY, handler{opDifficulty, 0, 2}) diff --git a/state/runtime/evm/evm_fuzz_test.go b/state/runtime/evm/evm_fuzz_test.go index c7c1889d4..b3de649af 100644 --- a/state/runtime/evm/evm_fuzz_test.go +++ b/state/runtime/evm/evm_fuzz_test.go @@ -142,6 +142,10 @@ func (m *mockHostF) GetRefund() uint64 { return m.refund } +func (m *mockHostF) GetPrevBlockHash() types.Hash { + return m.blockHash // Use the same block hash for simplicity in fuzz tests +} + func FuzzTestEVM(f *testing.F) { seed := []byte{ PUSH1, 0x01, PUSH1, 0x02, ADD, diff --git a/state/runtime/evm/evm_test.go b/state/runtime/evm/evm_test.go index e5ad31c87..dca407f0b 100644 --- a/state/runtime/evm/evm_test.go +++ b/state/runtime/evm/evm_test.go @@ -1,6 +1,7 @@ package evm import ( + "errors" "math/big" "testing" @@ -114,6 +115,10 @@ func (m *mockHost) GetRefund() uint64 { panic("Not implemented in tests") //nolint:gocritic } +func (m *mockHost) GetPrevBlockHash() types.Hash { + panic("Not implemented in tests") //nolint:gocritic +} + func TestRun(t *testing.T) { t.Parallel() @@ -386,3 +391,217 @@ func TestRunWithTracer(t *testing.T) { }) } } +func TestOpPush0(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + code []byte + config *chain.ForksInTime + expectedErr error + expectedGas uint64 + description string + }{ + { + name: "PUSH0_Shanghai_Enabled", + code: []byte{PUSH0}, + config: &chain.ForksInTime{Shanghai: true}, + expectedErr: nil, + expectedGas: 998, // 1000 - 2 + description: "PUSH0 should work when Shanghai fork is enabled", + }, + { + name: "PUSH0_Shanghai_Disabled", + code: []byte{PUSH0}, + config: &chain.ForksInTime{Shanghai: false}, + expectedErr: errOpCodeNotFound, + expectedGas: 0, // All gas consumed when opcode fails + description: "PUSH0 should fail when Shanghai fork is disabled", + }, + { + name: "PUSH0_Shanghai_Not_Configured", + code: []byte{PUSH0}, + config: &chain.ForksInTime{}, + expectedErr: errOpCodeNotFound, + expectedGas: 0, // All gas consumed when opcode fails + description: "PUSH0 should fail when Shanghai fork is not configured", + }, + { + name: "PUSH0_Multiple_Times", + code: []byte{PUSH0, PUSH0, PUSH0}, + config: &chain.ForksInTime{Shanghai: true}, + expectedErr: nil, + expectedGas: 994, // 1000 - (2 * 3) + description: "Multiple PUSH0 operations should work correctly", + }, + { + name: "PUSH0_With_Other_Ops", + code: []byte{PUSH0, PUSH1, 0x01, ADD}, + config: &chain.ForksInTime{Shanghai: true}, + expectedErr: nil, + expectedGas: 992, // 1000 - 2 - 3 - 3 + description: "PUSH0 should work with other operations", + }, + { + name: "PUSH0_With_PUSH1_Comparison", + code: []byte{PUSH0, PUSH1, 0x00, EQ}, // Compare PUSH0 with PUSH1 0x00 + config: &chain.ForksInTime{Shanghai: true}, + expectedErr: nil, + expectedGas: 992, // 1000 - 2 - 3 - 3 + description: "PUSH0 should be equivalent to PUSH1 0x00", + }, + { + name: "PUSH0_Gas_Underflow", + code: []byte{PUSH0}, + config: &chain.ForksInTime{Shanghai: true}, + expectedErr: errOutOfGas, + expectedGas: 0, + description: "PUSH0 should fail with insufficient gas", + }, + { + name: "PUSH0_Stack_Overflow", + code: make([]byte, 1025), // Fill with PUSH0 to cause stack overflow + config: &chain.ForksInTime{Shanghai: true}, + expectedErr: errors.New("stack overflow"), // Generic error check + expectedGas: 0, + description: "PUSH0 should fail with stack overflow", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + // Fill code with PUSH0 for stack overflow test + if tt.name == "PUSH0_Stack_Overflow" { + for i := 0; i < 1025; i++ { + tt.code[i] = PUSH0 + } + } + + // Adjust gas for gas underflow test + gas := uint64(1000) + if tt.name == "PUSH0_Gas_Underflow" { + gas = 1 // Insufficient gas + } + + contract := newMockContract(big.NewInt(0), gas, tt.code) + host := &mockHost{} + evm := NewEVM() + result := evm.Run(contract, host, tt.config) + + // Check error + if tt.expectedErr != nil { + assert.Error(t, result.Err) + assert.IsType(t, tt.expectedErr, result.Err) + } else { + assert.NoError(t, result.Err) + } + + // Check gas consumption + if tt.name == "PUSH0_Gas_Underflow" || tt.name == "PUSH0_Stack_Overflow" { + // For error cases, gas consumption depends on when the error occurs + assert.LessOrEqual(t, result.GasLeft, gas) + } else { + assert.Equal(t, tt.expectedGas, result.GasLeft) + } + + t.Logf("Test: %s - %s", tt.name, tt.description) + }) + } +} + +func TestPUSH0_Compatibility(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + code []byte + config *chain.ForksInTime + desc string + }{ + { + name: "PUSH0_vs_PUSH1_0x00_Equivalence", + code: []byte{PUSH0, PUSH1, 0x00, EQ}, + config: &chain.ForksInTime{Shanghai: true}, + desc: "PUSH0 should be equivalent to PUSH1 0x00", + }, + { + name: "PUSH0_Gas_Efficiency", + code: []byte{PUSH0, PUSH0, PUSH0, PUSH0, PUSH0}, // 5 * PUSH0 = 10 gas + config: &chain.ForksInTime{Shanghai: true}, + desc: "PUSH0 should be more gas efficient than PUSH1 0x00", + }, + { + name: "PUSH0_Code_Size_Efficiency", + code: []byte{PUSH0, PUSH0, PUSH0, PUSH0, PUSH0}, // 5 bytes vs 10 bytes for PUSH1 + config: &chain.ForksInTime{Shanghai: true}, + desc: "PUSH0 should use less code space than PUSH1 0x00", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + contract := newMockContract(big.NewInt(0), 1000, tt.code) + host := &mockHost{} + evm := NewEVM() + result := evm.Run(contract, host, tt.config) + + assert.NoError(t, result.Err) + t.Logf("Test: %s - %s", tt.name, tt.desc) + }) + } +} + +func TestPUSH0_Edge_Cases(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + code []byte + config *chain.ForksInTime + expectedErr error + desc string + }{ + { + name: "PUSH0_At_End_Of_Code", + code: []byte{PUSH0}, + config: &chain.ForksInTime{Shanghai: true}, + expectedErr: nil, + desc: "PUSH0 should work at the end of code", + }, + + { + name: "PUSH0_With_Empty_Code", + code: []byte{}, + config: &chain.ForksInTime{Shanghai: true}, + expectedErr: nil, + desc: "Empty code should not cause issues with PUSH0", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + contract := newMockContract(big.NewInt(0), 1000, tt.code) + host := &mockHost{} + evm := NewEVM() + result := evm.Run(contract, host, tt.config) + + if tt.expectedErr != nil { + assert.Error(t, result.Err) + assert.IsType(t, tt.expectedErr, result.Err) + } else { + assert.NoError(t, result.Err) + } + + t.Logf("Test: %s - %s", tt.name, tt.desc) + }) + } +} diff --git a/state/runtime/evm/instructions.go b/state/runtime/evm/instructions.go index d2a38374a..6f5616618 100644 --- a/state/runtime/evm/instructions.go +++ b/state/runtime/evm/instructions.go @@ -888,6 +888,19 @@ func opBlockHash(c *state) { } func opCoinbase(c *state) { + // EIP-3651: Warm COINBASE - reduce gas cost from 2600 to 100 gas when Shanghai is active + if c.config.Shanghai { + // Shanghai fork: 100 gas for COINBASE access (EIP-3651) + if !c.consumeGas(100) { + return + } + } else { + // Pre-Shanghai: 2600 gas for COINBASE access (original cost) + if !c.consumeGas(2600) { + return + } + } + c.push1().SetBytes(c.host.GetTxContext().Coinbase.Bytes()) } @@ -900,7 +913,14 @@ func opNumber(c *state) { } func opDifficulty(c *state) { - c.push1().SetBytes(c.host.GetTxContext().Difficulty.Bytes()) + if c.config.Paris { + // Paris fork: return previous block's hash as RANDAO (EIP-4399) + prevBlockHash := c.host.GetPrevBlockHash() + c.push1().SetBytes(prevBlockHash.Bytes()) + } else { + // Pre-Paris: return difficulty (original behavior) + c.push1().SetBytes(c.host.GetTxContext().Difficulty.Bytes()) + } } func opGasLimit(c *state) { @@ -991,6 +1011,16 @@ func opPush(n int) instruction { } } +func opPush0(c *state) { + // PUSH0 pushes 0 onto the stack (EIP-3855) + // Only available when Shanghai fork is enabled + if !c.config.Shanghai { + c.exit(errOpCodeNotFound) + return + } + c.push1().SetUint64(0) +} + func opDup(n int) instruction { return func(c *state) { if !c.stackAtLeast(n) { diff --git a/state/runtime/evm/instructions_test.go b/state/runtime/evm/instructions_test.go index b0d303ad5..8df5f44ec 100644 --- a/state/runtime/evm/instructions_test.go +++ b/state/runtime/evm/instructions_test.go @@ -1,6 +1,8 @@ package evm import ( + "bytes" + "fmt" "math" "math/big" "testing" @@ -121,6 +123,10 @@ func (m *mockHostForInstructions) GetCode(addr types.Address) []byte { return m.code } +func (m *mockHostForInstructions) GetPrevBlockHash() types.Hash { + return types.Hash{} // Return empty hash for existing tests +} + var ( addr1 = types.StringToAddress("1") ) @@ -797,3 +803,651 @@ func Test_opCall(t *testing.T) { }) } } + +// EIP-4399 (PREVRANDAO) Tests +// These tests verify that the DIFFICULTY opcode behavior changes correctly with Paris fork + +type mockHostForDifficulty struct { + mockHost + txContext runtime.TxContext + prevBlockHash types.Hash +} + +func (m *mockHostForDifficulty) GetTxContext() runtime.TxContext { + return m.txContext +} + +func (m *mockHostForDifficulty) GetPrevBlockHash() types.Hash { + return m.prevBlockHash +} + +func TestOpDifficulty_EIP4399(t *testing.T) { + t.Parallel() + + // Test cases based on Ethereum's EIP-4399 specification + tests := []struct { + name string + config *chain.ForksInTime + difficulty *big.Int + prevBlockHash types.Hash + expectedResult *big.Int + description string + }{ + { + name: "Pre-Paris: Should return difficulty", + config: &chain.ForksInTime{ + Paris: false, // Paris fork not active + }, + difficulty: big.NewInt(123456789), + prevBlockHash: types.StringToHash("0x1234567890abcdef"), + expectedResult: big.NewInt(123456789), + description: "Before Paris fork, DIFFICULTY opcode should return actual difficulty", + }, + { + name: "Post-Paris: Should return previous block hash as RANDAO", + config: &chain.ForksInTime{ + Paris: true, // Paris fork active + }, + difficulty: big.NewInt(123456789), + prevBlockHash: types.StringToHash("0xabcdef1234567890"), + expectedResult: new(big.Int).SetBytes(types.StringToHash("0xabcdef1234567890").Bytes()), + description: "After Paris fork, DIFFICULTY opcode should return previous block hash as RANDAO", + }, + { + name: "Post-Paris: Should return previous block hash even with zero difficulty", + config: &chain.ForksInTime{ + Paris: true, // Paris fork active + }, + difficulty: big.NewInt(0), + prevBlockHash: types.StringToHash("0x0000000000000001"), + expectedResult: new(big.Int).SetBytes(types.StringToHash("0x0000000000000001").Bytes()), + description: "After Paris fork, should return prev block hash regardless of difficulty value", + }, + { + name: "Post-Paris: Should handle large previous block hash", + config: &chain.ForksInTime{ + Paris: true, // Paris fork active + }, + difficulty: big.NewInt(999999999), + prevBlockHash: types.StringToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + expectedResult: new(big.Int).SetBytes(types.StringToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").Bytes()), + description: "After Paris fork, should handle maximum hash value correctly", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + // Create mock host with specific difficulty and previous block hash + mockHost := &mockHostForDifficulty{ + txContext: runtime.TxContext{ + Difficulty: types.BytesToHash(tt.difficulty.Bytes()), + }, + prevBlockHash: tt.prevBlockHash, + } + + // Create state with mock host and config + s, closeFn := getState() + defer closeFn() + + s.host = mockHost + s.config = tt.config + + // Execute DIFFICULTY opcode + opDifficulty(s) + + // Verify the result + result := s.pop() + assert.Equal(t, tt.expectedResult, result, tt.description) + }) + } +} + +// Test that the DIFFICULTY opcode behavior is consistent with Ethereum's EIP-4399 +func TestOpDifficulty_EIP4399_Consistency(t *testing.T) { + t.Parallel() + + // Test that the same opcode (0x44) behaves differently based on fork activation + s, closeFn := getState() + defer closeFn() + + // Test pre-Paris behavior + preParisConfig := &chain.ForksInTime{Paris: false} + preParisHost := &mockHostForDifficulty{ + txContext: runtime.TxContext{ + Difficulty: types.BytesToHash(big.NewInt(100).Bytes()), + }, + prevBlockHash: types.StringToHash("0x1234567890abcdef"), + } + + s.host = preParisHost + s.config = preParisConfig + opDifficulty(s) + preParisResult := s.pop() + + // Reset state + s, closeFn = getState() + defer closeFn() + + // Test post-Paris behavior + postParisConfig := &chain.ForksInTime{Paris: true} + postParisHost := &mockHostForDifficulty{ + txContext: runtime.TxContext{ + Difficulty: types.BytesToHash(big.NewInt(100).Bytes()), + }, + prevBlockHash: types.StringToHash("0x1234567890abcdef"), + } + + s.host = postParisHost + s.config = postParisConfig + opDifficulty(s) + postParisResult := s.pop() + + // Verify that the results are different (same input, different behavior) + assert.NotEqual(t, preParisResult, postParisResult, + "DIFFICULTY opcode should behave differently before and after Paris fork") + + // Verify pre-Paris returns difficulty + expectedPreParis := big.NewInt(100) + assert.Equal(t, expectedPreParis, preParisResult, + "Pre-Paris should return difficulty value") + + // Verify post-Paris returns previous block hash + expectedPostParis := new(big.Int).SetBytes(types.StringToHash("0x1234567890abcdef").Bytes()) + assert.Equal(t, expectedPostParis, postParisResult, + "Post-Paris should return previous block hash as RANDAO") +} + +type mockHostForCoinbase struct { + mockHost + txContext runtime.TxContext +} + +func (m *mockHostForCoinbase) GetTxContext() runtime.TxContext { + return m.txContext +} + +func TestOpCoinbase_EIP3651_Comprehensive(t *testing.T) { + tests := []struct { + name string + shanghai bool + gasLimit uint64 + expectedGas uint64 + shouldSucceed bool + expectedError error + description string + }{ + // Pre-Shanghai tests (should use 2600 gas) + { + name: "PreShanghai_SufficientGas", + shanghai: false, + gasLimit: 3000, + expectedGas: 2600, + shouldSucceed: true, + description: "Pre-Shanghai: Should succeed with sufficient gas (3000 > 2600)", + }, + { + name: "PreShanghai_InsufficientGas", + shanghai: false, + gasLimit: 1000, + expectedGas: 2600, + shouldSucceed: false, + expectedError: errOutOfGas, + description: "Pre-Shanghai: Should fail with insufficient gas (1000 < 2600)", + }, + { + name: "PreShanghai_ExactGas", + shanghai: false, + gasLimit: 2600, + expectedGas: 2600, + shouldSucceed: true, + description: "Pre-Shanghai: Should succeed with exact gas (2600 = 2600)", + }, + { + name: "PreShanghai_OneLessThanRequired", + shanghai: false, + gasLimit: 2599, + expectedGas: 2600, + shouldSucceed: false, + expectedError: errOutOfGas, + description: "Pre-Shanghai: Should fail with one less gas (2599 < 2600)", + }, + + // Shanghai tests (should use 100 gas) + { + name: "Shanghai_SufficientGas", + shanghai: true, + gasLimit: 200, + expectedGas: 100, + shouldSucceed: true, + description: "Shanghai: Should succeed with sufficient gas (200 > 100)", + }, + { + name: "Shanghai_InsufficientGas", + shanghai: true, + gasLimit: 50, + expectedGas: 100, + shouldSucceed: false, + expectedError: errOutOfGas, + description: "Shanghai: Should fail with insufficient gas (50 < 100)", + }, + { + name: "Shanghai_ExactGas", + shanghai: true, + gasLimit: 100, + expectedGas: 100, + shouldSucceed: true, + description: "Shanghai: Should succeed with exact gas (100 = 100)", + }, + { + name: "Shanghai_OneLessThanRequired", + shanghai: true, + gasLimit: 99, + expectedGas: 100, + shouldSucceed: false, + expectedError: errOutOfGas, + description: "Shanghai: Should fail with one less gas (99 < 100)", + }, + + // Edge cases + { + name: "Shanghai_ZeroGas", + shanghai: true, + gasLimit: 0, + expectedGas: 100, + shouldSucceed: false, + expectedError: errOutOfGas, + description: "Shanghai: Should fail with zero gas", + }, + { + name: "PreShanghai_ZeroGas", + shanghai: false, + gasLimit: 0, + expectedGas: 2600, + shouldSucceed: false, + expectedError: errOutOfGas, + description: "Pre-Shanghai: Should fail with zero gas", + }, + { + name: "Shanghai_MaxGas", + shanghai: true, + gasLimit: 0xffffffffffffffff, + expectedGas: 100, + shouldSucceed: true, + description: "Shanghai: Should succeed with maximum gas", + }, + { + name: "PreShanghai_MaxGas", + shanghai: false, + gasLimit: 0xffffffffffffffff, + expectedGas: 2600, + shouldSucceed: true, + description: "Pre-Shanghai: Should succeed with maximum gas", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create mock host with coinbase address + coinbaseAddr := types.StringToAddress("0x1234567890123456789012345678901234567890") + host := &mockHostForCoinbase{ + mockHost: mockHost{}, + txContext: runtime.TxContext{ + Coinbase: coinbaseAddr, + }, + } + + // Create EVM state with Shanghai config + config := &chain.ForksInTime{ + Shanghai: tt.shanghai, + } + + // Create EVM state with specified gas + state, closeFn := getState() + defer closeFn() + + state.config = config + state.host = host + state.gas = tt.gasLimit + + // Execute COINBASE opcode + opCoinbase(state) + + if tt.shouldSucceed { + // Should succeed + if state.err != nil { + t.Errorf("Expected success, but got error: %v", state.err) + } + if state.stackSize() != 1 { + t.Errorf("Expected stack length: 1, got: %d", state.stackSize()) + } + // Check that the pushed value matches the coinbase address + top := state.top() + expectedBytes := coinbaseAddr.Bytes() + if !bytes.Equal(top.Bytes(), expectedBytes) { + t.Errorf("Expected coinbase: %x, got: %x", expectedBytes, top.Bytes()) + } + // Check gas consumption + expectedGasLeft := tt.gasLimit - tt.expectedGas + if state.gas != expectedGasLeft { + t.Errorf("Expected gas left: %d, got: %d", expectedGasLeft, state.gas) + } + } else { + // Should fail + if state.err == nil { + t.Errorf("Expected error, but got none") + } + if state.err != tt.expectedError { + t.Errorf("Expected error: %v, got: %v", tt.expectedError, state.err) + } + if state.stackSize() != 0 { + t.Errorf("Expected empty stack on failure, got: %d", state.stackSize()) + } + } + }) + } +} + +func TestOpCoinbase_EIP3651_ForkActivation(t *testing.T) { + tests := []struct { + name string + shanghai bool + gasLimit uint64 + expectedGas uint64 + description string + }{ + { + name: "Shanghai_Enabled_100Gas", + shanghai: true, + gasLimit: 200, + expectedGas: 100, + description: "Shanghai enabled: Should use 100 gas (EIP-3651)", + }, + { + name: "Shanghai_Disabled_2600Gas", + shanghai: false, + gasLimit: 3000, + expectedGas: 2600, + description: "Shanghai disabled: Should use 2600 gas (pre-EIP-3651)", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create mock host with coinbase address + coinbaseAddr := types.StringToAddress("0x1234567890123456789012345678901234567890") + host := &mockHostForCoinbase{ + mockHost: mockHost{}, + txContext: runtime.TxContext{ + Coinbase: coinbaseAddr, + }, + } + + // Create EVM state with Shanghai config + config := &chain.ForksInTime{ + Shanghai: tt.shanghai, + } + + // Create EVM state with enough gas + state, closeFn := getState() + defer closeFn() + + state.config = config + state.host = host + state.gas = tt.gasLimit + + // Execute COINBASE opcode + opCoinbase(state) + + // Verify gas consumption + expectedGasLeft := tt.gasLimit - tt.expectedGas + if state.gas != expectedGasLeft { + t.Errorf("Expected gas left: %d, got: %d", expectedGasLeft, state.gas) + } + + // Verify successful execution + if state.err != nil { + t.Errorf("Expected success, but got error: %v", state.err) + } + if state.stackSize() != 1 { + t.Errorf("Expected stack length: 1, got: %d", state.stackSize()) + } + + // Verify coinbase address + top := state.top() + expectedBytes := coinbaseAddr.Bytes() + if !bytes.Equal(top.Bytes(), expectedBytes) { + t.Errorf("Expected coinbase: %x, got: %x", expectedBytes, top.Bytes()) + } + }) + } +} + +func TestOpCoinbase_EIP3651_GasEfficiency(t *testing.T) { + tests := []struct { + name string + shanghai bool + gasSavings uint64 + description string + }{ + { + name: "Shanghai_GasSavings", + shanghai: true, + gasSavings: 2500, // 2600 - 100 = 2500 gas saved + description: "Shanghai: Should save 2500 gas per COINBASE access", + }, + { + name: "PreShanghai_NoGasSavings", + shanghai: false, + gasSavings: 0, // No savings in pre-Shanghai + description: "Pre-Shanghai: No gas savings (original cost)", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create mock host with coinbase address + coinbaseAddr := types.StringToAddress("0x1234567890123456789012345678901234567890") + host := &mockHostForCoinbase{ + mockHost: mockHost{}, + txContext: runtime.TxContext{ + Coinbase: coinbaseAddr, + }, + } + + // Create EVM state with Shanghai config + config := &chain.ForksInTime{ + Shanghai: tt.shanghai, + } + + // Create EVM state with enough gas + state, closeFn := getState() + defer closeFn() + + state.config = config + state.host = host + state.gas = 10000 // Plenty of gas + + // Execute COINBASE opcode + opCoinbase(state) + + // Verify successful execution + if state.err != nil { + t.Errorf("Expected success, but got error: %v", state.err) + } + + // Calculate actual gas used + gasUsed := 10000 - state.gas + + // Verify gas efficiency + if tt.shanghai { + if gasUsed != 100 { + t.Errorf("Shanghai: Expected 100 gas, got %d", gasUsed) + } + } else { + if gasUsed != 2600 { + t.Errorf("Pre-Shanghai: Expected 2600 gas, got %d", gasUsed) + } + } + }) + } +} + +func TestOpCoinbase_EIP3651_MultipleAccesses(t *testing.T) { + tests := []struct { + name string + shanghai bool + accessCount int + expectedGas uint64 + description string + }{ + { + name: "Shanghai_MultipleAccesses", + shanghai: true, + accessCount: 5, + expectedGas: 500, // 5 * 100 gas + description: "Shanghai: Multiple COINBASE accesses should be efficient", + }, + { + name: "PreShanghai_MultipleAccesses", + shanghai: false, + accessCount: 3, + expectedGas: 7800, // 3 * 2600 gas + description: "Pre-Shanghai: Multiple COINBASE accesses should be expensive", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create mock host with coinbase address + coinbaseAddr := types.StringToAddress("0x1234567890123456789012345678901234567890") + host := &mockHostForCoinbase{ + mockHost: mockHost{}, + txContext: runtime.TxContext{ + Coinbase: coinbaseAddr, + }, + } + + // Create EVM state with Shanghai config + config := &chain.ForksInTime{ + Shanghai: tt.shanghai, + } + + // Create EVM state with enough gas + state, closeFn := getState() + defer closeFn() + + state.config = config + state.host = host + state.gas = 10000 // Plenty of gas + + // Execute multiple COINBASE opcodes + for i := 0; i < tt.accessCount; i++ { + opCoinbase(state) + } + + // Verify successful execution + if state.err != nil { + t.Errorf("Expected success, but got error: %v", state.err) + } + + // Verify stack has correct number of items + if state.stackSize() != tt.accessCount { + t.Errorf("Expected stack length: %d, got: %d", tt.accessCount, state.stackSize()) + } + + // Verify gas consumption + gasUsed := 10000 - state.gas + if gasUsed != tt.expectedGas { + t.Errorf("Expected gas used: %d, got: %d", tt.expectedGas, gasUsed) + } + + // Verify all stack items are the coinbase address by popping them + expectedBytes := coinbaseAddr.Bytes() + for i := 0; i < tt.accessCount; i++ { + item := state.pop() + if !bytes.Equal(item.Bytes(), expectedBytes) { + t.Errorf("Stack item %d: Expected coinbase: %x, got: %x", i, expectedBytes, item.Bytes()) + } + } + + // Verify stack is now empty + if state.stackSize() != 0 { + t.Errorf("Expected empty stack after popping all items, got: %d", state.stackSize()) + } + }) + } +} + +func TestOpCoinbase_EIP3651_Compatibility(t *testing.T) { + tests := []struct { + name string + shanghai bool + description string + }{ + { + name: "Shanghai_Compatibility", + shanghai: true, + description: "Shanghai: COINBASE should work with reduced gas cost", + }, + { + name: "PreShanghai_Compatibility", + shanghai: false, + description: "Pre-Shanghai: COINBASE should work with original gas cost", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Test with different coinbase addresses (excluding zero address) + coinbaseAddresses := []types.Address{ + types.StringToAddress("0x1234567890123456789012345678901234567890"), + types.StringToAddress("0xffffffffffffffffffffffffffffffffffffffff"), + } + + for _, coinbaseAddr := range coinbaseAddresses { + t.Run(fmt.Sprintf("Coinbase_%x", coinbaseAddr), func(t *testing.T) { + host := &mockHostForCoinbase{ + mockHost: mockHost{}, + txContext: runtime.TxContext{ + Coinbase: coinbaseAddr, + }, + } + + // Create EVM state with Shanghai config + config := &chain.ForksInTime{ + Shanghai: tt.shanghai, + } + + // Create EVM state with enough gas + state, closeFn := getState() + defer closeFn() + + state.config = config + state.host = host + state.gas = 10000 + + // Execute COINBASE opcode + opCoinbase(state) + + // Verify successful execution + if state.err != nil { + t.Errorf("Expected success, but got error: %v", state.err) + } + if state.stackSize() != 1 { + t.Errorf("Expected stack length: 1, got: %d", state.stackSize()) + } + + // Verify coinbase address + top := state.top() + expectedBytes := coinbaseAddr.Bytes() + if !bytes.Equal(top.Bytes(), expectedBytes) { + t.Errorf("Expected coinbase: %x, got: %x", expectedBytes, top.Bytes()) + } + }) + } + }) + } +} diff --git a/state/runtime/evm/opcodes.go b/state/runtime/evm/opcodes.go index 409180d4d..7539523a6 100644 --- a/state/runtime/evm/opcodes.go +++ b/state/runtime/evm/opcodes.go @@ -200,6 +200,9 @@ const ( // JUMPDEST corresponds to a possible jump destination JUMPDEST = 0x5B + // PUSH0 pushes a 0 value onto the stack (EIP-3855) + PUSH0 = 0x5F + // PUSH1 pushes a 1-byte value onto the stack PUSH1 = 0x60 @@ -324,6 +327,7 @@ var opCodeToString = map[OpCode]string{ MSIZE: "MSIZE", GAS: "GAS", JUMPDEST: "JUMPDEST", + PUSH0: "PUSH0", CREATE: "CREATE", CALL: "CALL", RETURN: "RETURN", diff --git a/state/runtime/precompiled/native_transfer_test.go b/state/runtime/precompiled/native_transfer_test.go index a6309f6d4..21705462c 100644 --- a/state/runtime/precompiled/native_transfer_test.go +++ b/state/runtime/precompiled/native_transfer_test.go @@ -197,3 +197,9 @@ func (d dummyHost) GetTracer() runtime.VMTracer { func (d dummyHost) GetRefund() uint64 { return 0 } + +func (d dummyHost) GetPrevBlockHash() types.Hash { + d.t.Fatalf("GetPrevBlockHash is not implemented") + + return types.ZeroHash +} diff --git a/state/runtime/runtime.go b/state/runtime/runtime.go index 248e9f3fd..ab47c3578 100644 --- a/state/runtime/runtime.go +++ b/state/runtime/runtime.go @@ -73,6 +73,7 @@ type Host interface { Selfdestruct(addr types.Address, beneficiary types.Address) GetTxContext() TxContext GetBlockHash(number int64) types.Hash + GetPrevBlockHash() types.Hash EmitLog(addr types.Address, topics []types.Hash, data []byte) Callx(*Contract, Host) *ExecutionResult Empty(addr types.Address) bool diff --git a/txpool/txpool.go b/txpool/txpool.go index 1b9dc1209..9a8ab5870 100644 --- a/txpool/txpool.go +++ b/txpool/txpool.go @@ -607,10 +607,21 @@ func (p *TxPool) validateTx(tx *types.Transaction) error { forks := p.forks.At(currentBlockNumber) // Check if transaction can deploy smart contract - if tx.IsContractCreation() && forks.EIP158 && len(tx.Input) > state.TxPoolMaxInitCodeSize { - metrics.IncrCounter([]string{txPoolMetrics, "contract_deploy_too_large_txs"}, 1) + if tx.IsContractCreation() && forks.EIP158 { + var maxInitCodeSize int + + // EIP-3860: Use new initcode size limit for Shanghai fork + if forks.Shanghai { + maxInitCodeSize = state.EIP3860MaxInitCodeSize + } else { + maxInitCodeSize = state.TxPoolMaxInitCodeSize + } + + if len(tx.Input) > maxInitCodeSize { + metrics.IncrCounter([]string{txPoolMetrics, "contract_deploy_too_large_txs"}, 1) - return runtime.ErrMaxCodeSizeExceeded + return runtime.ErrMaxCodeSizeExceeded + } } // Grab the state root, and block gas limit for the latest block @@ -703,7 +714,8 @@ func (p *TxPool) validateTx(tx *types.Transaction) error { } // Make sure the transaction has more gas than the basic transaction fee - intrinsicGas, err := state.TransactionGasCost(tx, forks.Homestead, forks.Istanbul) + // 4. there is no overflow when calculating intrinsic gas + intrinsicGas, err := state.TransactionGasCost(tx, forks.Homestead, forks.Istanbul, forks.Shanghai) if err != nil { metrics.IncrCounter([]string{txPoolMetrics, "invalid_intrinsic_gas_tx"}, 1)