diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AbstractShiftOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AbstractShiftOperationBenchmarkV2.java new file mode 100644 index 00000000000..cd83e28f46f --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AbstractShiftOperationBenchmarkV2.java @@ -0,0 +1,120 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import static org.hyperledger.besu.ethereum.vm.operations.v2.BenchmarkHelperV2.bytesToUInt256; +import static org.hyperledger.besu.ethereum.vm.operations.v2.BenchmarkHelperV2.randomUInt256Value; + +import org.hyperledger.besu.evm.UInt256; + +import java.util.concurrent.ThreadLocalRandom; + +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Setup; + +/** + * Abstract base class for shift operation benchmarks (SHL, SHR, SAR). + * + *
Provides shared test case definitions and setup logic. + */ +public abstract class AbstractShiftOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + /** Test cases covering different execution paths for shift operations. */ + public enum Case { + /** Shift by 0 - no shift needed. */ + SHIFT_0, + /** Small shift by 1 bit. */ + SHIFT_1, + /** Medium shift by 128 bits (half word). */ + SHIFT_128, + /** Large shift by 255 bits (max valid). */ + SHIFT_255, + /** Overflow: shift of exactly 256. */ + OVERFLOW_SHIFT_256, + /** Overflow: shift amount > 4 bytes. */ + OVERFLOW_LARGE_SHIFT, + /** Random values (original behavior). */ + FULL_RANDOM + } + + @Param({ + "SHIFT_0", + "SHIFT_1", + "SHIFT_128", + "SHIFT_255", + "OVERFLOW_SHIFT_256", + "OVERFLOW_LARGE_SHIFT", + "FULL_RANDOM" + }) + protected String caseName; + + @Setup(Level.Iteration) + @Override + public void setUp() { + frame = BenchmarkHelperV2.createMessageCallFrame(); + + final Case scenario = Case.valueOf(caseName); + aPool = new UInt256[SAMPLE_SIZE]; // shift amount (pushed second, popped first) + bPool = new UInt256[SAMPLE_SIZE]; // value (pushed first, popped second) + + final ThreadLocalRandom random = ThreadLocalRandom.current(); + + for (int i = 0; i < SAMPLE_SIZE; i++) { + switch (scenario) { + case SHIFT_0: + aPool[i] = UInt256.ZERO; + bPool[i] = randomUInt256Value(random); + break; + + case SHIFT_1: + aPool[i] = UInt256.ONE; + bPool[i] = randomUInt256Value(random); + break; + + case SHIFT_128: + aPool[i] = UInt256.fromInt(128); + bPool[i] = randomUInt256Value(random); + break; + + case SHIFT_255: + aPool[i] = UInt256.fromInt(255); + bPool[i] = randomUInt256Value(random); + break; + + case OVERFLOW_SHIFT_256: + aPool[i] = UInt256.fromInt(256); // 256 + bPool[i] = randomUInt256Value(random); + break; + + case OVERFLOW_LARGE_SHIFT: + aPool[i] = bytesToUInt256(new byte[] {0x01, 0x00, 0x00, 0x00, 0x00, 0x00}); + bPool[i] = randomUInt256Value(random); + break; + + case FULL_RANDOM: + default: + final byte[] shift = new byte[1 + random.nextInt(4)]; + final byte[] value = new byte[1 + random.nextInt(32)]; + random.nextBytes(shift); + random.nextBytes(value); + aPool[i] = bytesToUInt256(shift); + bPool[i] = bytesToUInt256(value); + break; + } + } + index = 0; + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AddModOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AddModOperationBenchmarkV2.java new file mode 100644 index 00000000000..01cf8d09683 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AddModOperationBenchmarkV2.java @@ -0,0 +1,172 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.AddModOperationV2; + +import java.util.concurrent.ThreadLocalRandom; + +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Setup; + +public class AddModOperationBenchmarkV2 extends TernaryOperationBenchmarkV2 { + + // Benches for (a + b) % c + + // Define available scenarios + public enum Case { + MULMOD_32_32_32(1, 1, 1), + MULMOD_64_32_32(2, 1, 1), + MULMOD_64_64_32(2, 2, 1), + MULMOD_64_64_64(2, 2, 2), + MULMOD_128_32_32(4, 1, 1), + MULMOD_128_64_32(4, 2, 1), + MULMOD_128_64_64(4, 2, 2), + MULMOD_128_128_32(4, 4, 1), + MULMOD_128_128_64(4, 4, 2), + MULMOD_128_128_128(4, 4, 3), + MULMOD_192_32_32(6, 1, 1), + MULMOD_192_64_32(6, 2, 1), + MULMOD_192_64_64(6, 2, 2), + MULMOD_192_128_32(6, 4, 1), + MULMOD_192_128_64(6, 4, 2), + MULMOD_192_128_128(6, 4, 4), + MULMOD_192_192_32(6, 6, 1), + MULMOD_192_192_64(6, 6, 2), + MULMOD_192_192_128(6, 6, 4), + MULMOD_192_192_192(6, 6, 6), + MULMOD_256_32_32(8, 1, 1), + MULMOD_256_64_32(8, 2, 1), + MULMOD_256_64_64(8, 2, 2), + MULMOD_256_64_128(8, 2, 4), + MULMOD_256_64_192(8, 2, 6), + MULMOD_256_128_32(8, 4, 1), + MULMOD_256_128_64(8, 4, 2), + MULMOD_256_128_128(8, 4, 4), + MULMOD_256_192_32(8, 6, 1), + MULMOD_256_192_64(8, 6, 2), + MULMOD_256_192_128(8, 6, 4), + MULMOD_256_192_192(8, 6, 6), + MULMOD_256_256_32(8, 8, 1), + MULMOD_256_256_64(8, 8, 2), + MULMOD_256_256_128(8, 8, 4), + MULMOD_256_256_192(8, 8, 6), + MULMOD_256_256_256(8, 8, 8), + LARGER_MULMOD_64_64_128(2, 2, 4), + LARGER_MULMOD_192_192_256(6, 6, 8), + ZERO_MULMOD_128_256_0(4, 8, 0), + FULL_RANDOM(-1, -1, -1); + + final int aSize; + final int bSize; + final int cSize; + + Case(final int aSize, final int bSize, final int cSize) { + this.aSize = aSize; + this.bSize = bSize; + this.cSize = cSize; + } + } + + @Param({ + "MULMOD_32_32_32", + "MULMOD_64_32_32", + "MULMOD_64_64_32", + "MULMOD_64_64_64", + "MULMOD_128_32_32", + "MULMOD_128_64_32", + "MULMOD_128_64_64", + "MULMOD_128_128_32", + "MULMOD_128_128_64", + "MULMOD_128_128_128", + "MULMOD_192_32_32", + "MULMOD_192_64_32", + "MULMOD_192_64_64", + "MULMOD_192_128_32", + "MULMOD_192_128_64", + "MULMOD_192_128_128", + "MULMOD_192_192_32", + "MULMOD_192_192_64", + "MULMOD_192_192_128", + "MULMOD_192_192_192", + "MULMOD_256_32_32", + "MULMOD_256_64_32", + "MULMOD_256_64_64", + "MULMOD_256_64_128", + "MULMOD_256_64_192", + "MULMOD_256_128_32", + "MULMOD_256_128_64", + "MULMOD_256_128_128", + "MULMOD_256_192_32", + "MULMOD_256_192_64", + "MULMOD_256_192_128", + "MULMOD_256_192_192", + "MULMOD_256_256_32", + "MULMOD_256_256_64", + "MULMOD_256_256_128", + "MULMOD_256_256_192", + "MULMOD_256_256_256", + "LARGER_MULMOD_64_64_128", + "LARGER_MULMOD_192_192_256", + "ZERO_MULMOD_128_256_0", + "FULL_RANDOM" + }) + private String caseName; + + @Setup(Level.Iteration) + @Override + public void setUp() { + frame = BenchmarkHelperV2.createMessageCallFrame(); + + AddModOperationBenchmarkV2.Case scenario = AddModOperationBenchmarkV2.Case.valueOf(caseName); + aPool = new UInt256[SAMPLE_SIZE]; + bPool = new UInt256[SAMPLE_SIZE]; + cPool = new UInt256[SAMPLE_SIZE]; + + final ThreadLocalRandom random = ThreadLocalRandom.current(); + int aSize; + int bSize; + int cSize; + + for (int i = 0; i < SAMPLE_SIZE; i++) { + if (scenario.aSize < 0) aSize = random.nextInt(1, 33); + else aSize = scenario.aSize * 4; + if (scenario.bSize < 0) bSize = random.nextInt(1, 33); + else bSize = scenario.bSize * 4; + if (scenario.cSize < 0) cSize = random.nextInt(1, 33); + else cSize = scenario.cSize * 4; + + final byte[] a = new byte[aSize]; + final byte[] b = new byte[bSize]; + final byte[] c = new byte[cSize]; + random.nextBytes(a); + random.nextBytes(b); + random.nextBytes(c); + aPool[i] = BenchmarkHelperV2.bytesToUInt256(a); + bPool[i] = BenchmarkHelperV2.bytesToUInt256(b); + cPool[i] = BenchmarkHelperV2.bytesToUInt256(c); + } + index = 0; + } + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return AddModOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AddOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AddOperationBenchmarkV2.java index c9e16edbd0e..97b7fa76aed 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AddOperationBenchmarkV2.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AddOperationBenchmarkV2.java @@ -16,7 +16,7 @@ import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.operation.Operation; -import org.hyperledger.besu.evm.operation.v2.AddOperationV2; +import org.hyperledger.besu.evm.v2.operation.AddOperationV2; public class AddOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BenchmarkHelperV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BenchmarkHelperV2.java index fa062fd5fb0..8afcbb92194 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BenchmarkHelperV2.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BenchmarkHelperV2.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; import org.apache.tuweni.bytes.Bytes32; @@ -101,4 +102,44 @@ static void pushUInt256(final MessageFrame frame, final org.hyperledger.besu.evm s[dst + 3] = value.u0(); frame.setTopV2(top + 1); } + + /** + * Generates a random UInt256 value. + * + * @param random thread-local random source + * @return random UInt256 value + */ + static org.hyperledger.besu.evm.UInt256 randomUInt256Value(final ThreadLocalRandom random) { + final byte[] value = new byte[32]; + random.nextBytes(value); + return org.hyperledger.besu.evm.UInt256.fromBytesBE(value); + } + + /** + * Generates a random positive signed 256-bit UInt256 value (sign bit cleared). + * + * @param random thread-local random source + * @return random positive UInt256 value + */ + static org.hyperledger.besu.evm.UInt256 randomPositiveUInt256Value( + final ThreadLocalRandom random) { + final byte[] value = new byte[32]; + random.nextBytes(value); + value[0] = (byte) (value[0] & 0x7F); + return org.hyperledger.besu.evm.UInt256.fromBytesBE(value); + } + + /** + * Generates a random negative signed 256-bit UInt256 value (sign bit set). + * + * @param random thread-local random source + * @return random negative UInt256 value + */ + static org.hyperledger.besu.evm.UInt256 randomNegativeUInt256Value( + final ThreadLocalRandom random) { + final byte[] value = new byte[32]; + random.nextBytes(value); + value[0] = (byte) (value[0] | 0x80); + return org.hyperledger.besu.evm.UInt256.fromBytesBE(value); + } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BlockHashOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BlockHashOperationBenchmarkV2.java new file mode 100644 index 00000000000..29066045135 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BlockHashOperationBenchmarkV2.java @@ -0,0 +1,128 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.frame.BlockValues; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.v2.operation.BlockHashOperationV2; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import org.apache.tuweni.bytes.Bytes32; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +/** Benchmark for BLOCKHASH EVM v2 operation. */ +@State(Scope.Thread) +@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(value = TimeUnit.NANOSECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@BenchmarkMode(Mode.AverageTime) +public class BlockHashOperationBenchmarkV2 { + + protected static final int SAMPLE_SIZE = 30_000; + private static final long CURRENT_BLOCK_NUMBER = 1_000L; + + @Param({"IN_RANGE", "OUT_OF_RANGE", "FUTURE"}) + public String blockCase; + + private long[] blockNumberPool; + private int index; + private MessageFrame frame; + + @Setup + public void setUp() { + final BlockValues blockValues = mock(BlockValues.class); + when(blockValues.getNumber()).thenReturn(CURRENT_BLOCK_NUMBER); + + frame = + MessageFrame.builder() + .enableEvmV2(true) + .worldUpdater(mock(WorldUpdater.class)) + .originator(Address.ZERO) + .gasPrice(Wei.ONE) + .blobGasPrice(Wei.ONE) + .blockValues(blockValues) + .miningBeneficiary(Address.ZERO) + .blockHashLookup((__, ___) -> Hash.ZERO) + .type(MessageFrame.Type.MESSAGE_CALL) + .initialGas(Long.MAX_VALUE) + .address(Address.ZERO) + .contract(Address.ZERO) + .inputData(Bytes32.ZERO) + .sender(Address.ZERO) + .value(Wei.ZERO) + .apparentValue(Wei.ZERO) + .code(Code.EMPTY_CODE) + .completer(__ -> {}) + .build(); + + blockNumberPool = new long[SAMPLE_SIZE]; + final Random random = new Random(42); + for (int i = 0; i < SAMPLE_SIZE; i++) { + blockNumberPool[i] = + switch (blockCase) { + case "IN_RANGE" -> + // Valid block in the lookback window + CURRENT_BLOCK_NUMBER - 1 - (random.nextInt(256)); + case "OUT_OF_RANGE" -> + // Outside lookback window + CURRENT_BLOCK_NUMBER - 257 - random.nextInt(100); + default -> + // Future block + CURRENT_BLOCK_NUMBER + random.nextInt(100); + }; + } + index = 0; + } + + @Benchmark + public void blockHash(final Blackhole blackhole) { + pushLong(blockNumberPool[index]); + blackhole.consume(BlockHashOperationV2.staticOperation(frame, frame.stackDataV2())); + // Pop the resulting hash + frame.setTopV2(frame.stackTopV2() - 1); + index = (index + 1) % SAMPLE_SIZE; + } + + private void pushLong(final long value) { + final long[] s = frame.stackDataV2(); + final int top = frame.stackTopV2(); + final int dst = top << 2; + s[dst] = 0L; + s[dst + 1] = 0L; + s[dst + 2] = 0L; + s[dst + 3] = value; + frame.setTopV2(top + 1); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/CallDataCopyBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/CallDataCopyBenchmarkV2.java new file mode 100644 index 00000000000..7d7eb98a42b --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/CallDataCopyBenchmarkV2.java @@ -0,0 +1,144 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import static org.mockito.Mockito.mock; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.frame.BlockValues; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator; +import org.hyperledger.besu.evm.v2.operation.CallDataCopyOperationV2; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.concurrent.TimeUnit; + +import org.apache.tuweni.bytes.Bytes; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +/** Benchmark for CALLDATACOPY EVM v2 operation. */ +@State(Scope.Thread) +@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(value = TimeUnit.NANOSECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@BenchmarkMode(Mode.AverageTime) +public class CallDataCopyBenchmarkV2 { + + protected static final int SAMPLE_SIZE = 30_000; + + @Param({"0", "100", "10240", "1048576"}) // 0 bytes, 100 bytes, 10KiB, 1MiB + public int dataSize; + + @Param({"false", "true"}) + public boolean fixedSrcDst; + + private CancunGasCalculator gasCalculator; + private long[] destOffsetPool; + private long[] srcOffsetPool; + private long[] sizePool; + private int index; + private MessageFrame frame; + + @Setup + public void setUp() { + gasCalculator = new CancunGasCalculator(); + + // Build call data payload + final byte[] data = new byte[dataSize]; + for (int i = 0; i < dataSize; i++) { + data[i] = (byte) (i % 256); + } + final Bytes callData = Bytes.wrap(data); + + frame = + MessageFrame.builder() + .enableEvmV2(true) + .worldUpdater(mock(WorldUpdater.class)) + .originator(Address.ZERO) + .gasPrice(Wei.ONE) + .blobGasPrice(Wei.ONE) + .blockValues(mock(BlockValues.class)) + .miningBeneficiary(Address.ZERO) + .blockHashLookup((__, ___) -> Hash.ZERO) + .type(MessageFrame.Type.MESSAGE_CALL) + .initialGas(Long.MAX_VALUE) + .address(Address.ZERO) + .contract(Address.ZERO) + .inputData(callData) + .sender(Address.ZERO) + .value(Wei.ZERO) + .apparentValue(Wei.ZERO) + .code(Code.EMPTY_CODE) + .completer(__ -> {}) + .build(); + + destOffsetPool = new long[SAMPLE_SIZE]; + srcOffsetPool = new long[SAMPLE_SIZE]; + sizePool = new long[SAMPLE_SIZE]; + + for (int i = 0; i < SAMPLE_SIZE; i++) { + sizePool[i] = dataSize; + if (fixedSrcDst) { + destOffsetPool[i] = 0L; + srcOffsetPool[i] = 0L; + } else { + destOffsetPool[i] = (long) ((i * 32) % 1024); + srcOffsetPool[i] = (long) (i % Math.max(1, dataSize)); + } + } + index = 0; + + // Pre-warm destination memory to avoid expansion costs during benchmark + final long maxDstOffset = 1024L + 32L; + for (long off = 0; off < maxDstOffset; off += 32) { + frame.writeMemory(off, 32, Bytes.EMPTY); + } + } + + @Benchmark + public void callDataCopy(final Blackhole blackhole) { + // Push: size, srcOffset, destOffset (stack order: destOffset is top) + pushLong(sizePool[index]); + pushLong(srcOffsetPool[index]); + pushLong(destOffsetPool[index]); + blackhole.consume( + CallDataCopyOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator)); + index = (index + 1) % SAMPLE_SIZE; + } + + private void pushLong(final long value) { + final long[] s = frame.stackDataV2(); + final int top = frame.stackTopV2(); + final int dst = top << 2; + s[dst] = 0L; + s[dst + 1] = 0L; + s[dst + 2] = 0L; + s[dst + 3] = value; + frame.setTopV2(top + 1); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ClzOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ClzOperationBenchmarkV2.java new file mode 100644 index 00000000000..5e7eededca6 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ClzOperationBenchmarkV2.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.ClzOperationV2; + +public class ClzOperationBenchmarkV2 extends UnaryOperationBenchmarkV2 { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return ClzOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/DivOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/DivOperationBenchmarkV2.java new file mode 100644 index 00000000000..16ed9ac6070 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/DivOperationBenchmarkV2.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.DivOperationV2; + +public class DivOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return DivOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/DupNOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/DupNOperationBenchmarkV2.java new file mode 100644 index 00000000000..097c3d364ea --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/DupNOperationBenchmarkV2.java @@ -0,0 +1,47 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.DupNOperation; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.DupNOperationV2; + +/** JMH benchmark for the EVM v2 DUPN operation (EIP-8024). */ +public class DupNOperationBenchmarkV2 extends ImmediateByteOperationBenchmarkV2 { + + @Override + protected int getOpcode() { + return DupNOperation.OPCODE; + } + + @Override + protected byte getImmediate() { + // Immediate 0x00 decodes to n=17 (duplicate 17th stack item) + return 0x00; + } + + @Override + protected Operation.OperationResult invoke( + final MessageFrame frame, final byte[] code, final int pc) { + return DupNOperationV2.staticOperation(frame, frame.stackDataV2(), code, pc); + } + + @Override + protected int getStackDelta() { + // DUPN adds one item to the stack + return 1; + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/EqOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/EqOperationBenchmarkV2.java new file mode 100644 index 00000000000..1f3baa470d8 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/EqOperationBenchmarkV2.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.EqOperationV2; + +public class EqOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return EqOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ExchangeOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ExchangeOperationBenchmarkV2.java new file mode 100644 index 00000000000..f2cd59bd00a --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ExchangeOperationBenchmarkV2.java @@ -0,0 +1,47 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.ExchangeOperation; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.ExchangeOperationV2; + +/** JMH benchmark for the EVM v2 EXCHANGE operation (EIP-8024). */ +public class ExchangeOperationBenchmarkV2 extends ImmediateByteOperationBenchmarkV2 { + + @Override + protected int getOpcode() { + return ExchangeOperation.OPCODE; + } + + @Override + protected byte getImmediate() { + // Immediate 0x01 gives n=1, m=2 (swap 2nd with 3rd stack item) + return 0x01; + } + + @Override + protected Operation.OperationResult invoke( + final MessageFrame frame, final byte[] code, final int pc) { + return ExchangeOperationV2.staticOperation(frame, frame.stackDataV2(), code, pc); + } + + @Override + protected int getStackDelta() { + // EXCHANGE does not change stack size + return 0; + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ExpOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ExpOperationBenchmarkV2.java new file mode 100644 index 00000000000..f312170b412 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ExpOperationBenchmarkV2.java @@ -0,0 +1,42 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.FrontierGasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.ExpOperationV2; + +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Setup; + +public class ExpOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + private static final FrontierGasCalculator FRONTIER_GAS_CALCULATOR = new FrontierGasCalculator(); + + private ExpOperationV2 op; + + @Setup(Level.Iteration) + @Override + public void setUp() { + super.setUp(); + op = new ExpOperationV2(FRONTIER_GAS_CALCULATOR); + } + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return op.execute(frame, null); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/GtOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/GtOperationBenchmarkV2.java new file mode 100644 index 00000000000..fde5cd22831 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/GtOperationBenchmarkV2.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.GtOperationV2; + +public class GtOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return GtOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ImmediateByteOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ImmediateByteOperationBenchmarkV2.java new file mode 100644 index 00000000000..1c80f6e59b5 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ImmediateByteOperationBenchmarkV2.java @@ -0,0 +1,115 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; + +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +/** + * Base benchmark class for EVM V2 operations that use immediate byte operands. + * + *
This class provides common setup for operations like DUPN, SWAPN, and EXCHANGE (EIP-8024) + * which read an immediate byte following the opcode to determine their behaviour. + */ +@State(Scope.Thread) +@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(value = TimeUnit.NANOSECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@BenchmarkMode(Mode.AverageTime) +public abstract class ImmediateByteOperationBenchmarkV2 { + + protected static final int SAMPLE_SIZE = 30_000; + protected static final int STACK_DEPTH = 50; + + protected UInt256[] valuePool; + protected int index; + protected MessageFrame frame; + protected byte[] code; + + @Setup + public void setUp() { + frame = BenchmarkHelperV2.createMessageCallFrame(); + valuePool = new UInt256[SAMPLE_SIZE]; + BenchmarkHelperV2.fillUInt256Pool(valuePool); + index = 0; + + code = new byte[] {(byte) getOpcode(), getImmediate()}; + + for (int i = 0; i < STACK_DEPTH; i++) { + BenchmarkHelperV2.pushUInt256(frame, valuePool[i]); + } + } + + /** + * Returns the opcode for this operation. + * + * @return the opcode value + */ + protected abstract int getOpcode(); + + /** + * Returns the immediate byte operand for this operation. + * + * @return the immediate byte value + */ + protected abstract byte getImmediate(); + + /** + * Invokes the operation. + * + * @param frame the message frame + * @param code the bytecode array containing opcode and immediate + * @param pc the program counter (typically 0 for benchmarks) + * @return the operation result + */ + protected abstract Operation.OperationResult invoke(MessageFrame frame, byte[] code, int pc); + + /** + * Returns the stack size change after operation execution. Positive means items were added, + * negative means items were removed. + * + * @return the stack delta + */ + protected abstract int getStackDelta(); + + @Benchmark + public void executeOperation(final Blackhole blackhole) { + blackhole.consume(invoke(frame, code, 0)); + + int delta = getStackDelta(); + if (delta > 0) { + frame.setTopV2(frame.stackTopV2() - delta); + } else if (delta < 0) { + for (int i = 0; i < -delta; i++) { + BenchmarkHelperV2.pushUInt256(frame, valuePool[index]); + index = (index + 1) % SAMPLE_SIZE; + } + } + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/Keccak256OperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/Keccak256OperationBenchmarkV2.java new file mode 100644 index 00000000000..f3056228233 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/Keccak256OperationBenchmarkV2.java @@ -0,0 +1,103 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator; +import org.hyperledger.besu.evm.v2.operation.Keccak256OperationV2; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +/** Benchmark for KECCAK256 EVM v2 operation. */ +@State(Scope.Thread) +@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(value = TimeUnit.NANOSECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@BenchmarkMode(Mode.AverageTime) +public class Keccak256OperationBenchmarkV2 { + + protected static final int SAMPLE_SIZE = 30_000; + + @Param({"32", "64", "128", "256", "512"}) + public int dataSize; + + private CancunGasCalculator gasCalculator; + private long[] offsetPool; + private long[] sizePool; + private int index; + private MessageFrame frame; + + @Setup + public void setUp() { + gasCalculator = new CancunGasCalculator(); + frame = BenchmarkHelperV2.createMessageCallFrame(); + offsetPool = new long[SAMPLE_SIZE]; + sizePool = new long[SAMPLE_SIZE]; + + final Random random = new Random(42); + // Use offsets that keep data within a bounded window to avoid memory expansion cost noise + final long maxOffset = 4096L; + for (int i = 0; i < SAMPLE_SIZE; i++) { + sizePool[i] = dataSize; + offsetPool[i] = (random.nextInt((int) (maxOffset / 32))) * 32L; + } + index = 0; + + // Pre-warm memory so expansion costs don't skew results + final byte[] data = new byte[dataSize]; + random.nextBytes(data); + for (long off = 0; off < maxOffset + dataSize; off += 32) { + frame.writeMemory(off, 32, org.apache.tuweni.bytes.Bytes.EMPTY); + } + // Write benchmark data into the region we'll be hashing + frame.writeMemory(0, dataSize, org.apache.tuweni.bytes.Bytes.wrap(data)); + } + + @Benchmark + public void keccak256(final Blackhole blackhole) { + // Push size then offset; KECCAK256 pops both and pushes hash + pushLong(sizePool[index]); + pushLong(offsetPool[index]); + blackhole.consume( + Keccak256OperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator)); + // Pop the resulting hash + frame.setTopV2(frame.stackTopV2() - 1); + index = (index + 1) % SAMPLE_SIZE; + } + + private void pushLong(final long value) { + final long[] s = frame.stackDataV2(); + final int top = frame.stackTopV2(); + final int dst = top << 2; + s[dst] = 0L; + s[dst + 1] = 0L; + s[dst + 2] = 0L; + s[dst + 3] = value; + frame.setTopV2(top + 1); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/LtOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/LtOperationBenchmarkV2.java new file mode 100644 index 00000000000..d3f6e18c53e --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/LtOperationBenchmarkV2.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.LtOperationV2; + +public class LtOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return LtOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MemoryOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MemoryOperationBenchmarkV2.java new file mode 100644 index 00000000000..3fa1a386467 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MemoryOperationBenchmarkV2.java @@ -0,0 +1,105 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator; +import org.hyperledger.besu.evm.v2.operation.MloadOperationV2; +import org.hyperledger.besu.evm.v2.operation.MstoreOperationV2; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +/** Benchmark for MSTORE and MLOAD V2 operations. */ +@State(Scope.Thread) +@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(value = TimeUnit.NANOSECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@BenchmarkMode(Mode.AverageTime) +public class MemoryOperationBenchmarkV2 { + + protected static final int SAMPLE_SIZE = 30_000; + // Use offsets within a small window to avoid unbounded memory expansion cost + private static final long MAX_OFFSET = 1024; + + private CancunGasCalculator gasCalculator; + protected UInt256[] valuePool; + protected long[] offsetPool; + protected int index; + protected MessageFrame frame; + + @Setup + public void setUp() { + gasCalculator = new CancunGasCalculator(); + frame = BenchmarkHelperV2.createMessageCallFrame(); + valuePool = new UInt256[SAMPLE_SIZE]; + offsetPool = new long[SAMPLE_SIZE]; + + BenchmarkHelperV2.fillUInt256Pool(valuePool); + final Random random = new Random(42); + for (int i = 0; i < SAMPLE_SIZE; i++) { + // Align offsets to 32-byte boundaries within the first 1KiB + offsetPool[i] = (random.nextInt((int) (MAX_OFFSET / 32))) * 32L; + } + index = 0; + + // Pre-warm memory to avoid expansion costs during benchmark + for (long off = 0; off < MAX_OFFSET + 32; off += 32) { + frame.writeMemory(off, 32, org.apache.tuweni.bytes.Bytes.EMPTY); + } + } + + @Benchmark + public void mstore(final Blackhole blackhole) { + // Push offset then value; MSTORE pops both + pushOffset(offsetPool[index]); + BenchmarkHelperV2.pushUInt256(frame, valuePool[index]); + blackhole.consume(MstoreOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator)); + index = (index + 1) % SAMPLE_SIZE; + } + + @Benchmark + public void mload(final Blackhole blackhole) { + // Push offset; MLOAD replaces it with the 32 bytes read from memory + pushOffset(offsetPool[index]); + blackhole.consume(MloadOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator)); + // MLOAD leaves one item on the stack; pop it + frame.setTopV2(frame.stackTopV2() - 1); + index = (index + 1) % SAMPLE_SIZE; + } + + private void pushOffset(final long offset) { + final long[] s = frame.stackDataV2(); + final int t = frame.stackTopV2(); + final int dst = t << 2; + s[dst] = 0; + s[dst + 1] = 0; + s[dst + 2] = 0; + s[dst + 3] = offset; + frame.setTopV2(t + 1); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ModOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ModOperationBenchmarkV2.java new file mode 100644 index 00000000000..dc982b4edbf --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ModOperationBenchmarkV2.java @@ -0,0 +1,130 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.ModOperationV2; + +import java.math.BigInteger; +import java.util.concurrent.ThreadLocalRandom; + +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Setup; + +public class ModOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + public enum Case { + MOD_32_32(1, 1), + MOD_64_32(2, 1), + MOD_64_64(2, 2), + MOD_128_32(4, 1), + MOD_128_64(4, 2), + MOD_128_128(4, 4), + MOD_192_32(6, 1), + MOD_192_64(6, 2), + MOD_192_128(6, 4), + MOD_192_192(6, 6), + MOD_256_32(8, 1), + MOD_256_64(8, 2), + MOD_256_128(8, 4), + MOD_256_192(8, 6), + MOD_256_256(8, 8), + LARGER_MOD_64_128(2, 4), + LARGER_MOD_192_256(6, 8), + ZERO_MOD_128_0(4, 0), + FULL_RANDOM(-1, -1); + + final int divSize; + final int modSize; + + Case(final int divSize, final int modSize) { + this.divSize = divSize; + this.modSize = modSize; + } + } + + @Param({ + "MOD_32_32", + "MOD_64_32", + "MOD_64_64", + "MOD_128_32", + "MOD_128_64", + "MOD_128_128", + "MOD_192_32", + "MOD_192_64", + "MOD_192_128", + "MOD_192_192", + "MOD_256_32", + "MOD_256_64", + "MOD_256_128", + "MOD_256_192", + "MOD_256_256", + "LARGER_MOD_64_128", + "LARGER_MOD_192_256", + "ZERO_MOD_128_0", + "FULL_RANDOM" + }) + private String caseName; + + @Setup(Level.Iteration) + @Override + public void setUp() { + frame = BenchmarkHelperV2.createMessageCallFrame(); + + Case scenario = Case.valueOf(caseName); + aPool = new UInt256[SAMPLE_SIZE]; + bPool = new UInt256[SAMPLE_SIZE]; + + final ThreadLocalRandom random = ThreadLocalRandom.current(); + int aSize; + int bSize; + + for (int i = 0; i < SAMPLE_SIZE; i++) { + if (scenario.divSize < 0) aSize = random.nextInt(1, 33); + else aSize = scenario.divSize * 4; + if (scenario.modSize < 0) bSize = random.nextInt(1, 33); + else bSize = scenario.modSize * 4; + + final byte[] a = new byte[aSize]; + final byte[] b = new byte[bSize]; + random.nextBytes(a); + random.nextBytes(b); + + if (scenario.divSize != scenario.modSize) { + aPool[i] = BenchmarkHelperV2.bytesToUInt256(a); + bPool[i] = BenchmarkHelperV2.bytesToUInt256(b); + } else { + BigInteger aInt = new BigInteger(a); + BigInteger bInt = new BigInteger(b); + if (aInt.compareTo(bInt) < 0) { + aPool[i] = BenchmarkHelperV2.bytesToUInt256(b); + bPool[i] = BenchmarkHelperV2.bytesToUInt256(a); + } else { + aPool[i] = BenchmarkHelperV2.bytesToUInt256(a); + bPool[i] = BenchmarkHelperV2.bytesToUInt256(b); + } + } + } + index = 0; + } + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return ModOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulModOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulModOperationBenchmarkV2.java new file mode 100644 index 00000000000..d635f404a62 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulModOperationBenchmarkV2.java @@ -0,0 +1,172 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.MulModOperationV2; + +import java.util.concurrent.ThreadLocalRandom; + +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Setup; + +public class MulModOperationBenchmarkV2 extends TernaryOperationBenchmarkV2 { + + // Benches for (a + b) % c + + // Define available scenarios + public enum Case { + MULMOD_32_32_32(1, 1, 1), + MULMOD_64_32_32(2, 1, 1), + MULMOD_64_64_32(2, 2, 1), + MULMOD_64_64_64(2, 2, 2), + MULMOD_128_32_32(4, 1, 1), + MULMOD_128_64_32(4, 2, 1), + MULMOD_128_64_64(4, 2, 2), + MULMOD_128_128_32(4, 4, 1), + MULMOD_128_128_64(4, 4, 2), + MULMOD_128_128_128(4, 4, 3), + MULMOD_192_32_32(6, 1, 1), + MULMOD_192_64_32(6, 2, 1), + MULMOD_192_64_64(6, 2, 2), + MULMOD_192_128_32(6, 4, 1), + MULMOD_192_128_64(6, 4, 2), + MULMOD_192_128_128(6, 4, 4), + MULMOD_192_192_32(6, 6, 1), + MULMOD_192_192_64(6, 6, 2), + MULMOD_192_192_128(6, 6, 4), + MULMOD_192_192_192(6, 6, 6), + MULMOD_256_32_32(8, 1, 1), + MULMOD_256_64_32(8, 2, 1), + MULMOD_256_64_64(8, 2, 2), + MULMOD_256_64_128(8, 2, 4), + MULMOD_256_64_192(8, 2, 6), + MULMOD_256_128_32(8, 4, 1), + MULMOD_256_128_64(8, 4, 2), + MULMOD_256_128_128(8, 4, 4), + MULMOD_256_192_32(8, 6, 1), + MULMOD_256_192_64(8, 6, 2), + MULMOD_256_192_128(8, 6, 4), + MULMOD_256_192_192(8, 6, 6), + MULMOD_256_256_32(8, 8, 1), + MULMOD_256_256_64(8, 8, 2), + MULMOD_256_256_128(8, 8, 4), + MULMOD_256_256_192(8, 8, 6), + MULMOD_256_256_256(8, 8, 8), + LARGER_MULMOD_64_64_128(2, 2, 4), + LARGER_MULMOD_192_192_256(6, 6, 8), + ZERO_MULMOD_128_256_0(4, 8, 0), + FULL_RANDOM(-1, -1, -1); + + final int aSize; + final int bSize; + final int cSize; + + Case(final int aSize, final int bSize, final int cSize) { + this.aSize = aSize; + this.bSize = bSize; + this.cSize = cSize; + } + } + + @Param({ + "MULMOD_32_32_32", + "MULMOD_64_32_32", + "MULMOD_64_64_32", + "MULMOD_64_64_64", + "MULMOD_128_32_32", + "MULMOD_128_64_32", + "MULMOD_128_64_64", + "MULMOD_128_128_32", + "MULMOD_128_128_64", + "MULMOD_128_128_128", + "MULMOD_192_32_32", + "MULMOD_192_64_32", + "MULMOD_192_64_64", + "MULMOD_192_128_32", + "MULMOD_192_128_64", + "MULMOD_192_128_128", + "MULMOD_192_192_32", + "MULMOD_192_192_64", + "MULMOD_192_192_128", + "MULMOD_192_192_192", + "MULMOD_256_32_32", + "MULMOD_256_64_32", + "MULMOD_256_64_64", + "MULMOD_256_64_128", + "MULMOD_256_64_192", + "MULMOD_256_128_32", + "MULMOD_256_128_64", + "MULMOD_256_128_128", + "MULMOD_256_192_32", + "MULMOD_256_192_64", + "MULMOD_256_192_128", + "MULMOD_256_192_192", + "MULMOD_256_256_32", + "MULMOD_256_256_64", + "MULMOD_256_256_128", + "MULMOD_256_256_192", + "MULMOD_256_256_256", + "LARGER_MULMOD_64_64_128", + "LARGER_MULMOD_192_192_256", + "ZERO_MULMOD_128_256_0", + "FULL_RANDOM" + }) + private String caseName; + + @Setup(Level.Iteration) + @Override + public void setUp() { + frame = BenchmarkHelperV2.createMessageCallFrame(); + + MulModOperationBenchmarkV2.Case scenario = MulModOperationBenchmarkV2.Case.valueOf(caseName); + aPool = new UInt256[SAMPLE_SIZE]; + bPool = new UInt256[SAMPLE_SIZE]; + cPool = new UInt256[SAMPLE_SIZE]; + + final ThreadLocalRandom random = ThreadLocalRandom.current(); + int aSize; + int bSize; + int cSize; + + for (int i = 0; i < SAMPLE_SIZE; i++) { + if (scenario.aSize < 0) aSize = random.nextInt(1, 33); + else aSize = scenario.aSize * 4; + if (scenario.bSize < 0) bSize = random.nextInt(1, 33); + else bSize = scenario.bSize * 4; + if (scenario.cSize < 0) cSize = random.nextInt(1, 33); + else cSize = scenario.cSize * 4; + + final byte[] a = new byte[aSize]; + final byte[] b = new byte[bSize]; + final byte[] c = new byte[cSize]; + random.nextBytes(a); + random.nextBytes(b); + random.nextBytes(c); + aPool[i] = BenchmarkHelperV2.bytesToUInt256(a); + bPool[i] = BenchmarkHelperV2.bytesToUInt256(b); + cPool[i] = BenchmarkHelperV2.bytesToUInt256(c); + } + index = 0; + } + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return MulModOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulOperationBenchmarkV2.java new file mode 100644 index 00000000000..7138bf83203 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulOperationBenchmarkV2.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.MulOperationV2; + +public class MulOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return MulOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/NotOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/NotOperationBenchmarkV2.java new file mode 100644 index 00000000000..9c94e6d9ece --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/NotOperationBenchmarkV2.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.NotOperationV2; + +public class NotOperationBenchmarkV2 extends UnaryOperationBenchmarkV2 { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return NotOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/OperandStackBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/OperandStackBenchmarkV2.java new file mode 100644 index 00000000000..43c3adffe28 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/OperandStackBenchmarkV2.java @@ -0,0 +1,80 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.v2.StackArithmetic; +import org.hyperledger.besu.evm.v2.operation.PopOperationV2; +import org.hyperledger.besu.evm.v2.operation.Push0OperationV2; + +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +/** + * Benchmarks for EVM v2 stack manipulation (POP and PUSH0) on the flat {@code long[]} stack. + * + *
Measures the cost of filling the stack to a given depth using PUSH0, then popping all items
+ * back to empty using POP.
+ */
+@State(Scope.Thread)
+@Warmup(iterations = 6, time = 2, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@OutputTimeUnit(value = TimeUnit.NANOSECONDS)
+@BenchmarkMode(Mode.AverageTime)
+public class OperandStackBenchmarkV2 {
+
+ private static final int OPERATIONS_PER_INVOCATION = 1000;
+
+ @Param({"6", "15", "34", "100", "234", "500", "800", "1024"})
+ private int stackDepth;
+
+ @Benchmark
+ @OperationsPerInvocation(OPERATIONS_PER_INVOCATION)
+ public void fillUpWithPush0() {
+ for (int i = 0; i < OPERATIONS_PER_INVOCATION; i++) {
+ MessageFrame frame = BenchmarkHelperV2.createMessageCallFrame();
+ long[] s = frame.stackDataV2();
+ for (int j = 0; j < stackDepth; j++) {
+ frame.setTopV2(StackArithmetic.pushZero(s, frame.stackTopV2()));
+ }
+ }
+ }
+
+ @Benchmark
+ @OperationsPerInvocation(OPERATIONS_PER_INVOCATION)
+ public void push0AndPop() {
+ for (int i = 0; i < OPERATIONS_PER_INVOCATION; i++) {
+ MessageFrame frame = BenchmarkHelperV2.createMessageCallFrame();
+ // Push stackDepth zeros
+ for (int j = 0; j < stackDepth; j++) {
+ Push0OperationV2.staticOperation(frame, frame.stackDataV2());
+ }
+ // Pop all of them
+ for (int j = 0; j < stackDepth; j++) {
+ PopOperationV2.staticOperation(frame);
+ }
+ }
+ }
+}
diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SDivOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SDivOperationBenchmarkV2.java
new file mode 100644
index 00000000000..40a28e4528a
--- /dev/null
+++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SDivOperationBenchmarkV2.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.ethereum.vm.operations.v2;
+
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.operation.SDivOperationV2;
+
+public class SDivOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 {
+
+ @Override
+ protected Operation.OperationResult invoke(final MessageFrame frame) {
+ return SDivOperationV2.staticOperation(frame, frame.stackDataV2());
+ }
+}
diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SModOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SModOperationBenchmarkV2.java
new file mode 100644
index 00000000000..fdb44ca562a
--- /dev/null
+++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SModOperationBenchmarkV2.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.ethereum.vm.operations.v2;
+
+import org.hyperledger.besu.evm.UInt256;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.operation.SModOperationV2;
+
+import java.math.BigInteger;
+import java.util.concurrent.ThreadLocalRandom;
+
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Setup;
+
+public class SModOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 {
+
+ public enum Case {
+ SMOD_32_32(1, 1),
+ SMOD_64_32(2, 1),
+ SMOD_64_64(2, 2),
+ SMOD_128_32(4, 1),
+ SMOD_128_64(4, 2),
+ SMOD_128_128(4, 4),
+ SMOD_192_32(6, 1),
+ SMOD_192_64(6, 2),
+ SMOD_192_128(6, 4),
+ SMOD_192_192(6, 6),
+ SMOD_256_32(8, 1),
+ SMOD_256_64(8, 2),
+ SMOD_256_128(8, 4),
+ SMOD_256_192(8, 6),
+ SMOD_256_256(8, 8),
+ LARGER_SMOD_64_128(2, 4),
+ LARGER_SMOD_192_256(6, 8),
+ ZERO_SMOD_128_0(4, 0),
+ FULL_RANDOM(-1, -1);
+
+ final int divSize;
+ final int modSize;
+
+ Case(final int divSize, final int modSize) {
+ this.divSize = divSize;
+ this.modSize = modSize;
+ }
+ }
+
+ @Param({
+ "SMOD_32_32",
+ "SMOD_64_32",
+ "SMOD_64_64",
+ "SMOD_128_32",
+ "SMOD_128_64",
+ "SMOD_128_128",
+ "SMOD_192_32",
+ "SMOD_192_64",
+ "SMOD_192_128",
+ "SMOD_192_192",
+ "SMOD_256_32",
+ "SMOD_256_64",
+ "SMOD_256_128",
+ "SMOD_256_192",
+ "SMOD_256_256",
+ "LARGER_SMOD_64_128",
+ "LARGER_SMOD_192_256",
+ "ZERO_SMOD_128_0",
+ "FULL_RANDOM"
+ })
+ private String caseName;
+
+ @Setup(Level.Iteration)
+ @Override
+ public void setUp() {
+ frame = BenchmarkHelperV2.createMessageCallFrame();
+
+ Case scenario = Case.valueOf(caseName);
+ aPool = new UInt256[SAMPLE_SIZE];
+ bPool = new UInt256[SAMPLE_SIZE];
+
+ final ThreadLocalRandom random = ThreadLocalRandom.current();
+ int aSize;
+ int bSize;
+
+ for (int i = 0; i < SAMPLE_SIZE; i++) {
+ if (scenario.divSize < 0) aSize = random.nextInt(1, 33);
+ else aSize = scenario.divSize * 4;
+ if (scenario.modSize < 0) bSize = random.nextInt(1, 33);
+ else bSize = scenario.modSize * 4;
+
+ final byte[] a = new byte[aSize];
+ final byte[] b = new byte[bSize];
+ random.nextBytes(a);
+ random.nextBytes(b);
+
+ if (scenario.divSize != scenario.modSize) {
+ aPool[i] = BenchmarkHelperV2.bytesToUInt256(a);
+ bPool[i] = BenchmarkHelperV2.bytesToUInt256(b);
+ } else {
+ BigInteger aInt = new BigInteger(a);
+ BigInteger bInt = new BigInteger(b);
+ if (aInt.abs().compareTo(bInt.abs()) < 0) {
+ aPool[i] = BenchmarkHelperV2.bytesToUInt256(b);
+ bPool[i] = BenchmarkHelperV2.bytesToUInt256(a);
+ } else {
+ aPool[i] = BenchmarkHelperV2.bytesToUInt256(a);
+ bPool[i] = BenchmarkHelperV2.bytesToUInt256(b);
+ }
+ }
+ }
+ index = 0;
+ }
+
+ @Override
+ protected Operation.OperationResult invoke(final MessageFrame frame) {
+ return SModOperationV2.staticOperation(frame, frame.stackDataV2());
+ }
+}
diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SarOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SarOperationBenchmarkV2.java
new file mode 100644
index 00000000000..e617026a236
--- /dev/null
+++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SarOperationBenchmarkV2.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.ethereum.vm.operations.v2;
+
+import static org.hyperledger.besu.ethereum.vm.operations.v2.BenchmarkHelperV2.bytesToUInt256;
+import static org.hyperledger.besu.ethereum.vm.operations.v2.BenchmarkHelperV2.randomNegativeUInt256Value;
+import static org.hyperledger.besu.ethereum.vm.operations.v2.BenchmarkHelperV2.randomPositiveUInt256Value;
+import static org.hyperledger.besu.ethereum.vm.operations.v2.BenchmarkHelperV2.randomUInt256Value;
+
+import org.hyperledger.besu.evm.UInt256;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.operation.SarOperationV2;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Setup;
+
+public class SarOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 {
+
+ /** Test cases covering different execution paths for SAR operations. */
+ public enum Case {
+ /** Shift by 0 - early return path. */
+ SHIFT_0,
+ /** Negative number with shift=1 - tests sign extension. */
+ NEGATIVE_SHIFT_1,
+ /** Value with all bits set with shift=1. */
+ ALL_BITS_SHIFT_1,
+ /** Positive number with shift=1 - no sign extension needed. */
+ POSITIVE_SHIFT_1,
+ /** Negative number with medium shift. */
+ NEGATIVE_SHIFT_128,
+ /** Negative number with max shift. */
+ NEGATIVE_SHIFT_255,
+ /** Positive number with medium shift. */
+ POSITIVE_SHIFT_128,
+ /** Positive number with max shift. */
+ POSITIVE_SHIFT_255,
+ /** Overflow: shift >= 256. */
+ OVERFLOW_SHIFT_256,
+ /** Overflow: shift amount > 4 bytes. */
+ OVERFLOW_LARGE_SHIFT,
+ /** Random values. */
+ FULL_RANDOM
+ }
+
+ @Param({
+ "SHIFT_0",
+ "NEGATIVE_SHIFT_1",
+ "POSITIVE_SHIFT_1",
+ "ALL_BITS_SHIFT_1",
+ "NEGATIVE_SHIFT_128",
+ "NEGATIVE_SHIFT_255",
+ "POSITIVE_SHIFT_128",
+ "POSITIVE_SHIFT_255",
+ "OVERFLOW_SHIFT_256",
+ "OVERFLOW_LARGE_SHIFT",
+ "FULL_RANDOM"
+ })
+ protected String caseName;
+
+ @Setup(Level.Iteration)
+ @Override
+ public void setUp() {
+ frame = BenchmarkHelperV2.createMessageCallFrame();
+
+ final Case scenario = Case.valueOf(caseName);
+ aPool = new UInt256[SAMPLE_SIZE];
+ bPool = new UInt256[SAMPLE_SIZE];
+
+ final ThreadLocalRandom random = ThreadLocalRandom.current();
+
+ for (int i = 0; i < SAMPLE_SIZE; i++) {
+ switch (scenario) {
+ case SHIFT_0:
+ aPool[i] = UInt256.fromInt(0);
+ bPool[i] = randomUInt256Value(random);
+ break;
+
+ case NEGATIVE_SHIFT_1:
+ aPool[i] = UInt256.fromInt(1);
+ bPool[i] = randomNegativeUInt256Value(random);
+ break;
+
+ case ALL_BITS_SHIFT_1:
+ aPool[i] = UInt256.fromInt(1);
+ bPool[i] = UInt256.MAX;
+ break;
+
+ case POSITIVE_SHIFT_1:
+ aPool[i] = UInt256.fromInt(1);
+ bPool[i] = randomPositiveUInt256Value(random);
+ break;
+
+ case NEGATIVE_SHIFT_128:
+ aPool[i] = UInt256.fromInt(128);
+ bPool[i] = randomNegativeUInt256Value(random);
+ break;
+
+ case NEGATIVE_SHIFT_255:
+ aPool[i] = UInt256.fromInt(255);
+ bPool[i] = randomNegativeUInt256Value(random);
+ break;
+
+ case POSITIVE_SHIFT_128:
+ aPool[i] = UInt256.fromInt(128);
+ bPool[i] = randomPositiveUInt256Value(random);
+ break;
+ case POSITIVE_SHIFT_255:
+ aPool[i] = UInt256.fromInt(255);
+ bPool[i] = randomPositiveUInt256Value(random);
+ break;
+
+ case OVERFLOW_SHIFT_256:
+ aPool[i] = UInt256.fromInt(256);
+ bPool[i] = randomUInt256Value(random);
+ break;
+
+ case OVERFLOW_LARGE_SHIFT:
+ aPool[i] = bytesToUInt256(new byte[] {0x01, 0x00, 0x00, 0x00, 0x00, 0x00});
+ bPool[i] = randomUInt256Value(random);
+ break;
+
+ case FULL_RANDOM:
+ default:
+ final byte[] shift = new byte[1 + random.nextInt(2)];
+ final byte[] value = new byte[1 + random.nextInt(32)];
+ random.nextBytes(shift);
+ random.nextBytes(value);
+ aPool[i] = bytesToUInt256(shift);
+ bPool[i] = bytesToUInt256(value);
+ break;
+ }
+ }
+ index = 0;
+ }
+
+ @Override
+ protected Operation.OperationResult invoke(final MessageFrame frame) {
+ return SarOperationV2.staticOperation(frame, frame.stackDataV2());
+ }
+}
diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SelfBalanceOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SelfBalanceOperationBenchmarkV2.java
new file mode 100644
index 00000000000..ce17e9d12e7
--- /dev/null
+++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SelfBalanceOperationBenchmarkV2.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.ethereum.vm.operations.v2;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.hyperledger.besu.datatypes.Address;
+import org.hyperledger.besu.datatypes.Wei;
+import org.hyperledger.besu.evm.account.MutableAccount;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.v2.operation.SelfBalanceOperationV2;
+import org.hyperledger.besu.evm.worldstate.WorldUpdater;
+
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+
+/** Benchmark for SELFBALANCE EVM v2 operation. */
+@State(Scope.Thread)
+@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS)
+@OutputTimeUnit(value = TimeUnit.NANOSECONDS)
+@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@BenchmarkMode(Mode.AverageTime)
+public class SelfBalanceOperationBenchmarkV2 {
+
+ protected static final int SAMPLE_SIZE = 30_000;
+
+ private MessageFrame frame;
+
+ @Setup
+ public void setUp() {
+ // Set up a mock world updater returning a non-null account with a balance
+ final WorldUpdater worldUpdater = mock(WorldUpdater.class);
+ final MutableAccount account = mock(MutableAccount.class);
+ final byte[] balanceBytes = new byte[32];
+ new Random(42).nextBytes(balanceBytes);
+ final Wei balance = Wei.of(new java.math.BigInteger(1, balanceBytes));
+ when(account.getBalance()).thenReturn(balance);
+
+ frame =
+ MessageFrame.builder()
+ .enableEvmV2(true)
+ .worldUpdater(worldUpdater)
+ .originator(Address.ZERO)
+ .gasPrice(Wei.ONE)
+ .blobGasPrice(Wei.ONE)
+ .blockValues(mock(org.hyperledger.besu.evm.frame.BlockValues.class))
+ .miningBeneficiary(Address.ZERO)
+ .blockHashLookup((__, ___) -> org.hyperledger.besu.datatypes.Hash.ZERO)
+ .type(MessageFrame.Type.MESSAGE_CALL)
+ .initialGas(Long.MAX_VALUE)
+ .address(Address.ZERO)
+ .contract(Address.ZERO)
+ .inputData(org.apache.tuweni.bytes.Bytes32.ZERO)
+ .sender(Address.ZERO)
+ .value(Wei.ZERO)
+ .apparentValue(Wei.ZERO)
+ .code(org.hyperledger.besu.evm.Code.EMPTY_CODE)
+ .completer(__ -> {})
+ .build();
+
+ // Configure world updater to return the account for Address.ZERO (frame's contract address)
+ when(worldUpdater.getAccount(Address.ZERO)).thenReturn(account);
+ }
+
+ @Benchmark
+ public void selfBalance(final Blackhole blackhole) {
+ blackhole.consume(SelfBalanceOperationV2.staticOperation(frame, frame.stackDataV2()));
+ // Pop the resulting balance
+ frame.setTopV2(frame.stackTopV2() - 1);
+ }
+}
diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SgtOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SgtOperationBenchmarkV2.java
new file mode 100644
index 00000000000..741b075daea
--- /dev/null
+++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SgtOperationBenchmarkV2.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.ethereum.vm.operations.v2;
+
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.operation.SgtOperationV2;
+
+public class SgtOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 {
+
+ @Override
+ protected Operation.OperationResult invoke(final MessageFrame frame) {
+ return SgtOperationV2.staticOperation(frame, frame.stackDataV2());
+ }
+}
diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ShlOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ShlOperationBenchmarkV2.java
new file mode 100644
index 00000000000..117f8148036
--- /dev/null
+++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ShlOperationBenchmarkV2.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.ethereum.vm.operations.v2;
+
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.operation.ShlOperationV2;
+
+public class ShlOperationBenchmarkV2 extends AbstractShiftOperationBenchmarkV2 {
+
+ @Override
+ protected Operation.OperationResult invoke(final MessageFrame frame) {
+ return ShlOperationV2.staticOperation(frame, frame.stackDataV2());
+ }
+}
diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ShrOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ShrOperationBenchmarkV2.java
new file mode 100644
index 00000000000..fa3dd952609
--- /dev/null
+++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ShrOperationBenchmarkV2.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.ethereum.vm.operations.v2;
+
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.operation.ShrOperationV2;
+
+public class ShrOperationBenchmarkV2 extends AbstractShiftOperationBenchmarkV2 {
+
+ @Override
+ protected Operation.OperationResult invoke(final MessageFrame frame) {
+ return ShrOperationV2.staticOperation(frame, frame.stackDataV2());
+ }
+}
diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SltOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SltOperationBenchmarkV2.java
new file mode 100644
index 00000000000..85faa762750
--- /dev/null
+++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SltOperationBenchmarkV2.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.ethereum.vm.operations.v2;
+
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.operation.SltOperationV2;
+
+public class SltOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 {
+
+ @Override
+ protected Operation.OperationResult invoke(final MessageFrame frame) {
+ return SltOperationV2.staticOperation(frame, frame.stackDataV2());
+ }
+}
diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SubOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SubOperationBenchmarkV2.java
new file mode 100644
index 00000000000..bae063fbccd
--- /dev/null
+++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SubOperationBenchmarkV2.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.ethereum.vm.operations.v2;
+
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.operation.SubOperationV2;
+
+public class SubOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 {
+
+ @Override
+ protected Operation.OperationResult invoke(final MessageFrame frame) {
+ return SubOperationV2.staticOperation(frame, frame.stackDataV2());
+ }
+}
diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SwapNOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SwapNOperationBenchmarkV2.java
new file mode 100644
index 00000000000..b3a9adf0c3a
--- /dev/null
+++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SwapNOperationBenchmarkV2.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.ethereum.vm.operations.v2;
+
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.operation.SwapNOperation;
+import org.hyperledger.besu.evm.v2.operation.SwapNOperationV2;
+
+/** JMH benchmark for the EVM v2 SWAPN operation (EIP-8024). */
+public class SwapNOperationBenchmarkV2 extends ImmediateByteOperationBenchmarkV2 {
+
+ @Override
+ protected int getOpcode() {
+ return SwapNOperation.OPCODE;
+ }
+
+ @Override
+ protected byte getImmediate() {
+ // Immediate 0x00 decodes to n=17 (swap top with 18th stack item)
+ return 0x00;
+ }
+
+ @Override
+ protected Operation.OperationResult invoke(
+ final MessageFrame frame, final byte[] code, final int pc) {
+ return SwapNOperationV2.staticOperation(frame, frame.stackDataV2(), code, pc);
+ }
+
+ @Override
+ protected int getStackDelta() {
+ // SWAPN does not change stack size
+ return 0;
+ }
+}
diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/TernaryOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/TernaryOperationBenchmarkV2.java
new file mode 100644
index 00000000000..857402a31c0
--- /dev/null
+++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/TernaryOperationBenchmarkV2.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.ethereum.vm.operations.v2;
+
+import org.hyperledger.besu.evm.UInt256;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.operation.Operation;
+
+import java.util.concurrent.TimeUnit;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+
+@State(Scope.Thread)
+@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS)
+@OutputTimeUnit(value = TimeUnit.NANOSECONDS)
+@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@BenchmarkMode(Mode.AverageTime)
+public abstract class TernaryOperationBenchmarkV2 {
+
+ protected static final int SAMPLE_SIZE = 30_000;
+
+ protected UInt256[] aPool;
+ protected UInt256[] bPool;
+ protected UInt256[] cPool;
+ protected int index;
+ protected MessageFrame frame;
+
+ @Setup()
+ public void setUp() {
+ frame = BenchmarkHelperV2.createMessageCallFrame();
+ aPool = new UInt256[SAMPLE_SIZE];
+ bPool = new UInt256[SAMPLE_SIZE];
+ cPool = new UInt256[SAMPLE_SIZE];
+ BenchmarkHelperV2.fillUInt256Pool(aPool);
+ BenchmarkHelperV2.fillUInt256Pool(bPool);
+ BenchmarkHelperV2.fillUInt256Pool(cPool);
+ index = 0;
+ }
+
+ @Benchmark
+ public void executeOperation(final Blackhole blackhole) {
+ BenchmarkHelperV2.pushUInt256(frame, cPool[index]);
+ BenchmarkHelperV2.pushUInt256(frame, bPool[index]);
+ BenchmarkHelperV2.pushUInt256(frame, aPool[index]);
+
+ blackhole.consume(invoke(frame));
+
+ frame.setTopV2(frame.stackTopV2() - 1);
+
+ index = (index + 1) % SAMPLE_SIZE;
+ }
+
+ protected abstract Operation.OperationResult invoke(MessageFrame frame);
+}
diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/TransientStorageOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/TransientStorageOperationBenchmarkV2.java
new file mode 100644
index 00000000000..d5f9ada11e5
--- /dev/null
+++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/TransientStorageOperationBenchmarkV2.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.ethereum.vm.operations.v2;
+
+import org.hyperledger.besu.evm.UInt256;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.v2.operation.TLoadOperationV2;
+import org.hyperledger.besu.evm.v2.operation.TStoreOperationV2;
+
+import java.util.concurrent.TimeUnit;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+
+/** Benchmarks for TLOAD and TSTORE EVM v2 operations on transient storage. */
+@State(Scope.Thread)
+@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
+@OutputTimeUnit(value = TimeUnit.NANOSECONDS)
+@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@BenchmarkMode(Mode.AverageTime)
+public class TransientStorageOperationBenchmarkV2 {
+
+ private static final int SAMPLE_SIZE = 30_000;
+
+ private UInt256[] keyPool;
+ private UInt256[] valuePool;
+ private int index;
+ private MessageFrame frame;
+
+ @Setup
+ public void setUp() {
+ frame = BenchmarkHelperV2.createMessageCallFrame();
+ keyPool = new UInt256[SAMPLE_SIZE];
+ valuePool = new UInt256[SAMPLE_SIZE];
+ BenchmarkHelperV2.fillUInt256Pool(keyPool);
+ BenchmarkHelperV2.fillUInt256Pool(valuePool);
+ index = 0;
+ }
+
+ /** Benchmark TSTORE followed by TLOAD (round-trip). */
+ @Benchmark
+ public void tStoreAndLoad(final Blackhole blackhole) {
+ final long[] s = frame.stackDataV2();
+
+ // TSTORE: depth 0 = key, depth 1 = value
+ BenchmarkHelperV2.pushUInt256(frame, valuePool[index]);
+ BenchmarkHelperV2.pushUInt256(frame, keyPool[index]);
+ blackhole.consume(TStoreOperationV2.staticOperation(frame, s));
+
+ // TLOAD: depth 0 = key; overwrites top slot with loaded value
+ BenchmarkHelperV2.pushUInt256(frame, keyPool[index]);
+ blackhole.consume(TLoadOperationV2.staticOperation(frame, s));
+
+ // Clean up the loaded value left on the stack
+ frame.setTopV2(frame.stackTopV2() - 1);
+
+ index = (index + 1) % SAMPLE_SIZE;
+ }
+
+ /** Benchmark TSTORE only. */
+ @Benchmark
+ public void tStoreOnly(final Blackhole blackhole) {
+ final long[] s = frame.stackDataV2();
+
+ // TSTORE: depth 0 = key, depth 1 = value
+ BenchmarkHelperV2.pushUInt256(frame, valuePool[index]);
+ BenchmarkHelperV2.pushUInt256(frame, keyPool[index]);
+ blackhole.consume(TStoreOperationV2.staticOperation(frame, s));
+
+ index = (index + 1) % SAMPLE_SIZE;
+ }
+
+ /** Benchmark TLOAD only (pre-stores a value to ensure a non-empty result). */
+ @Benchmark
+ public void tLoadOnly(final Blackhole blackhole) {
+ final long[] s = frame.stackDataV2();
+
+ // Pre-store a value for the key (not measured by JMH — this is setup work within the method)
+ BenchmarkHelperV2.pushUInt256(frame, valuePool[index]);
+ BenchmarkHelperV2.pushUInt256(frame, keyPool[index]);
+ TStoreOperationV2.staticOperation(frame, s);
+
+ // TLOAD: depth 0 = key; overwrites top slot with loaded value
+ BenchmarkHelperV2.pushUInt256(frame, keyPool[index]);
+ blackhole.consume(TLoadOperationV2.staticOperation(frame, s));
+
+ // Clean up the loaded value left on the stack
+ frame.setTopV2(frame.stackTopV2() - 1);
+
+ index = (index + 1) % SAMPLE_SIZE;
+ }
+}
diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/UnaryOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/UnaryOperationBenchmarkV2.java
new file mode 100644
index 00000000000..fc0eeb243e8
--- /dev/null
+++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/UnaryOperationBenchmarkV2.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.ethereum.vm.operations.v2;
+
+import org.hyperledger.besu.evm.UInt256;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.operation.Operation;
+
+import java.util.concurrent.TimeUnit;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+
+@State(Scope.Thread)
+@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
+@OutputTimeUnit(value = TimeUnit.NANOSECONDS)
+@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@BenchmarkMode(Mode.AverageTime)
+public abstract class UnaryOperationBenchmarkV2 {
+
+ protected static final int SAMPLE_SIZE = 30_000;
+
+ protected UInt256[] aPool;
+ protected int index;
+ protected MessageFrame frame;
+
+ @Setup()
+ public void setUp() {
+ frame = BenchmarkHelperV2.createMessageCallFrame();
+ aPool = new UInt256[SAMPLE_SIZE];
+ BenchmarkHelperV2.fillUInt256Pool(aPool);
+ index = 0;
+ }
+
+ @Benchmark
+ public void executeOperation(final Blackhole blackhole) {
+ BenchmarkHelperV2.pushUInt256(frame, aPool[index]);
+
+ blackhole.consume(invoke(frame));
+
+ frame.setTopV2(frame.stackTopV2() - 1);
+
+ index = (index + 1) % SAMPLE_SIZE;
+ }
+
+ protected abstract Operation.OperationResult invoke(MessageFrame frame);
+}
diff --git a/ethereum/referencetests/build.gradle b/ethereum/referencetests/build.gradle
index 3aba4c6cbee..820374d8203 100644
--- a/ethereum/referencetests/build.gradle
+++ b/ethereum/referencetests/build.gradle
@@ -330,6 +330,16 @@ dependencies {
tasks.register('referenceTests', Test) {
useJUnitPlatform()
+ for (String name : [
+ 'test.evm.v2',
+ 'test.ethereum.state.eips',
+ 'test.ethereum.blockchain.eips',
+ 'test.ethereum.include'
+ ]) {
+ if (System.getProperty(name) != null) {
+ systemProperty name, System.getProperty(name)
+ }
+ }
doFirst {
if (!file("src/reference-test/external-resources/README.md").exists()) {
throw new GradleException("ethereum/referencetest/src/reference-test/external-resources/README.md missing: please clone submodules (git submodule update --init --recursive)")
@@ -342,6 +352,16 @@ tasks.register('referenceTests', Test) {
tasks.register('referenceTestsDevnet', Test) {
useJUnitPlatform()
+ for (String name : [
+ 'test.evm.v2',
+ 'test.ethereum.state.eips',
+ 'test.ethereum.blockchain.eips',
+ 'test.ethereum.include'
+ ]) {
+ if (System.getProperty(name) != null) {
+ systemProperty name, System.getProperty(name)
+ }
+ }
description = 'Runs execution-spec-tests pre-release (devnet) reference tests.'
testClassesDirs = sourceSets.referenceTestDevnet.output.classesDirs
classpath = sourceSets.referenceTestDevnet.runtimeClasspath
diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java
index 74a0abee69f..354d2a50f44 100644
--- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java
+++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java
@@ -63,7 +63,16 @@ public class ReferenceTestProtocolSchedules {
"eip158tobyzantiumat5");
public static ReferenceTestProtocolSchedules create() {
- return create(new StubGenesisConfigOptions(), EvmConfiguration.DEFAULT);
+ final boolean evmV2 = Boolean.getBoolean("test.evm.v2");
+ final EvmConfiguration evmConfiguration =
+ evmV2
+ ? new EvmConfiguration(
+ EvmConfiguration.DEFAULT.jumpDestCacheWeightKB(),
+ EvmConfiguration.DEFAULT.worldUpdaterMode(),
+ EvmConfiguration.DEFAULT.enableOptimizedOpcodes(),
+ true)
+ : EvmConfiguration.DEFAULT;
+ return create(new StubGenesisConfigOptions(), evmConfiguration);
}
public static ReferenceTestProtocolSchedules create(final EvmConfiguration evmConfiguration) {
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java
index 99c4eb8014b..47b93809356 100644
--- a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java
+++ b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java
@@ -27,6 +27,8 @@
import org.hyperledger.besu.evm.internal.JumpDestOnlyCodeCache;
import org.hyperledger.besu.evm.internal.OverflowException;
import org.hyperledger.besu.evm.internal.UnderflowException;
+import org.hyperledger.besu.evm.log.EIP7708TransferLogEmitter;
+import org.hyperledger.besu.evm.log.TransferLogEmitter;
import org.hyperledger.besu.evm.operation.AddModOperation;
import org.hyperledger.besu.evm.operation.AddModOperationOptimized;
import org.hyperledger.besu.evm.operation.AddOperation;
@@ -86,8 +88,98 @@
import org.hyperledger.besu.evm.operation.VirtualOperation;
import org.hyperledger.besu.evm.operation.XorOperation;
import org.hyperledger.besu.evm.operation.XorOperationOptimized;
-import org.hyperledger.besu.evm.operation.v2.AddOperationV2;
import org.hyperledger.besu.evm.tracing.OperationTracer;
+import org.hyperledger.besu.evm.v2.operation.AddModOperationV2;
+import org.hyperledger.besu.evm.v2.operation.AddOperationV2;
+import org.hyperledger.besu.evm.v2.operation.AddressOperationV2;
+import org.hyperledger.besu.evm.v2.operation.AndOperationV2;
+import org.hyperledger.besu.evm.v2.operation.BalanceOperationV2;
+import org.hyperledger.besu.evm.v2.operation.BaseFeeOperationV2;
+import org.hyperledger.besu.evm.v2.operation.BlobBaseFeeOperationV2;
+import org.hyperledger.besu.evm.v2.operation.BlobHashOperationV2;
+import org.hyperledger.besu.evm.v2.operation.BlockHashOperationV2;
+import org.hyperledger.besu.evm.v2.operation.ByteOperationV2;
+import org.hyperledger.besu.evm.v2.operation.CallCodeOperationV2;
+import org.hyperledger.besu.evm.v2.operation.CallDataCopyOperationV2;
+import org.hyperledger.besu.evm.v2.operation.CallDataLoadOperationV2;
+import org.hyperledger.besu.evm.v2.operation.CallDataSizeOperationV2;
+import org.hyperledger.besu.evm.v2.operation.CallOperationV2;
+import org.hyperledger.besu.evm.v2.operation.CallValueOperationV2;
+import org.hyperledger.besu.evm.v2.operation.CallerOperationV2;
+import org.hyperledger.besu.evm.v2.operation.ChainIdOperationV2;
+import org.hyperledger.besu.evm.v2.operation.ClzOperationV2;
+import org.hyperledger.besu.evm.v2.operation.CodeCopyOperationV2;
+import org.hyperledger.besu.evm.v2.operation.CodeSizeOperationV2;
+import org.hyperledger.besu.evm.v2.operation.CoinbaseOperationV2;
+import org.hyperledger.besu.evm.v2.operation.Create2OperationV2;
+import org.hyperledger.besu.evm.v2.operation.CreateOperationV2;
+import org.hyperledger.besu.evm.v2.operation.DelegateCallOperationV2;
+import org.hyperledger.besu.evm.v2.operation.DifficultyOperationV2;
+import org.hyperledger.besu.evm.v2.operation.DivOperationV2;
+import org.hyperledger.besu.evm.v2.operation.DupNOperationV2;
+import org.hyperledger.besu.evm.v2.operation.DupOperationV2;
+import org.hyperledger.besu.evm.v2.operation.EqOperationV2;
+import org.hyperledger.besu.evm.v2.operation.ExchangeOperationV2;
+import org.hyperledger.besu.evm.v2.operation.ExpOperationV2;
+import org.hyperledger.besu.evm.v2.operation.ExtCodeCopyOperationV2;
+import org.hyperledger.besu.evm.v2.operation.ExtCodeHashOperationV2;
+import org.hyperledger.besu.evm.v2.operation.ExtCodeSizeOperationV2;
+import org.hyperledger.besu.evm.v2.operation.GasLimitOperationV2;
+import org.hyperledger.besu.evm.v2.operation.GasOperationV2;
+import org.hyperledger.besu.evm.v2.operation.GasPriceOperationV2;
+import org.hyperledger.besu.evm.v2.operation.GtOperationV2;
+import org.hyperledger.besu.evm.v2.operation.InvalidOperationV2;
+import org.hyperledger.besu.evm.v2.operation.IsZeroOperationV2;
+import org.hyperledger.besu.evm.v2.operation.JumpDestOperationV2;
+import org.hyperledger.besu.evm.v2.operation.JumpOperationV2;
+import org.hyperledger.besu.evm.v2.operation.JumpiOperationV2;
+import org.hyperledger.besu.evm.v2.operation.Keccak256OperationV2;
+import org.hyperledger.besu.evm.v2.operation.LogOperationV2;
+import org.hyperledger.besu.evm.v2.operation.LtOperationV2;
+import org.hyperledger.besu.evm.v2.operation.MCopyOperationV2;
+import org.hyperledger.besu.evm.v2.operation.MSizeOperationV2;
+import org.hyperledger.besu.evm.v2.operation.MloadOperationV2;
+import org.hyperledger.besu.evm.v2.operation.ModOperationV2;
+import org.hyperledger.besu.evm.v2.operation.Mstore8OperationV2;
+import org.hyperledger.besu.evm.v2.operation.MstoreOperationV2;
+import org.hyperledger.besu.evm.v2.operation.MulModOperationV2;
+import org.hyperledger.besu.evm.v2.operation.MulOperationV2;
+import org.hyperledger.besu.evm.v2.operation.NotOperationV2;
+import org.hyperledger.besu.evm.v2.operation.NumberOperationV2;
+import org.hyperledger.besu.evm.v2.operation.OrOperationV2;
+import org.hyperledger.besu.evm.v2.operation.OriginOperationV2;
+import org.hyperledger.besu.evm.v2.operation.PayOperationV2;
+import org.hyperledger.besu.evm.v2.operation.PcOperationV2;
+import org.hyperledger.besu.evm.v2.operation.PopOperationV2;
+import org.hyperledger.besu.evm.v2.operation.PrevRandaoOperationV2;
+import org.hyperledger.besu.evm.v2.operation.Push0OperationV2;
+import org.hyperledger.besu.evm.v2.operation.PushOperationV2;
+import org.hyperledger.besu.evm.v2.operation.ReturnDataCopyOperationV2;
+import org.hyperledger.besu.evm.v2.operation.ReturnDataSizeOperationV2;
+import org.hyperledger.besu.evm.v2.operation.ReturnOperationV2;
+import org.hyperledger.besu.evm.v2.operation.RevertOperationV2;
+import org.hyperledger.besu.evm.v2.operation.SDivOperationV2;
+import org.hyperledger.besu.evm.v2.operation.SLoadOperationV2;
+import org.hyperledger.besu.evm.v2.operation.SModOperationV2;
+import org.hyperledger.besu.evm.v2.operation.SStoreOperationV2;
+import org.hyperledger.besu.evm.v2.operation.SarOperationV2;
+import org.hyperledger.besu.evm.v2.operation.SelfBalanceOperationV2;
+import org.hyperledger.besu.evm.v2.operation.SelfDestructOperationV2;
+import org.hyperledger.besu.evm.v2.operation.SgtOperationV2;
+import org.hyperledger.besu.evm.v2.operation.ShlOperationV2;
+import org.hyperledger.besu.evm.v2.operation.ShrOperationV2;
+import org.hyperledger.besu.evm.v2.operation.SignExtendOperationV2;
+import org.hyperledger.besu.evm.v2.operation.SlotNumOperationV2;
+import org.hyperledger.besu.evm.v2.operation.SltOperationV2;
+import org.hyperledger.besu.evm.v2.operation.StaticCallOperationV2;
+import org.hyperledger.besu.evm.v2.operation.StopOperationV2;
+import org.hyperledger.besu.evm.v2.operation.SubOperationV2;
+import org.hyperledger.besu.evm.v2.operation.SwapNOperationV2;
+import org.hyperledger.besu.evm.v2.operation.SwapOperationV2;
+import org.hyperledger.besu.evm.v2.operation.TLoadOperationV2;
+import org.hyperledger.besu.evm.v2.operation.TStoreOperationV2;
+import org.hyperledger.besu.evm.v2.operation.TimestampOperationV2;
+import org.hyperledger.besu.evm.v2.operation.XorOperationV2;
import java.util.Optional;
import java.util.function.Function;
@@ -115,11 +207,20 @@ public class EVM {
private final EvmSpecVersion evmSpecVersion;
// Optimized operation flags
+ private final boolean enableByzantium;
private final boolean enableConstantinople;
+ private final boolean enableIstanbul;
+ private final boolean enableLondon;
+ private final boolean enableParis;
private final boolean enableShanghai;
+ private final boolean enableCancun;
private final boolean enableAmsterdam;
private final boolean enableOsaka;
+ // V2 operation instances that require constructor arguments
+ private final ChainIdOperationV2 chainIdOperationV2;
+ private final GasOperationV2 gasOperationV2;
+
private final JumpDestOnlyCodeCache jumpDestOnlyCodeCache;
/**
@@ -142,10 +243,25 @@ public EVM(
this.evmSpecVersion = evmSpecVersion;
this.jumpDestOnlyCodeCache = new JumpDestOnlyCodeCache(evmConfiguration);
+ enableByzantium = EvmSpecVersion.BYZANTIUM.ordinal() <= evmSpecVersion.ordinal();
enableConstantinople = EvmSpecVersion.CONSTANTINOPLE.ordinal() <= evmSpecVersion.ordinal();
+ enableIstanbul = EvmSpecVersion.ISTANBUL.ordinal() <= evmSpecVersion.ordinal();
+ enableLondon = EvmSpecVersion.LONDON.ordinal() <= evmSpecVersion.ordinal();
+ enableParis = EvmSpecVersion.PARIS.ordinal() <= evmSpecVersion.ordinal();
enableShanghai = EvmSpecVersion.SHANGHAI.ordinal() <= evmSpecVersion.ordinal();
+ enableCancun = EvmSpecVersion.CANCUN.ordinal() <= evmSpecVersion.ordinal();
enableAmsterdam = EvmSpecVersion.AMSTERDAM.ordinal() <= evmSpecVersion.ordinal();
enableOsaka = EvmSpecVersion.OSAKA.ordinal() <= evmSpecVersion.ordinal();
+
+ // Pre-compute V2 operation instances that require constructor arguments.
+ // ChainIdOperation is only registered for Istanbul+, so the instanceof check is the gate.
+ Operation chainIdOp = operations.get(ChainIdOperation.OPCODE);
+ if (chainIdOp instanceof ChainIdOperation cid) {
+ chainIdOperationV2 = new ChainIdOperationV2(gasCalculator, cid.getChainId());
+ } else {
+ chainIdOperationV2 = null;
+ }
+ gasOperationV2 = new GasOperationV2(gasCalculator);
}
/**
@@ -220,6 +336,7 @@ public Optional All methods take {@code (long[] s, int top)} and return the new {@code top}. The caller
+ * (operation) is responsible for underflow/overflow checks before calling.
+ */
+public class StackArithmetic {
+
+ private static final VarHandle LONG_BE =
+ MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN);
+ private static final VarHandle INT_BE =
+ MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.BIG_ENDIAN);
+
+ /** Utility class — not instantiable. */
+ private StackArithmetic() {}
+
+ // region SHL (Shift Left)
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Performs EVM SHL (shift left) on the two top stack items.
+ *
+ * Pops the shift amount (unsigned) and the value, pushes {@code value << shift}. Shifts >= 256
+ * or a zero value produce 0.
+ *
+ * @param stack the flat limb array
+ * @param top current stack-top (item count)
+ * @return the new stack-top after consuming one item
+ */
+ public static int shl(final long[] stack, final int top) {
+ final int shiftOffset = (top - 1) << 2;
+ final int valueOffset = (top - 2) << 2;
+ // If shift amount > 255 or value is zero, result is zero
+ if (stack[shiftOffset] != 0
+ || stack[shiftOffset + 1] != 0
+ || stack[shiftOffset + 2] != 0
+ || Long.compareUnsigned(stack[shiftOffset + 3], 256) >= 0
+ || (stack[valueOffset] == 0
+ && stack[valueOffset + 1] == 0
+ && stack[valueOffset + 2] == 0
+ && stack[valueOffset + 3] == 0)) {
+ stack[valueOffset] = 0;
+ stack[valueOffset + 1] = 0;
+ stack[valueOffset + 2] = 0;
+ stack[valueOffset + 3] = 0;
+ return top - 1;
+ }
+ int shift = (int) stack[shiftOffset + 3];
+ shiftLeftInPlace(stack, valueOffset, shift);
+ return top - 1;
+ }
+
+ /**
+ * Left-shifts a 256-bit value in place by 1..255 bits, zero-filling from the right.
+ *
+ * @param stack the flat limb array
+ * @param valueOffset index of the value's most-significant limb
+ * @param shift number of bits to shift (must be in [1, 255])
+ */
+ private static void shiftLeftInPlace(final long[] stack, final int valueOffset, final int shift) {
+ if (shift == 0) return;
+ long w0 = stack[valueOffset],
+ w1 = stack[valueOffset + 1],
+ w2 = stack[valueOffset + 2],
+ w3 = stack[valueOffset + 3];
+ final int wordShift = shift >>> 6;
+ final int bitShift = shift & 63;
+ switch (wordShift) {
+ case 0:
+ w0 = shiftLeftWord(w0, w1, bitShift);
+ w1 = shiftLeftWord(w1, w2, bitShift);
+ w2 = shiftLeftWord(w2, w3, bitShift);
+ w3 = shiftLeftWord(w3, 0, bitShift);
+ break;
+ case 1:
+ w0 = shiftLeftWord(w1, w2, bitShift);
+ w1 = shiftLeftWord(w2, w3, bitShift);
+ w2 = shiftLeftWord(w3, 0, bitShift);
+ w3 = 0;
+ break;
+ case 2:
+ w0 = shiftLeftWord(w2, w3, bitShift);
+ w1 = shiftLeftWord(w3, 0, bitShift);
+ w2 = 0;
+ w3 = 0;
+ break;
+ case 3:
+ w0 = shiftLeftWord(w3, 0, bitShift);
+ w1 = 0;
+ w2 = 0;
+ w3 = 0;
+ break;
+ }
+ stack[valueOffset] = w0;
+ stack[valueOffset + 1] = w1;
+ stack[valueOffset + 2] = w2;
+ stack[valueOffset + 3] = w3;
+ }
+
+ /**
+ * Shifts a 64-bit word left and carries in bits from the next less-significant word.
+ *
+ * @param value the current word
+ * @param nextValue the next less-significant word (bits carry in from its top)
+ * @param bitShift the intra-word shift amount in [0, 63]; 0 returns {@code value} unchanged to
+ * avoid Java's mod-64 shift semantics on {@code nextValue >>> 64}
+ * @return the shifted word
+ */
+ private static long shiftLeftWord(final long value, final long nextValue, final int bitShift) {
+ if (bitShift == 0) return value;
+ return (value << bitShift) | (nextValue >>> (64 - bitShift));
+ }
+
+ // endregion
+
+ // region SHR (Shift Right)
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Performs EVM SHR (logical shift right) on the two top stack items.
+ *
+ * Pops the shift amount (unsigned) and the value, pushes {@code value >>> shift}. Shifts >=
+ * 256 or a zero value produce 0.
+ *
+ * @param stack the flat limb array
+ * @param top current stack-top (item count)
+ * @return the new stack-top after consuming one item
+ */
+ public static int shr(final long[] stack, final int top) {
+ final int shiftOffset = (top - 1) << 2;
+ final int valueOffset = (top - 2) << 2;
+ if (stack[shiftOffset] != 0
+ || stack[shiftOffset + 1] != 0
+ || stack[shiftOffset + 2] != 0
+ || Long.compareUnsigned(stack[shiftOffset + 3], 256) >= 0
+ || (stack[valueOffset] == 0
+ && stack[valueOffset + 1] == 0
+ && stack[valueOffset + 2] == 0
+ && stack[valueOffset + 3] == 0)) {
+ stack[valueOffset] = 0;
+ stack[valueOffset + 1] = 0;
+ stack[valueOffset + 2] = 0;
+ stack[valueOffset + 3] = 0;
+ return top - 1;
+ }
+ int shift = (int) stack[shiftOffset + 3];
+ shiftRightInPlace(stack, valueOffset, shift);
+ return top - 1;
+ }
+
+ /**
+ * Logically right-shifts a 256-bit value in place by 1..255 bits, zero-filling from the left.
+ *
+ * @param s the flat limb array
+ * @param valueOffset index of the value's most-significant limb
+ * @param shift number of bits to shift (must be in [1, 255])
+ */
+ private static void shiftRightInPlace(final long[] s, final int valueOffset, final int shift) {
+ if (shift == 0) return;
+ long w0 = s[valueOffset],
+ w1 = s[valueOffset + 1],
+ w2 = s[valueOffset + 2],
+ w3 = s[valueOffset + 3];
+ final int wordShift = shift >>> 6;
+ final int bitShift = shift & 63;
+ switch (wordShift) {
+ case 0:
+ w3 = shiftRightWord(w3, w2, bitShift);
+ w2 = shiftRightWord(w2, w1, bitShift);
+ w1 = shiftRightWord(w1, w0, bitShift);
+ w0 = shiftRightWord(w0, 0, bitShift);
+ break;
+ case 1:
+ w3 = shiftRightWord(w2, w1, bitShift);
+ w2 = shiftRightWord(w1, w0, bitShift);
+ w1 = shiftRightWord(w0, 0, bitShift);
+ w0 = 0;
+ break;
+ case 2:
+ w3 = shiftRightWord(w1, w0, bitShift);
+ w2 = shiftRightWord(w0, 0, bitShift);
+ w1 = 0;
+ w0 = 0;
+ break;
+ case 3:
+ w3 = shiftRightWord(w0, 0, bitShift);
+ w2 = 0;
+ w1 = 0;
+ w0 = 0;
+ break;
+ }
+ s[valueOffset] = w0;
+ s[valueOffset + 1] = w1;
+ s[valueOffset + 2] = w2;
+ s[valueOffset + 3] = w3;
+ }
+
+ // endregion
+
+ // region SAR (Shift Arithmetic Right)
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Performs EVM SAR (arithmetic shift right) on the two top stack items.
+ *
+ * Pops the shift amount (unsigned) and the value (signed), pushes {@code value >> shift}.
+ * Shifts >= 256 produce 0 for positive values and -1 for negative values.
+ *
+ * @param stack the flat limb array
+ * @param top current stack-top (item count)
+ * @return the new stack-top after consuming one item
+ */
+ public static int sar(final long[] stack, final int top) {
+ final int shiftOffset = (top - 1) << 2;
+ final int valueOffset = (top - 2) << 2;
+ boolean negative = stack[valueOffset] < 0;
+ if (stack[shiftOffset] != 0
+ || stack[shiftOffset + 1] != 0
+ || stack[shiftOffset + 2] != 0
+ || Long.compareUnsigned(stack[shiftOffset + 3], 256) >= 0) {
+ long fill = negative ? -1L : 0L;
+ stack[valueOffset] = fill;
+ stack[valueOffset + 1] = fill;
+ stack[valueOffset + 2] = fill;
+ stack[valueOffset + 3] = fill;
+ return top - 1;
+ }
+ int shift = (int) stack[shiftOffset + 3];
+ sarInPlace(stack, valueOffset, shift, negative);
+ return top - 1;
+ }
+
+ /**
+ * Arithmetic right-shifts a 256-bit value in place by 0..255 bits, sign-extending with {@code
+ * fill}.
+ *
+ * @param stack the flat limb array
+ * @param valueOffset index of the value's most-significant limb
+ * @param shift number of bits to shift (must be in [0, 255])
+ * @param negative true if the original value is negative (fill = -1)
+ */
+ private static void sarInPlace(
+ final long[] stack, final int valueOffset, final int shift, final boolean negative) {
+ if (shift == 0) return;
+ long w0 = stack[valueOffset],
+ w1 = stack[valueOffset + 1],
+ w2 = stack[valueOffset + 2],
+ w3 = stack[valueOffset + 3];
+ final long fill = negative ? -1L : 0L;
+ final int wordShift = shift >>> 6;
+ final int bitShift = shift & 63;
+ switch (wordShift) {
+ case 0:
+ w3 = shiftRightWord(w3, w2, bitShift);
+ w2 = shiftRightWord(w2, w1, bitShift);
+ w1 = shiftRightWord(w1, w0, bitShift);
+ w0 = shiftRightWord(w0, fill, bitShift);
+ break;
+ case 1:
+ w3 = shiftRightWord(w2, w1, bitShift);
+ w2 = shiftRightWord(w1, w0, bitShift);
+ w1 = shiftRightWord(w0, fill, bitShift);
+ w0 = fill;
+ break;
+ case 2:
+ w3 = shiftRightWord(w1, w0, bitShift);
+ w2 = shiftRightWord(w0, fill, bitShift);
+ w1 = fill;
+ w0 = fill;
+ break;
+ case 3:
+ w3 = shiftRightWord(w0, fill, bitShift);
+ w2 = fill;
+ w1 = fill;
+ w0 = fill;
+ break;
+ }
+ stack[valueOffset] = w0;
+ stack[valueOffset + 1] = w1;
+ stack[valueOffset + 2] = w2;
+ stack[valueOffset + 3] = w3;
+ }
+
+ // endregion
+
+ // region Binary Arithmetic (pop 2, push 1, return top-1)
+ // ---------------------------------------------------------------------------
+
+ /**
+ * ADD: s[top-2] = s[top-1] + s[top-2], return top-1.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @return the new top index
+ */
+ public static int add(final long[] s, final int top) {
+ final int a = (top - 1) << 2;
+ final int b = (top - 2) << 2;
+ // Fast path: both values fit in a single limb (common case)
+ if ((s[a] | s[a + 1] | s[a + 2] | s[b] | s[b + 1] | s[b + 2]) == 0) {
+ long z = s[a + 3] + s[b + 3];
+ s[b + 2] = ((s[a + 3] & s[b + 3]) | ((s[a + 3] | s[b + 3]) & ~z)) >>> 63;
+ s[b + 3] = z;
+ return top - 1;
+ }
+ long a0 = s[a + 3], a1 = s[a + 2], a2 = s[a + 1], a3 = s[a];
+ long b0 = s[b + 3], b1 = s[b + 2], b2 = s[b + 1], b3 = s[b];
+ long z0 = a0 + b0;
+ long c = ((a0 & b0) | ((a0 | b0) & ~z0)) >>> 63;
+ long t1 = a1 + b1;
+ long c1 = ((a1 & b1) | ((a1 | b1) & ~t1)) >>> 63;
+ long z1 = t1 + c;
+ c = c1 | (((t1 & c) | ((t1 | c) & ~z1)) >>> 63);
+ long t2 = a2 + b2;
+ long c2 = ((a2 & b2) | ((a2 | b2) & ~t2)) >>> 63;
+ long z2 = t2 + c;
+ c = c2 | (((t2 & c) | ((t2 | c) & ~z2)) >>> 63);
+ long z3 = a3 + b3 + c;
+ s[b] = z3;
+ s[b + 1] = z2;
+ s[b + 2] = z1;
+ s[b + 3] = z0;
+ return top - 1;
+ }
+
+ /**
+ * SUB: s[top-2] = s[top-1] - s[top-2], return top-1.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @return the new top index
+ */
+ public static int sub(final long[] s, final int top) {
+ final int a = (top - 1) << 2; // first operand (top)
+ final int b = (top - 2) << 2; // second operand
+ // Fast path: both values fit in a single limb (common case)
+ if ((s[a] | s[a + 1] | s[a + 2] | s[b] | s[b + 1] | s[b + 2]) == 0) {
+ long z = s[a + 3] - s[b + 3];
+ // borrow is 0 or 1; negate to get 0 or -1L (0xFFFFFFFFFFFFFFFF) for upper limbs
+ long fill = -(((~s[a + 3] & s[b + 3]) | (~(s[a + 3] ^ s[b + 3]) & z)) >>> 63);
+ s[b] = fill;
+ s[b + 1] = fill;
+ s[b + 2] = fill;
+ s[b + 3] = z;
+ return top - 1;
+ }
+ long a0 = s[a + 3], a1 = s[a + 2], a2 = s[a + 1], a3 = s[a];
+ long b0 = s[b + 3], b1 = s[b + 2], b2 = s[b + 1], b3 = s[b];
+ // a - b with branchless borrow chain
+ long z0 = a0 - b0;
+ long w = ((~a0 & b0) | (~(a0 ^ b0) & z0)) >>> 63;
+ long t1 = a1 - b1;
+ long w1 = ((~a1 & b1) | (~(a1 ^ b1) & t1)) >>> 63;
+ long z1 = t1 - w;
+ w = w1 | (((~t1 & w) | (~(t1 ^ w) & z1)) >>> 63);
+ long t2 = a2 - b2;
+ long w2 = ((~a2 & b2) | (~(a2 ^ b2) & t2)) >>> 63;
+ long z2 = t2 - w;
+ w = w2 | (((~t2 & w) | (~(t2 ^ w) & z2)) >>> 63);
+ long z3 = a3 - b3 - w;
+ s[b] = z3;
+ s[b + 1] = z2;
+ s[b + 2] = z1;
+ s[b + 3] = z0;
+ return top - 1;
+ }
+
+ /**
+ * MUL: s[top-2] = s[top-1] * s[top-2], return top-1. Delegates to UInt256 for now.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @return the new top index
+ */
+ public static int mul(final long[] s, final int top) {
+ final int a = (top - 1) << 2;
+ final int b = (top - 2) << 2;
+ UInt256 va = new UInt256(s[a], s[a + 1], s[a + 2], s[a + 3]);
+ UInt256 vb = new UInt256(s[b], s[b + 1], s[b + 2], s[b + 3]);
+ UInt256 r = va.mul(vb);
+ s[b] = r.u3();
+ s[b + 1] = r.u2();
+ s[b + 2] = r.u1();
+ s[b + 3] = r.u0();
+ return top - 1;
+ }
+
+ /**
+ * DIV: s[top-2] = s[top-1] / s[top-2], return top-1. Delegates to UInt256.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @return the new top index
+ */
+ public static int div(final long[] s, final int top) {
+ final int a = (top - 1) << 2;
+ final int b = (top - 2) << 2;
+ UInt256 va = new UInt256(s[a], s[a + 1], s[a + 2], s[a + 3]);
+ UInt256 vb = new UInt256(s[b], s[b + 1], s[b + 2], s[b + 3]);
+ UInt256 r = va.div(vb);
+ s[b] = r.u3();
+ s[b + 1] = r.u2();
+ s[b + 2] = r.u1();
+ s[b + 3] = r.u0();
+ return top - 1;
+ }
+
+ /**
+ * SDIV: s[top-2] = s[top-1] sdiv s[top-2], return top-1. Delegates to UInt256.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @return the new top index
+ */
+ public static int signedDiv(final long[] s, final int top) {
+ final int a = (top - 1) << 2;
+ final int b = (top - 2) << 2;
+ UInt256 va = new UInt256(s[a], s[a + 1], s[a + 2], s[a + 3]);
+ UInt256 vb = new UInt256(s[b], s[b + 1], s[b + 2], s[b + 3]);
+ UInt256 r = va.signedDiv(vb);
+ s[b] = r.u3();
+ s[b + 1] = r.u2();
+ s[b + 2] = r.u1();
+ s[b + 3] = r.u0();
+ return top - 1;
+ }
+
+ /**
+ * MOD: s[top-2] = s[top-1] mod s[top-2], return top-1. Delegates to UInt256.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @return the new top index
+ */
+ public static int mod(final long[] s, final int top) {
+ final int a = (top - 1) << 2;
+ final int b = (top - 2) << 2;
+ UInt256 va = new UInt256(s[a], s[a + 1], s[a + 2], s[a + 3]);
+ UInt256 vb = new UInt256(s[b], s[b + 1], s[b + 2], s[b + 3]);
+ UInt256 r = va.mod(vb);
+ s[b] = r.u3();
+ s[b + 1] = r.u2();
+ s[b + 2] = r.u1();
+ s[b + 3] = r.u0();
+ return top - 1;
+ }
+
+ /**
+ * SMOD: s[top-2] = s[top-1] smod s[top-2], return top-1. Delegates to UInt256.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @return the new top index
+ */
+ public static int signedMod(final long[] s, final int top) {
+ final int a = (top - 1) << 2;
+ final int b = (top - 2) << 2;
+ UInt256 va = new UInt256(s[a], s[a + 1], s[a + 2], s[a + 3]);
+ UInt256 vb = new UInt256(s[b], s[b + 1], s[b + 2], s[b + 3]);
+ UInt256 r = va.signedMod(vb);
+ s[b] = r.u3();
+ s[b + 1] = r.u2();
+ s[b + 2] = r.u1();
+ s[b + 3] = r.u0();
+ return top - 1;
+ }
+
+ /**
+ * EXP: s[top-2] = s[top-1] ** s[top-2] mod 2^256, return top-1.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @return the new top index
+ */
+ public static int exp(final long[] s, final int top) {
+ final int a = (top - 1) << 2; // base
+ final int b = (top - 2) << 2; // exponent
+ UInt256 base = new UInt256(s[a], s[a + 1], s[a + 2], s[a + 3]);
+ UInt256 power = new UInt256(s[b], s[b + 1], s[b + 2], s[b + 3]);
+ BigInteger result = base.toBigInteger().modPow(power.toBigInteger(), BigInteger.TWO.pow(256));
+ byte[] rBytes = result.toByteArray();
+ if (rBytes.length > 32) {
+ rBytes = Arrays.copyOfRange(rBytes, rBytes.length - 32, rBytes.length);
+ } else if (rBytes.length < 32) {
+ byte[] padded = new byte[32];
+ System.arraycopy(rBytes, 0, padded, 32 - rBytes.length, rBytes.length);
+ rBytes = padded;
+ }
+ UInt256 r = UInt256.fromBytesBE(rBytes);
+ s[b] = r.u3();
+ s[b + 1] = r.u2();
+ s[b + 2] = r.u1();
+ s[b + 3] = r.u0();
+ return top - 1;
+ }
+
+ // endregion
+
+ // region Bitwise Binary (pop 2, push 1, return top-1)
+ // ---------------------------------------------------------------------------
+
+ /**
+ * AND: s[top-2] = s[top-1] & s[top-2], return top-1.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @return the new top index
+ */
+ public static int and(final long[] s, final int top) {
+ final int a = (top - 1) << 2;
+ final int b = (top - 2) << 2;
+ s[b] = s[a] & s[b];
+ s[b + 1] = s[a + 1] & s[b + 1];
+ s[b + 2] = s[a + 2] & s[b + 2];
+ s[b + 3] = s[a + 3] & s[b + 3];
+ return top - 1;
+ }
+
+ /**
+ * OR: s[top-2] = s[top-1] | s[top-2], return top-1.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @return the new top index
+ */
+ public static int or(final long[] s, final int top) {
+ final int a = (top - 1) << 2;
+ final int b = (top - 2) << 2;
+ s[b] = s[a] | s[b];
+ s[b + 1] = s[a + 1] | s[b + 1];
+ s[b + 2] = s[a + 2] | s[b + 2];
+ s[b + 3] = s[a + 3] | s[b + 3];
+ return top - 1;
+ }
+
+ /**
+ * XOR: s[top-2] = s[top-1] ^ s[top-2], return top-1.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @return the new top index
+ */
+ public static int xor(final long[] s, final int top) {
+ final int a = (top - 1) << 2;
+ final int b = (top - 2) << 2;
+ s[b] = s[a] ^ s[b];
+ s[b + 1] = s[a + 1] ^ s[b + 1];
+ s[b + 2] = s[a + 2] ^ s[b + 2];
+ s[b + 3] = s[a + 3] ^ s[b + 3];
+ return top - 1;
+ }
+
+ /**
+ * BYTE: s[top-2] = byte at offset s[top-1] of s[top-2], return top-1.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @return the new top index
+ */
+ public static int byte_(final long[] s, final int top) {
+ final int a = (top - 1) << 2; // offset
+ final int b = (top - 2) << 2; // value
+ // offset must be 0..31
+ if (s[a] != 0 || s[a + 1] != 0 || s[a + 2] != 0 || s[a + 3] < 0 || s[a + 3] >= 32) {
+ s[b] = 0;
+ s[b + 1] = 0;
+ s[b + 2] = 0;
+ s[b + 3] = 0;
+ return top - 1;
+ }
+ int idx = (int) s[a + 3]; // 0..31, big-endian byte index
+ // Determine which limb and bit position
+ // byte 0 is the MSB of u3, byte 31 is the LSB of u0
+ int limbIdx = idx >> 3; // which limb offset from base (0=u3, 3=u0)
+ int byteInLimb = 7 - (idx & 7); // byte position within limb (7=MSB, 0=LSB)
+ long limb = s[b + limbIdx];
+ long result = (limb >>> (byteInLimb << 3)) & 0xFFL;
+ s[b] = 0;
+ s[b + 1] = 0;
+ s[b + 2] = 0;
+ s[b + 3] = result;
+ return top - 1;
+ }
+
+ /**
+ * SIGNEXTEND: sign-extend s[top-2] from byte s[top-1], return top-1.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @return the new top index
+ */
+ public static int signExtend(final long[] s, final int top) {
+ final int a = (top - 1) << 2; // byte index b
+ final int b = (top - 2) << 2; // value
+ // If b >= 31, no extension needed
+ if (s[a] != 0 || s[a + 1] != 0 || s[a + 2] != 0 || s[a + 3] >= 31) {
+ // result is just the value unchanged
+ return top - 1;
+ }
+ int byteIdx = (int) s[a + 3]; // 0..30
+ // The sign bit is at bit (byteIdx * 8 + 7) from LSB
+ int signBit = byteIdx * 8 + 7;
+ int limbIdx = signBit >> 6; // which limb (0=u0/LSB)
+ int bitInLimb = signBit & 63;
+ // Read from the value slot - limbs are stored [u3, u2, u1, u0] at [b, b+1, b+2, b+3]
+ long limb = s[b + 3 - limbIdx];
+ boolean isNeg = ((limb >>> bitInLimb) & 1L) != 0;
+ if (isNeg) {
+ // Set all bits above signBit to 1
+ s[b + 3 - limbIdx] = limb | (-1L << bitInLimb);
+ for (int i = limbIdx + 1; i < 4; i++) {
+ s[b + 3 - i] = -1L;
+ }
+ } else {
+ // Clear all bits above signBit to 0
+ if (bitInLimb < 63) {
+ s[b + 3 - limbIdx] = limb & ((1L << (bitInLimb + 1)) - 1);
+ }
+ for (int i = limbIdx + 1; i < 4; i++) {
+ s[b + 3 - i] = 0;
+ }
+ }
+ return top - 1;
+ }
+
+ // endregion
+
+ // region Unary Operations (pop 1, push 1, return top)
+ // ---------------------------------------------------------------------------
+
+ /**
+ * NOT: s[top-1] = ~s[top-1], return top.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @return the new top index
+ */
+ public static int not(final long[] s, final int top) {
+ final int a = (top - 1) << 2;
+ s[a] = ~s[a];
+ s[a + 1] = ~s[a + 1];
+ s[a + 2] = ~s[a + 2];
+ s[a + 3] = ~s[a + 3];
+ return top;
+ }
+
+ /**
+ * ISZERO: s[top-1] = (s[top-1] == 0) ? 1 : 0, return top.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @return the new top index
+ */
+ public static int isZero(final long[] s, final int top) {
+ final int a = (top - 1) << 2;
+ boolean zero = (s[a] | s[a + 1] | s[a + 2] | s[a + 3]) == 0;
+ s[a] = 0;
+ s[a + 1] = 0;
+ s[a + 2] = 0;
+ s[a + 3] = zero ? 1L : 0L;
+ return top;
+ }
+
+ /**
+ * CLZ: s[top-1] = count leading zeros of s[top-1], return top.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @return the new top index
+ */
+ public static int clz(final long[] s, final int top) {
+ final int a = (top - 1) << 2;
+ int result;
+ if (s[a] != 0) {
+ result = Long.numberOfLeadingZeros(s[a]);
+ } else if (s[a + 1] != 0) {
+ result = 64 + Long.numberOfLeadingZeros(s[a + 1]);
+ } else if (s[a + 2] != 0) {
+ result = 128 + Long.numberOfLeadingZeros(s[a + 2]);
+ } else {
+ result = 192 + Long.numberOfLeadingZeros(s[a + 3]);
+ }
+ s[a] = 0;
+ s[a + 1] = 0;
+ s[a + 2] = 0;
+ s[a + 3] = result;
+ return top;
+ }
+
+ // endregion
+
+ // region Comparison Operations (pop 2, push 1, return top-1)
+ // ---------------------------------------------------------------------------
+
+ /**
+ * LT: s[top-2] = (s[top-1] < s[top-2]) ? 1 : 0, return top-1. Unsigned.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @return the new top index
+ */
+ public static int lt(final long[] s, final int top) {
+ final int a = (top - 1) << 2;
+ final int b = (top - 2) << 2;
+ boolean less = unsignedLt(s, a, s, b);
+ s[b] = 0;
+ s[b + 1] = 0;
+ s[b + 2] = 0;
+ s[b + 3] = less ? 1L : 0L;
+ return top - 1;
+ }
+
+ /**
+ * GT: s[top-2] = (s[top-1] > s[top-2]) ? 1 : 0, return top-1. Unsigned.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @return the new top index
+ */
+ public static int gt(final long[] s, final int top) {
+ final int a = (top - 1) << 2;
+ final int b = (top - 2) << 2;
+ boolean greater = unsignedLt(s, b, s, a);
+ s[b] = 0;
+ s[b + 1] = 0;
+ s[b + 2] = 0;
+ s[b + 3] = greater ? 1L : 0L;
+ return top - 1;
+ }
+
+ /**
+ * SLT: s[top-2] = (s[top-1] <s s[top-2]) ? 1 : 0, return top-1. Signed.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @return the new top index
+ */
+ public static int slt(final long[] s, final int top) {
+ final int a = (top - 1) << 2;
+ final int b = (top - 2) << 2;
+ int cmp = signedCompare(s, a, s, b);
+ s[b] = 0;
+ s[b + 1] = 0;
+ s[b + 2] = 0;
+ s[b + 3] = cmp < 0 ? 1L : 0L;
+ return top - 1;
+ }
+
+ /**
+ * SGT: s[top-2] = (s[top-1] >s s[top-2]) ? 1 : 0, return top-1. Signed.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @return the new top index
+ */
+ public static int sgt(final long[] s, final int top) {
+ final int a = (top - 1) << 2;
+ final int b = (top - 2) << 2;
+ int cmp = signedCompare(s, a, s, b);
+ s[b] = 0;
+ s[b + 1] = 0;
+ s[b + 2] = 0;
+ s[b + 3] = cmp > 0 ? 1L : 0L;
+ return top - 1;
+ }
+
+ /**
+ * EQ: s[top-2] = (s[top-1] == s[top-2]) ? 1 : 0, return top-1.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @return the new top index
+ */
+ public static int eq(final long[] s, final int top) {
+ final int a = (top - 1) << 2;
+ final int b = (top - 2) << 2;
+ boolean equal =
+ s[a] == s[b] && s[a + 1] == s[b + 1] && s[a + 2] == s[b + 2] && s[a + 3] == s[b + 3];
+ s[b] = 0;
+ s[b + 1] = 0;
+ s[b + 2] = 0;
+ s[b + 3] = equal ? 1L : 0L;
+ return top - 1;
+ }
+
+ // endregion
+
+ // region Ternary Operations (pop 3, push 1, return top-2)
+ // ---------------------------------------------------------------------------
+
+ /**
+ * ADDMOD: s[top-3] = (s[top-1] + s[top-2]) mod s[top-3], return top-2.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @return the new top index
+ */
+ public static int addMod(final long[] s, final int top) {
+ final int a = (top - 1) << 2;
+ final int b = (top - 2) << 2;
+ final int c = (top - 3) << 2;
+ UInt256 va = new UInt256(s[a], s[a + 1], s[a + 2], s[a + 3]);
+ UInt256 vb = new UInt256(s[b], s[b + 1], s[b + 2], s[b + 3]);
+ UInt256 vc = new UInt256(s[c], s[c + 1], s[c + 2], s[c + 3]);
+ UInt256 r = vc.isZero() ? UInt256.ZERO : va.addMod(vb, vc);
+ s[c] = r.u3();
+ s[c + 1] = r.u2();
+ s[c + 2] = r.u1();
+ s[c + 3] = r.u0();
+ return top - 2;
+ }
+
+ /**
+ * MULMOD: s[top-3] = (s[top-1] * s[top-2]) mod s[top-3], return top-2.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @return the new top index
+ */
+ public static int mulMod(final long[] s, final int top) {
+ final int a = (top - 1) << 2;
+ final int b = (top - 2) << 2;
+ final int c = (top - 3) << 2;
+ UInt256 va = new UInt256(s[a], s[a + 1], s[a + 2], s[a + 3]);
+ UInt256 vb = new UInt256(s[b], s[b + 1], s[b + 2], s[b + 3]);
+ UInt256 vc = new UInt256(s[c], s[c + 1], s[c + 2], s[c + 3]);
+ UInt256 r = vc.isZero() ? UInt256.ZERO : va.mulMod(vb, vc);
+ s[c] = r.u3();
+ s[c + 1] = r.u2();
+ s[c + 2] = r.u1();
+ s[c + 3] = r.u0();
+ return top - 2;
+ }
+
+ // endregion
+
+ // region Stack Manipulation
+ // ---------------------------------------------------------------------------
+
+ /**
+ * DUP: copy slot at depth to new top, return top+1. depth is 1-based (DUP1 = depth 1).
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @param depth the 1-based depth of the slot to duplicate
+ * @return the new top index
+ */
+ public static int dup(final long[] s, final int top, final int depth) {
+ final int src = (top - depth) << 2;
+ final int dst = top << 2;
+ s[dst] = s[src];
+ s[dst + 1] = s[src + 1];
+ s[dst + 2] = s[src + 2];
+ s[dst + 3] = s[src + 3];
+ return top + 1;
+ }
+
+ /**
+ * SWAP: swap top with slot at depth, return top. depth is 1-based (SWAP1 = depth 1).
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @param depth the 1-based depth of the slot to swap with the top
+ * @return the new top index
+ */
+ public static int swap(final long[] s, final int top, final int depth) {
+ final int a = (top - 1) << 2;
+ final int b = (top - 1 - depth) << 2;
+ long t;
+ t = s[a];
+ s[a] = s[b];
+ s[b] = t;
+ t = s[a + 1];
+ s[a + 1] = s[b + 1];
+ s[b + 1] = t;
+ t = s[a + 2];
+ s[a + 2] = s[b + 2];
+ s[b + 2] = t;
+ t = s[a + 3];
+ s[a + 3] = s[b + 3];
+ s[b + 3] = t;
+ return top;
+ }
+
+ /**
+ * EXCHANGE: swap slot at n with slot at m (both 0-indexed from top), return top.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @param n the 0-based index of the first slot (from top)
+ * @param m the 0-based index of the second slot (from top)
+ * @return the new top index
+ */
+ public static int exchange(final long[] s, final int top, final int n, final int m) {
+ final int a = (top - 1 - n) << 2;
+ final int b = (top - 1 - m) << 2;
+ long t;
+ t = s[a];
+ s[a] = s[b];
+ s[b] = t;
+ t = s[a + 1];
+ s[a + 1] = s[b + 1];
+ s[b + 1] = t;
+ t = s[a + 2];
+ s[a + 2] = s[b + 2];
+ s[b + 2] = t;
+ t = s[a + 3];
+ s[a + 3] = s[b + 3];
+ s[b + 3] = t;
+ return top;
+ }
+
+ /**
+ * PUSH0: push zero, return top+1.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @return the new top index
+ */
+ public static int pushZero(final long[] s, final int top) {
+ final int dst = top << 2;
+ s[dst] = 0;
+ s[dst + 1] = 0;
+ s[dst + 2] = 0;
+ s[dst + 3] = 0;
+ return top + 1;
+ }
+
+ /**
+ * PUSH1..PUSH32: decode bytes from code into a new top slot, return top+1.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @param code the bytecode array
+ * @param start the start offset within the code array
+ * @param len the number of bytes to push (1-32)
+ * @return the new top index
+ */
+ public static int pushFromBytes(
+ final long[] s, final int top, final byte[] code, final int start, final int len) {
+ final int dst = top << 2;
+
+ if (start >= code.length) {
+ s[dst] = 0;
+ s[dst + 1] = 0;
+ s[dst + 2] = 0;
+ s[dst + 3] = 0;
+ return top + 1;
+ }
+ final int copyLen = Math.min(len, code.length - start);
+
+ s[dst] = 0;
+ s[dst + 1] = 0;
+ s[dst + 2] = 0;
+ s[dst + 3] = 0;
+
+ if (copyLen == len) {
+ // Fast path: all bytes available (common case — not near end of code)
+ if (len <= 8) {
+ s[dst + 3] = buildLong(code, start, len);
+ } else if (len <= 16) {
+ final int hiLen = len - 8;
+ s[dst + 2] = buildLong(code, start, hiLen);
+ s[dst + 3] = bytesToLong(code, start + hiLen);
+ } else if (len <= 24) {
+ final int hiLen = len - 16;
+ s[dst + 1] = buildLong(code, start, hiLen);
+ s[dst + 2] = bytesToLong(code, start + hiLen);
+ s[dst + 3] = bytesToLong(code, start + hiLen + 8);
+ } else {
+ final int hiLen = len - 24;
+ s[dst] = buildLong(code, start, hiLen);
+ s[dst + 1] = bytesToLong(code, start + hiLen);
+ s[dst + 2] = bytesToLong(code, start + hiLen + 8);
+ s[dst + 3] = bytesToLong(code, start + hiLen + 16);
+ }
+ } else {
+ // Truncated push (rare: near end of code). Right-pad with zeros.
+ int bytePos = len - 1;
+ for (int i = 0; i < copyLen; i++) {
+ int limbOffset = 3 - (bytePos >> 3);
+ int shift = (bytePos & 7) << 3;
+ s[dst + limbOffset] |= (code[start + i] & 0xFFL) << shift;
+ bytePos--;
+ }
+ }
+ return top + 1;
+ }
+
+ /**
+ * Push a long value (GAS, NUMBER, etc.), return top+1.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @param value the long value to push
+ * @return the new top index
+ */
+ public static int pushLong(final long[] s, final int top, final long value) {
+ final int dst = top << 2;
+ s[dst] = 0;
+ s[dst + 1] = 0;
+ s[dst + 2] = 0;
+ s[dst + 3] = value;
+ return top + 1;
+ }
+
+ /**
+ * Push a Wei value onto the stack, return top+1.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @param value the Wei value to push
+ * @return the new top index
+ */
+ public static int pushWei(final long[] s, final int top, final Wei value) {
+ final int dst = top << 2;
+ final byte[] bytes = value.toArrayUnsafe();
+ s[dst] = getLong(bytes, 0);
+ s[dst + 1] = getLong(bytes, 8);
+ s[dst + 2] = getLong(bytes, 16);
+ s[dst + 3] = getLong(bytes, 24);
+ return top + 1;
+ }
+
+ /**
+ * Push an Address (20 bytes), return top+1.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @param addr the address to push
+ * @return the new top index
+ */
+ public static int pushAddress(final long[] s, final int top, final Address addr) {
+ final int dst = top << 2;
+ byte[] bytes = addr.getBytes().toArrayUnsafe();
+ // Address is 20 bytes: fits in u2(4 bytes) + u1(8 bytes) + u0(8 bytes)
+ s[dst] = 0; // u3
+ s[dst + 1] = getInt(bytes, 0) & 0xFFFFFFFFL; // u2 (top 4 bytes)
+ s[dst + 2] = getLong(bytes, 4); // u1
+ s[dst + 3] = getLong(bytes, 12); // u0
+ return top + 1;
+ }
+
+ // endregion
+
+ // region Boundary Helpers (read/write slots without changing top)
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Extract u0 (LSB limb) of slot at depth from top.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @param depth the 0-based depth from the top
+ * @return the least-significant limb of the slot
+ */
+ public static long longAt(final long[] s, final int top, final int depth) {
+ return s[((top - 1 - depth) << 2) + 3];
+ }
+
+ /**
+ * Check if slot at depth is zero.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @param depth the 0-based depth from the top
+ * @return true if the slot is zero
+ */
+ public static boolean isZeroAt(final long[] s, final int top, final int depth) {
+ final int off = (top - 1 - depth) << 2;
+ return (s[off] | s[off + 1] | s[off + 2] | s[off + 3]) == 0;
+ }
+
+ /**
+ * Check if slot at depth fits in a non-negative int.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @param depth the 0-based depth from the top
+ * @return true if the slot value fits in a non-negative int
+ */
+ public static boolean fitsInInt(final long[] s, final int top, final int depth) {
+ final int off = (top - 1 - depth) << 2;
+ return s[off] == 0
+ && s[off + 1] == 0
+ && s[off + 2] == 0
+ && s[off + 3] >= 0
+ && s[off + 3] <= Integer.MAX_VALUE;
+ }
+
+ /**
+ * Check if slot at depth fits in a non-negative long.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @param depth the 0-based depth from the top
+ * @return true if the slot value fits in a non-negative long
+ */
+ public static boolean fitsInLong(final long[] s, final int top, final int depth) {
+ final int off = (top - 1 - depth) << 2;
+ return s[off] == 0 && s[off + 1] == 0 && s[off + 2] == 0 && s[off + 3] >= 0;
+ }
+
+ /**
+ * Clamp slot at depth to long, returning Long.MAX_VALUE if it doesn't fit in a non-negative long.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @param depth the 0-based depth from the top
+ * @return the slot value as a long, or Long.MAX_VALUE if it does not fit
+ */
+ public static long clampedToLong(final long[] s, final int top, final int depth) {
+ final int off = (top - 1 - depth) << 2;
+ if (s[off] != 0 || s[off + 1] != 0 || s[off + 2] != 0 || s[off + 3] < 0) {
+ return Long.MAX_VALUE;
+ }
+ return s[off + 3];
+ }
+
+ /**
+ * Clamp slot at depth to int, returning Integer.MAX_VALUE if it doesn't fit in a non-negative
+ * int.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @param depth the 0-based depth from the top
+ * @return the slot value as an int, or Integer.MAX_VALUE if it does not fit
+ */
+ public static int clampedToInt(final long[] s, final int top, final int depth) {
+ final int off = (top - 1 - depth) << 2;
+ if (s[off] != 0
+ || s[off + 1] != 0
+ || s[off + 2] != 0
+ || s[off + 3] < 0
+ || s[off + 3] > Integer.MAX_VALUE) {
+ return Integer.MAX_VALUE;
+ }
+ return (int) s[off + 3];
+ }
+
+ /**
+ * Write 32 big-endian bytes from slot at depth into dst.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @param depth the 0-based depth from the top
+ * @param dst the destination byte array (must have at least 32 bytes)
+ */
+ public static void toBytesAt(final long[] s, final int top, final int depth, final byte[] dst) {
+ final int off = (top - 1 - depth) << 2;
+ longIntoBytes(dst, 0, s[off]);
+ longIntoBytes(dst, 8, s[off + 1]);
+ longIntoBytes(dst, 16, s[off + 2]);
+ longIntoBytes(dst, 24, s[off + 3]);
+ }
+
+ /**
+ * Read bytes into slot at depth from src[srcOff..srcOff+len). Pads with zeros.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @param depth the 0-based depth from the top
+ * @param src the source byte array
+ * @param srcOff the start offset within the source array
+ * @param len the number of bytes to read (up to 32)
+ */
+ public static void fromBytesAt(
+ final long[] s,
+ final int top,
+ final int depth,
+ final byte[] src,
+ final int srcOff,
+ final int len) {
+ final int off = (top - 1 - depth) << 2;
+ // Fast path: full 32-byte read — decode 8 bytes per limb
+ if (len >= 32 && srcOff + 32 <= src.length) {
+ s[off] = bytesToLong(src, srcOff);
+ s[off + 1] = bytesToLong(src, srcOff + 8);
+ s[off + 2] = bytesToLong(src, srcOff + 16);
+ s[off + 3] = bytesToLong(src, srcOff + 24);
+ return;
+ }
+ // Slow path: variable-length, byte-by-byte
+ s[off] = 0;
+ s[off + 1] = 0;
+ s[off + 2] = 0;
+ s[off + 3] = 0;
+ int end = srcOff + Math.min(len, 32);
+ if (end > src.length) end = src.length;
+ int pos = 0;
+ for (int i = srcOff; i < end; i++, pos++) {
+ int limbIdx = pos >> 3;
+ int shift = (7 - (pos & 7)) << 3;
+ s[off + limbIdx] |= (src[i] & 0xFFL) << shift;
+ }
+ }
+
+ /**
+ * Extract 20-byte Address from slot at depth.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @param depth the 0-based depth from the top
+ * @return the address stored in the slot
+ */
+ public static Address toAddressAt(final long[] s, final int top, final int depth) {
+ final int off = (top - 1 - depth) << 2;
+ byte[] bytes = new byte[20];
+ // u2 has top 4 bytes, u1 has next 8, u0 has last 8
+ putInt(bytes, 0, (int) s[off + 1]);
+ putLong(bytes, 4, s[off + 2]);
+ putLong(bytes, 12, s[off + 3]);
+ return Address.wrap(org.apache.tuweni.bytes.Bytes.wrap(bytes));
+ }
+
+ /**
+ * Materialize UInt256 record from slot at depth (boundary only).
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @param depth the 0-based depth from the top
+ * @return the UInt256 value at the slot
+ */
+ public static UInt256 getAt(final long[] s, final int top, final int depth) {
+ final int off = (top - 1 - depth) << 2;
+ return new UInt256(s[off], s[off + 1], s[off + 2], s[off + 3]);
+ }
+
+ /**
+ * Write a Wei value into slot at depth.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @param depth the 0-based depth from the top
+ * @param value the Wei value to write
+ */
+ public static void putWeiAt(final long[] s, final int top, final int depth, final Wei value) {
+ final int off = (top - 1 - depth) << 2;
+ final byte[] bytes = value.toArrayUnsafe();
+ s[off] = getLong(bytes, 0);
+ s[off + 1] = getLong(bytes, 8);
+ s[off + 2] = getLong(bytes, 16);
+ s[off + 3] = getLong(bytes, 24);
+ }
+
+ /**
+ * Write UInt256 record into slot at depth.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @param depth the 0-based depth from the top
+ * @param val the UInt256 value to write
+ */
+ public static void putAt(final long[] s, final int top, final int depth, final UInt256 val) {
+ final int off = (top - 1 - depth) << 2;
+ s[off] = val.u3();
+ s[off + 1] = val.u2();
+ s[off + 2] = val.u1();
+ s[off + 3] = val.u0();
+ }
+
+ /**
+ * Write raw limbs into slot at depth.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @param depth the 0-based depth from the top
+ * @param u3 the most-significant limb
+ * @param u2 the second limb
+ * @param u1 the third limb
+ * @param u0 the least-significant limb
+ */
+ public static void putAt(
+ final long[] s,
+ final int top,
+ final int depth,
+ final long u3,
+ final long u2,
+ final long u1,
+ final long u0) {
+ final int off = (top - 1 - depth) << 2;
+ s[off] = u3;
+ s[off + 1] = u2;
+ s[off + 2] = u1;
+ s[off + 3] = u0;
+ }
+
+ /**
+ * Number of significant bytes in slot at depth. Used by EXP gas calculation.
+ *
+ * @param s the stack array
+ * @param top the current top index
+ * @param depth the 0-based depth from the top
+ * @return the number of significant bytes in the slot
+ */
+ public static int byteLengthAt(final long[] s, final int top, final int depth) {
+ final int off = (top - 1 - depth) << 2;
+ if (s[off] != 0) return 24 + byteLen(s[off]);
+ if (s[off + 1] != 0) return 16 + byteLen(s[off + 1]);
+ if (s[off + 2] != 0) return 8 + byteLen(s[off + 2]);
+ if (s[off + 3] != 0) return byteLen(s[off + 3]);
+ return 0;
+ }
+
+ // endregion
+
+ // region Private Helpers
+ // ---------------------------------------------------------------------------
+
+ private static long getLong(final byte[] b, final int off) {
+ return (long) LONG_BE.get(b, off);
+ }
+
+ private static void putLong(final byte[] b, final int off, final long v) {
+ LONG_BE.set(b, off, v);
+ }
+
+ private static int getInt(final byte[] b, final int off) {
+ return (int) INT_BE.get(b, off);
+ }
+
+ private static void putInt(final byte[] b, final int off, final int v) {
+ INT_BE.set(b, off, v);
+ }
+
+ /** Build a long from 1-8 big-endian bytes. */
+ private static long buildLong(final byte[] src, final int off, final int len) {
+ long v = 0;
+ for (int i = off, end = off + len; i < end; i++) {
+ v = (v << 8) | (src[i] & 0xFFL);
+ }
+ return v;
+ }
+
+ /** Decode 8 big-endian bytes from src[off] into a long. */
+ private static long bytesToLong(final byte[] src, final int off) {
+ return getLong(src, off);
+ }
+
+ private static void longIntoBytes(final byte[] bytes, final int offset, final long value) {
+ putLong(bytes, offset, value);
+ }
+
+ private static int byteLen(final long v) {
+ return (64 - Long.numberOfLeadingZeros(v) + 7) / 8;
+ }
+
+ /** Unsigned less-than comparison of two slots. */
+ private static boolean unsignedLt(
+ final long[] s1, final int off1, final long[] s2, final int off2) {
+ // Compare u3 first (MSB)
+ if (s1[off1] != s2[off2]) return Long.compareUnsigned(s1[off1], s2[off2]) < 0;
+ if (s1[off1 + 1] != s2[off2 + 1]) return Long.compareUnsigned(s1[off1 + 1], s2[off2 + 1]) < 0;
+ if (s1[off1 + 2] != s2[off2 + 2]) return Long.compareUnsigned(s1[off1 + 2], s2[off2 + 2]) < 0;
+ return Long.compareUnsigned(s1[off1 + 3], s2[off2 + 3]) < 0;
+ }
+
+ /** Signed comparison of two slots. */
+ private static int signedCompare(
+ final long[] s1, final int off1, final long[] s2, final int off2) {
+ boolean aNeg = s1[off1] < 0;
+ boolean bNeg = s2[off2] < 0;
+ if (aNeg && !bNeg) return -1;
+ if (!aNeg && bNeg) return 1;
+ // Same sign: unsigned compare gives correct result
+ if (s1[off1] != s2[off2]) return Long.compareUnsigned(s1[off1], s2[off2]);
+ if (s1[off1 + 1] != s2[off2 + 1]) return Long.compareUnsigned(s1[off1 + 1], s2[off2 + 1]);
+ if (s1[off1 + 2] != s2[off2 + 2]) return Long.compareUnsigned(s1[off1 + 2], s2[off2 + 2]);
+ return Long.compareUnsigned(s1[off1 + 3], s2[off2 + 3]);
+ }
+
+ /**
+ * Shifts a 64-bit word right and carries in bits from the previous more-significant word.
+ *
+ * The {@code bitShift == 0} fast path avoids Java long-shift masking, where a shift by 64 is
+ * treated as a shift by 0.
+ *
+ * @param value the current word
+ * @param prevValue the previous more-significant word
+ * @param bitShift the intra-word shift amount in the range {@code [0..63]}
+ * @return the shifted word
+ */
+ private static long shiftRightWord(final long value, final long prevValue, final int bitShift) {
+ if (bitShift == 0) return value;
+ return (value >>> bitShift) | (prevValue << (64 - bitShift));
+ }
+
+ // endregion
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/StackPool.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/StackPool.java
new file mode 100644
index 00000000000..63f7ba8bc8e
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/StackPool.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright contributors to Hyperledger Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2;
+
+/**
+ * Thread-local pool of {@code long[]} arrays used as EVM operand stacks. Replaces the previous
+ * {@code OperandStack} pooling with direct array pooling to eliminate one level of indirection.
+ *
+ * The pool tracks peak usage with an exponential moving average and periodically shrinks to
+ * reclaim memory after usage spikes subside.
+ */
+public final class StackPool {
+
+ private static final int DEFAULT_MAX_SIZE = 1024;
+ private static final int INITIAL_CAPACITY = 16;
+ private static final int MAINTENANCE_INTERVAL = 256;
+
+ private static final ThreadLocal Reads arguments from the v2 {@code long[]} stack, creates a child {@link MessageFrame} with
+ * {@code enableEvmV2(true)}, suspends the parent, and writes the result back when the child
+ * completes.
+ */
+public abstract class AbstractCallOperationV2 extends AbstractOperationV2 {
+
+ /** Underflow response returned when the stack does not have enough items. */
+ protected static final OperationResult UNDERFLOW_RESPONSE =
+ new OperationResult(0L, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS);
+
+ /**
+ * Instantiates a new abstract call operation for EVM v2.
+ *
+ * @param opcode the opcode
+ * @param name the name
+ * @param stackItemsConsumed the stack items consumed
+ * @param stackItemsProduced the stack items produced
+ * @param gasCalculator the gas calculator
+ */
+ AbstractCallOperationV2(
+ final int opcode,
+ final String name,
+ final int stackItemsConsumed,
+ final int stackItemsProduced,
+ final GasCalculator gasCalculator) {
+ super(opcode, name, stackItemsConsumed, stackItemsProduced, gasCalculator);
+ }
+
+ /**
+ * Returns the gas stipend from the v2 stack (depth 0).
+ *
+ * @param s the v2 stack array
+ * @param top the current stack top
+ * @return the gas stipend, clamped to long
+ */
+ protected long gas(final long[] s, final int top) {
+ return StackArithmetic.clampedToLong(s, top, 0);
+ }
+
+ /**
+ * Returns the target address from the v2 stack.
+ *
+ * @param s the v2 stack array
+ * @param top the current stack top
+ * @return the target address
+ */
+ protected abstract Address to(long[] s, int top);
+
+ /**
+ * Returns the value (Wei) to transfer.
+ *
+ * @param s the v2 stack array
+ * @param top the current stack top
+ * @return the transfer value
+ */
+ protected abstract Wei value(long[] s, int top);
+
+ /**
+ * Returns the apparent value (Wei) for the child frame.
+ *
+ * @param frame the current message frame
+ * @param s the v2 stack array
+ * @param top the current stack top
+ * @return the apparent value
+ */
+ protected abstract Wei apparentValue(MessageFrame frame, long[] s, int top);
+
+ /**
+ * Returns the input data memory offset.
+ *
+ * @param s the v2 stack array
+ * @param top the current stack top
+ * @return the input data offset
+ */
+ protected abstract long inputDataOffset(long[] s, int top);
+
+ /**
+ * Returns the input data length.
+ *
+ * @param s the v2 stack array
+ * @param top the current stack top
+ * @return the input data length
+ */
+ protected abstract long inputDataLength(long[] s, int top);
+
+ /**
+ * Returns the output data memory offset.
+ *
+ * @param s the v2 stack array
+ * @param top the current stack top
+ * @return the output data offset
+ */
+ protected abstract long outputDataOffset(long[] s, int top);
+
+ /**
+ * Returns the output data length.
+ *
+ * @param s the v2 stack array
+ * @param top the current stack top
+ * @return the output data length
+ */
+ protected abstract long outputDataLength(long[] s, int top);
+
+ /**
+ * Returns the address used as the recipient in the child frame.
+ *
+ * @param frame the current message frame
+ * @param s the v2 stack array
+ * @param top the current stack top
+ * @return the recipient address
+ */
+ protected abstract Address address(MessageFrame frame, long[] s, int top);
+
+ /**
+ * Returns the sender address for the child frame.
+ *
+ * @param frame the current message frame
+ * @return the sender address
+ */
+ protected abstract Address sender(MessageFrame frame);
+
+ /**
+ * Returns the gas available for the child call.
+ *
+ * @param frame the current message frame
+ * @param s the v2 stack array
+ * @param top the current stack top
+ * @return the gas available for the child call
+ */
+ public abstract long gasAvailableForChildCall(MessageFrame frame, long[] s, int top);
+
+ /**
+ * Returns whether the child call should be static.
+ *
+ * @param frame the current message frame
+ * @return {@code true} if the child call should be static
+ */
+ protected boolean isStatic(final MessageFrame frame) {
+ return frame.isStatic();
+ }
+
+ /**
+ * Returns whether this is a delegate call.
+ *
+ * @return {@code true} if this is a delegate call
+ */
+ protected boolean isDelegate() {
+ return false;
+ }
+
+ @Override
+ public OperationResult execute(final MessageFrame frame, final EVM evm) {
+ if (!frame.stackHasItems(getStackItemsConsumed())) {
+ return UNDERFLOW_RESPONSE;
+ }
+
+ final long[] s = frame.stackDataV2();
+ final int top = frame.stackTopV2();
+
+ final Address to = to(s, top);
+ final boolean accountIsWarm = frame.warmUpAddress(to) || gasCalculator().isPrecompile(to);
+ final long stipend = gas(s, top);
+ final long inputOffset = inputDataOffset(s, top);
+ final long inputLength = inputDataLength(s, top);
+ final long outputOffset = outputDataOffset(s, top);
+ final long outputLength = outputDataLength(s, top);
+ final Wei transferValue = value(s, top);
+ final Address recipientAddress = address(frame, s, top);
+
+ final long staticCost =
+ gasCalculator()
+ .callOperationStaticGasCost(
+ frame,
+ stipend,
+ inputOffset,
+ inputLength,
+ outputOffset,
+ outputLength,
+ transferValue,
+ recipientAddress,
+ accountIsWarm);
+
+ if (frame.getRemainingGas() < staticCost) {
+ return new OperationResult(staticCost, ExceptionalHaltReason.INSUFFICIENT_GAS);
+ }
+
+ long cost =
+ gasCalculator()
+ .callOperationGasCost(
+ frame,
+ staticCost,
+ stipend,
+ inputOffset,
+ inputLength,
+ outputOffset,
+ outputLength,
+ transferValue,
+ recipientAddress,
+ accountIsWarm);
+ if (frame.getRemainingGas() < cost) {
+ return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS);
+ }
+
+ final Account contract = getAccount(to, frame);
+ cost = clampedAdd(cost, gasCalculator().calculateCodeDelegationResolutionGas(frame, contract));
+
+ if (frame.getRemainingGas() < cost) {
+ return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS);
+ }
+ frame.decrementRemainingGas(cost);
+
+ // EIP-8037: Charge state gas for new account creation in CALL
+ if (!gasCalculator()
+ .stateGasCostCalculator()
+ .chargeCallNewAccountStateGas(frame, recipientAddress, transferValue)) {
+ return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS);
+ }
+
+ final long gasForChild = gasAvailableForChildCall(frame, s, top);
+
+ frame.clearReturnData();
+
+ final Account account = getAccount(frame.getRecipientAddress(), frame);
+ final Wei balance = account == null ? Wei.ZERO : account.getBalance();
+
+ final boolean insufficientBalance = transferValue.compareTo(balance) > 0;
+ final boolean isFrameDepthTooDeep = frame.getDepth() >= 1024;
+ if (insufficientBalance || isFrameDepthTooDeep) {
+ frame.expandMemory(inputOffset, inputLength);
+ frame.expandMemory(outputOffset, outputLength);
+ frame.incrementRemainingGas(gasForChild + cost);
+ final int newTop = top - getStackItemsConsumed() + 1;
+ StackArithmetic.putAt(s, newTop, 0, 0L, 0L, 0L, 0L);
+ frame.setTopV2(newTop);
+ final SoftFailureReason softFailureReason =
+ insufficientBalance ? LEGACY_INSUFFICIENT_BALANCE : LEGACY_MAX_CALL_DEPTH;
+ return new OperationResult(cost, 1, softFailureReason, gasForChild);
+ }
+
+ final Bytes inputData = frame.readMutableMemory(inputOffset, inputLength);
+ final Code code = getCode(evm, frame, contract);
+
+ MessageFrame.Builder builder =
+ MessageFrame.builder()
+ .parentMessageFrame(frame)
+ .type(MessageFrame.Type.MESSAGE_CALL)
+ .initialGas(gasForChild)
+ .address(recipientAddress)
+ .contract(to)
+ .inputData(inputData)
+ .sender(sender(frame))
+ .value(transferValue)
+ .apparentValue(apparentValue(frame, s, top))
+ .code(code)
+ .isStatic(isStatic(frame))
+ .enableEvmV2(true)
+ .completer(child -> complete(frame, child));
+
+ if (frame.getEip7928AccessList().isPresent()) {
+ builder.eip7928AccessList(frame.getEip7928AccessList().get());
+ }
+
+ builder.build();
+ frame.incrementRemainingGas(cost);
+
+ frame.setState(MessageFrame.State.CODE_SUSPENDED);
+ return new OperationResult(cost, null, 0);
+ }
+
+ /**
+ * Called when the child frame has finished executing. Restores the parent frame and pushes the
+ * result onto the v2 stack.
+ *
+ * @param frame the parent message frame
+ * @param childFrame the completed child message frame
+ */
+ public void complete(final MessageFrame frame, final MessageFrame childFrame) {
+ frame.setState(MessageFrame.State.CODE_EXECUTING);
+
+ final long[] s = frame.stackDataV2();
+ final int top = frame.stackTopV2();
+
+ final long outputOffset = outputDataOffset(s, top);
+ final long outputSize = outputDataLength(s, top);
+ final Bytes outputData = childFrame.getOutputData();
+
+ if (outputSize > outputData.size()) {
+ frame.expandMemory(outputOffset, outputSize);
+ frame.writeMemory(outputOffset, outputData.size(), outputData, true);
+ } else if (outputSize > 0) {
+ frame.writeMemory(outputOffset, outputSize, outputData, true);
+ }
+
+ frame.setReturnData(outputData);
+ if (!childFrame.getLogs().isEmpty()) {
+ frame.addLogs(childFrame.getLogs());
+ }
+ if (!childFrame.getSelfDestructs().isEmpty()) {
+ frame.addSelfDestructs(childFrame.getSelfDestructs());
+ }
+ if (!childFrame.getCreates().isEmpty()) {
+ frame.addCreates(childFrame.getCreates());
+ }
+
+ frame.incrementRemainingGas(childFrame.getRemainingGas());
+
+ final int newTop = top - getStackItemsConsumed() + 1;
+ final long resultU0 = childFrame.getState() == State.COMPLETED_SUCCESS ? 1L : 0L;
+ StackArithmetic.putAt(s, newTop, 0, 0L, 0L, 0L, resultU0);
+ frame.setTopV2(newTop);
+
+ frame.setPC(frame.getPC() + 1);
+ }
+
+ /**
+ * Gets the executable code for the given account, resolving EIP-7702 code delegation if present.
+ *
+ * @param evm the EVM
+ * @param frame the current message frame
+ * @param account the account whose code is needed
+ * @return the resolved code, or {@link Code#EMPTY_CODE} if none
+ */
+ protected Code getCode(final EVM evm, final MessageFrame frame, final Account account) {
+ if (account == null) {
+ return Code.EMPTY_CODE;
+ }
+
+ final Hash codeHash = account.getCodeHash();
+ frame.getEip7928AccessList().ifPresent(t -> t.addTouchedAccount(account.getAddress()));
+ if (codeHash == null || codeHash.equals(Hash.EMPTY)) {
+ return Code.EMPTY_CODE;
+ }
+
+ final boolean accountHasCodeCache = account.getCodeCache() != null;
+ final Code code;
+ if (accountHasCodeCache) {
+ code = account.getOrCreateCachedCode();
+ } else {
+ code = evm.getOrCreateCachedJumpDest(codeHash, account.getCode());
+ }
+
+ if (!hasCodeDelegation(code.getBytes())) {
+ return code;
+ }
+
+ final CodeDelegationHelper.Target target =
+ getTarget(
+ frame.getWorldUpdater(),
+ evm.getGasCalculator()::isPrecompile,
+ account,
+ frame.getEip7928AccessList());
+
+ if (accountHasCodeCache) {
+ return target.code();
+ }
+ final Code targetCode = target.code();
+ return evm.getOrCreateCachedJumpDest(targetCode.getCodeHash(), targetCode.getBytes());
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCreateOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCreateOperationV2.java
new file mode 100644
index 00000000000..c9a990fbc0d
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCreateOperationV2.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import static org.hyperledger.besu.evm.frame.SoftFailureReason.INVALID_STATE;
+import static org.hyperledger.besu.evm.frame.SoftFailureReason.LEGACY_INSUFFICIENT_BALANCE;
+import static org.hyperledger.besu.evm.frame.SoftFailureReason.LEGACY_MAX_CALL_DEPTH;
+
+import org.hyperledger.besu.datatypes.Address;
+import org.hyperledger.besu.datatypes.Wei;
+import org.hyperledger.besu.evm.Code;
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.account.MutableAccount;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.frame.SoftFailureReason;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+import java.util.Optional;
+import java.util.function.Supplier;
+
+import com.google.common.base.Suppliers;
+import org.apache.tuweni.bytes.Bytes;
+
+/**
+ * Base class for EVM v2 CREATE-family operations (CREATE, CREATE2).
+ *
+ * Reads arguments from the v2 {@code long[]} stack, creates a child {@link MessageFrame} with
+ * {@code enableEvmV2(true)}, suspends the parent, and writes the result back when the child
+ * completes.
+ */
+public abstract class AbstractCreateOperationV2 extends AbstractOperationV2 {
+
+ /** Underflow response returned when the stack does not have enough items. */
+ protected static final OperationResult UNDERFLOW_RESPONSE =
+ new OperationResult(0L, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS);
+
+ /**
+ * Instantiates a new abstract create operation for EVM v2.
+ *
+ * @param opcode the opcode
+ * @param name the name
+ * @param stackItemsConsumed the stack items consumed
+ * @param stackItemsProduced the stack items produced
+ * @param gasCalculator the gas calculator
+ */
+ protected AbstractCreateOperationV2(
+ final int opcode,
+ final String name,
+ final int stackItemsConsumed,
+ final int stackItemsProduced,
+ final GasCalculator gasCalculator) {
+ super(opcode, name, stackItemsConsumed, stackItemsProduced, gasCalculator);
+ }
+
+ @Override
+ public OperationResult execute(final MessageFrame frame, final EVM evm) {
+ if (!frame.stackHasItems(getStackItemsConsumed())) {
+ return UNDERFLOW_RESPONSE;
+ }
+
+ Supplier Implements {@link Operation} directly (no dependency on V1 {@code AbstractOperation}) so that
+ * V1 operation code can eventually be removed. Static helpers mirror the instance helpers of V1's
+ * {@code AbstractOperation} but are callable from {@code staticOperation()} methods used by the v2
+ * dispatch loop.
+ */
+public abstract class AbstractOperationV2 implements Operation {
+
+ private final int opcode;
+ private final String name;
+ private final int stackItemsConsumed;
+ private final int stackItemsProduced;
+ private final GasCalculator gasCalculator;
+
+ /**
+ * Instantiates a new v2 operation.
+ *
+ * @param opcode the opcode
+ * @param name the operation name
+ * @param stackItemsConsumed the number of stack items consumed
+ * @param stackItemsProduced the number of stack items produced
+ * @param gasCalculator the gas calculator
+ */
+ protected AbstractOperationV2(
+ final int opcode,
+ final String name,
+ final int stackItemsConsumed,
+ final int stackItemsProduced,
+ final GasCalculator gasCalculator) {
+ this.opcode = opcode & 0xff;
+ this.name = name;
+ this.stackItemsConsumed = stackItemsConsumed;
+ this.stackItemsProduced = stackItemsProduced;
+ this.gasCalculator = gasCalculator;
+ }
+
+ /**
+ * Gets the gas calculator.
+ *
+ * @return the gas calculator
+ */
+ protected GasCalculator gasCalculator() {
+ return gasCalculator;
+ }
+
+ @Override
+ public int getOpcode() {
+ return opcode;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public int getStackItemsConsumed() {
+ return stackItemsConsumed;
+ }
+
+ @Override
+ public int getStackItemsProduced() {
+ return stackItemsProduced;
+ }
+
+ /**
+ * Reads an account (immutable) and records it in the EIP-7928 block access list if active.
+ *
+ * Use this instead of {@code frame.getWorldUpdater().getAccount(address)} for read-only
+ * account access. {@code getAccount()} wraps the account in a tracking object that appears in
+ * {@code getTouchedAccounts()}, which can cause empty accounts to be spuriously deleted by {@code
+ * clearAccountsThatAreEmpty()}. This method uses the side-effect-free {@code get()} instead.
+ *
+ * @param address the account address
+ * @param frame the current message frame
+ * @return the account, or {@code null} if it does not exist
+ */
+ protected static Account getAccount(final Address address, final MessageFrame frame) {
+ final Account account = frame.getWorldUpdater().get(address);
+ frame.getEip7928AccessList().ifPresent(t -> t.addTouchedAccount(address));
+ return account;
+ }
+
+ /**
+ * Retrieves a mutable view of the account at the specified address.
+ *
+ * If an EIP-7928 Block Access List is active, the address is added to the access list.
+ *
+ * @param address the account address
+ * @param frame the current message frame
+ * @return the {@link MutableAccount}, or {@code null} if it does not exist
+ */
+ protected static MutableAccount getMutableAccount(
+ final Address address, final MessageFrame frame) {
+ final MutableAccount account = frame.getWorldUpdater().getAccount(address);
+ frame.getEip7928AccessList().ifPresent(t -> t.addTouchedAccount(address));
+ return account;
+ }
+
+ /**
+ * Retrieves a mutable view of the account at the specified address, creating it if it does not
+ * exist.
+ *
+ * If an EIP-7928 Block Access List is active, the address is added to the access list.
+ *
+ * @param address the account address
+ * @param frame the current message frame
+ * @return the existing or newly created {@link MutableAccount}
+ */
+ protected static MutableAccount getOrCreateAccount(
+ final Address address, final MessageFrame frame) {
+ final MutableAccount account = frame.getWorldUpdater().getOrCreate(address);
+ frame.getEip7928AccessList().ifPresent(t -> t.addTouchedAccount(address));
+ return account;
+ }
+
+ /**
+ * Retrieves a mutable view of the sender's account.
+ *
+ * If an EIP-7928 Block Access List is active, the sender address is added to the access list.
+ *
+ * @param frame the current message frame
+ * @return the {@link MutableAccount} for the sender
+ */
+ protected static MutableAccount getSenderAccount(final MessageFrame frame) {
+ final MutableAccount account = frame.getWorldUpdater().getSenderAccount(frame);
+ frame.getEip7928AccessList().ifPresent(t -> t.addTouchedAccount(account.getAddress()));
+ return account;
+ }
+
+ /**
+ * Reads a storage slot from the specified account.
+ *
+ * If an EIP-7928 Block Access List is active, the slot access is recorded for the account.
+ *
+ * @param account the account whose storage is being accessed
+ * @param slotKey the key of the storage slot
+ * @param frame the current message frame
+ * @return the value stored at the specified key
+ */
+ protected static UInt256 getStorageValue(
+ final Account account, final UInt256 slotKey, final MessageFrame frame) {
+ final UInt256 slotValue = account.getStorageValue(slotKey);
+ frame
+ .getEip7928AccessList()
+ .ifPresent(t -> t.addSlotAccessForAccount(account.getAddress(), slotKey));
+ return slotValue;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddModOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddModOperationV2.java
new file mode 100644
index 00000000000..a8fe33ee6d9
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddModOperationV2.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** The Add mod operation. */
+public class AddModOperationV2 extends AbstractFixedCostOperationV2 {
+
+ private static final OperationResult addModSuccess = new OperationResult(8, null);
+
+ /**
+ * Instantiates a new Add mod operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public AddModOperationV2(final GasCalculator gasCalculator) {
+ super(0x08, "ADDMOD", 3, 1, gasCalculator, gasCalculator.getMidTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs AddMod operation.
+ *
+ * @param frame the frame
+ * @param s the stack data array
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] s) {
+ if (!frame.stackHasItems(3)) return UNDERFLOW_RESPONSE;
+ frame.setTopV2(StackArithmetic.addMod(s, frame.stackTopV2()));
+ return addModSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/v2/AddOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddOperationV2.java
similarity index 60%
rename from evm/src/main/java/org/hyperledger/besu/evm/operation/v2/AddOperationV2.java
rename to evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddOperationV2.java
index 4c866bcf61f..a0a007ff6e0 100644
--- a/evm/src/main/java/org/hyperledger/besu/evm/operation/v2/AddOperationV2.java
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddOperationV2.java
@@ -12,25 +12,19 @@
*
* SPDX-License-Identifier: Apache-2.0
*/
-package org.hyperledger.besu.evm.operation.v2;
+package org.hyperledger.besu.evm.v2.operation;
import org.hyperledger.besu.evm.EVM;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
-/**
- * EVM v2 ADD operation using long[] stack representation.
- *
- * Each 256-bit word is stored as four longs: index 0 = most significant 64 bits, index 3 = least
- * significant 64 bits. Addition is performed with carry propagation from least to most significant
- * word, with overflow silently truncated (mod 2^256).
- */
+/** The Add operation. */
public class AddOperationV2 extends AbstractFixedCostOperationV2 {
- @SuppressWarnings("UnusedVariable")
- private static final Operation.OperationResult ADD_SUCCESS =
- new Operation.OperationResult(3, null);
+ /** The Add operation success result. */
+ static final OperationResult addSuccess = new OperationResult(3, null);
/**
* Instantiates a new Add operation.
@@ -48,15 +42,15 @@ public Operation.OperationResult executeFixedCostOperation(
}
/**
- * Execute the ADD opcode on the v2 long[] stack.
+ * Performs add operation.
*
- * @param frame the message frame
- * @param stackData the stack operands as a long[] array
+ * @param frame the frame
+ * @param stack the v2 operand stack ({@code long[]} in big-endian limb order)
* @return the operation result
*/
- @SuppressWarnings("DoNotCallSuggester")
- public static Operation.OperationResult staticOperation(
- final MessageFrame frame, final long[] stackData) {
- throw new UnsupportedOperationException("ADD operation not yet implemented for evm v2");
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE;
+ frame.setTopV2(StackArithmetic.add(stack, frame.stackTopV2()));
+ return addSuccess;
}
}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddressOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddressOperationV2.java
new file mode 100644
index 00000000000..083e5b092ba
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddressOperationV2.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** EVM v2 ADDRESS operation — pushes the recipient address onto the stack. */
+public class AddressOperationV2 extends AbstractFixedCostOperationV2 {
+
+ private static final OperationResult addressSuccess = new OperationResult(2, null);
+
+ /**
+ * Instantiates a new Address operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public AddressOperationV2(final GasCalculator gasCalculator) {
+ super(0x30, "ADDRESS", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs the ADDRESS operation.
+ *
+ * @param frame the message frame
+ * @param stack the v2 long[] stack
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE;
+ frame.setTopV2(
+ StackArithmetic.pushAddress(stack, frame.stackTopV2(), frame.getRecipientAddress()));
+ return addressSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AndOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AndOperationV2.java
new file mode 100644
index 00000000000..97210e79895
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AndOperationV2.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** EVM v2 AND operation (0x16). */
+public class AndOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /** The And operation success result. */
+ static final OperationResult andSuccess = new OperationResult(3, null);
+
+ /**
+ * Instantiates a new And operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public AndOperationV2(final GasCalculator gasCalculator) {
+ super(0x16, "AND", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs AND operation.
+ *
+ * @param frame the frame
+ * @param stack the v2 operand stack ({@code long[]} in big-endian limb order)
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE;
+ frame.setTopV2(StackArithmetic.and(stack, frame.stackTopV2()));
+ return andSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java
new file mode 100644
index 00000000000..b5e5a50ba9b
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.datatypes.Address;
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.UInt256;
+import org.hyperledger.besu.evm.account.Account;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/**
+ * EVM v2 BALANCE operation (0x31).
+ *
+ * Pops an address from the stack and replaces it with the account's balance in Wei. Applies
+ * warm/cold account access cost.
+ */
+public class BalanceOperationV2 extends AbstractOperationV2 {
+
+ /**
+ * Instantiates a new Balance operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public BalanceOperationV2(final GasCalculator gasCalculator) {
+ super(0x31, "BALANCE", 1, 1, gasCalculator);
+ }
+
+ /**
+ * Gas cost including warm/cold access.
+ *
+ * @param accountIsWarm whether the account was already warm
+ * @return the total gas cost
+ */
+ protected long cost(final boolean accountIsWarm) {
+ return gasCalculator().getBalanceOperationGasCost()
+ + (accountIsWarm
+ ? gasCalculator().getWarmStorageReadCost()
+ : gasCalculator().getColdAccountAccessCost());
+ }
+
+ @Override
+ public OperationResult execute(final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2(), gasCalculator());
+ }
+
+ /**
+ * Execute BALANCE on the v2 long[] stack.
+ *
+ * @param frame the message frame
+ * @param s the stack data
+ * @param gasCalculator the gas calculator
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) {
+ final long warmCost =
+ gasCalculator.getBalanceOperationGasCost() + gasCalculator.getWarmStorageReadCost();
+ final long coldCost =
+ gasCalculator.getBalanceOperationGasCost() + gasCalculator.getColdAccountAccessCost();
+ if (!frame.stackHasItems(1)) {
+ return new OperationResult(warmCost, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS);
+ }
+ final int top = frame.stackTopV2();
+ final Address address = StackArithmetic.toAddressAt(s, top, 0);
+ final boolean accountIsWarm =
+ frame.warmUpAddress(address) || gasCalculator.isPrecompile(address);
+ final long cost = accountIsWarm ? warmCost : coldCost;
+ if (frame.getRemainingGas() < cost) {
+ return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS);
+ }
+ final Account account = getAccount(address, frame);
+ // Overwrite in place (pop 1, push 1)
+ if (account == null) {
+ StackArithmetic.putAt(s, top, 0, UInt256.ZERO);
+ } else {
+ StackArithmetic.putAt(s, top, 0, UInt256.fromBytesBE(account.getBalance().toArrayUnsafe()));
+ }
+ return new OperationResult(cost, null);
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java
new file mode 100644
index 00000000000..f21c8badf35
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.datatypes.Wei;
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+import java.util.Optional;
+
+/** EVM v2 BASEFEE operation — pushes the block base fee onto the stack (London+). */
+public class BaseFeeOperationV2 extends AbstractFixedCostOperationV2 {
+
+ private static final OperationResult baseFeeSuccess = new OperationResult(2, null);
+ private static final OperationResult INVALID_OPERATION_RESPONSE =
+ new OperationResult(2, ExceptionalHaltReason.INVALID_OPERATION);
+
+ /**
+ * Instantiates a new Base fee operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public BaseFeeOperationV2(final GasCalculator gasCalculator) {
+ super(0x48, "BASEFEE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs the BASEFEE operation.
+ *
+ * @param frame the message frame
+ * @param stack the v2 long[] stack
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE;
+ final Optional Reads an index from the top of the stack and replaces it with the versioned hash at that index
+ * in the transaction's blob versioned hashes list, or zero if the index is out of range.
+ */
+public class BlobHashOperationV2 extends AbstractOperationV2 {
+
+ /** BLOBHASH opcode number */
+ public static final int OPCODE = 0x49;
+
+ /**
+ * Instantiates a new BlobHash operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public BlobHashOperationV2(final GasCalculator gasCalculator) {
+ super(OPCODE, "BLOBHASH", 1, 1, gasCalculator);
+ }
+
+ @Override
+ public OperationResult execute(final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Execute BLOBHASH on the v2 long[] stack.
+ *
+ * @param frame the message frame
+ * @param s the stack data
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] s) {
+ if (!frame.stackHasItems(1)) {
+ return new OperationResult(3, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS);
+ }
+ final int top = frame.stackTopV2();
+ if (frame.getVersionedHashes().isPresent()) {
+ final List Pops a block number from the stack and pushes its hash, or zero if the block is out of the
+ * lookback window (up to 256 blocks prior to the current block).
+ */
+public class BlockHashOperationV2 extends AbstractOperationV2 {
+
+ /**
+ * Instantiates a new BlockHash operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public BlockHashOperationV2(final GasCalculator gasCalculator) {
+ super(0x40, "BLOCKHASH", 1, 1, gasCalculator);
+ }
+
+ @Override
+ public OperationResult execute(final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Execute BLOCKHASH on the v2 long[] stack.
+ *
+ * @param frame the message frame
+ * @param s the stack data
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] s) {
+ final long cost = 20L;
+ if (!frame.stackHasItems(1)) {
+ return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS);
+ }
+ if (frame.getRemainingGas() < cost) {
+ return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS);
+ }
+
+ final int top = frame.stackTopV2();
+ // If blockArg doesn't fit in a non-negative long, it's out of range
+ if (!StackArithmetic.fitsInLong(s, top, 0)) {
+ StackArithmetic.putAt(s, top, 0, 0L, 0L, 0L, 0L);
+ return new OperationResult(cost, null);
+ }
+
+ final long soughtBlock = StackArithmetic.longAt(s, top, 0);
+ final BlockValues blockValues = frame.getBlockValues();
+ final long currentBlockNumber = blockValues.getNumber();
+ final BlockHashLookup blockHashLookup = frame.getBlockHashLookup();
+
+ // If the sought block is in the future, the current block, or outside the lookback window,
+ // return zero.
+ if (soughtBlock < 0
+ || soughtBlock >= currentBlockNumber
+ || soughtBlock < (currentBlockNumber - blockHashLookup.getLookback())) {
+ StackArithmetic.putAt(s, top, 0, 0L, 0L, 0L, 0L);
+ } else {
+ final Hash blockHash = blockHashLookup.apply(frame, soughtBlock);
+ final byte[] hashBytes = blockHash.getBytes().toArrayUnsafe();
+ StackArithmetic.fromBytesAt(s, top, 0, hashBytes, 0, hashBytes.length);
+ }
+
+ return new OperationResult(cost, null);
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ByteOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ByteOperationV2.java
new file mode 100644
index 00000000000..d4024e30ee9
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ByteOperationV2.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** EVM v2 BYTE operation (0x1A). */
+public class ByteOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /** The Byte operation success result. */
+ static final OperationResult byteSuccess = new OperationResult(3, null);
+
+ /**
+ * Instantiates a new Byte operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public ByteOperationV2(final GasCalculator gasCalculator) {
+ super(0x1A, "BYTE", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs BYTE operation.
+ *
+ * @param frame the frame
+ * @param stack the v2 operand stack ({@code long[]} in big-endian limb order)
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE;
+ frame.setTopV2(StackArithmetic.byte_(stack, frame.stackTopV2()));
+ return byteSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallCodeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallCodeOperationV2.java
new file mode 100644
index 00000000000..53130b4fe49
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallCodeOperationV2.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.datatypes.Address;
+import org.hyperledger.besu.datatypes.Wei;
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+import org.apache.tuweni.bytes.Bytes;
+
+/**
+ * EVM v2 CALLCODE operation (opcode 0xF2). Stack layout: gas, to, value, inOffset, inSize,
+ * outOffset, outSize → result.
+ */
+public class CallCodeOperationV2 extends AbstractCallOperationV2 {
+
+ /**
+ * Instantiates a new CALLCODE operation for EVM v2.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public CallCodeOperationV2(final GasCalculator gasCalculator) {
+ super(0xF2, "CALLCODE", 7, 1, gasCalculator);
+ }
+
+ @Override
+ protected Address to(final long[] s, final int top) {
+ return StackArithmetic.toAddressAt(s, top, 1);
+ }
+
+ @Override
+ protected Wei value(final long[] s, final int top) {
+ return Wei.wrap(Bytes.wrap(StackArithmetic.getAt(s, top, 2).toBytesBE()));
+ }
+
+ @Override
+ protected Wei apparentValue(final MessageFrame frame, final long[] s, final int top) {
+ return value(s, top);
+ }
+
+ @Override
+ protected long inputDataOffset(final long[] s, final int top) {
+ return StackArithmetic.clampedToLong(s, top, 3);
+ }
+
+ @Override
+ protected long inputDataLength(final long[] s, final int top) {
+ return StackArithmetic.clampedToLong(s, top, 4);
+ }
+
+ @Override
+ protected long outputDataOffset(final long[] s, final int top) {
+ return StackArithmetic.clampedToLong(s, top, 5);
+ }
+
+ @Override
+ protected long outputDataLength(final long[] s, final int top) {
+ return StackArithmetic.clampedToLong(s, top, 6);
+ }
+
+ @Override
+ protected Address address(final MessageFrame frame, final long[] s, final int top) {
+ return frame.getRecipientAddress();
+ }
+
+ @Override
+ protected Address sender(final MessageFrame frame) {
+ return frame.getRecipientAddress();
+ }
+
+ @Override
+ public long gasAvailableForChildCall(final MessageFrame frame, final long[] s, final int top) {
+ return gasCalculator().gasAvailableForChildCall(frame, gas(s, top), !value(s, top).isZero());
+ }
+
+ /**
+ * Performs the CALLCODE operation on the v2 stack.
+ *
+ * @param frame the current message frame
+ * @param s the v2 stack array
+ * @param gasCalculator the gas calculator
+ * @param evm the EVM
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame, final long[] s, final GasCalculator gasCalculator, final EVM evm) {
+ return new CallCodeOperationV2(gasCalculator).execute(frame, evm);
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallDataCopyOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallDataCopyOperationV2.java
new file mode 100644
index 00000000000..af731bc1a2f
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallDataCopyOperationV2.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/**
+ * EVM v2 CALLDATACOPY operation (0x37).
+ *
+ * Pops destOffset, sourceOffset, and size from the stack, then copies {@code size} bytes of
+ * input data starting at {@code sourceOffset} into memory at {@code destOffset}.
+ */
+public class CallDataCopyOperationV2 extends AbstractOperationV2 {
+
+ /**
+ * Instantiates a new CallDataCopy operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public CallDataCopyOperationV2(final GasCalculator gasCalculator) {
+ super(0x37, "CALLDATACOPY", 3, 0, gasCalculator);
+ }
+
+ @Override
+ public OperationResult execute(final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2(), gasCalculator());
+ }
+
+ /**
+ * Execute CALLDATACOPY on the v2 long[] stack.
+ *
+ * @param frame the message frame
+ * @param s the stack data
+ * @param gasCalculator the gas calculator
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) {
+ if (!frame.stackHasItems(3)) {
+ return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS);
+ }
+ final int top = frame.stackTopV2();
+ final long memOffset = StackArithmetic.clampedToLong(s, top, 0);
+ final long sourceOffset = StackArithmetic.clampedToLong(s, top, 1);
+ final long numBytes = StackArithmetic.clampedToLong(s, top, 2);
+ frame.setTopV2(top - 3);
+
+ final long cost = gasCalculator.dataCopyOperationGasCost(frame, memOffset, numBytes);
+ if (frame.getRemainingGas() < cost) {
+ return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS);
+ }
+
+ if (numBytes == 0) {
+ return new OperationResult(cost, null);
+ }
+
+ frame.writeMemory(memOffset, sourceOffset, numBytes, frame.getInputData(), true);
+
+ return new OperationResult(cost, null);
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallDataLoadOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallDataLoadOperationV2.java
new file mode 100644
index 00000000000..4c225d91aea
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallDataLoadOperationV2.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+import org.apache.tuweni.bytes.Bytes;
+
+/**
+ * EVM v2 CALLDATALOAD operation (0x35).
+ *
+ * Pops an offset from the stack and pushes 32 bytes of input data starting at that offset,
+ * right-padding with zeros if the offset is beyond the end of the input data.
+ */
+public class CallDataLoadOperationV2 extends AbstractFixedCostOperationV2 {
+
+ private static final Operation.OperationResult CALLDATALOAD_SUCCESS =
+ new Operation.OperationResult(3, null);
+
+ /**
+ * Instantiates a new CallDataLoad operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public CallDataLoadOperationV2(final GasCalculator gasCalculator) {
+ super(0x35, "CALLDATALOAD", 1, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Execute CALLDATALOAD on the v2 long[] stack.
+ *
+ * @param frame the message frame
+ * @param s the stack data
+ * @return the operation result
+ */
+ public static Operation.OperationResult staticOperation(
+ final MessageFrame frame, final long[] s) {
+ if (!frame.stackHasItems(1)) return UNDERFLOW_RESPONSE;
+ final int top = frame.stackTopV2();
+ final int off = (top - 1) << 2;
+
+ // If the offset doesn't fit in a non-negative int, result is zero
+ if (s[off] != 0 || s[off + 1] != 0 || s[off + 2] != 0 || (s[off + 3] >>> 31) != 0) {
+ s[off] = 0;
+ s[off + 1] = 0;
+ s[off + 2] = 0;
+ s[off + 3] = 0;
+ return CALLDATALOAD_SUCCESS;
+ }
+
+ final int offset = (int) s[off + 3];
+ final Bytes data = frame.getInputData();
+ if (offset < data.size()) {
+ final byte[] result = new byte[32];
+ final int toCopy = Math.min(32, data.size() - offset);
+ System.arraycopy(data.slice(offset, toCopy).toArrayUnsafe(), 0, result, 0, toCopy);
+ StackArithmetic.fromBytesAt(s, top, 0, result, 0, 32);
+ } else {
+ s[off] = 0;
+ s[off + 1] = 0;
+ s[off + 2] = 0;
+ s[off + 3] = 0;
+ }
+
+ return CALLDATALOAD_SUCCESS;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallDataSizeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallDataSizeOperationV2.java
new file mode 100644
index 00000000000..57958499a15
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallDataSizeOperationV2.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** EVM v2 CALLDATASIZE operation — pushes the size of the call input data onto the stack. */
+public class CallDataSizeOperationV2 extends AbstractFixedCostOperationV2 {
+
+ private static final OperationResult callDataSizeSuccess = new OperationResult(2, null);
+
+ /**
+ * Instantiates a new Call data size operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public CallDataSizeOperationV2(final GasCalculator gasCalculator) {
+ super(0x36, "CALLDATASIZE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs the CALLDATASIZE operation.
+ *
+ * @param frame the message frame
+ * @param stack the v2 long[] stack
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE;
+ frame.setTopV2(
+ StackArithmetic.pushLong(stack, frame.stackTopV2(), frame.getInputData().size()));
+ return callDataSizeSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallOperationV2.java
new file mode 100644
index 00000000000..666e3e2ac87
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallOperationV2.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.datatypes.Address;
+import org.hyperledger.besu.datatypes.Wei;
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+import org.apache.tuweni.bytes.Bytes;
+
+/**
+ * EVM v2 CALL operation (opcode 0xF1). Stack layout: gas, to, value, inOffset, inSize, outOffset,
+ * outSize → result.
+ */
+public class CallOperationV2 extends AbstractCallOperationV2 {
+
+ /**
+ * Instantiates a new CALL operation for EVM v2.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public CallOperationV2(final GasCalculator gasCalculator) {
+ super(0xF1, "CALL", 7, 1, gasCalculator);
+ }
+
+ @Override
+ protected Address to(final long[] s, final int top) {
+ return StackArithmetic.toAddressAt(s, top, 1);
+ }
+
+ @Override
+ protected Wei value(final long[] s, final int top) {
+ return Wei.wrap(Bytes.wrap(StackArithmetic.getAt(s, top, 2).toBytesBE()));
+ }
+
+ @Override
+ protected Wei apparentValue(final MessageFrame frame, final long[] s, final int top) {
+ return value(s, top);
+ }
+
+ @Override
+ protected long inputDataOffset(final long[] s, final int top) {
+ return StackArithmetic.clampedToLong(s, top, 3);
+ }
+
+ @Override
+ protected long inputDataLength(final long[] s, final int top) {
+ return StackArithmetic.clampedToLong(s, top, 4);
+ }
+
+ @Override
+ protected long outputDataOffset(final long[] s, final int top) {
+ return StackArithmetic.clampedToLong(s, top, 5);
+ }
+
+ @Override
+ protected long outputDataLength(final long[] s, final int top) {
+ return StackArithmetic.clampedToLong(s, top, 6);
+ }
+
+ @Override
+ protected Address address(final MessageFrame frame, final long[] s, final int top) {
+ return to(s, top);
+ }
+
+ @Override
+ protected Address sender(final MessageFrame frame) {
+ return frame.getRecipientAddress();
+ }
+
+ @Override
+ public long gasAvailableForChildCall(final MessageFrame frame, final long[] s, final int top) {
+ return gasCalculator().gasAvailableForChildCall(frame, gas(s, top), !value(s, top).isZero());
+ }
+
+ /**
+ * Performs the CALL operation on the v2 stack.
+ *
+ * @param frame the current message frame
+ * @param s the v2 stack array
+ * @param gasCalculator the gas calculator
+ * @param evm the EVM
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame, final long[] s, final GasCalculator gasCalculator, final EVM evm) {
+ if (frame.isStatic()
+ && !Wei.wrap(Bytes.wrap(StackArithmetic.getAt(s, frame.stackTopV2(), 2).toBytesBE()))
+ .isZero()) {
+ return new OperationResult(0, ExceptionalHaltReason.ILLEGAL_STATE_CHANGE);
+ }
+ return new CallOperationV2(gasCalculator).execute(frame, evm);
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java
new file mode 100644
index 00000000000..407b7ff34c7
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** EVM v2 CALLVALUE operation — pushes the apparent call value onto the stack. */
+public class CallValueOperationV2 extends AbstractFixedCostOperationV2 {
+
+ private static final OperationResult callValueSuccess = new OperationResult(2, null);
+
+ /**
+ * Instantiates a new Call value operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public CallValueOperationV2(final GasCalculator gasCalculator) {
+ super(0x34, "CALLVALUE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs the CALLVALUE operation.
+ *
+ * @param frame the message frame
+ * @param stack the v2 long[] stack
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE;
+ frame.setTopV2(StackArithmetic.pushWei(stack, frame.stackTopV2(), frame.getApparentValue()));
+ return callValueSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallerOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallerOperationV2.java
new file mode 100644
index 00000000000..c927f172ba9
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallerOperationV2.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** EVM v2 CALLER operation — pushes the sender address onto the stack. */
+public class CallerOperationV2 extends AbstractFixedCostOperationV2 {
+
+ private static final OperationResult callerSuccess = new OperationResult(2, null);
+
+ /**
+ * Instantiates a new Caller operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public CallerOperationV2(final GasCalculator gasCalculator) {
+ super(0x33, "CALLER", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs the CALLER operation.
+ *
+ * @param frame the message frame
+ * @param stack the v2 long[] stack
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE;
+ frame.setTopV2(
+ StackArithmetic.pushAddress(stack, frame.stackTopV2(), frame.getSenderAddress()));
+ return callerSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ChainIdOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ChainIdOperationV2.java
new file mode 100644
index 00000000000..11d632da728
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ChainIdOperationV2.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.nio.ByteOrder;
+
+import org.apache.tuweni.bytes.Bytes;
+import org.apache.tuweni.bytes.Bytes32;
+
+/**
+ * EVM v2 CHAINID operation — pushes the pre-computed chain ID onto the stack (Istanbul+).
+ *
+ * The four 64-bit limbs of the chain ID are computed once at construction time.
+ */
+public class ChainIdOperationV2 extends AbstractFixedCostOperationV2 {
+
+ private static final VarHandle LONG_BE =
+ MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN);
+
+ /** CHAINID opcode number. */
+ public static final int OPCODE = 0x46;
+
+ private final long chainIdU3;
+ private final long chainIdU2;
+ private final long chainIdU1;
+ private final long chainIdU0;
+
+ /**
+ * Instantiates a new Chain ID operation.
+ *
+ * @param gasCalculator the gas calculator
+ * @param chainId the chain ID (left-padded to 32 bytes big-endian)
+ */
+ public ChainIdOperationV2(final GasCalculator gasCalculator, final Bytes chainId) {
+ super(OPCODE, "CHAINID", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost());
+ final byte[] b = Bytes32.leftPad(chainId).toArrayUnsafe();
+ this.chainIdU3 = (long) LONG_BE.get(b, 0);
+ this.chainIdU2 = (long) LONG_BE.get(b, 8);
+ this.chainIdU1 = (long) LONG_BE.get(b, 16);
+ this.chainIdU0 = (long) LONG_BE.get(b, 24);
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE;
+ final long[] s = frame.stackDataV2();
+ final int top = frame.stackTopV2();
+ final int dst = top << 2;
+ s[dst] = chainIdU3;
+ s[dst + 1] = chainIdU2;
+ s[dst + 2] = chainIdU1;
+ s[dst + 3] = chainIdU0;
+ frame.setTopV2(top + 1);
+ return successResponse;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ClzOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ClzOperationV2.java
new file mode 100644
index 00000000000..811c949b362
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ClzOperationV2.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** The Clz (Count Leading Zeros) operation. Introduced in Osaka. */
+public class ClzOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /** The Clz operation success result. */
+ static final OperationResult clzSuccess = new OperationResult(5, null);
+
+ /**
+ * Instantiates a new Clz operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public ClzOperationV2(final GasCalculator gasCalculator) {
+ super(0x1e, "CLZ", 1, 1, gasCalculator, gasCalculator.getLowTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs clz operation.
+ *
+ * @param frame the frame
+ * @param stack the v2 operand stack ({@code long[]} in big-endian limb order)
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasItems(1)) return UNDERFLOW_RESPONSE;
+ frame.setTopV2(StackArithmetic.clz(stack, frame.stackTopV2()));
+ return clzSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CodeCopyOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CodeCopyOperationV2.java
new file mode 100644
index 00000000000..e02fca7bdfe
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CodeCopyOperationV2.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/**
+ * EVM v2 CODECOPY operation (0x39).
+ *
+ * Pops destOffset, sourceOffset, and size from the stack, then copies {@code size} bytes of the
+ * current executing contract's code into memory at {@code destOffset}.
+ */
+public class CodeCopyOperationV2 extends AbstractOperationV2 {
+
+ /**
+ * Instantiates a new CodeCopy operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public CodeCopyOperationV2(final GasCalculator gasCalculator) {
+ super(0x39, "CODECOPY", 3, 0, gasCalculator);
+ }
+
+ @Override
+ public OperationResult execute(final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2(), gasCalculator());
+ }
+
+ /**
+ * Execute CODECOPY on the v2 long[] stack.
+ *
+ * @param frame the message frame
+ * @param s the stack data
+ * @param gasCalculator the gas calculator
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) {
+ if (!frame.stackHasItems(3)) {
+ return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS);
+ }
+ final int top = frame.stackTopV2();
+ final long memOffset = StackArithmetic.clampedToLong(s, top, 0);
+ final long sourceOffset = StackArithmetic.clampedToLong(s, top, 1);
+ final long numBytes = StackArithmetic.clampedToLong(s, top, 2);
+ frame.setTopV2(top - 3);
+
+ final long cost = gasCalculator.dataCopyOperationGasCost(frame, memOffset, numBytes);
+ if (frame.getRemainingGas() < cost) {
+ return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS);
+ }
+
+ frame.writeMemory(memOffset, sourceOffset, numBytes, frame.getCode().getBytes(), true);
+
+ return new OperationResult(cost, null);
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CodeSizeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CodeSizeOperationV2.java
new file mode 100644
index 00000000000..c121dd837a8
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CodeSizeOperationV2.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** EVM v2 CODESIZE operation — pushes the size of the executing contract code onto the stack. */
+public class CodeSizeOperationV2 extends AbstractFixedCostOperationV2 {
+
+ private static final OperationResult codeSizeSuccess = new OperationResult(2, null);
+
+ /**
+ * Instantiates a new Code size operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public CodeSizeOperationV2(final GasCalculator gasCalculator) {
+ super(0x38, "CODESIZE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs the CODESIZE operation.
+ *
+ * @param frame the message frame
+ * @param stack the v2 long[] stack
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE;
+ frame.setTopV2(StackArithmetic.pushLong(stack, frame.stackTopV2(), frame.getCode().getSize()));
+ return codeSizeSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CoinbaseOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CoinbaseOperationV2.java
new file mode 100644
index 00000000000..c9f5fc5b82f
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CoinbaseOperationV2.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** EVM v2 COINBASE operation — pushes the block beneficiary address onto the stack. */
+public class CoinbaseOperationV2 extends AbstractFixedCostOperationV2 {
+
+ private static final OperationResult coinbaseSuccess = new OperationResult(2, null);
+
+ /**
+ * Instantiates a new Coinbase operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public CoinbaseOperationV2(final GasCalculator gasCalculator) {
+ super(0x41, "COINBASE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs the COINBASE operation.
+ *
+ * @param frame the message frame
+ * @param stack the v2 long[] stack
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE;
+ frame.setTopV2(
+ StackArithmetic.pushAddress(stack, frame.stackTopV2(), frame.getMiningBeneficiary()));
+ return coinbaseSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Create2OperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Create2OperationV2.java
new file mode 100644
index 00000000000..01912658b8d
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Create2OperationV2.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import static org.hyperledger.besu.crypto.Hash.keccak256;
+import static org.hyperledger.besu.evm.internal.Words.clampedAdd;
+
+import org.hyperledger.besu.datatypes.Address;
+import org.hyperledger.besu.evm.Code;
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.EvmSpecVersion;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.InvalidOperation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+import java.util.function.Supplier;
+
+import org.apache.tuweni.bytes.Bytes;
+import org.apache.tuweni.bytes.Bytes32;
+
+/**
+ * EVM v2 CREATE2 operation (opcode 0xF5, Constantinople+). Stack layout: value, offset, size, salt
+ * → contractAddress.
+ */
+public class Create2OperationV2 extends AbstractCreateOperationV2 {
+
+ private static final Bytes PREFIX = Bytes.fromHexString("0xFF");
+
+ /**
+ * Instantiates a new CREATE2 operation for EVM v2.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public Create2OperationV2(final GasCalculator gasCalculator) {
+ super(0xF5, "CREATE2", 4, 1, gasCalculator);
+ }
+
+ @Override
+ protected long cost(
+ final MessageFrame frame, final long[] s, final int top, final Supplier Duplicates the n'th stack item to the top of the stack, where n is decoded from a 1-byte
+ * immediate operand. PC increment is 2. Gas cost is veryLow tier (3). Requires Amsterdam fork or
+ * later.
+ */
+public class DupNOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /** The DUPN opcode value. */
+ public static final int OPCODE = 0xe6;
+
+ private static final OperationResult DUPN_SUCCESS = new OperationResult(3, null, 2);
+
+ private static final OperationResult INVALID_IMMEDIATE =
+ new OperationResult(3, ExceptionalHaltReason.INVALID_OPERATION, 2);
+
+ private static final OperationResult DUPN_UNDERFLOW =
+ new OperationResult(3, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS, 2);
+
+ private static final OperationResult DUPN_OVERFLOW =
+ new OperationResult(3, ExceptionalHaltReason.TOO_MANY_STACK_ITEMS, 2);
+
+ /**
+ * Instantiates a new DUPN operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public DupNOperationV2(final GasCalculator gasCalculator) {
+ super(OPCODE, "DUPN", 0, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(
+ frame, frame.stackDataV2(), frame.getCode().getBytes().toArrayUnsafe(), frame.getPC());
+ }
+
+ /**
+ * Performs DUPN operation.
+ *
+ * @param frame the message frame
+ * @param s the stack data array
+ * @param code the bytecode array
+ * @param pc the current program counter
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame, final long[] s, final byte[] code, final int pc) {
+ final int imm = (pc + 1 >= code.length) ? 0 : code[pc + 1] & 0xFF;
+
+ if (!Eip8024Decoder.VALID_SINGLE[imm]) {
+ return INVALID_IMMEDIATE;
+ }
+
+ final int n = Eip8024Decoder.DECODE_SINGLE[imm];
+
+ if (!frame.stackHasItems(n)) {
+ return DUPN_UNDERFLOW;
+ }
+ if (!frame.stackHasSpace(1)) {
+ return DUPN_OVERFLOW;
+ }
+ frame.setTopV2(StackArithmetic.dup(s, frame.stackTopV2(), n));
+ return DUPN_SUCCESS;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DupOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DupOperationV2.java
new file mode 100644
index 00000000000..91fd29fcce7
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DupOperationV2.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/**
+ * EVM v2 DUP1-16 operation (opcodes 0x80–0x8F).
+ *
+ * Duplicates the item at depth {@code index} (1-based) to the top of the stack. Gas cost is
+ * veryLow tier (3).
+ */
+public class DupOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /** The DUP opcode base (DUP1 = 0x80, so base = 0x7F). */
+ public static final int DUP_BASE = 0x7F;
+
+ static final OperationResult DUP_SUCCESS = new OperationResult(3, null);
+
+ private final int index;
+
+ /**
+ * Instantiates a new Dup operation.
+ *
+ * @param index the 1-based depth to duplicate (1–16)
+ * @param gasCalculator the gas calculator
+ */
+ public DupOperationV2(final int index, final GasCalculator gasCalculator) {
+ super(
+ DUP_BASE + index,
+ "DUP" + index,
+ index,
+ index + 1,
+ gasCalculator,
+ gasCalculator.getVeryLowTierGasCost());
+ this.index = index;
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2(), index);
+ }
+
+ /**
+ * Performs DUP operation.
+ *
+ * @param frame the frame
+ * @param s the stack data array
+ * @param index the 1-based depth to duplicate
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame, final long[] s, final int index) {
+ if (!frame.stackHasItems(index)) return UNDERFLOW_RESPONSE;
+ if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE;
+ frame.setTopV2(StackArithmetic.dup(s, frame.stackTopV2(), index));
+ return DUP_SUCCESS;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/EqOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/EqOperationV2.java
new file mode 100644
index 00000000000..d47ec17bd5f
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/EqOperationV2.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** The Eq operation. */
+public class EqOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /** The Eq operation success result. */
+ static final OperationResult eqSuccess = new OperationResult(3, null);
+
+ /**
+ * Instantiates a new Eq operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public EqOperationV2(final GasCalculator gasCalculator) {
+ super(0x14, "EQ", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs eq operation.
+ *
+ * @param frame the frame
+ * @param stack the v2 operand stack ({@code long[]} in big-endian limb order)
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE;
+ frame.setTopV2(StackArithmetic.eq(stack, frame.stackTopV2()));
+ return eqSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExchangeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExchangeOperationV2.java
new file mode 100644
index 00000000000..bc31c421733
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExchangeOperationV2.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Eip8024Decoder;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/**
+ * EVM v2 EXCHANGE operation (0xE8, EIP-8024).
+ *
+ * Swaps the (n+1)'th stack item with the (m+1)'th stack item, where n and m are decoded from a
+ * 1-byte immediate operand. PC increment is 2. Gas cost is veryLow tier (3). Requires Amsterdam
+ * fork or later.
+ */
+public class ExchangeOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /** The EXCHANGE opcode value. */
+ public static final int OPCODE = 0xe8;
+
+ private static final OperationResult EXCHANGE_SUCCESS = new OperationResult(3, null, 2);
+
+ private static final OperationResult INVALID_IMMEDIATE =
+ new OperationResult(3, ExceptionalHaltReason.INVALID_OPERATION, 2);
+
+ private static final OperationResult EXCHANGE_UNDERFLOW =
+ new OperationResult(3, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS, 2);
+
+ /**
+ * Instantiates a new EXCHANGE operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public ExchangeOperationV2(final GasCalculator gasCalculator) {
+ super(OPCODE, "EXCHANGE", 0, 0, gasCalculator, gasCalculator.getVeryLowTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(
+ frame, frame.stackDataV2(), frame.getCode().getBytes().toArrayUnsafe(), frame.getPC());
+ }
+
+ /**
+ * Performs EXCHANGE operation.
+ *
+ * @param frame the message frame
+ * @param s the stack data array
+ * @param code the bytecode array
+ * @param pc the current program counter
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame, final long[] s, final byte[] code, final int pc) {
+ final int imm = (pc + 1 >= code.length) ? 0 : code[pc + 1] & 0xFF;
+
+ final int packed = Eip8024Decoder.DECODE_PAIR_PACKED[imm];
+
+ if (packed == Eip8024Decoder.INVALID_PAIR) {
+ return INVALID_IMMEDIATE;
+ }
+
+ final int n = packed & 0xFF;
+ final int m = (packed >>> 8) & 0xFF;
+
+ if (!frame.stackHasItems(Math.max(n, m) + 1)) {
+ return EXCHANGE_UNDERFLOW;
+ }
+ StackArithmetic.exchange(s, frame.stackTopV2(), n, m);
+ return EXCHANGE_SUCCESS;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExpOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExpOperationV2.java
new file mode 100644
index 00000000000..e7e34096b0c
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExpOperationV2.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** The Exp operation. */
+public class ExpOperationV2 extends AbstractOperationV2 {
+
+ /**
+ * Instantiates a new Exp operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public ExpOperationV2(final GasCalculator gasCalculator) {
+ super(0x0A, "EXP", 2, 1, gasCalculator);
+ }
+
+ @Override
+ public OperationResult execute(final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2(), gasCalculator());
+ }
+
+ /**
+ * Performs EXP operation.
+ *
+ * @param frame the frame
+ * @param stack the stack data array
+ * @param gasCalculator the gas calculator
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame, final long[] stack, final GasCalculator gasCalculator) {
+ if (!frame.stackHasItems(2)) {
+ return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS);
+ }
+ final int byteCount = StackArithmetic.byteLengthAt(stack, frame.stackTopV2(), 1);
+ final long gasCost = gasCalculator.expOperationGasCost(byteCount);
+ if (frame.getRemainingGas() < gasCost) {
+ return new OperationResult(gasCost, ExceptionalHaltReason.INSUFFICIENT_GAS);
+ }
+ frame.setTopV2(StackArithmetic.exp(stack, frame.stackTopV2()));
+ return new OperationResult(gasCost, null);
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExtCodeCopyOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExtCodeCopyOperationV2.java
new file mode 100644
index 00000000000..41c9a4c44d7
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExtCodeCopyOperationV2.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import static org.hyperledger.besu.evm.internal.Words.clampedAdd;
+
+import org.hyperledger.besu.datatypes.Address;
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.account.Account;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+import org.apache.tuweni.bytes.Bytes;
+
+/**
+ * EVM v2 EXTCODECOPY operation (0x3C).
+ *
+ * Pops address, destOffset, sourceOffset, and size from the stack, applies warm/cold account
+ * access cost, and copies the external contract's code into memory.
+ */
+public class ExtCodeCopyOperationV2 extends AbstractOperationV2 {
+
+ /**
+ * Instantiates a new ExtCodeCopy operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public ExtCodeCopyOperationV2(final GasCalculator gasCalculator) {
+ super(0x3C, "EXTCODECOPY", 4, 0, gasCalculator);
+ }
+
+ /**
+ * Cost of EXTCODECOPY including warm/cold access.
+ *
+ * @param frame the frame
+ * @param memOffset the memory destination offset
+ * @param length the number of bytes to copy
+ * @param accountIsWarm whether the address was already warm
+ * @return the total gas cost
+ */
+ protected long cost(
+ final MessageFrame frame,
+ final long memOffset,
+ final long length,
+ final boolean accountIsWarm) {
+ return clampedAdd(
+ gasCalculator().extCodeCopyOperationGasCost(frame, memOffset, length),
+ accountIsWarm
+ ? gasCalculator().getWarmStorageReadCost()
+ : gasCalculator().getColdAccountAccessCost());
+ }
+
+ @Override
+ public OperationResult execute(final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2(), gasCalculator());
+ }
+
+ /**
+ * Execute EXTCODECOPY on the v2 long[] stack.
+ *
+ * @param frame the message frame
+ * @param s the stack data
+ * @param gasCalculator the gas calculator
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) {
+ if (!frame.stackHasItems(4)) {
+ return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS);
+ }
+ final int top = frame.stackTopV2();
+ final Address address = StackArithmetic.toAddressAt(s, top, 0);
+ final long memOffset = StackArithmetic.clampedToLong(s, top, 1);
+ final long sourceOffset = StackArithmetic.clampedToLong(s, top, 2);
+ final long numBytes = StackArithmetic.clampedToLong(s, top, 3);
+ frame.setTopV2(top - 4);
+
+ final boolean accountIsWarm =
+ frame.warmUpAddress(address) || gasCalculator.isPrecompile(address);
+ final long cost =
+ clampedAdd(
+ gasCalculator.extCodeCopyOperationGasCost(frame, memOffset, numBytes),
+ accountIsWarm
+ ? gasCalculator.getWarmStorageReadCost()
+ : gasCalculator.getColdAccountAccessCost());
+
+ if (frame.getRemainingGas() < cost) {
+ return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS);
+ }
+
+ final Account account = getAccount(address, frame);
+ final Bytes code = account != null ? account.getCode() : Bytes.EMPTY;
+
+ frame.writeMemory(memOffset, sourceOffset, numBytes, code);
+
+ return new OperationResult(cost, null);
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExtCodeHashOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExtCodeHashOperationV2.java
new file mode 100644
index 00000000000..759ffb49b9a
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExtCodeHashOperationV2.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.datatypes.Address;
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.account.Account;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/**
+ * EVM v2 EXTCODEHASH operation (0x3F, Constantinople+).
+ *
+ * Pops an address from the stack and replaces it with the Keccak-256 hash of the external
+ * account's code. Returns zero for empty or non-existent accounts. Applies warm/cold access cost.
+ */
+public class ExtCodeHashOperationV2 extends AbstractOperationV2 {
+
+ /**
+ * Instantiates a new ExtCodeHash operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public ExtCodeHashOperationV2(final GasCalculator gasCalculator) {
+ super(0x3F, "EXTCODEHASH", 1, 1, gasCalculator);
+ }
+
+ /**
+ * Gas cost including warm/cold access.
+ *
+ * @param accountIsWarm whether the account was already warm
+ * @return the total gas cost
+ */
+ protected long cost(final boolean accountIsWarm) {
+ return gasCalculator().extCodeHashOperationGasCost()
+ + (accountIsWarm
+ ? gasCalculator().getWarmStorageReadCost()
+ : gasCalculator().getColdAccountAccessCost());
+ }
+
+ @Override
+ public OperationResult execute(final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2(), gasCalculator());
+ }
+
+ /**
+ * Execute EXTCODEHASH on the v2 long[] stack.
+ *
+ * @param frame the message frame
+ * @param s the stack data
+ * @param gasCalculator the gas calculator
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) {
+ final long warmCost =
+ gasCalculator.extCodeHashOperationGasCost() + gasCalculator.getWarmStorageReadCost();
+ final long coldCost =
+ gasCalculator.extCodeHashOperationGasCost() + gasCalculator.getColdAccountAccessCost();
+ if (!frame.stackHasItems(1)) {
+ return new OperationResult(warmCost, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS);
+ }
+ final int top = frame.stackTopV2();
+ final Address address = StackArithmetic.toAddressAt(s, top, 0);
+ final boolean accountIsWarm =
+ frame.warmUpAddress(address) || gasCalculator.isPrecompile(address);
+ final long cost = accountIsWarm ? warmCost : coldCost;
+ if (frame.getRemainingGas() < cost) {
+ return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS);
+ }
+
+ final Account account = getAccount(address, frame);
+
+ // Overwrite in place (pop 1, push 1)
+ if (account == null || account.isEmpty()) {
+ StackArithmetic.putAt(s, top, 0, 0L, 0L, 0L, 0L);
+ } else {
+ final byte[] hashBytes = account.getCodeHash().getBytes().toArrayUnsafe();
+ StackArithmetic.fromBytesAt(s, top, 0, hashBytes, 0, hashBytes.length);
+ }
+ return new OperationResult(cost, null);
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExtCodeSizeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExtCodeSizeOperationV2.java
new file mode 100644
index 00000000000..f0708bfc954
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExtCodeSizeOperationV2.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.datatypes.Address;
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.account.Account;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/**
+ * EVM v2 EXTCODESIZE operation (0x3B).
+ *
+ * Pops an address from the stack and replaces it with the external account's code size. Applies
+ * warm/cold account access cost.
+ */
+public class ExtCodeSizeOperationV2 extends AbstractOperationV2 {
+
+ /**
+ * Instantiates a new ExtCodeSize operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public ExtCodeSizeOperationV2(final GasCalculator gasCalculator) {
+ super(0x3B, "EXTCODESIZE", 1, 1, gasCalculator);
+ }
+
+ /**
+ * Gas cost including warm/cold access.
+ *
+ * @param accountIsWarm whether the account was already warm
+ * @return the total gas cost
+ */
+ protected long cost(final boolean accountIsWarm) {
+ return gasCalculator().getExtCodeSizeOperationGasCost()
+ + (accountIsWarm
+ ? gasCalculator().getWarmStorageReadCost()
+ : gasCalculator().getColdAccountAccessCost());
+ }
+
+ @Override
+ public OperationResult execute(final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2(), gasCalculator());
+ }
+
+ /**
+ * Execute EXTCODESIZE on the v2 long[] stack.
+ *
+ * @param frame the message frame
+ * @param s the stack data
+ * @param gasCalculator the gas calculator
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) {
+ final long warmCost =
+ gasCalculator.getExtCodeSizeOperationGasCost() + gasCalculator.getWarmStorageReadCost();
+ final long coldCost =
+ gasCalculator.getExtCodeSizeOperationGasCost() + gasCalculator.getColdAccountAccessCost();
+ if (!frame.stackHasItems(1)) {
+ return new OperationResult(warmCost, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS);
+ }
+ final int top = frame.stackTopV2();
+ final Address address = StackArithmetic.toAddressAt(s, top, 0);
+ final boolean accountIsWarm =
+ frame.warmUpAddress(address) || gasCalculator.isPrecompile(address);
+ final long cost = accountIsWarm ? warmCost : coldCost;
+ if (frame.getRemainingGas() < cost) {
+ return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS);
+ }
+ final Account account = getAccount(address, frame);
+ // Overwrite in place (pop 1, push 1)
+ final long codeSize = account == null ? 0L : account.getCode().size();
+ StackArithmetic.putAt(s, top, 0, 0L, 0L, 0L, codeSize);
+ return new OperationResult(cost, null);
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasLimitOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasLimitOperationV2.java
new file mode 100644
index 00000000000..65cb9590c3b
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasLimitOperationV2.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** EVM v2 GASLIMIT operation — pushes the block gas limit onto the stack. */
+public class GasLimitOperationV2 extends AbstractFixedCostOperationV2 {
+
+ private static final OperationResult gasLimitSuccess = new OperationResult(2, null);
+
+ /**
+ * Instantiates a new Gas limit operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public GasLimitOperationV2(final GasCalculator gasCalculator) {
+ super(0x45, "GASLIMIT", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs the GASLIMIT operation.
+ *
+ * @param frame the message frame
+ * @param stack the v2 long[] stack
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE;
+ frame.setTopV2(
+ StackArithmetic.pushLong(stack, frame.stackTopV2(), frame.getBlockValues().getGasLimit()));
+ return gasLimitSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasOperationV2.java
new file mode 100644
index 00000000000..af48a72eca7
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasOperationV2.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/**
+ * EVM v2 GAS operation — pushes the amount of available gas remaining (after this instruction's
+ * cost) onto the stack.
+ */
+public class GasOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /**
+ * Instantiates a new Gas operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public GasOperationV2(final GasCalculator gasCalculator) {
+ super(0x5A, "GAS", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE;
+ // Gas remaining after deducting this instruction's cost
+ frame.setTopV2(
+ StackArithmetic.pushLong(
+ frame.stackDataV2(), frame.stackTopV2(), frame.getRemainingGas() - gasCost));
+ return successResponse;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java
new file mode 100644
index 00000000000..2197637499e
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** EVM v2 GASPRICE operation — pushes the gas price onto the stack. */
+public class GasPriceOperationV2 extends AbstractFixedCostOperationV2 {
+
+ private static final OperationResult gasPriceSuccess = new OperationResult(2, null);
+
+ /**
+ * Instantiates a new Gas price operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public GasPriceOperationV2(final GasCalculator gasCalculator) {
+ super(0x3A, "GASPRICE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs the GASPRICE operation.
+ *
+ * @param frame the message frame
+ * @param stack the v2 long[] stack
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE;
+ frame.setTopV2(StackArithmetic.pushWei(stack, frame.stackTopV2(), frame.getGasPrice()));
+ return gasPriceSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GtOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GtOperationV2.java
new file mode 100644
index 00000000000..2588e14fac1
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GtOperationV2.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** The Gt operation. */
+public class GtOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /** The Gt operation success result. */
+ static final OperationResult gtSuccess = new OperationResult(3, null);
+
+ /**
+ * Instantiates a new Gt operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public GtOperationV2(final GasCalculator gasCalculator) {
+ super(0x11, "GT", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs gt operation.
+ *
+ * @param frame the frame
+ * @param stack the v2 operand stack ({@code long[]} in big-endian limb order)
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE;
+ frame.setTopV2(StackArithmetic.gt(stack, frame.stackTopV2()));
+ return gtSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/InvalidOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/InvalidOperationV2.java
new file mode 100644
index 00000000000..98df3487d21
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/InvalidOperationV2.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+
+/** EVM v2 INVALID operation. Always halts with INVALID_OPERATION. */
+public class InvalidOperationV2 extends AbstractOperationV2 {
+
+ /** The constant OPCODE. */
+ public static final int OPCODE = 0xFE;
+
+ /** The constant INVALID_RESULT. */
+ public static final OperationResult INVALID_RESULT =
+ new OperationResult(0, ExceptionalHaltReason.INVALID_OPERATION);
+
+ /**
+ * Instantiates a new Invalid operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public InvalidOperationV2(final GasCalculator gasCalculator) {
+ super(OPCODE, "INVALID", -1, -1, gasCalculator);
+ }
+
+ @Override
+ public OperationResult execute(final MessageFrame frame, final EVM evm) {
+ return INVALID_RESULT;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/IsZeroOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/IsZeroOperationV2.java
new file mode 100644
index 00000000000..6ddddeba055
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/IsZeroOperationV2.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** The IsZero operation. */
+public class IsZeroOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /** The IsZero operation success result. */
+ static final OperationResult isZeroSuccess = new OperationResult(3, null);
+
+ /**
+ * Instantiates a new IsZero operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public IsZeroOperationV2(final GasCalculator gasCalculator) {
+ super(0x15, "ISZERO", 1, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs iszero operation.
+ *
+ * @param frame the frame
+ * @param stack the v2 operand stack ({@code long[]} in big-endian limb order)
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasItems(1)) return UNDERFLOW_RESPONSE;
+ frame.setTopV2(StackArithmetic.isZero(stack, frame.stackTopV2()));
+ return isZeroSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/JumpDestOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/JumpDestOperationV2.java
new file mode 100644
index 00000000000..f2f4aec7557
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/JumpDestOperationV2.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+
+/** EVM v2 JUMPDEST operation. A marker opcode that is a no-op at runtime. */
+public class JumpDestOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /** The constant OPCODE. */
+ public static final int OPCODE = 0x5B;
+
+ private static final OperationResult JUMPDEST_SUCCESS = new OperationResult(1L, null);
+
+ /**
+ * Instantiates a new Jump dest operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public JumpDestOperationV2(final GasCalculator gasCalculator) {
+ super(OPCODE, "JUMPDEST", 0, 0, gasCalculator, gasCalculator.getJumpDestOperationGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame);
+ }
+
+ /**
+ * Performs JUMPDEST operation (no-op marker).
+ *
+ * @param frame the frame
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame) {
+ return JUMPDEST_SUCCESS;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/JumpOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/JumpOperationV2.java
new file mode 100644
index 00000000000..0a0ca80e3b8
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/JumpOperationV2.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+
+/** EVM v2 JUMP operation using long[] stack representation. */
+public class JumpOperationV2 extends AbstractFixedCostOperationV2 {
+
+ private static final OperationResult INVALID_JUMP_RESPONSE =
+ new OperationResult(8L, ExceptionalHaltReason.INVALID_JUMP_DESTINATION);
+ private static final OperationResult JUMP_RESPONSE = new OperationResult(8L, null, 0);
+
+ /**
+ * Instantiates a new Jump operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public JumpOperationV2(final GasCalculator gasCalculator) {
+ super(0x56, "JUMP", 1, 0, gasCalculator, gasCalculator.getMidTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs Jump operation.
+ *
+ * @param frame the frame
+ * @param s the stack data array
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] s) {
+ if (!frame.stackHasItems(1)) return UNDERFLOW_RESPONSE;
+ final int top = frame.stackTopV2();
+ final int destOff = (top - 1) << 2;
+ frame.setTopV2(top - 1);
+ return performJump(frame, s, destOff, JUMP_RESPONSE, INVALID_JUMP_RESPONSE);
+ }
+
+ static OperationResult performJump(
+ final MessageFrame frame,
+ final long[] s,
+ final int off,
+ final OperationResult validResponse,
+ final OperationResult invalidResponse) {
+ // Destination must fit in a non-negative int (code size is bounded by int).
+ if (s[off] != 0
+ || s[off + 1] != 0
+ || s[off + 2] != 0
+ || s[off + 3] < 0
+ || s[off + 3] > Integer.MAX_VALUE) {
+ return invalidResponse;
+ }
+ final int jumpDestination = (int) s[off + 3];
+ if (frame.getCode().isJumpDestInvalid(jumpDestination)) {
+ return invalidResponse;
+ }
+ frame.setPC(jumpDestination);
+ return validResponse;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/JumpiOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/JumpiOperationV2.java
new file mode 100644
index 00000000000..36ab1bed872
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/JumpiOperationV2.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+
+/** EVM v2 JUMPI (conditional jump) operation using long[] stack representation. */
+public class JumpiOperationV2 extends AbstractFixedCostOperationV2 {
+
+ private static final OperationResult INVALID_JUMP_RESPONSE =
+ new OperationResult(10L, ExceptionalHaltReason.INVALID_JUMP_DESTINATION);
+ private static final OperationResult JUMPI_RESPONSE = new OperationResult(10L, null, 0);
+ private static final OperationResult NOJUMP_RESPONSE = new OperationResult(10L, null);
+
+ /**
+ * Instantiates a new JUMPI operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public JumpiOperationV2(final GasCalculator gasCalculator) {
+ super(0x57, "JUMPI", 2, 0, gasCalculator, gasCalculator.getHighTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs JUMPI operation.
+ *
+ * @param frame the frame
+ * @param s the stack data array
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] s) {
+ if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE;
+ final int top = frame.stackTopV2();
+ final int destOff = (top - 1) << 2;
+ final int condOff = (top - 2) << 2;
+ frame.setTopV2(top - 2);
+
+ // If condition is zero (false), no jump will be performed.
+ if (s[condOff] == 0 && s[condOff + 1] == 0 && s[condOff + 2] == 0 && s[condOff + 3] == 0) {
+ return NOJUMP_RESPONSE;
+ }
+
+ return JumpOperationV2.performJump(frame, s, destOff, JUMPI_RESPONSE, INVALID_JUMP_RESPONSE);
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Keccak256OperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Keccak256OperationV2.java
new file mode 100644
index 00000000000..3ccc0349601
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Keccak256OperationV2.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import static org.hyperledger.besu.crypto.Hash.keccak256;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+import org.apache.tuweni.bytes.Bytes;
+
+/**
+ * EVM v2 KECCAK256 operation (0x20).
+ *
+ * Pops offset and size from the stack, hashes the corresponding memory region with Keccak-256,
+ * and pushes the 32-byte hash result. Net stack effect: 2 popped, 1 pushed (top - 1).
+ */
+public class Keccak256OperationV2 extends AbstractOperationV2 {
+
+ /**
+ * Instantiates a new Keccak256 operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public Keccak256OperationV2(final GasCalculator gasCalculator) {
+ super(0x20, "KECCAK256", 2, 1, gasCalculator);
+ }
+
+ @Override
+ public OperationResult execute(final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2(), gasCalculator());
+ }
+
+ /**
+ * Execute KECCAK256 on the v2 long[] stack.
+ *
+ * @param frame the message frame
+ * @param s the stack data
+ * @param gasCalculator the gas calculator
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) {
+ if (!frame.stackHasItems(2)) {
+ return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS);
+ }
+ final int top = frame.stackTopV2();
+ final long from = StackArithmetic.clampedToLong(s, top, 0);
+ final long length = StackArithmetic.clampedToLong(s, top, 1);
+
+ final long cost = gasCalculator.keccak256OperationGasCost(frame, from, length);
+ if (frame.getRemainingGas() < cost) {
+ return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS);
+ }
+
+ final Bytes bytes = frame.readMutableMemory(from, length);
+ final byte[] hashBytes = keccak256(bytes).toArrayUnsafe();
+ // Pop 2, push 1: net effect is top - 1
+ final int newTop = top - 1;
+ frame.setTopV2(newTop);
+ StackArithmetic.fromBytesAt(s, newTop, 0, hashBytes, 0, hashBytes.length);
+
+ return new OperationResult(cost, null);
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/LogOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/LogOperationV2.java
new file mode 100644
index 00000000000..11c9d10f696
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/LogOperationV2.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.datatypes.Address;
+import org.hyperledger.besu.datatypes.Log;
+import org.hyperledger.besu.datatypes.LogTopic;
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.tuweni.bytes.Bytes;
+import org.apache.tuweni.bytes.Bytes32;
+
+/**
+ * EVM v2 LOG operation (LOG0-LOG4) using long[] stack representation. Parameterized by topic count.
+ */
+public class LogOperationV2 extends AbstractOperationV2 {
+
+ private final int numTopics;
+
+ /**
+ * Instantiates a new Log operation.
+ *
+ * @param numTopics the num topics (0-4)
+ * @param gasCalculator the gas calculator
+ */
+ public LogOperationV2(final int numTopics, final GasCalculator gasCalculator) {
+ super(0xA0 + numTopics, "LOG" + numTopics, numTopics + 2, 0, gasCalculator);
+ this.numTopics = numTopics;
+ }
+
+ @Override
+ public OperationResult execute(final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2(), numTopics, gasCalculator());
+ }
+
+ /**
+ * Performs LOG operation.
+ *
+ * @param frame the frame
+ * @param s the stack data array
+ * @param numTopics the number of topics to pop
+ * @param gasCalculator the gas calculator
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame,
+ final long[] s,
+ final int numTopics,
+ final GasCalculator gasCalculator) {
+ if (!frame.stackHasItems(2 + numTopics)) {
+ return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS);
+ }
+ final int top = frame.stackTopV2();
+ final long dataLocation = StackArithmetic.clampedToLong(s, top, 0);
+ final long numBytes = StackArithmetic.clampedToLong(s, top, 1);
+
+ if (frame.isStatic()) {
+ return new OperationResult(0, ExceptionalHaltReason.ILLEGAL_STATE_CHANGE);
+ }
+
+ final long cost = gasCalculator.logOperationGasCost(frame, dataLocation, numBytes, numTopics);
+ if (frame.getRemainingGas() < cost) {
+ return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS);
+ }
+
+ final Address address = frame.getRecipientAddress();
+ final Bytes data = frame.readMemory(dataLocation, numBytes);
+
+ final ImmutableList.Builder Discards the top stack item by decrementing the stack pointer. Gas cost is base tier (2).
+ */
+public class PopOperationV2 extends AbstractFixedCostOperationV2 {
+
+ private static final OperationResult POP_SUCCESS = new OperationResult(2, null);
+
+ /**
+ * Instantiates a new Pop operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public PopOperationV2(final GasCalculator gasCalculator) {
+ super(0x50, "POP", 1, 0, gasCalculator, gasCalculator.getBaseTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame);
+ }
+
+ /**
+ * Performs POP operation.
+ *
+ * @param frame the frame
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame) {
+ if (!frame.stackHasItems(1)) return UNDERFLOW_RESPONSE;
+ frame.setTopV2(frame.stackTopV2() - 1);
+ return POP_SUCCESS;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PrevRandaoOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PrevRandaoOperationV2.java
new file mode 100644
index 00000000000..fce5b1aceed
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PrevRandaoOperationV2.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/**
+ * EVM v2 PREVRANDAO (DIFFICULTY) operation — pushes the mix hash / prev randao value onto the
+ * stack.
+ */
+public class PrevRandaoOperationV2 extends AbstractFixedCostOperationV2 {
+
+ private static final OperationResult prevRandaoSuccess = new OperationResult(2, null);
+
+ /**
+ * Instantiates a new PrevRandao operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public PrevRandaoOperationV2(final GasCalculator gasCalculator) {
+ super(0x44, "PREVRANDAO", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs the PREVRANDAO operation.
+ *
+ * @param frame the message frame
+ * @param stack the v2 long[] stack
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE;
+ final byte[] randao = frame.getBlockValues().getMixHashOrPrevRandao().toArrayUnsafe();
+ frame.setTopV2(StackArithmetic.pushFromBytes(stack, frame.stackTopV2(), randao, 0, 32));
+ return prevRandaoSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Push0OperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Push0OperationV2.java
new file mode 100644
index 00000000000..c0b10f4729f
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Push0OperationV2.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/**
+ * EVM v2 PUSH0 operation (0x5F).
+ *
+ * Pushes the constant value 0 onto the stack. Requires Shanghai fork or later. Gas cost is base
+ * tier (2).
+ */
+public class Push0OperationV2 extends AbstractFixedCostOperationV2 {
+
+ private static final OperationResult PUSH0_SUCCESS = new OperationResult(2, null);
+
+ /**
+ * Instantiates a new Push0 operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public Push0OperationV2(final GasCalculator gasCalculator) {
+ super(0x5F, "PUSH0", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs PUSH0 operation.
+ *
+ * @param frame the frame
+ * @param s the stack data array
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] s) {
+ if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE;
+ frame.setTopV2(StackArithmetic.pushZero(s, frame.stackTopV2()));
+ return PUSH0_SUCCESS;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PushOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PushOperationV2.java
new file mode 100644
index 00000000000..4e154a88fe0
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PushOperationV2.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/**
+ * EVM v2 PUSH1-32 operation (opcodes 0x60–0x7F).
+ *
+ * Reads {@code length} immediate bytes from bytecode at {@code pc+1} and pushes the resulting
+ * value onto the stack. Gas cost is veryLow tier (3). PC increment is {@code 1 + length}.
+ */
+public class PushOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /** The PUSH opcode base (PUSH0 = 0x5F, so PUSH1 = 0x60). */
+ public static final int PUSH_BASE = 0x5F;
+
+ private static final OperationResult PUSH_SUCCESS = new OperationResult(3, null);
+
+ private final int length;
+
+ /**
+ * Instantiates a new Push operation for PUSH{length}.
+ *
+ * @param length the number of bytes to push (1–32)
+ * @param gasCalculator the gas calculator
+ */
+ public PushOperationV2(final int length, final GasCalculator gasCalculator) {
+ super(
+ PUSH_BASE + length,
+ "PUSH" + length,
+ 0,
+ 1,
+ gasCalculator,
+ gasCalculator.getVeryLowTierGasCost());
+ this.length = length;
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ final byte[] code = frame.getCode().getBytes().toArrayUnsafe();
+ return staticOperation(frame, frame.stackDataV2(), code, frame.getPC(), length);
+ }
+
+ /**
+ * Performs PUSH operation.
+ *
+ * @param frame the frame
+ * @param s the stack data array
+ * @param code the bytecode array
+ * @param pc the current program counter
+ * @param pushSize the number of bytes to push
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame,
+ final long[] s,
+ final byte[] code,
+ final int pc,
+ final int pushSize) {
+ if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE;
+ frame.setTopV2(StackArithmetic.pushFromBytes(s, frame.stackTopV2(), code, pc + 1, pushSize));
+ frame.setPC(pc + pushSize);
+ return PUSH_SUCCESS;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ReturnDataCopyOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ReturnDataCopyOperationV2.java
new file mode 100644
index 00000000000..dcedc0b8d28
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ReturnDataCopyOperationV2.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+import org.apache.tuweni.bytes.Bytes;
+
+/**
+ * EVM v2 RETURNDATACOPY operation (0x3E).
+ *
+ * Pops destOffset, sourceOffset, and size from the stack, then copies {@code size} bytes of the
+ * return data buffer into memory at {@code destOffset}. Halts with
+ * INVALID_RETURN_DATA_BUFFER_ACCESS if the source range exceeds the buffer.
+ */
+public class ReturnDataCopyOperationV2 extends AbstractOperationV2 {
+
+ private static final OperationResult INVALID_RETURN_DATA_BUFFER_ACCESS =
+ new OperationResult(0L, ExceptionalHaltReason.INVALID_RETURN_DATA_BUFFER_ACCESS);
+
+ private static final OperationResult OUT_OF_BOUNDS =
+ new OperationResult(0L, ExceptionalHaltReason.OUT_OF_BOUNDS);
+
+ /**
+ * Instantiates a new ReturnDataCopy operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public ReturnDataCopyOperationV2(final GasCalculator gasCalculator) {
+ super(0x3E, "RETURNDATACOPY", 3, 0, gasCalculator);
+ }
+
+ @Override
+ public OperationResult execute(final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2(), gasCalculator());
+ }
+
+ /**
+ * Execute RETURNDATACOPY on the v2 long[] stack.
+ *
+ * @param frame the message frame
+ * @param s the stack data
+ * @param gasCalculator the gas calculator
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) {
+ if (!frame.stackHasItems(3)) {
+ return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS);
+ }
+ final int top = frame.stackTopV2();
+ final long memOffset = StackArithmetic.clampedToLong(s, top, 0);
+ final long sourceOffset = StackArithmetic.clampedToLong(s, top, 1);
+ final long numBytes = StackArithmetic.clampedToLong(s, top, 2);
+ frame.setTopV2(top - 3);
+
+ final Bytes returnData = frame.getReturnData();
+ final int returnDataLength = returnData.size();
+
+ try {
+ final long end = Math.addExact(sourceOffset, numBytes);
+ if (end > returnDataLength) {
+ return INVALID_RETURN_DATA_BUFFER_ACCESS;
+ }
+ } catch (final ArithmeticException ae) {
+ return OUT_OF_BOUNDS;
+ }
+
+ final long cost = gasCalculator.dataCopyOperationGasCost(frame, memOffset, numBytes);
+ if (frame.getRemainingGas() < cost) {
+ return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS);
+ }
+
+ frame.writeMemory(memOffset, sourceOffset, numBytes, returnData, true);
+
+ return new OperationResult(cost, null);
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ReturnDataSizeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ReturnDataSizeOperationV2.java
new file mode 100644
index 00000000000..d7e6e90eb87
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ReturnDataSizeOperationV2.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** EVM v2 RETURNDATASIZE operation — pushes the size of the return data buffer onto the stack. */
+public class ReturnDataSizeOperationV2 extends AbstractFixedCostOperationV2 {
+
+ private static final OperationResult returnDataSizeSuccess = new OperationResult(2, null);
+
+ /**
+ * Instantiates a new Return data size operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public ReturnDataSizeOperationV2(final GasCalculator gasCalculator) {
+ super(0x3D, "RETURNDATASIZE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs the RETURNDATASIZE operation.
+ *
+ * @param frame the message frame
+ * @param stack the v2 long[] stack
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE;
+ frame.setTopV2(
+ StackArithmetic.pushLong(stack, frame.stackTopV2(), frame.getReturnData().size()));
+ return returnDataSizeSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ReturnOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ReturnOperationV2.java
new file mode 100644
index 00000000000..4fd802b7a86
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ReturnOperationV2.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** EVM v2 RETURN operation using long[] stack representation. */
+public class ReturnOperationV2 extends AbstractOperationV2 {
+
+ /** RETURN opcode number */
+ public static final int OPCODE = 0xF3;
+
+ /**
+ * Instantiates a new Return operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public ReturnOperationV2(final GasCalculator gasCalculator) {
+ super(OPCODE, "RETURN", 2, 0, gasCalculator);
+ }
+
+ @Override
+ public OperationResult execute(final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2(), gasCalculator());
+ }
+
+ /**
+ * Performs Return operation.
+ *
+ * @param frame the frame
+ * @param s the stack data array
+ * @param gasCalculator the gas calculator
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) {
+ if (!frame.stackHasItems(2)) {
+ return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS);
+ }
+ final int top = frame.stackTopV2();
+ final long from = StackArithmetic.clampedToLong(s, top, 0);
+ final long length = StackArithmetic.clampedToLong(s, top, 1);
+ frame.setTopV2(top - 2);
+
+ final long cost = gasCalculator.memoryExpansionGasCost(frame, from, length);
+ if (frame.getRemainingGas() < cost) {
+ return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS);
+ }
+
+ frame.setOutputData(frame.readMemory(from, length));
+ frame.setState(MessageFrame.State.CODE_SUCCESS);
+ return new OperationResult(cost, null);
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/RevertOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/RevertOperationV2.java
new file mode 100644
index 00000000000..41cb2206ed5
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/RevertOperationV2.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+import org.apache.tuweni.bytes.Bytes;
+
+/** EVM v2 REVERT operation using long[] stack representation. */
+public class RevertOperationV2 extends AbstractOperationV2 {
+
+ /** REVERT opcode number */
+ public static final int OPCODE = 0xFD;
+
+ /**
+ * Instantiates a new Revert operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public RevertOperationV2(final GasCalculator gasCalculator) {
+ super(OPCODE, "REVERT", 2, 0, gasCalculator);
+ }
+
+ @Override
+ public OperationResult execute(final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2(), gasCalculator());
+ }
+
+ /**
+ * Performs Revert operation.
+ *
+ * @param frame the frame
+ * @param s the stack data array
+ * @param gasCalculator the gas calculator
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) {
+ if (!frame.stackHasItems(2)) {
+ return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS);
+ }
+ final int top = frame.stackTopV2();
+ final long from = StackArithmetic.clampedToLong(s, top, 0);
+ final long length = StackArithmetic.clampedToLong(s, top, 1);
+ frame.setTopV2(top - 2);
+
+ final long cost = gasCalculator.memoryExpansionGasCost(frame, from, length);
+ if (frame.getRemainingGas() < cost) {
+ return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS);
+ }
+
+ final Bytes reason = frame.readMemory(from, length);
+ frame.setOutputData(reason);
+ frame.setRevertReason(reason);
+ frame.setState(MessageFrame.State.REVERT);
+ return new OperationResult(cost, null);
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SDivOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SDivOperationV2.java
new file mode 100644
index 00000000000..ea41754d391
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SDivOperationV2.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** The SDiv operation. */
+public class SDivOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /** The SDiv operation success result. */
+ static final OperationResult sdivSuccess = new OperationResult(5, null);
+
+ /**
+ * Instantiates a new SDiv operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public SDivOperationV2(final GasCalculator gasCalculator) {
+ super(0x05, "SDIV", 2, 1, gasCalculator, gasCalculator.getLowTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs sdiv operation.
+ *
+ * @param frame the frame
+ * @param stack the v2 operand stack ({@code long[]} in big-endian limb order)
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE;
+ frame.setTopV2(StackArithmetic.signedDiv(stack, frame.stackTopV2()));
+ return sdivSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SLoadOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SLoadOperationV2.java
new file mode 100644
index 00000000000..482d4269f8e
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SLoadOperationV2.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.account.Account;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+import org.apache.tuweni.bytes.Bytes32;
+import org.apache.tuweni.units.bigints.UInt256;
+
+/**
+ * EVM v2 SLOAD operation using long[] stack representation.
+ *
+ * Pops a storage key from the stack, loads the value from the contract's storage, and pushes the
+ * result back. Applies warm/cold access costs per EIP-2929.
+ */
+public class SLoadOperationV2 extends AbstractOperationV2 {
+
+ private final long warmCost;
+ private final long coldCost;
+ private final OperationResult warmSuccess;
+ private final OperationResult coldSuccess;
+
+ /**
+ * Instantiates a new SLoad operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public SLoadOperationV2(final GasCalculator gasCalculator) {
+ super(0x54, "SLOAD", 1, 1, gasCalculator);
+ final long baseCost = gasCalculator.getSloadOperationGasCost();
+ warmCost = baseCost + gasCalculator.getWarmStorageReadCost();
+ coldCost = baseCost + gasCalculator.getColdSloadCost();
+ warmSuccess = new OperationResult(warmCost, null);
+ coldSuccess = new OperationResult(coldCost, null);
+ }
+
+ @Override
+ public OperationResult execute(final MessageFrame frame, final EVM evm) {
+ return staticOperation(
+ frame, frame.stackDataV2(), warmCost, coldCost, warmSuccess, coldSuccess);
+ }
+
+ /**
+ * Execute the SLOAD opcode on the v2 long[] stack.
+ *
+ * Called from EVM.java dispatch. Computes costs from the gas calculator on first call; uses
+ * pre-cached results when called via the instance {@code execute()} method.
+ *
+ * @param frame the message frame
+ * @param s the stack as a long[] array
+ * @param gasCalculator the gas calculator
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) {
+ final long baseCost = gasCalculator.getSloadOperationGasCost();
+ final long warmCost = baseCost + gasCalculator.getWarmStorageReadCost();
+ final long coldCost = baseCost + gasCalculator.getColdSloadCost();
+ return staticOperation(
+ frame,
+ s,
+ warmCost,
+ coldCost,
+ new OperationResult(warmCost, null),
+ new OperationResult(coldCost, null));
+ }
+
+ private static OperationResult staticOperation(
+ final MessageFrame frame,
+ final long[] s,
+ final long warmCost,
+ final long coldCost,
+ final OperationResult warmSuccess,
+ final OperationResult coldSuccess) {
+ if (!frame.stackHasItems(1)) {
+ return new OperationResult(warmCost, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS);
+ }
+
+ final int top = frame.stackTopV2();
+
+ final byte[] keyBytes = new byte[32];
+ StackArithmetic.toBytesAt(s, top, 0, keyBytes);
+ final Bytes32 keyBytes32 = Bytes32.wrap(keyBytes);
+ final UInt256 key = UInt256.fromBytes(keyBytes32);
+
+ final boolean slotIsWarm = frame.warmUpStorage(frame.getRecipientAddress(), keyBytes32);
+ final long cost = slotIsWarm ? warmCost : coldCost;
+
+ if (frame.getRemainingGas() < cost) {
+ return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS);
+ }
+
+ final Account account = frame.getWorldUpdater().get(frame.getRecipientAddress());
+ frame.getEip7928AccessList().ifPresent(t -> t.addTouchedAccount(frame.getRecipientAddress()));
+
+ final UInt256 value = account == null ? UInt256.ZERO : account.getStorageValue(key);
+ frame
+ .getEip7928AccessList()
+ .ifPresent(t -> t.addSlotAccessForAccount(frame.getRecipientAddress(), key));
+
+ final byte[] valueBytes = value.toArrayUnsafe();
+ StackArithmetic.fromBytesAt(s, top, 0, valueBytes, 0, valueBytes.length);
+
+ return slotIsWarm ? warmSuccess : coldSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SModOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SModOperationV2.java
new file mode 100644
index 00000000000..dd4745fb57f
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SModOperationV2.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** The SMod operation. */
+public class SModOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /** The SMod operation success result. */
+ static final OperationResult smodSuccess = new OperationResult(5, null);
+
+ /**
+ * Instantiates a new SMod operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public SModOperationV2(final GasCalculator gasCalculator) {
+ super(0x07, "SMOD", 2, 1, gasCalculator, gasCalculator.getLowTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs smod operation.
+ *
+ * @param frame the frame
+ * @param stack the v2 operand stack ({@code long[]} in big-endian limb order)
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE;
+ frame.setTopV2(StackArithmetic.signedMod(stack, frame.stackTopV2()));
+ return smodSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SStoreOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SStoreOperationV2.java
new file mode 100644
index 00000000000..76c4d0523f9
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SStoreOperationV2.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.account.MutableAccount;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+import java.util.function.Supplier;
+
+import com.google.common.base.Suppliers;
+import org.apache.tuweni.bytes.Bytes;
+import org.apache.tuweni.bytes.Bytes32;
+import org.apache.tuweni.units.bigints.UInt256;
+
+/**
+ * EVM v2 SSTORE operation using long[] stack representation.
+ *
+ * Pops a storage key and new value from the stack, then writes the value to contract storage.
+ * Applies EIP-2200 gas costs and refund accounting.
+ */
+public class SStoreOperationV2 extends AbstractOperationV2 {
+
+ /** Minimum gas remaining for Frontier (no minimum). */
+ public static final long FRONTIER_MINIMUM = 0L;
+
+ /** Minimum gas remaining for EIP-1706. */
+ public static final long EIP_1706_MINIMUM = 2300L;
+
+ /** Illegal state change result (static context or missing account). */
+ protected static final OperationResult ILLEGAL_STATE_CHANGE =
+ new OperationResult(0L, ExceptionalHaltReason.ILLEGAL_STATE_CHANGE);
+
+ private final long minimumGasRemaining;
+
+ /**
+ * Instantiates a new SStore operation.
+ *
+ * @param gasCalculator the gas calculator
+ * @param minimumGasRemaining the minimum gas remaining
+ */
+ public SStoreOperationV2(final GasCalculator gasCalculator, final long minimumGasRemaining) {
+ super(0x55, "SSTORE", 2, 0, gasCalculator);
+ this.minimumGasRemaining = minimumGasRemaining;
+ }
+
+ /**
+ * Gets minimum gas remaining.
+ *
+ * @return the minimum gas remaining
+ */
+ public long getMinimumGasRemaining() {
+ return minimumGasRemaining;
+ }
+
+ @Override
+ public OperationResult execute(final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2(), gasCalculator(), minimumGasRemaining);
+ }
+
+ /**
+ * Execute the SSTORE opcode on the v2 long[] stack.
+ *
+ * @param frame the message frame
+ * @param s the stack as a long[] array
+ * @param gasCalculator the gas calculator
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) {
+ return staticOperation(frame, s, gasCalculator, FRONTIER_MINIMUM);
+ }
+
+ /**
+ * Execute the SSTORE opcode with an explicit minimum-gas-remaining check.
+ *
+ * @param frame the message frame
+ * @param s the stack as a long[] array
+ * @param gasCalculator the gas calculator
+ * @param minimumGasRemaining minimum gas required before executing (EIP-1706: 2300 for Istanbul+)
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame,
+ final long[] s,
+ final GasCalculator gasCalculator,
+ final long minimumGasRemaining) {
+ if (!frame.stackHasItems(2)) {
+ return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS);
+ }
+
+ final int top = frame.stackTopV2();
+
+ // Extract key (depth 0) and new value (depth 1) as raw 32-byte arrays
+ final byte[] keyBytes = new byte[32];
+ final byte[] newValueBytes = new byte[32];
+ StackArithmetic.toBytesAt(s, top, 0, keyBytes);
+ StackArithmetic.toBytesAt(s, top, 1, newValueBytes);
+
+ // Pop 2
+ frame.setTopV2(top - 2);
+
+ final MutableAccount account = frame.getWorldUpdater().getAccount(frame.getRecipientAddress());
+ frame.getEip7928AccessList().ifPresent(t -> t.addTouchedAccount(frame.getRecipientAddress()));
+
+ if (account == null) {
+ return ILLEGAL_STATE_CHANGE;
+ }
+
+ final long remainingGas = frame.getRemainingGas();
+
+ if (frame.isStatic()) {
+ return new OperationResult(remainingGas, ExceptionalHaltReason.ILLEGAL_STATE_CHANGE);
+ }
+
+ if (remainingGas <= minimumGasRemaining) {
+ return new OperationResult(minimumGasRemaining, ExceptionalHaltReason.INSUFFICIENT_GAS);
+ }
+
+ final Bytes32 keyBytes32 = Bytes32.wrap(keyBytes);
+ final UInt256 key = UInt256.fromBytes(keyBytes32);
+ final UInt256 newValue = UInt256.fromBytes(Bytes32.wrap(newValueBytes));
+
+ final boolean slotIsWarm = frame.warmUpStorage(frame.getRecipientAddress(), keyBytes32);
+ final Supplier Pushes the balance of the currently executing contract onto the stack. Fixed cost (low tier).
+ */
+public class SelfBalanceOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /**
+ * Instantiates a new SelfBalance operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public SelfBalanceOperationV2(final GasCalculator gasCalculator) {
+ super(0x47, "SELFBALANCE", 0, 1, gasCalculator, gasCalculator.getLowTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Execute SELFBALANCE on the v2 long[] stack.
+ *
+ * @param frame the message frame
+ * @param s the stack data
+ * @return the operation result
+ */
+ public static Operation.OperationResult staticOperation(
+ final MessageFrame frame, final long[] s) {
+ if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE;
+ final int top = frame.stackTopV2();
+ final Account account = getAccount(frame.getRecipientAddress(), frame);
+ if (account == null) {
+ frame.setTopV2(StackArithmetic.pushZero(s, top));
+ } else {
+ frame.setTopV2(StackArithmetic.pushWei(s, top, account.getBalance()));
+ }
+ return new Operation.OperationResult(5, null);
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfDestructOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfDestructOperationV2.java
new file mode 100644
index 00000000000..2de0cec99b8
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfDestructOperationV2.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.datatypes.Address;
+import org.hyperledger.besu.datatypes.Wei;
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.account.Account;
+import org.hyperledger.besu.evm.account.MutableAccount;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.log.TransferLogEmitter;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** EVM v2 SELFDESTRUCT operation using long[] stack representation. */
+public class SelfDestructOperationV2 extends AbstractOperationV2 {
+
+ private final boolean eip6780Semantics;
+ private final TransferLogEmitter transferLogEmitter;
+
+ /**
+ * Instantiates a new Self destruct operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public SelfDestructOperationV2(final GasCalculator gasCalculator) {
+ this(gasCalculator, false, TransferLogEmitter.NOOP);
+ }
+
+ /**
+ * Instantiates a new Self destruct operation with optional EIP-6780 semantics.
+ *
+ * @param gasCalculator the gas calculator
+ * @param eip6780Semantics enforce EIP-6780 semantics (only destroy if created in same tx)
+ */
+ public SelfDestructOperationV2(
+ final GasCalculator gasCalculator, final boolean eip6780Semantics) {
+ this(gasCalculator, eip6780Semantics, TransferLogEmitter.NOOP);
+ }
+
+ /**
+ * Instantiates a new Self destruct operation with EIP-6780 and transfer log emission support.
+ *
+ * @param gasCalculator the gas calculator
+ * @param eip6780Semantics enforce EIP-6780 semantics (only destroy if created in same tx)
+ * @param transferLogEmitter strategy for emitting transfer logs
+ */
+ public SelfDestructOperationV2(
+ final GasCalculator gasCalculator,
+ final boolean eip6780Semantics,
+ final TransferLogEmitter transferLogEmitter) {
+ super(0xFF, "SELFDESTRUCT", 1, 0, gasCalculator);
+ this.eip6780Semantics = eip6780Semantics;
+ this.transferLogEmitter = transferLogEmitter;
+ }
+
+ @Override
+ public OperationResult execute(final MessageFrame frame, final EVM evm) {
+ return staticOperation(
+ frame, frame.stackDataV2(), gasCalculator(), eip6780Semantics, transferLogEmitter);
+ }
+
+ /**
+ * Performs SELFDESTRUCT operation.
+ *
+ * @param frame the frame
+ * @param s the stack data array
+ * @param gasCalculator the gas calculator
+ * @param eip6780Semantics true if EIP-6780 semantics should apply
+ * @param transferLogEmitter strategy for emitting transfer logs
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame,
+ final long[] s,
+ final GasCalculator gasCalculator,
+ final boolean eip6780Semantics,
+ final TransferLogEmitter transferLogEmitter) {
+ // Check for static violations first — fewer account accesses on failure.
+ if (frame.isStatic()) {
+ return new OperationResult(0, ExceptionalHaltReason.ILLEGAL_STATE_CHANGE);
+ }
+
+ if (!frame.stackHasItems(1)) {
+ return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS);
+ }
+
+ final int top = frame.stackTopV2();
+ final Address beneficiaryAddress = StackArithmetic.toAddressAt(s, top, 0);
+ frame.setTopV2(top - 1);
+
+ final boolean beneficiaryIsWarm =
+ frame.warmUpAddress(beneficiaryAddress) || gasCalculator.isPrecompile(beneficiaryAddress);
+ final long beneficiaryAccessCost =
+ beneficiaryIsWarm ? 0L : gasCalculator.getColdAccountAccessCost();
+ final long staticCost =
+ gasCalculator.selfDestructOperationStaticGasCost() + beneficiaryAccessCost;
+
+ if (frame.getRemainingGas() < staticCost) {
+ return new OperationResult(staticCost, ExceptionalHaltReason.INSUFFICIENT_GAS);
+ }
+
+ final Account beneficiaryNullable = getAccount(beneficiaryAddress, frame);
+ final Address originatorAddress = frame.getRecipientAddress();
+ final MutableAccount originatorAccount = getMutableAccount(originatorAddress, frame);
+ final Wei originatorBalance = originatorAccount.getBalance();
+
+ final long cost =
+ gasCalculator.selfDestructOperationGasCost(beneficiaryNullable, originatorBalance)
+ + beneficiaryAccessCost;
+
+ if (frame.getRemainingGas() < cost) {
+ return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS);
+ }
+
+ // EIP-8037: Deduct regular gas before charging state gas (ordering requirement).
+ frame.decrementRemainingGas(cost);
+
+ // EIP-8037: Charge state gas for new account creation in SELFDESTRUCT
+ if (!gasCalculator
+ .stateGasCostCalculator()
+ .chargeSelfDestructNewAccountStateGas(frame, beneficiaryNullable, originatorBalance)) {
+ return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS);
+ }
+
+ // Add regular gas back — the EVM loop will deduct it via the OperationResult.
+ frame.incrementRemainingGas(cost);
+
+ final MutableAccount beneficiaryAccount = getOrCreateAccount(beneficiaryAddress, frame);
+
+ final boolean willBeDestroyed =
+ !eip6780Semantics || frame.wasCreatedInTransaction(originatorAccount.getAddress());
+
+ // Transfer all originator balance to beneficiary.
+ originatorAccount.decrementBalance(originatorBalance);
+ beneficiaryAccount.incrementBalance(originatorBalance);
+
+ // Emit transfer log if applicable.
+ if (!originatorAddress.equals(beneficiaryAddress) || willBeDestroyed) {
+ transferLogEmitter.emitSelfDestructLog(
+ frame, originatorAddress, beneficiaryAddress, originatorBalance);
+ }
+
+ // If actually destroying the originator, zero its balance and tag for cleanup.
+ if (willBeDestroyed) {
+ frame.addSelfDestruct(originatorAccount.getAddress());
+ originatorAccount.setBalance(Wei.ZERO);
+ }
+
+ frame.addRefund(beneficiaryAddress, originatorBalance);
+ frame.setState(MessageFrame.State.CODE_SUCCESS);
+
+ return new OperationResult(cost, null);
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SgtOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SgtOperationV2.java
new file mode 100644
index 00000000000..e0616d3601b
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SgtOperationV2.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** The Sgt operation. */
+public class SgtOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /** The Sgt operation success result. */
+ static final OperationResult sgtSuccess = new OperationResult(3, null);
+
+ /**
+ * Instantiates a new Sgt operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public SgtOperationV2(final GasCalculator gasCalculator) {
+ super(0x13, "SGT", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs sgt operation.
+ *
+ * @param frame the frame
+ * @param stack the v2 operand stack ({@code long[]} in big-endian limb order)
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE;
+ frame.setTopV2(StackArithmetic.sgt(stack, frame.stackTopV2()));
+ return sgtSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ShlOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ShlOperationV2.java
new file mode 100644
index 00000000000..2f765e94269
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ShlOperationV2.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** The Shl (Shift Left) operation. */
+public class ShlOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /** The Shl operation success result. */
+ static final OperationResult shlSuccess = new OperationResult(3, null);
+
+ /**
+ * Instantiates a new Shl operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public ShlOperationV2(final GasCalculator gasCalculator) {
+ super(0x1b, "SHL", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs Shift Left operation.
+ *
+ * @param frame the frame
+ * @param stack the v2 operand stack ({@code long[]} in big-endian limb order)
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE;
+ frame.setTopV2(StackArithmetic.shl(stack, frame.stackTopV2()));
+ return shlSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ShrOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ShrOperationV2.java
new file mode 100644
index 00000000000..71c412a04ab
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ShrOperationV2.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** The Shr (Shift Right) operation. */
+public class ShrOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /** The Shr operation success result. */
+ static final OperationResult shrSuccess = new OperationResult(3, null);
+
+ /**
+ * Instantiates a new Shr operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public ShrOperationV2(final GasCalculator gasCalculator) {
+ super(0x1c, "SHR", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs SHR operation.
+ *
+ * @param frame the frame
+ * @param stack the v2 operand stack ({@code long[]} in big-endian limb order)
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE;
+ frame.setTopV2(StackArithmetic.shr(stack, frame.stackTopV2()));
+ return shrSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SignExtendOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SignExtendOperationV2.java
new file mode 100644
index 00000000000..fcd32b5b62d
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SignExtendOperationV2.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** EVM v2 SIGNEXTEND operation (0x0B). */
+public class SignExtendOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /** The SignExtend operation success result. */
+ static final OperationResult signExtendSuccess = new OperationResult(5, null);
+
+ /**
+ * Instantiates a new SignExtend operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public SignExtendOperationV2(final GasCalculator gasCalculator) {
+ super(0x0B, "SIGNEXTEND", 2, 1, gasCalculator, gasCalculator.getLowTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs SIGNEXTEND operation.
+ *
+ * @param frame the frame
+ * @param stack the v2 operand stack ({@code long[]} in big-endian limb order)
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE;
+ frame.setTopV2(StackArithmetic.signExtend(stack, frame.stackTopV2()));
+ return signExtendSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SlotNumOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SlotNumOperationV2.java
new file mode 100644
index 00000000000..71c659daf24
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SlotNumOperationV2.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** EVM v2 SLOTNUM operation — pushes the slot number onto the stack (Osaka+). */
+public class SlotNumOperationV2 extends AbstractFixedCostOperationV2 {
+
+ private static final OperationResult slotNumSuccess = new OperationResult(2, null);
+
+ /**
+ * Instantiates a new SlotNum operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public SlotNumOperationV2(final GasCalculator gasCalculator) {
+ super(0x4b, "SLOTNUM", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs the SLOTNUM operation.
+ *
+ * @param frame the message frame
+ * @param stack the v2 long[] stack
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE;
+ frame.setTopV2(
+ StackArithmetic.pushLong(
+ stack, frame.stackTopV2(), frame.getBlockValues().getSlotNumber()));
+ return slotNumSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SltOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SltOperationV2.java
new file mode 100644
index 00000000000..a8ab7be5307
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SltOperationV2.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** The Slt operation. */
+public class SltOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /** The Slt operation success result. */
+ static final OperationResult sltSuccess = new OperationResult(3, null);
+
+ /**
+ * Instantiates a new Slt operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public SltOperationV2(final GasCalculator gasCalculator) {
+ super(0x12, "SLT", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs slt operation.
+ *
+ * @param frame the frame
+ * @param stack the v2 operand stack ({@code long[]} in big-endian limb order)
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE;
+ frame.setTopV2(StackArithmetic.slt(stack, frame.stackTopV2()));
+ return sltSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/StaticCallOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/StaticCallOperationV2.java
new file mode 100644
index 00000000000..077bf6d86e6
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/StaticCallOperationV2.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.datatypes.Address;
+import org.hyperledger.besu.datatypes.Wei;
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.EvmSpecVersion;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.InvalidOperation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/**
+ * EVM v2 STATICCALL operation (opcode 0xFA, Byzantium+). Stack layout: gas, to, inOffset, inSize,
+ * outOffset, outSize → result.
+ */
+public class StaticCallOperationV2 extends AbstractCallOperationV2 {
+
+ /**
+ * Instantiates a new STATICCALL operation for EVM v2.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public StaticCallOperationV2(final GasCalculator gasCalculator) {
+ super(0xFA, "STATICCALL", 6, 1, gasCalculator);
+ }
+
+ @Override
+ protected Address to(final long[] s, final int top) {
+ return StackArithmetic.toAddressAt(s, top, 1);
+ }
+
+ @Override
+ protected Wei value(final long[] s, final int top) {
+ return Wei.ZERO;
+ }
+
+ @Override
+ protected Wei apparentValue(final MessageFrame frame, final long[] s, final int top) {
+ return value(s, top);
+ }
+
+ @Override
+ protected long inputDataOffset(final long[] s, final int top) {
+ return StackArithmetic.clampedToLong(s, top, 2);
+ }
+
+ @Override
+ protected long inputDataLength(final long[] s, final int top) {
+ return StackArithmetic.clampedToLong(s, top, 3);
+ }
+
+ @Override
+ protected long outputDataOffset(final long[] s, final int top) {
+ return StackArithmetic.clampedToLong(s, top, 4);
+ }
+
+ @Override
+ protected long outputDataLength(final long[] s, final int top) {
+ return StackArithmetic.clampedToLong(s, top, 5);
+ }
+
+ @Override
+ protected Address address(final MessageFrame frame, final long[] s, final int top) {
+ return to(s, top);
+ }
+
+ @Override
+ protected Address sender(final MessageFrame frame) {
+ return frame.getRecipientAddress();
+ }
+
+ @Override
+ public long gasAvailableForChildCall(final MessageFrame frame, final long[] s, final int top) {
+ return gasCalculator().gasAvailableForChildCall(frame, gas(s, top), false);
+ }
+
+ @Override
+ protected boolean isStatic(final MessageFrame frame) {
+ return true;
+ }
+
+ /**
+ * Performs the STATICCALL operation on the v2 stack. Only available from Byzantium onwards.
+ *
+ * @param frame the current message frame
+ * @param s the v2 stack array
+ * @param gasCalculator the gas calculator
+ * @param evm the EVM
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame, final long[] s, final GasCalculator gasCalculator, final EVM evm) {
+ if (EvmSpecVersion.BYZANTIUM.ordinal() > evm.getEvmVersion().ordinal()) {
+ return InvalidOperation.invalidOperationResult(0xFA);
+ }
+ return new StaticCallOperationV2(gasCalculator).execute(frame, evm);
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/StopOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/StopOperationV2.java
new file mode 100644
index 00000000000..5be0ec74ca7
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/StopOperationV2.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+
+import org.apache.tuweni.bytes.Bytes;
+
+/** EVM v2 STOP operation. Sets frame state to CODE_SUCCESS with empty output. */
+public class StopOperationV2 extends AbstractFixedCostOperationV2 {
+
+ private static final OperationResult STOP_SUCCESS = new OperationResult(0, null);
+
+ /**
+ * Instantiates a new Stop operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public StopOperationV2(final GasCalculator gasCalculator) {
+ super(0x00, "STOP", 0, 0, gasCalculator, gasCalculator.getZeroTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame);
+ }
+
+ /**
+ * Performs Stop operation.
+ *
+ * @param frame the frame
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame) {
+ frame.setState(MessageFrame.State.CODE_SUCCESS);
+ frame.setOutputData(Bytes.EMPTY);
+ return STOP_SUCCESS;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SubOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SubOperationV2.java
new file mode 100644
index 00000000000..731a4f3b872
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SubOperationV2.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** The Sub operation. */
+public class SubOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /** The Sub operation success result. */
+ static final OperationResult subSuccess = new OperationResult(3, null);
+
+ /**
+ * Instantiates a new Sub operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public SubOperationV2(final GasCalculator gasCalculator) {
+ super(0x03, "SUB", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs sub operation.
+ *
+ * @param frame the frame
+ * @param stack the v2 operand stack ({@code long[]} in big-endian limb order)
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE;
+ frame.setTopV2(StackArithmetic.sub(stack, frame.stackTopV2()));
+ return subSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SwapNOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SwapNOperationV2.java
new file mode 100644
index 00000000000..b84b969da2b
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SwapNOperationV2.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Eip8024Decoder;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/**
+ * EVM v2 SWAPN operation (0xE7, EIP-8024).
+ *
+ * Swaps the top of the stack with the (n+1)'th stack item, where n is decoded from a 1-byte
+ * immediate operand. PC increment is 2. Gas cost is veryLow tier (3). Requires Amsterdam fork or
+ * later.
+ */
+public class SwapNOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /** The SWAPN opcode value. */
+ public static final int OPCODE = 0xe7;
+
+ private static final OperationResult SWAPN_SUCCESS = new OperationResult(3, null, 2);
+
+ private static final OperationResult INVALID_IMMEDIATE =
+ new OperationResult(3, ExceptionalHaltReason.INVALID_OPERATION, 2);
+
+ private static final OperationResult SWAPN_UNDERFLOW =
+ new OperationResult(3, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS, 2);
+
+ /**
+ * Instantiates a new SWAPN operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public SwapNOperationV2(final GasCalculator gasCalculator) {
+ super(OPCODE, "SWAPN", 0, 0, gasCalculator, gasCalculator.getVeryLowTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(
+ frame, frame.stackDataV2(), frame.getCode().getBytes().toArrayUnsafe(), frame.getPC());
+ }
+
+ /**
+ * Performs SWAPN operation.
+ *
+ * @param frame the message frame
+ * @param s the stack data array
+ * @param code the bytecode array
+ * @param pc the current program counter
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame, final long[] s, final byte[] code, final int pc) {
+ final int imm = (pc + 1 >= code.length) ? 0 : code[pc + 1] & 0xFF;
+
+ if (!Eip8024Decoder.VALID_SINGLE[imm]) {
+ return INVALID_IMMEDIATE;
+ }
+
+ final int n = Eip8024Decoder.DECODE_SINGLE[imm];
+
+ if (!frame.stackHasItems(n + 1)) {
+ return SWAPN_UNDERFLOW;
+ }
+ StackArithmetic.swap(s, frame.stackTopV2(), n);
+ return SWAPN_SUCCESS;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SwapOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SwapOperationV2.java
new file mode 100644
index 00000000000..e3f14487944
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SwapOperationV2.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/**
+ * EVM v2 SWAP1-16 operation (opcodes 0x90–0x9F).
+ *
+ * Swaps the top stack item with the item at depth {@code index} (1-based). Gas cost is veryLow
+ * tier (3).
+ */
+public class SwapOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /** The SWAP opcode base (SWAP1 = 0x90, so base = 0x8F). */
+ public static final int SWAP_BASE = 0x8F;
+
+ static final OperationResult SWAP_SUCCESS = new OperationResult(3, null);
+
+ private final int index;
+
+ /**
+ * Instantiates a new Swap operation.
+ *
+ * @param index the 1-based swap depth (1–16)
+ * @param gasCalculator the gas calculator
+ */
+ public SwapOperationV2(final int index, final GasCalculator gasCalculator) {
+ super(
+ SWAP_BASE + index,
+ "SWAP" + index,
+ index + 1,
+ index + 1,
+ gasCalculator,
+ gasCalculator.getVeryLowTierGasCost());
+ this.index = index;
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2(), index);
+ }
+
+ /**
+ * Performs SWAP operation.
+ *
+ * @param frame the frame
+ * @param s the stack data array
+ * @param index the 1-based swap depth
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame, final long[] s, final int index) {
+ if (!frame.stackHasItems(index + 1)) return UNDERFLOW_RESPONSE;
+ StackArithmetic.swap(s, frame.stackTopV2(), index);
+ return SWAP_SUCCESS;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/TLoadOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/TLoadOperationV2.java
new file mode 100644
index 00000000000..ff91e65a1c9
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/TLoadOperationV2.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+import org.apache.tuweni.bytes.Bytes32;
+
+/**
+ * EVM v2 TLOAD operation (EIP-1153) using long[] stack representation.
+ *
+ * Pops a slot key from the stack, loads the value from transient storage, and pushes the result
+ * back. Gas cost is WARM_STORAGE_READ_COST (100) per EIP-1153.
+ */
+public class TLoadOperationV2 extends AbstractFixedCostOperationV2 {
+
+ private static final long GAS_COST = 100L;
+ private static final OperationResult TLOAD_SUCCESS = new OperationResult(GAS_COST, null);
+ private static final OperationResult TLOAD_OUT_OF_GAS =
+ new OperationResult(GAS_COST, ExceptionalHaltReason.INSUFFICIENT_GAS);
+
+ /**
+ * Instantiates a new TLoad operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public TLoadOperationV2(final GasCalculator gasCalculator) {
+ super(0x5C, "TLOAD", 1, 1, gasCalculator, gasCalculator.getTransientLoadOperationGasCost());
+ }
+
+ @Override
+ public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Execute the TLOAD opcode on the v2 long[] stack.
+ *
+ * @param frame the message frame
+ * @param s the stack as a long[] array
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] s) {
+ if (!frame.stackHasItems(1)) {
+ return UNDERFLOW_RESPONSE;
+ }
+
+ if (frame.getRemainingGas() < GAS_COST) {
+ return TLOAD_OUT_OF_GAS;
+ }
+
+ final int top = frame.stackTopV2();
+
+ final byte[] keyBytes = new byte[32];
+ StackArithmetic.toBytesAt(s, top, 0, keyBytes);
+ final Bytes32 slot = Bytes32.wrap(keyBytes);
+
+ final byte[] result =
+ frame.getTransientStorageValue(frame.getRecipientAddress(), slot).toArrayUnsafe();
+ StackArithmetic.fromBytesAt(s, top, 0, result, 0, result.length);
+
+ return TLOAD_SUCCESS;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/TStoreOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/TStoreOperationV2.java
new file mode 100644
index 00000000000..71e9cea9d13
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/TStoreOperationV2.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+import org.apache.tuweni.bytes.Bytes32;
+
+/**
+ * EVM v2 TSTORE operation (EIP-1153) using long[] stack representation.
+ *
+ * Pops a slot key and value from the stack, then writes the value to transient storage. Gas cost
+ * is WARM_STORAGE_READ_COST (100) per EIP-1153.
+ */
+public class TStoreOperationV2 extends AbstractFixedCostOperationV2 {
+
+ private static final long GAS_COST = 100L;
+ private static final OperationResult TSTORE_SUCCESS = new OperationResult(GAS_COST, null);
+ private static final OperationResult TSTORE_OUT_OF_GAS =
+ new OperationResult(GAS_COST, ExceptionalHaltReason.INSUFFICIENT_GAS);
+
+ /**
+ * Instantiates a new TStore operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public TStoreOperationV2(final GasCalculator gasCalculator) {
+ super(0x5D, "TSTORE", 2, 0, gasCalculator, gasCalculator.getTransientStoreOperationGasCost());
+ }
+
+ @Override
+ public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Execute the TSTORE opcode on the v2 long[] stack.
+ *
+ * @param frame the message frame
+ * @param s the stack as a long[] array
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] s) {
+ if (!frame.stackHasItems(2)) {
+ return UNDERFLOW_RESPONSE;
+ }
+
+ final int top = frame.stackTopV2();
+
+ final byte[] keyBytes = new byte[32];
+ final byte[] valueBytes = new byte[32];
+ StackArithmetic.toBytesAt(s, top, 0, keyBytes);
+ StackArithmetic.toBytesAt(s, top, 1, valueBytes);
+
+ frame.setTopV2(top - 2);
+
+ final long remainingGas = frame.getRemainingGas();
+ if (frame.isStatic()) {
+ return new OperationResult(remainingGas, ExceptionalHaltReason.ILLEGAL_STATE_CHANGE);
+ }
+
+ if (remainingGas < GAS_COST) {
+ return TSTORE_OUT_OF_GAS;
+ }
+
+ final Bytes32 slot = Bytes32.wrap(keyBytes);
+ final Bytes32 value = Bytes32.wrap(valueBytes);
+
+ frame.setTransientStorageValue(frame.getRecipientAddress(), slot, value);
+
+ return TSTORE_SUCCESS;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/TimestampOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/TimestampOperationV2.java
new file mode 100644
index 00000000000..7fdd8f98aaa
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/TimestampOperationV2.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** EVM v2 TIMESTAMP operation — pushes the block timestamp onto the stack. */
+public class TimestampOperationV2 extends AbstractFixedCostOperationV2 {
+
+ private static final OperationResult timestampSuccess = new OperationResult(2, null);
+
+ /**
+ * Instantiates a new Timestamp operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public TimestampOperationV2(final GasCalculator gasCalculator) {
+ super(0x42, "TIMESTAMP", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs the TIMESTAMP operation.
+ *
+ * @param frame the message frame
+ * @param stack the v2 long[] stack
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE;
+ frame.setTopV2(
+ StackArithmetic.pushLong(stack, frame.stackTopV2(), frame.getBlockValues().getTimestamp()));
+ return timestampSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/XorOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/XorOperationV2.java
new file mode 100644
index 00000000000..9c80a1bdfcc
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/XorOperationV2.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** EVM v2 XOR operation (0x18). */
+public class XorOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /** The Xor operation success result. */
+ static final OperationResult xorSuccess = new OperationResult(3, null);
+
+ /**
+ * Instantiates a new Xor operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public XorOperationV2(final GasCalculator gasCalculator) {
+ super(0x18, "XOR", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs XOR operation.
+ *
+ * @param frame the frame
+ * @param stack the v2 operand stack ({@code long[]} in big-endian limb order)
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE;
+ frame.setTopV2(StackArithmetic.xor(stack, frame.stackTopV2()));
+ return xorSuccess;
+ }
+}
diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2Test.java
new file mode 100644
index 00000000000..528b9515782
--- /dev/null
+++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2Test.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.hyperledger.besu.evm.UInt256;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.gascalculator.SpuriousDragonGasCalculator;
+import org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2;
+
+import java.util.List;
+
+import org.apache.tuweni.bytes.Bytes32;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class SarOperationV2Test {
+
+ private final GasCalculator gasCalculator = new SpuriousDragonGasCalculator();
+ private final SarOperationV2 operation = new SarOperationV2(gasCalculator);
+
+ static Iterable codeSupplier = Suppliers.memoize(() -> getInitCode(frame, evm));
+
+ if (frame.isStatic()) {
+ return new OperationResult(0, ExceptionalHaltReason.ILLEGAL_STATE_CHANGE);
+ }
+
+ final long[] s = frame.stackDataV2();
+ final int top = frame.stackTopV2();
+
+ final long cost = cost(frame, s, top, codeSupplier);
+ if (frame.getRemainingGas() < cost) {
+ return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS);
+ }
+
+ final Wei value = Wei.wrap(Bytes.wrap(StackArithmetic.getAt(s, top, 0).toBytesBE()));
+
+ final Address address = frame.getRecipientAddress();
+ final MutableAccount account = getMutableAccount(address, frame);
+
+ frame.clearReturnData();
+
+ Code code = codeSupplier.get();
+
+ if (code != null && code.getSize() > evm.getMaxInitcodeSize()) {
+ final int newTop = top - getStackItemsConsumed();
+ frame.setTopV2(newTop);
+ return new OperationResult(cost, ExceptionalHaltReason.CODE_TOO_LARGE);
+ }
+
+ final boolean insufficientBalance = value.compareTo(account.getBalance()) > 0;
+ final boolean maxDepthReached = frame.getDepth() >= 1024;
+ final boolean invalidState = account.getNonce() == -1 || code == null;
+
+ if (insufficientBalance || maxDepthReached || invalidState) {
+ fail(frame, s, top);
+ final SoftFailureReason softFailureReason =
+ insufficientBalance
+ ? LEGACY_INSUFFICIENT_BALANCE
+ : (maxDepthReached ? LEGACY_MAX_CALL_DEPTH : INVALID_STATE);
+ return new OperationResult(cost, getPcIncrement(), softFailureReason);
+ }
+
+ account.incrementNonce();
+ frame.decrementRemainingGas(cost);
+
+ // EIP-8037: Charge state gas for CREATE
+ if (!gasCalculator().stateGasCostCalculator().chargeCreateStateGas(frame)) {
+ return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS);
+ }
+
+ spawnChildMessage(frame, s, top, value, code);
+ frame.incrementRemainingGas(cost);
+
+ return new OperationResult(cost, null, getPcIncrement());
+ }
+
+ /**
+ * How many bytes does this operation occupy?
+ *
+ * @return the number of bytes the operation and immediate arguments occupy
+ */
+ protected int getPcIncrement() {
+ return 1;
+ }
+
+ /**
+ * Computes the gas cost for this create operation.
+ *
+ * @param frame the current message frame
+ * @param s the v2 stack array
+ * @param top the current stack top
+ * @param codeSupplier a supplier for the initcode, if needed for costing
+ * @return the gas cost
+ */
+ protected abstract long cost(MessageFrame frame, long[] s, int top, Supplier codeSupplier);
+
+ /**
+ * Generates the target contract address.
+ *
+ * @param frame the current message frame
+ * @param s the v2 stack array
+ * @param top the current stack top
+ * @param initcode the initcode
+ * @return the target address
+ */
+ protected abstract Address generateTargetContractAddress(
+ MessageFrame frame, long[] s, int top, Code initcode);
+
+ /**
+ * Gets the initcode to run.
+ *
+ * @param frame the current message frame
+ * @param evm the EVM executing the message frame
+ * @return the initcode
+ */
+ protected abstract Code getInitCode(MessageFrame frame, EVM evm);
+
+ /**
+ * Handles stack when the operation fails (insufficient balance, depth limit, invalid state).
+ *
+ * @param frame the current execution frame
+ * @param s the v2 stack array
+ * @param top the current stack top
+ */
+ protected void fail(final MessageFrame frame, final long[] s, final int top) {
+ final long inputOffset = StackArithmetic.clampedToLong(s, top, 1);
+ final long inputSize = StackArithmetic.clampedToLong(s, top, 2);
+ frame.readMutableMemory(inputOffset, inputSize);
+ final int newTop = top - getStackItemsConsumed() + 1;
+ StackArithmetic.putAt(s, newTop, 0, 0L, 0L, 0L, 0L);
+ frame.setTopV2(newTop);
+ }
+
+ private void spawnChildMessage(
+ final MessageFrame parent, final long[] s, final int top, final Wei value, final Code code) {
+ final Address contractAddress = generateTargetContractAddress(parent, s, top, code);
+ final Bytes inputData = getInputData(parent);
+
+ final long childGasStipend =
+ gasCalculator().gasAvailableForChildCreate(parent.getRemainingGas());
+ parent.decrementRemainingGas(childGasStipend);
+
+ MessageFrame.Builder builder =
+ MessageFrame.builder()
+ .parentMessageFrame(parent)
+ .type(MessageFrame.Type.CONTRACT_CREATION)
+ .initialGas(childGasStipend)
+ .address(contractAddress)
+ .contract(contractAddress)
+ .inputData(inputData)
+ .sender(parent.getRecipientAddress())
+ .value(value)
+ .apparentValue(value)
+ .code(code)
+ .enableEvmV2(true)
+ .completer(child -> complete(parent, child));
+
+ if (parent.getEip7928AccessList().isPresent()) {
+ builder.eip7928AccessList(parent.getEip7928AccessList().get());
+ }
+
+ builder.build();
+
+ parent.setState(MessageFrame.State.CODE_SUSPENDED);
+ }
+
+ /**
+ * Returns the input data to append for this create operation. Default is empty (for CREATE and
+ * CREATE2).
+ *
+ * @param frame the current message frame
+ * @return the input data bytes
+ */
+ protected Bytes getInputData(final MessageFrame frame) {
+ return Bytes.EMPTY;
+ }
+
+ private void complete(final MessageFrame frame, final MessageFrame childFrame) {
+ frame.setState(MessageFrame.State.CODE_EXECUTING);
+
+ frame.incrementRemainingGas(childFrame.getRemainingGas());
+ frame.addLogs(childFrame.getLogs());
+ frame.addSelfDestructs(childFrame.getSelfDestructs());
+ frame.addCreates(childFrame.getCreates());
+
+ final long[] s = frame.stackDataV2();
+ final int top = frame.stackTopV2();
+ final int newTop = top - getStackItemsConsumed() + 1;
+
+ if (childFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) {
+ final Address createdAddress = childFrame.getContractAddress();
+ frame.setTopV2(StackArithmetic.pushAddress(s, newTop - 1, createdAddress));
+ frame.setReturnData(Bytes.EMPTY);
+ onSuccess(frame, createdAddress);
+ } else {
+ frame.setReturnData(childFrame.getOutputData());
+ StackArithmetic.putAt(s, newTop, 0, 0L, 0L, 0L, 0L);
+ frame.setTopV2(newTop);
+ onFailure(frame, childFrame.getExceptionalHaltReason());
+ }
+
+ frame.setPC(frame.getPC() + getPcIncrement());
+ }
+
+ /**
+ * Called on successful child contract creation.
+ *
+ * @param frame the parent frame
+ * @param createdAddress the address of the newly created contract
+ */
+ protected void onSuccess(final MessageFrame frame, final Address createdAddress) {}
+
+ /**
+ * Called on failed child contract creation.
+ *
+ * @param frame the parent frame
+ * @param haltReason the exceptional halt reason
+ */
+ protected void onFailure(
+ final MessageFrame frame, final Optional unused) {
+ final int inputOffset = StackArithmetic.clampedToInt(s, top, 1);
+ final int inputSize = StackArithmetic.clampedToInt(s, top, 2);
+ return clampedAdd(
+ clampedAdd(
+ gasCalculator().txCreateCost(),
+ gasCalculator().memoryExpansionGasCost(frame, inputOffset, inputSize)),
+ clampedAdd(
+ gasCalculator().createKeccakCost(inputSize), gasCalculator().initcodeCost(inputSize)));
+ }
+
+ @Override
+ protected Address generateTargetContractAddress(
+ final MessageFrame frame, final long[] s, final int top, final Code initcode) {
+ final Address sender = frame.getRecipientAddress();
+ final byte[] saltBytes = new byte[32];
+ StackArithmetic.toBytesAt(s, top, 3, saltBytes);
+ final Bytes32 salt = Bytes32.wrap(saltBytes);
+ final Bytes32 hash =
+ keccak256(
+ Bytes.concatenate(PREFIX, sender.getBytes(), salt, initcode.getCodeHash().getBytes()));
+ return Address.extract(hash);
+ }
+
+ @Override
+ protected Code getInitCode(final MessageFrame frame, final EVM evm) {
+ final long[] s = frame.stackDataV2();
+ final int top = frame.stackTopV2();
+ final long inputOffset = StackArithmetic.clampedToLong(s, top, 1);
+ final long inputSize = StackArithmetic.clampedToLong(s, top, 2);
+ final Bytes inputData = frame.readMemory(inputOffset, inputSize);
+ return new Code(inputData);
+ }
+
+ /**
+ * Performs the CREATE2 operation on the v2 stack. Only available from Constantinople onwards.
+ *
+ * @param frame the current message frame
+ * @param s the v2 stack array
+ * @param gasCalculator the gas calculator
+ * @param evm the EVM
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame, final long[] s, final GasCalculator gasCalculator, final EVM evm) {
+ if (EvmSpecVersion.CONSTANTINOPLE.ordinal() > evm.getEvmVersion().ordinal()) {
+ return InvalidOperation.invalidOperationResult(0xF5);
+ }
+ return new Create2OperationV2(gasCalculator).execute(frame, evm);
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CreateOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CreateOperationV2.java
new file mode 100644
index 00000000000..c99dee056b0
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CreateOperationV2.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import static org.hyperledger.besu.evm.internal.Words.clampedAdd;
+
+import org.hyperledger.besu.datatypes.Address;
+import org.hyperledger.besu.evm.Code;
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.account.Account;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+import java.util.function.Supplier;
+
+import org.apache.tuweni.bytes.Bytes;
+
+/** EVM v2 CREATE operation (opcode 0xF0). Stack layout: value, offset, size → contractAddress. */
+public class CreateOperationV2 extends AbstractCreateOperationV2 {
+
+ /**
+ * Instantiates a new CREATE operation for EVM v2.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public CreateOperationV2(final GasCalculator gasCalculator) {
+ super(0xF0, "CREATE", 3, 1, gasCalculator);
+ }
+
+ @Override
+ protected long cost(
+ final MessageFrame frame, final long[] s, final int top, final Supplier unused) {
+ final int inputOffset = StackArithmetic.clampedToInt(s, top, 1);
+ final int inputSize = StackArithmetic.clampedToInt(s, top, 2);
+ return clampedAdd(
+ clampedAdd(
+ gasCalculator().txCreateCost(),
+ gasCalculator().memoryExpansionGasCost(frame, inputOffset, inputSize)),
+ gasCalculator().initcodeCost(inputSize));
+ }
+
+ @Override
+ protected Address generateTargetContractAddress(
+ final MessageFrame frame, final long[] s, final int top, final Code initcode) {
+ final Account sender = getAccount(frame.getRecipientAddress(), frame);
+ return Address.contractAddress(frame.getRecipientAddress(), sender.getNonce() - 1L);
+ }
+
+ @Override
+ protected Code getInitCode(final MessageFrame frame, final EVM evm) {
+ final long[] s = frame.stackDataV2();
+ final int top = frame.stackTopV2();
+ final long inputOffset = StackArithmetic.clampedToLong(s, top, 1);
+ final long inputSize = StackArithmetic.clampedToLong(s, top, 2);
+ final Bytes inputData = frame.readMemory(inputOffset, inputSize);
+ return new Code(inputData);
+ }
+
+ /**
+ * Performs the CREATE operation on the v2 stack.
+ *
+ * @param frame the current message frame
+ * @param s the v2 stack array
+ * @param gasCalculator the gas calculator
+ * @param evm the EVM
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame, final long[] s, final GasCalculator gasCalculator, final EVM evm) {
+ return new CreateOperationV2(gasCalculator).execute(frame, evm);
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DelegateCallOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DelegateCallOperationV2.java
new file mode 100644
index 00000000000..e7b4221c883
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DelegateCallOperationV2.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.datatypes.Address;
+import org.hyperledger.besu.datatypes.Wei;
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.EvmSpecVersion;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.InvalidOperation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/**
+ * EVM v2 DELEGATECALL operation (opcode 0xF4, Homestead+). Stack layout: gas, to, inOffset, inSize,
+ * outOffset, outSize → result.
+ */
+public class DelegateCallOperationV2 extends AbstractCallOperationV2 {
+
+ /**
+ * Instantiates a new DELEGATECALL operation for EVM v2.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public DelegateCallOperationV2(final GasCalculator gasCalculator) {
+ super(0xF4, "DELEGATECALL", 6, 1, gasCalculator);
+ }
+
+ @Override
+ protected Address to(final long[] s, final int top) {
+ return StackArithmetic.toAddressAt(s, top, 1);
+ }
+
+ @Override
+ protected Wei value(final long[] s, final int top) {
+ return Wei.ZERO;
+ }
+
+ @Override
+ protected Wei apparentValue(final MessageFrame frame, final long[] s, final int top) {
+ return frame.getApparentValue();
+ }
+
+ @Override
+ protected long inputDataOffset(final long[] s, final int top) {
+ return StackArithmetic.clampedToLong(s, top, 2);
+ }
+
+ @Override
+ protected long inputDataLength(final long[] s, final int top) {
+ return StackArithmetic.clampedToLong(s, top, 3);
+ }
+
+ @Override
+ protected long outputDataOffset(final long[] s, final int top) {
+ return StackArithmetic.clampedToLong(s, top, 4);
+ }
+
+ @Override
+ protected long outputDataLength(final long[] s, final int top) {
+ return StackArithmetic.clampedToLong(s, top, 5);
+ }
+
+ @Override
+ protected Address address(final MessageFrame frame, final long[] s, final int top) {
+ return frame.getRecipientAddress();
+ }
+
+ @Override
+ protected Address sender(final MessageFrame frame) {
+ return frame.getSenderAddress();
+ }
+
+ @Override
+ public long gasAvailableForChildCall(final MessageFrame frame, final long[] s, final int top) {
+ return gasCalculator().gasAvailableForChildCall(frame, gas(s, top), false);
+ }
+
+ @Override
+ protected boolean isDelegate() {
+ return true;
+ }
+
+ /**
+ * Performs the DELEGATECALL operation on the v2 stack. Only available from Homestead onwards.
+ *
+ * @param frame the current message frame
+ * @param s the v2 stack array
+ * @param gasCalculator the gas calculator
+ * @param evm the EVM
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(
+ final MessageFrame frame, final long[] s, final GasCalculator gasCalculator, final EVM evm) {
+ if (EvmSpecVersion.HOMESTEAD.ordinal() > evm.getEvmVersion().ordinal()) {
+ return InvalidOperation.invalidOperationResult(0xF4);
+ }
+ return new DelegateCallOperationV2(gasCalculator).execute(frame, evm);
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DifficultyOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DifficultyOperationV2.java
new file mode 100644
index 00000000000..52a8675f726
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DifficultyOperationV2.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/**
+ * EVM v2 DIFFICULTY operation — pushes the block difficulty value onto the stack (pre-Paris forks).
+ */
+public class DifficultyOperationV2 extends AbstractFixedCostOperationV2 {
+
+ private static final OperationResult difficultySuccess = new OperationResult(2, null);
+
+ /**
+ * Instantiates a new Difficulty operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public DifficultyOperationV2(final GasCalculator gasCalculator) {
+ super(0x44, "DIFFICULTY", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs the DIFFICULTY operation.
+ *
+ * @param frame the message frame
+ * @param stack the v2 long[] stack
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE;
+ final byte[] diffBytes = frame.getBlockValues().getDifficultyBytes().toArrayUnsafe();
+ frame.setTopV2(
+ StackArithmetic.pushFromBytes(stack, frame.stackTopV2(), diffBytes, 0, diffBytes.length));
+ return difficultySuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DivOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DivOperationV2.java
new file mode 100644
index 00000000000..f408d94f113
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DivOperationV2.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/** The Div operation. */
+public class DivOperationV2 extends AbstractFixedCostOperationV2 {
+
+ /** The Div operation success result. */
+ static final OperationResult divSuccess = new OperationResult(5, null);
+
+ /**
+ * Instantiates a new Div operation.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ public DivOperationV2(final GasCalculator gasCalculator) {
+ super(0x04, "DIV", 2, 1, gasCalculator, gasCalculator.getLowTierGasCost());
+ }
+
+ @Override
+ public Operation.OperationResult executeFixedCostOperation(
+ final MessageFrame frame, final EVM evm) {
+ return staticOperation(frame, frame.stackDataV2());
+ }
+
+ /**
+ * Performs div operation.
+ *
+ * @param frame the frame
+ * @param stack the v2 operand stack ({@code long[]} in big-endian limb order)
+ * @return the operation result
+ */
+ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) {
+ if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE;
+ frame.setTopV2(StackArithmetic.div(stack, frame.stackTopV2()));
+ return divSuccess;
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DupNOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DupNOperationV2.java
new file mode 100644
index 00000000000..5d23d2e8fe7
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DupNOperationV2.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright contributors to Besu.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.v2.operation;
+
+import org.hyperledger.besu.evm.EVM;
+import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.operation.Eip8024Decoder;
+import org.hyperledger.besu.evm.operation.Operation;
+import org.hyperledger.besu.evm.v2.StackArithmetic;
+
+/**
+ * EVM v2 DUPN operation (0xE6, EIP-8024).
+ *
+ *