From 60fdfe0c1ad115a2fba9394b7d011af343824fa7 Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Wed, 1 Apr 2026 16:07:37 +0200 Subject: [PATCH 01/24] Add SHL, SHR and SAR implementations and benchmarks for EVM v2 Signed-off-by: Ameziane H. --- .../v2/AbstractSarOperationBenchmarkV2.java | 153 +++++++++++ .../v2/AbstractShiftOperationBenchmarkV2.java | 120 +++++++++ .../v2/AddOperationBenchmarkV2.java | 2 +- .../vm/operations/v2/BenchmarkHelperV2.java | 41 +++ .../v2/SarOperationBenchmarkV2.java | 27 ++ .../v2/ShlOperationBenchmarkV2.java | 27 ++ .../v2/ShrOperationBenchmarkV2.java | 27 ++ .../java/org/hyperledger/besu/evm/EVM.java | 17 +- .../besu/evm/v2/StackArithmetic.java | 255 ++++++++++++++++++ .../AbstractFixedCostOperationV2.java | 2 +- .../v2 => v2/operation}/AddOperationV2.java | 2 +- .../besu/evm/v2/operation/SarOperationV2.java | 55 ++++ .../besu/evm/v2/operation/ShlOperationV2.java | 55 ++++ .../besu/evm/v2/operation/ShrOperationV2.java | 55 ++++ 14 files changed, 834 insertions(+), 4 deletions(-) create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AbstractSarOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AbstractShiftOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SarOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ShlOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ShrOperationBenchmarkV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java rename evm/src/main/java/org/hyperledger/besu/evm/{operation/v2 => v2/operation}/AbstractFixedCostOperationV2.java (98%) rename evm/src/main/java/org/hyperledger/besu/evm/{operation/v2 => v2/operation}/AddOperationV2.java (97%) create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ShlOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ShrOperationV2.java diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AbstractSarOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AbstractSarOperationBenchmarkV2.java new file mode 100644 index 00000000000..215e75adb1d --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AbstractSarOperationBenchmarkV2.java @@ -0,0 +1,153 @@ +/* + * 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 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 SAR (Shift Arithmetic Right) operation benchmarks. + * + *

SAR has additional test cases for negative/positive values to test sign extension behavior. + */ +public abstract class AbstractSarOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + /** Test cases covering different execution paths for SAR operations. */ + public enum Case { + /** Shift by 0 - early return path. */ + SHIFT_0, + /** Negative number (ALL_BITS) with shift=1 - tests sign extension OR path. */ + NEGATIVE_SHIFT_1, + /** value with all bits to 1 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 (original behavior). */ + 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]; // 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.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; + } +} 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/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/SarOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SarOperationBenchmarkV2.java new file mode 100644 index 00000000000..00b8e116dbe --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SarOperationBenchmarkV2.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.SarOperationV2; + +public class SarOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + @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/ShlOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ShlOperationBenchmarkV2.java new file mode 100644 index 00000000000..1c3fb3a2498 --- /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 BinaryOperationBenchmarkV2 { + + @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..3df751052d5 --- /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 BinaryOperationBenchmarkV2 { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return ShrOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} 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..4384c5e3320 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java @@ -86,8 +86,11 @@ 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.AddOperationV2; +import org.hyperledger.besu.evm.v2.operation.SarOperationV2; +import org.hyperledger.besu.evm.v2.operation.ShlOperationV2; +import org.hyperledger.besu.evm.v2.operation.ShrOperationV2; import java.util.Optional; import java.util.function.Function; @@ -485,6 +488,18 @@ private void runToHaltV2(final MessageFrame frame, final OperationTracer tracing result = switch (opcode) { case 0x01 -> AddOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x1b -> + enableConstantinople + ? ShlOperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); + case 0x1c -> + enableConstantinople + ? ShrOperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); + case 0x1d -> + enableConstantinople + ? SarOperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); // TODO: implement remaining opcodes in v2; until then fall through to v1 default -> { frame.setCurrentOperation(currentOperation); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java new file mode 100644 index 00000000000..a24ae1dbc3f --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java @@ -0,0 +1,255 @@ +/* + * 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; + +/** + * Static utility operating directly on the flat {@code long[]} operand stack. Each slot occupies 4 + * consecutive longs in big-endian limb order: {@code [u3, u2, u1, u0]} where u3 is the most + * significant limb. + * + *

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 { + + /** SHL: s[top-2] = s[top-2] << s[top-1], return top-1. */ + public static int shl(final long[] s, final int top) { + final int a = (top - 1) << 2; // shift amount + final int b = (top - 2) << 2; // value + // If shift amount > 255 or value is zero, result is zero + if (s[a] != 0 + || s[a + 1] != 0 + || s[a + 2] != 0 + || Long.compareUnsigned(s[a + 3], 256) >= 0 + || (s[b] == 0 && s[b + 1] == 0 && s[b + 2] == 0 && s[b + 3] == 0)) { + s[b] = 0; + s[b + 1] = 0; + s[b + 2] = 0; + s[b + 3] = 0; + return top - 1; + } + int shift = (int) s[a + 3]; + shiftLeftInPlace(s, b, shift); + return top - 1; + } + + /** Shift left in place. shift must be 0..255. */ + private static void shiftLeftInPlace(final long[] s, final int off, final int shift) { + if (shift == 0) return; + int limbShift = shift >>> 6; + int bitShift = shift & 63; + + // Move limbs (stored as [u3, u2, u1, u0] at [off, off+1, off+2, off+3]) + // u3=off, u2=off+1, u1=off+2, u0=off+3 + long u0 = s[off + 3], u1 = s[off + 2], u2 = s[off + 1], u3 = s[off]; + long a0, a1, a2, a3; + switch (limbShift) { + case 0: + a0 = u0; + a1 = u1; + a2 = u2; + a3 = u3; + break; + case 1: + a0 = 0; + a1 = u0; + a2 = u1; + a3 = u2; + break; + case 2: + a0 = 0; + a1 = 0; + a2 = u0; + a3 = u1; + break; + case 3: + a0 = 0; + a1 = 0; + a2 = 0; + a3 = u0; + break; + default: + s[off] = 0; + s[off + 1] = 0; + s[off + 2] = 0; + s[off + 3] = 0; + return; + } + + if (bitShift == 0) { + s[off] = a3; + s[off + 1] = a2; + s[off + 2] = a1; + s[off + 3] = a0; + } else { + int inv = 64 - bitShift; + s[off + 3] = a0 << bitShift; + s[off + 2] = (a1 << bitShift) | (a0 >>> inv); + s[off + 1] = (a2 << bitShift) | (a1 >>> inv); + s[off] = (a3 << bitShift) | (a2 >>> inv); + } + } + + /** SHR: s[top-2] = s[top-2] >>> s[top-1], return top-1. */ + public static int shr(final long[] s, final int top) { + final int a = (top - 1) << 2; // shift amount + final int b = (top - 2) << 2; // value + if (s[a] != 0 + || s[a + 1] != 0 + || s[a + 2] != 0 + || Long.compareUnsigned(s[a + 3], 256) >= 0 + || (s[b] == 0 && s[b + 1] == 0 && s[b + 2] == 0 && s[b + 3] == 0)) { + s[b] = 0; + s[b + 1] = 0; + s[b + 2] = 0; + s[b + 3] = 0; + return top - 1; + } + int shift = (int) s[a + 3]; + shiftRightInPlace(s, b, shift); + return top - 1; + } + + /** Logical shift right in place. shift must be 0..255. */ + private static void shiftRightInPlace(final long[] s, final int off, final int shift) { + if (shift == 0) return; + int limbShift = shift >>> 6; + int bitShift = shift & 63; + + long u0 = s[off + 3], u1 = s[off + 2], u2 = s[off + 1], u3 = s[off]; + long a0, a1, a2, a3; + switch (limbShift) { + case 0: + a0 = u0; + a1 = u1; + a2 = u2; + a3 = u3; + break; + case 1: + a0 = u1; + a1 = u2; + a2 = u3; + a3 = 0; + break; + case 2: + a0 = u2; + a1 = u3; + a2 = 0; + a3 = 0; + break; + case 3: + a0 = u3; + a1 = 0; + a2 = 0; + a3 = 0; + break; + default: + s[off] = 0; + s[off + 1] = 0; + s[off + 2] = 0; + s[off + 3] = 0; + return; + } + + if (bitShift == 0) { + s[off] = a3; + s[off + 1] = a2; + s[off + 2] = a1; + s[off + 3] = a0; + } else { + int inv = 64 - bitShift; + s[off] = a3 >>> bitShift; + s[off + 1] = (a2 >>> bitShift) | (a3 << inv); + s[off + 2] = (a1 >>> bitShift) | (a2 << inv); + s[off + 3] = (a0 >>> bitShift) | (a1 << inv); + } + } + + /** SAR: s[top-2] = s[top-2] >> s[top-1] (arithmetic), return top-1. */ + public static int sar(final long[] s, final int top) { + final int a = (top - 1) << 2; // shift amount + final int b = (top - 2) << 2; // value + boolean negative = s[b] < 0; // MSB of u3 + + if (s[a] != 0 || s[a + 1] != 0 || s[a + 2] != 0 || Long.compareUnsigned(s[a + 3], 256) >= 0) { + long fill = negative ? -1L : 0L; + s[b] = fill; + s[b + 1] = fill; + s[b + 2] = fill; + s[b + 3] = fill; + return top - 1; + } + int shift = (int) s[a + 3]; + sarInPlace(s, b, shift, negative); + return top - 1; + } + + /** Arithmetic shift right in place. shift must be 0..255. */ + private static void sarInPlace( + final long[] s, final int off, final int shift, final boolean negative) { + if (shift == 0) return; + int limbShift = shift >>> 6; + int bitShift = shift & 63; + long fill = negative ? -1L : 0L; + + long u0 = s[off + 3], u1 = s[off + 2], u2 = s[off + 1], u3 = s[off]; + long a0, a1, a2, a3; + switch (limbShift) { + case 0: + a0 = u0; + a1 = u1; + a2 = u2; + a3 = u3; + break; + case 1: + a0 = u1; + a1 = u2; + a2 = u3; + a3 = fill; + break; + case 2: + a0 = u2; + a1 = u3; + a2 = fill; + a3 = fill; + break; + case 3: + a0 = u3; + a1 = fill; + a2 = fill; + a3 = fill; + break; + default: + s[off] = fill; + s[off + 1] = fill; + s[off + 2] = fill; + s[off + 3] = fill; + return; + } + + if (bitShift == 0) { + s[off] = a3; + s[off + 1] = a2; + s[off + 2] = a1; + s[off + 3] = a0; + } else { + int inv = 64 - bitShift; + s[off] = a3 >> bitShift; // arithmetic shift for MSB + s[off + 1] = (a2 >>> bitShift) | (a3 << inv); + s[off + 2] = (a1 >>> bitShift) | (a2 << inv); + s[off + 3] = (a0 >>> bitShift) | (a1 << inv); + } + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/v2/AbstractFixedCostOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java similarity index 98% rename from evm/src/main/java/org/hyperledger/besu/evm/operation/v2/AbstractFixedCostOperationV2.java rename to evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java index 95e059d9605..0480e77b067 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/v2/AbstractFixedCostOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java @@ -12,7 +12,7 @@ * * 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.ExceptionalHaltReason; 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 97% 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..b4109d29ad1 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,7 +12,7 @@ * * 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; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2.java new file mode 100644 index 00000000000..0a84bca8526 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2.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 Sar operation. */ +public class SarOperationV2 extends AbstractFixedCostOperationV2 { + + /** The Sar operation success result. */ + static final OperationResult sarSuccess = new OperationResult(3, null); + + /** + * Instantiates a new Sar operation. + * + * @param gasCalculator the gas calculator + */ + public SarOperationV2(final GasCalculator gasCalculator) { + super(0x1d, "SAR", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs sar operation. + * + * @param frame the frame + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.sar(s, frame.stackTopV2())); + return sarSuccess; + } +} 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..8aa84cab477 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ShlOperationV2.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 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 + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.shl(s, 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..feebb57e702 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ShrOperationV2.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 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 + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.shr(s, frame.stackTopV2())); + return shrSuccess; + } +} From 1dd435b8ceb0284651365488bbc4c2da592c23c6 Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Wed, 1 Apr 2026 16:16:49 +0200 Subject: [PATCH 02/24] Use more accurate benchmarks on shift operations Signed-off-by: Ameziane H. --- .../besu/ethereum/vm/operations/v2/SarOperationBenchmarkV2.java | 2 +- .../besu/ethereum/vm/operations/v2/ShlOperationBenchmarkV2.java | 2 +- .../besu/ethereum/vm/operations/v2/ShrOperationBenchmarkV2.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 index 00b8e116dbe..626b1912e62 100644 --- 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 @@ -18,7 +18,7 @@ import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.v2.operation.SarOperationV2; -public class SarOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { +public class SarOperationBenchmarkV2 extends AbstractSarOperationBenchmarkV2 { @Override protected Operation.OperationResult invoke(final MessageFrame frame) { 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 index 1c3fb3a2498..117f8148036 100644 --- 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 @@ -18,7 +18,7 @@ import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.v2.operation.ShlOperationV2; -public class ShlOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { +public class ShlOperationBenchmarkV2 extends AbstractShiftOperationBenchmarkV2 { @Override protected Operation.OperationResult invoke(final MessageFrame frame) { 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 index 3df751052d5..fa3dd952609 100644 --- 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 @@ -18,7 +18,7 @@ import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.v2.operation.ShrOperationV2; -public class ShrOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { +public class ShrOperationBenchmarkV2 extends AbstractShiftOperationBenchmarkV2 { @Override protected Operation.OperationResult invoke(final MessageFrame frame) { From 45f61e5e2f96475c6c3a3f1783e29123ddd60335 Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Thu, 2 Apr 2026 15:18:39 +0200 Subject: [PATCH 03/24] Use existing implementation on SAR, SHR and SHL Signed-off-by: Ameziane H. --- .../besu/evm/v2/StackArithmetic.java | 387 ++++++++++-------- .../besu/evm/v2/operation/SarOperationV2.java | 4 +- 2 files changed, 216 insertions(+), 175 deletions(-) diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java index a24ae1dbc3f..514307e09e2 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java @@ -24,232 +24,273 @@ */ public class StackArithmetic { - /** SHL: s[top-2] = s[top-2] << s[top-1], return top-1. */ - public static int shl(final long[] s, final int top) { - final int a = (top - 1) << 2; // shift amount - final int b = (top - 2) << 2; // value + /** + * 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 (s[a] != 0 - || s[a + 1] != 0 - || s[a + 2] != 0 - || Long.compareUnsigned(s[a + 3], 256) >= 0 - || (s[b] == 0 && s[b + 1] == 0 && s[b + 2] == 0 && s[b + 3] == 0)) { - s[b] = 0; - s[b + 1] = 0; - s[b + 2] = 0; - s[b + 3] = 0; + 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) s[a + 3]; - shiftLeftInPlace(s, b, shift); + int shift = (int) stack[shiftOffset + 3]; + shiftLeftInPlace(stack, valueOffset, shift); return top - 1; } - /** Shift left in place. shift must be 0..255. */ - private static void shiftLeftInPlace(final long[] s, final int off, final int shift) { + /** + * 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; - int limbShift = shift >>> 6; - int bitShift = shift & 63; - - // Move limbs (stored as [u3, u2, u1, u0] at [off, off+1, off+2, off+3]) - // u3=off, u2=off+1, u1=off+2, u0=off+3 - long u0 = s[off + 3], u1 = s[off + 2], u2 = s[off + 1], u3 = s[off]; - long a0, a1, a2, a3; - switch (limbShift) { + 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: - a0 = u0; - a1 = u1; - a2 = u2; - a3 = u3; + w0 = shiftLeftWord(w0, w1, bitShift); + w1 = shiftLeftWord(w1, w2, bitShift); + w2 = shiftLeftWord(w2, w3, bitShift); + w3 = shiftLeftWord(w3, 0, bitShift); break; case 1: - a0 = 0; - a1 = u0; - a2 = u1; - a3 = u2; + w0 = shiftLeftWord(w1, w2, bitShift); + w1 = shiftLeftWord(w2, w3, bitShift); + w2 = shiftLeftWord(w3, 0, bitShift); + w3 = 0; break; case 2: - a0 = 0; - a1 = 0; - a2 = u0; - a3 = u1; + w0 = shiftLeftWord(w2, w3, bitShift); + w1 = shiftLeftWord(w3, 0, bitShift); + w2 = 0; + w3 = 0; break; case 3: - a0 = 0; - a1 = 0; - a2 = 0; - a3 = u0; + w0 = shiftLeftWord(w3, 0, bitShift); + w1 = 0; + w2 = 0; + w3 = 0; break; - default: - s[off] = 0; - s[off + 1] = 0; - s[off + 2] = 0; - s[off + 3] = 0; - return; } + stack[valueOffset] = w0; + stack[valueOffset + 1] = w1; + stack[valueOffset + 2] = w2; + stack[valueOffset + 3] = w3; + } - if (bitShift == 0) { - s[off] = a3; - s[off + 1] = a2; - s[off + 2] = a1; - s[off + 3] = a0; - } else { - int inv = 64 - bitShift; - s[off + 3] = a0 << bitShift; - s[off + 2] = (a1 << bitShift) | (a0 >>> inv); - s[off + 1] = (a2 << bitShift) | (a1 >>> inv); - s[off] = (a3 << bitShift) | (a2 >>> inv); - } + /** + * 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)); } - /** SHR: s[top-2] = s[top-2] >>> s[top-1], return top-1. */ - public static int shr(final long[] s, final int top) { - final int a = (top - 1) << 2; // shift amount - final int b = (top - 2) << 2; // value - if (s[a] != 0 - || s[a + 1] != 0 - || s[a + 2] != 0 - || Long.compareUnsigned(s[a + 3], 256) >= 0 - || (s[b] == 0 && s[b + 1] == 0 && s[b + 2] == 0 && s[b + 3] == 0)) { - s[b] = 0; - s[b + 1] = 0; - s[b + 2] = 0; - s[b + 3] = 0; + /** + * 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) s[a + 3]; - shiftRightInPlace(s, b, shift); + int shift = (int) stack[shiftOffset + 3]; + shiftRightInPlace(stack, valueOffset, shift); return top - 1; } - /** Logical shift right in place. shift must be 0..255. */ - private static void shiftRightInPlace(final long[] s, final int off, final int shift) { + /** + * 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; - int limbShift = shift >>> 6; - int bitShift = shift & 63; - - long u0 = s[off + 3], u1 = s[off + 2], u2 = s[off + 1], u3 = s[off]; - long a0, a1, a2, a3; - switch (limbShift) { + 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: - a0 = u0; - a1 = u1; - a2 = u2; - a3 = u3; + w3 = shiftRightWord(w3, w2, bitShift); + w2 = shiftRightWord(w2, w1, bitShift); + w1 = shiftRightWord(w1, w0, bitShift); + w0 = shiftRightWord(w0, 0, bitShift); break; case 1: - a0 = u1; - a1 = u2; - a2 = u3; - a3 = 0; + w3 = shiftRightWord(w2, w1, bitShift); + w2 = shiftRightWord(w1, w0, bitShift); + w1 = shiftRightWord(w0, 0, bitShift); + w0 = 0; break; case 2: - a0 = u2; - a1 = u3; - a2 = 0; - a3 = 0; + w3 = shiftRightWord(w1, w0, bitShift); + w2 = shiftRightWord(w0, 0, bitShift); + w1 = 0; + w0 = 0; break; case 3: - a0 = u3; - a1 = 0; - a2 = 0; - a3 = 0; + w3 = shiftRightWord(w0, 0, bitShift); + w2 = 0; + w1 = 0; + w0 = 0; break; - default: - s[off] = 0; - s[off + 1] = 0; - s[off + 2] = 0; - s[off + 3] = 0; - return; - } - - if (bitShift == 0) { - s[off] = a3; - s[off + 1] = a2; - s[off + 2] = a1; - s[off + 3] = a0; - } else { - int inv = 64 - bitShift; - s[off] = a3 >>> bitShift; - s[off + 1] = (a2 >>> bitShift) | (a3 << inv); - s[off + 2] = (a1 >>> bitShift) | (a2 << inv); - s[off + 3] = (a0 >>> bitShift) | (a1 << inv); } + s[valueOffset] = w0; + s[valueOffset + 1] = w1; + s[valueOffset + 2] = w2; + s[valueOffset + 3] = w3; } - /** SAR: s[top-2] = s[top-2] >> s[top-1] (arithmetic), return top-1. */ - public static int sar(final long[] s, final int top) { - final int a = (top - 1) << 2; // shift amount - final int b = (top - 2) << 2; // value - boolean negative = s[b] < 0; // MSB of u3 - - if (s[a] != 0 || s[a + 1] != 0 || s[a + 2] != 0 || Long.compareUnsigned(s[a + 3], 256) >= 0) { + /** + * 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; - s[b] = fill; - s[b + 1] = fill; - s[b + 2] = fill; - s[b + 3] = fill; + stack[valueOffset] = fill; + stack[valueOffset + 1] = fill; + stack[valueOffset + 2] = fill; + stack[valueOffset + 3] = fill; return top - 1; } - int shift = (int) s[a + 3]; - sarInPlace(s, b, shift, negative); + int shift = (int) stack[shiftOffset + 3]; + sarInPlace(stack, valueOffset, shift, negative); return top - 1; } - /** Arithmetic shift right in place. shift must be 0..255. */ + /** + * 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[] s, final int off, final int shift, final boolean negative) { + final long[] stack, final int valueOffset, final int shift, final boolean negative) { if (shift == 0) return; - int limbShift = shift >>> 6; - int bitShift = shift & 63; - long fill = negative ? -1L : 0L; - - long u0 = s[off + 3], u1 = s[off + 2], u2 = s[off + 1], u3 = s[off]; - long a0, a1, a2, a3; - switch (limbShift) { + 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: - a0 = u0; - a1 = u1; - a2 = u2; - a3 = u3; + w3 = shiftRightWord(w3, w2, bitShift); + w2 = shiftRightWord(w2, w1, bitShift); + w1 = shiftRightWord(w1, w0, bitShift); + w0 = shiftRightWord(w0, fill, bitShift); break; case 1: - a0 = u1; - a1 = u2; - a2 = u3; - a3 = fill; + w3 = shiftRightWord(w2, w1, bitShift); + w2 = shiftRightWord(w1, w0, bitShift); + w1 = shiftRightWord(w0, fill, bitShift); + w0 = fill; break; case 2: - a0 = u2; - a1 = u3; - a2 = fill; - a3 = fill; + w3 = shiftRightWord(w1, w0, bitShift); + w2 = shiftRightWord(w0, fill, bitShift); + w1 = fill; + w0 = fill; break; case 3: - a0 = u3; - a1 = fill; - a2 = fill; - a3 = fill; + w3 = shiftRightWord(w0, fill, bitShift); + w2 = fill; + w1 = fill; + w0 = fill; break; - default: - s[off] = fill; - s[off + 1] = fill; - s[off + 2] = fill; - s[off + 3] = fill; - return; } + stack[valueOffset] = w0; + stack[valueOffset + 1] = w1; + stack[valueOffset + 2] = w2; + stack[valueOffset + 3] = w3; + } - if (bitShift == 0) { - s[off] = a3; - s[off + 1] = a2; - s[off + 2] = a1; - s[off + 3] = a0; - } else { - int inv = 64 - bitShift; - s[off] = a3 >> bitShift; // arithmetic shift for MSB - s[off + 1] = (a2 >>> bitShift) | (a3 << inv); - s[off + 2] = (a1 >>> bitShift) | (a2 << inv); - s[off + 3] = (a0 >>> bitShift) | (a1 << inv); - } + /** + * 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)); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2.java index 0a84bca8526..4a3dafc1b1f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2.java @@ -47,9 +47,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; - frame.setTopV2(StackArithmetic.sar(s, frame.stackTopV2())); + frame.setTopV2(StackArithmetic.sar(stack, frame.stackTopV2())); return sarSuccess; } } From 531a87be06cacbdd5affac0506e9f1a4f99cf2f2 Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Thu, 2 Apr 2026 16:43:37 +0200 Subject: [PATCH 04/24] Add unit tests. Signed-off-by: Ameziane H. --- .../evm/v2/operation/SarOperationV2Test.java | 196 ++++++++++++++++++ .../evm/v2/operation/ShlOperationV2Test.java | 132 ++++++++++++ .../evm/v2/operation/ShrOperationV2Test.java | 144 +++++++++++++ .../testutils/TestMessageFrameBuilderV2.java | 190 +++++++++++++++++ 4 files changed, 662 insertions(+) create mode 100644 evm/src/test/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2Test.java create mode 100644 evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShlOperationV2Test.java create mode 100644 evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShrOperationV2Test.java create mode 100644 evm/src/test/java/org/hyperledger/besu/evm/v2/testutils/TestMessageFrameBuilderV2.java 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 data() { + return List.of( + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x00", + "0x0000000000000000000000000000000000000000000000000000000000000001"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000000"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000001"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000004", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000002"), + Arguments.of( + "0x000000000000000000000000000000000000000000000000000000000000000f", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000007"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000004"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0x01", + "0xc000000000000000000000000000000000000000000000000000000000000000"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0xff", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0x100", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0x101", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x0", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x01", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0xff", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x100", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000000"), + Arguments.of( + "0x4000000000000000000000000000000000000000000000000000000000000000", + "0xfe", + "0x0000000000000000000000000000000000000000000000000000000000000001"), + Arguments.of( + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0xf8", + "0x000000000000000000000000000000000000000000000000000000000000007f"), + Arguments.of( + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0xfe", + "0x0000000000000000000000000000000000000000000000000000000000000001"), + Arguments.of( + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0xff", + "0x0000000000000000000000000000000000000000000000000000000000000000"), + Arguments.of( + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0x100", "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x80", + "0x0000000000000000000000000000000000000000000000000000000000000000"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", "0x8000", "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x80000000", + "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x8000000000000000", + "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x80000000000000000000000000000000", + "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0x"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000400", + "0x80", + "0xffffffffffffffffffffffffffffffff80000000000000000000000000000000"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000400", + "0x8000", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000400", + "0x80000000", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000400", + "0x8000000000000000", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000400", + "0x80000000000000000000000000000000", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000400", + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); + } + + @ParameterizedTest(name = "{index}: {0}, {1}, {2}") + @MethodSource("data") + void shiftOperation(final String number, final String shift, final String expectedResult) { + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .pushStackItem(Bytes32.fromHexStringLenient(number)) + .pushStackItem(Bytes32.fromHexStringLenient(shift)) + .build(); + operation.execute(frame, null); + UInt256 expected; + if (expectedResult.equals("0x") || expectedResult.equals("0x0")) { + expected = UInt256.ZERO; + } else { + expected = UInt256.fromBytesBE(Bytes32.fromHexStringLenient(expectedResult).toArrayUnsafe()); + } + assertThat(getV2StackItem(frame, 0)).isEqualTo(expected); + } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } + + private static UInt256 getV2StackItem(final MessageFrame frame, final int offset) { + final long[] s = frame.stackDataV2(); + final int idx = (frame.stackTopV2() - 1 - offset) << 2; + return new UInt256(s[idx], s[idx + 1], s[idx + 2], s[idx + 3]); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShlOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShlOperationV2Test.java new file mode 100644 index 00000000000..246c4198201 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShlOperationV2Test.java @@ -0,0 +1,132 @@ +/* + * 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.Arrays; + +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 ShlOperationV2Test { + + private final GasCalculator gasCalculator = new SpuriousDragonGasCalculator(); + private final ShlOperationV2 operation = new ShlOperationV2(gasCalculator); + + static Iterable data() { + return Arrays.asList( + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x00", + "0x0000000000000000000000000000000000000000000000000000000000000001"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000002"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000004"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000004", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000008"), + Arguments.of( + "0x000000000000000000000000000000000000000000000000000000000000000f", + "0x01", + "0x000000000000000000000000000000000000000000000000000000000000001e"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000010"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000001", "0x100", "0x"), + Arguments.of( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x01", + "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000000"), + Arguments.of( + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x01", + "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x80", + "0x0000000000000000000000000000040000000000000000000000000000000000"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", "0x8000", "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x80000000", + "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x8000000000000000", + "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x80000000000000000000000000000000", + "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0x")); + } + + @ParameterizedTest + @MethodSource("data") + void shiftOperation(final String number, final String shift, final String expectedResult) { + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .pushStackItem(Bytes32.fromHexStringLenient(number)) + .pushStackItem(Bytes32.fromHexStringLenient(shift)) + .build(); + operation.execute(frame, null); + UInt256 expected; + if (expectedResult.equals("0x") || expectedResult.equals("0x0")) { + expected = UInt256.ZERO; + } else { + expected = UInt256.fromBytesBE(Bytes32.fromHexStringLenient(expectedResult).toArrayUnsafe()); + } + assertThat(getV2StackItem(frame, 0)).isEqualTo(expected); + } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } + + private static UInt256 getV2StackItem(final MessageFrame frame, final int offset) { + final long[] s = frame.stackDataV2(); + final int idx = (frame.stackTopV2() - 1 - offset) << 2; + return new UInt256(s[idx], s[idx + 1], s[idx + 2], s[idx + 3]); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShrOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShrOperationV2Test.java new file mode 100644 index 00000000000..65949ff7a27 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShrOperationV2Test.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.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.Arrays; + +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 ShrOperationV2Test { + + private final GasCalculator gasCalculator = new SpuriousDragonGasCalculator(); + private final ShrOperationV2 operation = new ShrOperationV2(gasCalculator); + + static Iterable data() { + return Arrays.asList( + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x00", + "0x0000000000000000000000000000000000000000000000000000000000000001"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000000"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000001"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000004", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000002"), + Arguments.of( + "0x000000000000000000000000000000000000000000000000000000000000000f", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000007"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000004"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0xff", + "0x0000000000000000000000000000000000000000000000000000000000000001"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000000", "0x100", "0x"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000000", "0x101", "0x"), + Arguments.of( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x0", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x01", + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0xff", + "0x0000000000000000000000000000000000000000000000000000000000000001"), + Arguments.of( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0x100", "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000000"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x80", + "0x0000000000000000000000000000000000000000000000000000000000000000"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", "0x8000", "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x80000000", + "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x8000000000000000", + "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x80000000000000000000000000000000", + "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0x")); + } + + @ParameterizedTest + @MethodSource("data") + void shiftOperation(final String number, final String shift, final String expectedResult) { + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .pushStackItem(Bytes32.fromHexStringLenient(number)) + .pushStackItem(Bytes32.fromHexStringLenient(shift)) + .build(); + operation.execute(frame, null); + UInt256 expected; + if (expectedResult.equals("0x") || expectedResult.equals("0x0")) { + expected = UInt256.ZERO; + } else { + expected = UInt256.fromBytesBE(Bytes32.fromHexStringLenient(expectedResult).toArrayUnsafe()); + } + assertThat(getV2StackItem(frame, 0)).isEqualTo(expected); + } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } + + private static UInt256 getV2StackItem(final MessageFrame frame, final int offset) { + final long[] s = frame.stackDataV2(); + final int idx = (frame.stackTopV2() - 1 - offset) << 2; + return new UInt256(s[idx], s[idx + 1], s[idx + 2], s[idx + 3]); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/testutils/TestMessageFrameBuilderV2.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/testutils/TestMessageFrameBuilderV2.java new file mode 100644 index 00000000000..ebf852e768a --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/testutils/TestMessageFrameBuilderV2.java @@ -0,0 +1,190 @@ +/* + * 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.testutils; + +import static org.hyperledger.besu.evm.frame.MessageFrame.DEFAULT_MAX_STACK_SIZE; + +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.UInt256; +import org.hyperledger.besu.evm.blockhash.BlockHashLookup; +import org.hyperledger.besu.evm.frame.BlockValues; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.testutils.FakeBlockValues; +import org.hyperledger.besu.evm.toy.ToyWorld; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; + +public class TestMessageFrameBuilderV2 { + + public static final Address DEFAULT_ADDRESS = Address.fromHexString("0xe8f1b89"); + private static final int maxStackSize = DEFAULT_MAX_STACK_SIZE; + + private Optional blockValues = Optional.empty(); + private Optional worldUpdater = Optional.empty(); + private long initialGas = Long.MAX_VALUE; + private Address address = DEFAULT_ADDRESS; + private Address sender = DEFAULT_ADDRESS; + private Address originator = DEFAULT_ADDRESS; + private Address contract = DEFAULT_ADDRESS; + private Wei gasPrice = Wei.ZERO; + private Wei blobGasPrice = Wei.ZERO; + private Wei value = Wei.ZERO; + private Bytes inputData = Bytes.EMPTY; + private Code code = Code.EMPTY_CODE; + private int pc = 0; + private final List stackItems = new ArrayList<>(); + private Optional blockHashLookup = Optional.empty(); + private Bytes memory = Bytes.EMPTY; + private boolean isStatic = false; + + public TestMessageFrameBuilderV2 worldUpdater(final WorldUpdater worldUpdater) { + this.worldUpdater = Optional.of(worldUpdater); + return this; + } + + public TestMessageFrameBuilderV2 initialGas(final long initialGas) { + this.initialGas = initialGas; + return this; + } + + public TestMessageFrameBuilderV2 sender(final Address sender) { + this.sender = sender; + return this; + } + + public TestMessageFrameBuilderV2 address(final Address address) { + this.address = address; + return this; + } + + TestMessageFrameBuilderV2 originator(final Address originator) { + this.originator = originator; + return this; + } + + public TestMessageFrameBuilderV2 contract(final Address contract) { + this.contract = contract; + return this; + } + + public TestMessageFrameBuilderV2 gasPrice(final Wei gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public TestMessageFrameBuilderV2 blobGasPrice(final Wei blobGasPrice) { + this.blobGasPrice = blobGasPrice; + return this; + } + + public TestMessageFrameBuilderV2 value(final Wei value) { + this.value = value; + return this; + } + + public TestMessageFrameBuilderV2 inputData(final Bytes inputData) { + this.inputData = inputData; + return this; + } + + public TestMessageFrameBuilderV2 code(final Code code) { + this.code = code; + return this; + } + + public TestMessageFrameBuilderV2 pc(final int pc) { + this.pc = pc; + return this; + } + + public TestMessageFrameBuilderV2 blockValues(final BlockValues blockValues) { + this.blockValues = Optional.of(blockValues); + return this; + } + + public TestMessageFrameBuilderV2 pushStackItem(final Bytes item) { + stackItems.add(item); + return this; + } + + public TestMessageFrameBuilderV2 blockHashLookup(final BlockHashLookup blockHashLookup) { + this.blockHashLookup = Optional.of(blockHashLookup); + return this; + } + + public TestMessageFrameBuilderV2 memory(final Bytes memory) { + this.memory = memory; + return this; + } + + public TestMessageFrameBuilderV2 isStatic(final boolean isStatic) { + this.isStatic = isStatic; + return this; + } + + public MessageFrame build() { + final MessageFrame frame = + MessageFrame.builder() + .type(MessageFrame.Type.MESSAGE_CALL) + .worldUpdater(worldUpdater.orElseGet(this::createDefaultWorldUpdater)) + .initialGas(initialGas) + .address(address) + .originator(originator) + .gasPrice(gasPrice) + .blobGasPrice(blobGasPrice) + .inputData(inputData) + .sender(sender) + .value(value) + .apparentValue(value) + .contract(contract) + .code(code) + .blockValues(blockValues.orElseGet(() -> new FakeBlockValues(1337))) + .completer(c -> {}) + .miningBeneficiary(Address.ZERO) + .blockHashLookup( + blockHashLookup.orElse((__, number) -> Hash.hash(Words.longBytes(number)))) + .maxStackSize(maxStackSize) + .isStatic(isStatic) + .enableEvmV2(true) + .build(); + frame.setPC(pc); + stackItems.forEach( + item -> { + final UInt256 val = UInt256.fromBytesBE(item.toArrayUnsafe()); + final long[] s = frame.stackDataV2(); + final int dst = frame.stackTopV2() << 2; + s[dst] = val.u3(); + s[dst + 1] = val.u2(); + s[dst + 2] = val.u1(); + s[dst + 3] = val.u0(); + frame.setTopV2(frame.stackTopV2() + 1); + }); + frame.writeMemory(0, memory.size(), memory); + return frame; + } + + private WorldUpdater createDefaultWorldUpdater() { + return new ToyWorld(); + } +} From 9b88b6d27686243268af0a08523a8845ae161cfd Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Thu, 2 Apr 2026 16:50:50 +0200 Subject: [PATCH 05/24] Address review comments. Signed-off-by: Ameziane H. --- .../v2/AbstractSarOperationBenchmarkV2.java | 153 ------------------ .../v2/SarOperationBenchmarkV2.java | 131 ++++++++++++++- .../besu/evm/v2/StackArithmetic.java | 16 ++ 3 files changed, 146 insertions(+), 154 deletions(-) delete mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AbstractSarOperationBenchmarkV2.java diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AbstractSarOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AbstractSarOperationBenchmarkV2.java deleted file mode 100644 index 215e75adb1d..00000000000 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AbstractSarOperationBenchmarkV2.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * 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 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 SAR (Shift Arithmetic Right) operation benchmarks. - * - *

SAR has additional test cases for negative/positive values to test sign extension behavior. - */ -public abstract class AbstractSarOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { - - /** Test cases covering different execution paths for SAR operations. */ - public enum Case { - /** Shift by 0 - early return path. */ - SHIFT_0, - /** Negative number (ALL_BITS) with shift=1 - tests sign extension OR path. */ - NEGATIVE_SHIFT_1, - /** value with all bits to 1 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 (original behavior). */ - 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]; // 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.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; - } -} 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 index 626b1912e62..e617026a236 100644 --- 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 @@ -14,11 +14,140 @@ */ 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; -public class SarOperationBenchmarkV2 extends AbstractSarOperationBenchmarkV2 { +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) { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java index 514307e09e2..7c7817d9d0e 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java @@ -24,6 +24,9 @@ */ public class StackArithmetic { + // region SHL (Shift Left) + // --------------------------------------------------------------------------- + /** * Performs EVM SHL (shift left) on the two top stack items. * @@ -118,6 +121,11 @@ private static long shiftLeftWord(final long value, final long nextValue, final return (value << bitShift) | (nextValue >>> (64 - bitShift)); } + // endregion + + // region SHR (Shift Right) + // --------------------------------------------------------------------------- + /** * Performs EVM SHR (logical shift right) on the two top stack items. * @@ -197,6 +205,11 @@ private static void shiftRightInPlace(final long[] s, final int valueOffset, fin s[valueOffset + 3] = w3; } + // endregion + + // region SAR (Shift Arithmetic Right) + // --------------------------------------------------------------------------- + /** * Performs EVM SAR (arithmetic shift right) on the two top stack items. * @@ -293,4 +306,7 @@ private static long shiftRightWord(final long value, final long prevValue, final if (bitShift == 0) return value; return (value >>> bitShift) | (prevValue << (64 - bitShift)); } + + // endregion + } From 189a3c84c9b6384942d205919318b403bad82b68 Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Thu, 2 Apr 2026 17:28:12 +0200 Subject: [PATCH 06/24] Add property based testing for v2 shift operations Signed-off-by: Ameziane H. --- .../besu/evm/v2/StackArithmetic.java | 9 +- .../ShiftOperationsV2PropertyBasedTest.java | 592 ++++++++++++++++++ 2 files changed, 600 insertions(+), 1 deletion(-) create mode 100644 evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShiftOperationsV2PropertyBasedTest.java diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java index 7c7817d9d0e..a9685d4ffd6 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java @@ -24,6 +24,9 @@ */ public class StackArithmetic { + /** Utility class — not instantiable. */ + private StackArithmetic() {} + // region SHL (Shift Left) // --------------------------------------------------------------------------- @@ -291,6 +294,11 @@ private static void sarInPlace( stack[valueOffset + 3] = w3; } + // endregion + + // region Private Helpers + // --------------------------------------------------------------------------- + /** * Shifts a 64-bit word right and carries in bits from the previous more-significant word. * @@ -308,5 +316,4 @@ private static long shiftRightWord(final long value, final long prevValue, final } // endregion - } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShiftOperationsV2PropertyBasedTest.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShiftOperationsV2PropertyBasedTest.java new file mode 100644 index 00000000000..5f5655130e0 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShiftOperationsV2PropertyBasedTest.java @@ -0,0 +1,592 @@ +/* + * 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 static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +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.UInt256; +import org.hyperledger.besu.evm.frame.BlockValues; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.operation.SarOperation; +import org.hyperledger.besu.evm.operation.ShlOperation; +import org.hyperledger.besu.evm.operation.ShrOperation; +import org.hyperledger.besu.evm.v2.StackArithmetic; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.ArrayDeque; +import java.util.Deque; + +import net.jqwik.api.Arbitraries; +import net.jqwik.api.Arbitrary; +import net.jqwik.api.ForAll; +import net.jqwik.api.Property; +import net.jqwik.api.Provide; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +/** + * Property-based tests comparing v2 StackArithmetic shift operations against the original + * non-optimized Bytes-based implementations. + */ +public class ShiftOperationsV2PropertyBasedTest { + + // region Arbitrary Providers + + @Provide + Arbitrary values1to32() { + return Arbitraries.bytes().array(byte[].class).ofMinSize(1).ofMaxSize(32); + } + + @Provide + Arbitrary shiftAmounts() { + return Arbitraries.bytes().array(byte[].class).ofMinSize(0).ofMaxSize(32); + } + + @Provide + Arbitrary smallShifts() { + return Arbitraries.integers().between(0, 255); + } + + @Provide + Arbitrary overflowShifts() { + return Arbitraries.integers().between(256, 1024); + } + + @Provide + Arbitrary negativeValues() { + return Arbitraries.bytes() + .array(byte[].class) + .ofSize(32) + .map( + bytes -> { + bytes[0] = (byte) (bytes[0] | 0x80); + return bytes; + }); + } + + @Provide + Arbitrary positiveValues() { + return Arbitraries.bytes() + .array(byte[].class) + .ofMinSize(1) + .ofMaxSize(32) + .map( + bytes -> { + bytes[0] = (byte) (bytes[0] & 0x7F); + return bytes; + }); + } + + // endregion + + // region SHL Property Tests + + @Property(tries = 10000) + void property_shlV2_matchesOriginal_randomInputs( + @ForAll("values1to32") final byte[] valueBytes, + @ForAll("shiftAmounts") final byte[] shiftBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shift = Bytes.wrap(shiftBytes); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalShl(shift, value)); + final Bytes32 v2Result = runV2Shl(shift, value); + + assertThat(v2Result) + .as( + "SHL v2 mismatch for shift=%s, value=%s", + shift.toHexString(), Bytes32.leftPad(value).toHexString()) + .isEqualTo(originalResult); + } + + @Property(tries = 5000) + void property_shlV2_matchesOriginal_smallShifts( + @ForAll("values1to32") final byte[] valueBytes, @ForAll("smallShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = Bytes.of(shift); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalShl(shiftBytes, value)); + final Bytes32 v2Result = runV2Shl(shiftBytes, value); + + assertThat(v2Result).as("SHL v2 mismatch for shift=%d", shift).isEqualTo(originalResult); + } + + @Property(tries = 1000) + void property_shlV2_matchesOriginal_overflowShifts( + @ForAll("values1to32") final byte[] valueBytes, @ForAll("overflowShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = intToMinimalBytes(shift); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalShl(shiftBytes, value)); + final Bytes32 v2Result = runV2Shl(shiftBytes, value); + + assertThat(v2Result) + .as("SHL v2 overflow mismatch for shift=%d", shift) + .isEqualTo(originalResult); + } + + // endregion + + // region SHR Property Tests + + @Property(tries = 10000) + void property_shrV2_matchesOriginal_randomInputs( + @ForAll("values1to32") final byte[] valueBytes, + @ForAll("shiftAmounts") final byte[] shiftBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shift = Bytes.wrap(shiftBytes); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalShr(shift, value)); + final Bytes32 v2Result = runV2Shr(shift, value); + + assertThat(v2Result) + .as( + "SHR v2 mismatch for shift=%s, value=%s", + shift.toHexString(), Bytes32.leftPad(value).toHexString()) + .isEqualTo(originalResult); + } + + @Property(tries = 5000) + void property_shrV2_matchesOriginal_smallShifts( + @ForAll("values1to32") final byte[] valueBytes, @ForAll("smallShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = Bytes.of(shift); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalShr(shiftBytes, value)); + final Bytes32 v2Result = runV2Shr(shiftBytes, value); + + assertThat(v2Result).as("SHR v2 mismatch for shift=%d", shift).isEqualTo(originalResult); + } + + @Property(tries = 1000) + void property_shrV2_matchesOriginal_overflowShifts( + @ForAll("values1to32") final byte[] valueBytes, @ForAll("overflowShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = intToMinimalBytes(shift); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalShr(shiftBytes, value)); + final Bytes32 v2Result = runV2Shr(shiftBytes, value); + + assertThat(v2Result) + .as("SHR v2 overflow mismatch for shift=%d", shift) + .isEqualTo(originalResult); + } + + // endregion + + // region SAR Property Tests - Random Inputs + + @Property(tries = 10000) + void property_sarV2_matchesOriginal_randomInputs( + @ForAll("values1to32") final byte[] valueBytes, + @ForAll("shiftAmounts") final byte[] shiftBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shift = Bytes.wrap(shiftBytes); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalSar(shift, value)); + final Bytes32 v2Result = runV2Sar(shift, value); + + assertThat(v2Result) + .as( + "SAR v2 mismatch for shift=%s, value=%s", + shift.toHexString(), Bytes32.leftPad(value).toHexString()) + .isEqualTo(originalResult); + } + + @Property(tries = 5000) + void property_sarV2_matchesOriginal_smallShifts( + @ForAll("values1to32") final byte[] valueBytes, @ForAll("smallShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = Bytes.of(shift); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalSar(shiftBytes, value)); + final Bytes32 v2Result = runV2Sar(shiftBytes, value); + + assertThat(v2Result).as("SAR v2 mismatch for shift=%d", shift).isEqualTo(originalResult); + } + + @Property(tries = 1000) + void property_sarV2_matchesOriginal_overflowShifts( + @ForAll("values1to32") final byte[] valueBytes, @ForAll("overflowShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = intToMinimalBytes(shift); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalSar(shiftBytes, value)); + final Bytes32 v2Result = runV2Sar(shiftBytes, value); + + assertThat(v2Result) + .as("SAR v2 overflow mismatch for shift=%d", shift) + .isEqualTo(originalResult); + } + + // endregion + + // region SAR Property Tests - Negative Values (Sign Extension) + + @Property(tries = 5000) + void property_sarV2_matchesOriginal_negativeValues_smallShifts( + @ForAll("negativeValues") final byte[] valueBytes, @ForAll("smallShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = Bytes.of(shift); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalSar(shiftBytes, value)); + final Bytes32 v2Result = runV2Sar(shiftBytes, value); + + assertThat(v2Result) + .as("SAR v2 negative mismatch for shift=%d, value=%s", shift, value.toHexString()) + .isEqualTo(originalResult); + } + + @Property(tries = 1000) + void property_sarV2_matchesOriginal_negativeValues_overflowShifts( + @ForAll("negativeValues") final byte[] valueBytes, + @ForAll("overflowShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = intToMinimalBytes(shift); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalSar(shiftBytes, value)); + final Bytes32 v2Result = runV2Sar(shiftBytes, value); + + assertThat(v2Result) + .as("SAR v2 negative overflow mismatch for shift=%d", shift) + .isEqualTo(originalResult); + } + + // endregion + + // region SAR Property Tests - Negative/Positive Values at Shift 255 + + @Property(tries = 3000) + void property_sarV2_matchesOriginal_negativeValues_shift255( + @ForAll("negativeValues") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shift = Bytes.of(255); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalSar(shift, value)); + final Bytes32 v2Result = runV2Sar(shift, value); + + assertThat(v2Result) + .as("SAR v2 negative shift=255 mismatch for value=%s", value.toHexString()) + .isEqualTo(originalResult); + } + + @Property(tries = 3000) + void property_sarV2_matchesOriginal_positiveValues_shift255( + @ForAll("positiveValues") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shift = Bytes.of(255); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalSar(shift, value)); + final Bytes32 v2Result = runV2Sar(shift, value); + + assertThat(v2Result) + .as("SAR v2 positive shift=255 mismatch for value=%s", value.toHexString()) + .isEqualTo(originalResult); + } + + // endregion + + // region SAR Property Tests - Positive Values + + @Property(tries = 5000) + void property_sarV2_matchesOriginal_positiveValues_smallShifts( + @ForAll("positiveValues") final byte[] valueBytes, @ForAll("smallShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = Bytes.of(shift); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalSar(shiftBytes, value)); + final Bytes32 v2Result = runV2Sar(shiftBytes, value); + + assertThat(v2Result) + .as("SAR v2 positive mismatch for shift=%d, value=%s", shift, value.toHexString()) + .isEqualTo(originalResult); + } + + @Property(tries = 1000) + void property_sarV2_matchesOriginal_positiveValues_overflowShifts( + @ForAll("positiveValues") final byte[] valueBytes, + @ForAll("overflowShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = intToMinimalBytes(shift); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalSar(shiftBytes, value)); + final Bytes32 v2Result = runV2Sar(shiftBytes, value); + + assertThat(v2Result) + .as("SAR v2 positive overflow mismatch for shift=%d", shift) + .isEqualTo(originalResult); + } + + // endregion + + // region Edge Case Tests - SHL / SHR + + @Property(tries = 1000) + void property_shlV2_shiftByZero_returnsOriginalValue( + @ForAll("values1to32") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shift = Bytes.of(0); + + final Bytes32 v2Result = runV2Shl(shift, value); + + assertThat(v2Result).isEqualTo(Bytes32.leftPad(value)); + } + + @Property(tries = 1000) + void property_shrV2_shiftByZero_returnsOriginalValue( + @ForAll("values1to32") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shift = Bytes.of(0); + + final Bytes32 v2Result = runV2Shr(shift, value); + + assertThat(v2Result).isEqualTo(Bytes32.leftPad(value)); + } + + @Property(tries = 500) + void property_shlV2_largeShift_returnsZero(@ForAll("values1to32") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes largeShift = Bytes.fromHexString("0x010000000000"); + + final Bytes32 v2Result = runV2Shl(largeShift, value); + + assertThat(v2Result).isEqualTo(Bytes32.ZERO); + } + + @Property(tries = 500) + void property_shrV2_largeShift_returnsZero(@ForAll("values1to32") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes largeShift = Bytes.fromHexString("0x010000000000"); + + final Bytes32 v2Result = runV2Shr(largeShift, value); + + assertThat(v2Result).isEqualTo(Bytes32.ZERO); + } + + // endregion + + // region Edge Case Tests - SAR + + @Property(tries = 1000) + void property_sarV2_shiftByZero_returnsOriginalValue( + @ForAll("values1to32") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shift = Bytes.of(0); + + final Bytes32 v2Result = runV2Sar(shift, value); + + assertThat(v2Result).isEqualTo(Bytes32.leftPad(value)); + } + + @Property(tries = 500) + void property_sarV2_negativeValue_largeShift_returnsAllOnes( + @ForAll("negativeValues") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes largeShift = Bytes.fromHexString("0x010000000000"); + + final Bytes32 v2Result = runV2Sar(largeShift, value); + + final Bytes32 allOnes = + Bytes32.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + assertThat(v2Result).isEqualTo(allOnes); + } + + @Property(tries = 500) + void property_sarV2_positiveValue_largeShift_returnsZero( + @ForAll("positiveValues") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes largeShift = Bytes.fromHexString("0x010000000000"); + + final Bytes32 v2Result = runV2Sar(largeShift, value); + + assertThat(v2Result).isEqualTo(Bytes32.ZERO); + } + + @Property(tries = 500) + void property_sarV2_allOnes_anyShift_returnsAllOnes(@ForAll("smallShifts") final int shift) { + + final Bytes value = + Bytes.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + final Bytes shiftBytes = Bytes.of(shift); + + final Bytes32 v2Result = runV2Sar(shiftBytes, value); + + assertThat(v2Result).isEqualTo(Bytes32.leftPad(value)); + } + + @Property(tries = 500) + void property_sarV2_minValue_shift255_returnsAllOnes() { + + final Bytes value = + Bytes.fromHexString("0x8000000000000000000000000000000000000000000000000000000000000000"); + final Bytes shift = Bytes.of(255); + + final Bytes32 v2Result = runV2Sar(shift, value); + + final Bytes32 allOnes = + Bytes32.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + assertThat(v2Result).isEqualTo(allOnes); + } + + @Property(tries = 500) + void property_sarV2_maxPositive_shift255_returnsZero() { + + final Bytes value = + Bytes.fromHexString("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + final Bytes shift = Bytes.of(255); + + final Bytes32 v2Result = runV2Sar(shift, value); + + assertThat(v2Result).isEqualTo(Bytes32.ZERO); + } + + // endregion + + // region V1 Oracle Helpers (mock-based, Bytes stack) + + private Bytes runOriginalShl(final Bytes shift, final Bytes value) { + return runOriginalOperation(shift, value, ShlOperation::staticOperation); + } + + private Bytes runOriginalShr(final Bytes shift, final Bytes value) { + return runOriginalOperation(shift, value, ShrOperation::staticOperation); + } + + private Bytes runOriginalSar(final Bytes shift, final Bytes value) { + return runOriginalOperation(shift, value, SarOperation::staticOperation); + } + + @FunctionalInterface + interface OriginalOperationExecutor { + Operation.OperationResult execute(MessageFrame frame); + } + + private Bytes runOriginalOperation( + final Bytes shift, final Bytes value, final OriginalOperationExecutor executor) { + final MessageFrame frame = mock(MessageFrame.class); + final Deque stack = new ArrayDeque<>(); + stack.push(value); + stack.push(shift); + + when(frame.popStackItem()).thenAnswer(invocation -> stack.pop()); + + final Bytes[] result = new Bytes[1]; + doAnswer( + invocation -> { + result[0] = invocation.getArgument(0); + return null; + }) + .when(frame) + .pushStackItem(any(Bytes.class)); + + executor.execute(frame); + return result[0]; + } + + // endregion + + // region V2 Helpers (long[] stack) + + private Bytes32 runV2Shl(final Bytes shift, final Bytes value) { + return runV2Operation(shift, value, StackArithmetic::shl); + } + + private Bytes32 runV2Shr(final Bytes shift, final Bytes value) { + return runV2Operation(shift, value, StackArithmetic::shr); + } + + private Bytes32 runV2Sar(final Bytes shift, final Bytes value) { + return runV2Operation(shift, value, StackArithmetic::sar); + } + + @FunctionalInterface + interface V2OperationExecutor { + int execute(long[] stack, int top); + } + + private Bytes32 runV2Operation( + final Bytes shift, final Bytes value, final V2OperationExecutor executor) { + final UInt256 shiftVal = UInt256.fromBytesBE(Bytes32.leftPad(shift).toArrayUnsafe()); + final UInt256 valueVal = UInt256.fromBytesBE(Bytes32.leftPad(value).toArrayUnsafe()); + + final long[] s = new long[8]; + writeLimbs(s, 0, valueVal); + writeLimbs(s, 4, shiftVal); + + executor.execute(s, 2); + + final UInt256 result = new UInt256(s[0], s[1], s[2], s[3]); + return Bytes32.wrap(result.toBytesBE()); + } + + private static void writeLimbs(final long[] s, final int offset, final UInt256 val) { + s[offset] = val.u3(); + s[offset + 1] = val.u2(); + s[offset + 2] = val.u1(); + s[offset + 3] = val.u0(); + } + + // endregion + + // region Utility + + private Bytes intToMinimalBytes(final int value) { + if (value == 0) { + return Bytes.EMPTY; + } + if (value <= 0xFF) { + return Bytes.of(value); + } + if (value <= 0xFFFF) { + return Bytes.of(value >> 8, value & 0xFF); + } + if (value <= 0xFFFFFF) { + return Bytes.of(value >> 16, (value >> 8) & 0xFF, value & 0xFF); + } + return Bytes.of(value >> 24, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF); + } + + // endregion +} From 9ccfea9f8039af1bfa6db357af026ceb1534df7c Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Thu, 2 Apr 2026 18:39:07 +0200 Subject: [PATCH 07/24] spotless Signed-off-by: Ameziane H. --- .../v2/operation/ShiftOperationsV2PropertyBasedTest.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShiftOperationsV2PropertyBasedTest.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShiftOperationsV2PropertyBasedTest.java index 5f5655130e0..e9d43c145ed 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShiftOperationsV2PropertyBasedTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShiftOperationsV2PropertyBasedTest.java @@ -20,19 +20,13 @@ 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.UInt256; -import org.hyperledger.besu.evm.frame.BlockValues; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.operation.SarOperation; import org.hyperledger.besu.evm.operation.ShlOperation; import org.hyperledger.besu.evm.operation.ShrOperation; import org.hyperledger.besu.evm.v2.StackArithmetic; -import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.ArrayDeque; import java.util.Deque; From 0b688ffd320d28a1546fac5e37414e50642e85be Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Thu, 2 Apr 2026 20:08:14 +0200 Subject: [PATCH 08/24] Fix javadoc Signed-off-by: Ameziane H. --- .../hyperledger/besu/evm/v2/operation/SarOperationV2.java | 1 + .../hyperledger/besu/evm/v2/operation/ShlOperationV2.java | 5 +++-- .../hyperledger/besu/evm/v2/operation/ShrOperationV2.java | 5 +++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2.java index 4a3dafc1b1f..d488886c5df 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2.java @@ -45,6 +45,7 @@ public Operation.OperationResult executeFixedCostOperation( * Performs sar 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) { 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 index 8aa84cab477..2f765e94269 100644 --- 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 @@ -45,11 +45,12 @@ public Operation.OperationResult executeFixedCostOperation( * 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[] s) { + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; - frame.setTopV2(StackArithmetic.shl(s, frame.stackTopV2())); + 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 index feebb57e702..71c412a04ab 100644 --- 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 @@ -45,11 +45,12 @@ public Operation.OperationResult executeFixedCostOperation( * 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[] s) { + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; - frame.setTopV2(StackArithmetic.shr(s, frame.stackTopV2())); + frame.setTopV2(StackArithmetic.shr(stack, frame.stackTopV2())); return shrSuccess; } } From be07eab38a12a0671a200e9b3961cac11554b348 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Thu, 2 Apr 2026 22:09:01 +1000 Subject: [PATCH 09/24] Add MulModOperationV2 and Benchmarks Signed-off-by: Simon Dudley # Conflicts: # evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java --- .../v2/MulModOperationBenchmarkV2.java | 168 ++++++++++++++++++ .../v2/TernaryOperationBenchmarkV2.java | 75 ++++++++ .../java/org/hyperledger/besu/evm/EVM.java | 2 + .../besu/evm/v2/StackArithmetic.java | 24 ++- .../evm/v2/operation/MulModOperationV2.java | 54 ++++++ 5 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulModOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/TernaryOperationBenchmarkV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2.java 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..8c13118a536 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulModOperationBenchmarkV2.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.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_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_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/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/evm/src/main/java/org/hyperledger/besu/evm/EVM.java b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java index 4384c5e3320..a1657823bc7 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java @@ -88,6 +88,7 @@ import org.hyperledger.besu.evm.operation.XorOperationOptimized; import org.hyperledger.besu.evm.tracing.OperationTracer; import org.hyperledger.besu.evm.v2.operation.AddOperationV2; +import org.hyperledger.besu.evm.v2.operation.MulModOperationV2; import org.hyperledger.besu.evm.v2.operation.SarOperationV2; import org.hyperledger.besu.evm.v2.operation.ShlOperationV2; import org.hyperledger.besu.evm.v2.operation.ShrOperationV2; @@ -488,6 +489,7 @@ private void runToHaltV2(final MessageFrame frame, final OperationTracer tracing result = switch (opcode) { case 0x01 -> AddOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x09 -> MulModOperationV2.staticOperation(frame, frame.stackDataV2()); case 0x1b -> enableConstantinople ? ShlOperationV2.staticOperation(frame, frame.stackDataV2()) diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java index a9685d4ffd6..6d09453320f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.evm.v2; +import org.hyperledger.besu.evm.UInt256; + /** * Static utility operating directly on the flat {@code long[]} operand stack. Each slot occupies 4 * consecutive longs in big-endian limb order: {@code [u3, u2, u1, u0]} where u3 is the most @@ -315,5 +317,25 @@ private static long shiftRightWord(final long value, final long prevValue, final return (value >>> bitShift) | (prevValue << (64 - bitShift)); } - // endregion + // region Arithmetic Operations + // -------------------------------------------------------------------------- + + /** MULMOD: s[top-3] = (s[top-1] * s[top-2]) mod s[top-3], return top-2. */ + 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; + } + + // -------------------------------------------------------------------------- + // end region } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2.java new file mode 100644 index 00000000000..0ac7602f524 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2.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; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The Mul mod operation. */ +public class MulModOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult mulModSuccess = new OperationResult(8, null); + + /** + * Instantiates a new Mul mod operation. + * + * @param gasCalculator the gas calculator + */ + public MulModOperationV2(final GasCalculator gasCalculator) { + super(0x09, "MULMOD", 3, 1, gasCalculator, gasCalculator.getMidTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs MulMod operation. + * + * @param frame the frame + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(3)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.mulMod(s, frame.stackTopV2())); + return mulModSuccess; + } +} From 28b94fff6622e71ee5a8e87f2eba9376b0d56da1 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Fri, 3 Apr 2026 08:16:05 +1000 Subject: [PATCH 10/24] Extra benchmarks Signed-off-by: Simon Dudley --- .../ethereum/vm/operations/v2/MulModOperationBenchmarkV2.java | 4 ++++ 1 file changed, 4 insertions(+) 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 index 8c13118a536..d635f404a62 100644 --- 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 @@ -54,6 +54,8 @@ public enum Case { 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), @@ -106,6 +108,8 @@ public enum Case { "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", From 2553d6ff1dc77f41c79f5bea872c4ee06f35494d Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Sat, 4 Apr 2026 09:27:11 +1000 Subject: [PATCH 11/24] Phase 1: Expand StackArithmetic with full StackMath method set and V2 infrastructure - Expand StackArithmetic.java with all arithmetic, bitwise, comparison, unary, ternary, stack manipulation and boundary-helper methods from StackMath - Add VarHandle fields (LONG_BE, INT_BE) for zero-allocation byte array access - Add stackHasSpace(int n) to MessageFrame for overflow checking in push/dup ops - Add static OVERFLOW_RESPONSE to AbstractFixedCostOperationV2 - Add UnaryOperationBenchmarkV2 and ImmediateByteOperationBenchmarkV2 base classes for benchmark suites covering unary ops and DUPN/SWAPN/EXCHANGE respectively Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Simon Dudley --- .../v2/ImmediateByteOperationBenchmarkV2.java | 115 +++ .../v2/UnaryOperationBenchmarkV2.java | 67 ++ .../besu/evm/frame/MessageFrame.java | 10 + .../besu/evm/v2/StackArithmetic.java | 819 +++++++++++++++++- .../AbstractFixedCostOperationV2.java | 4 + 5 files changed, 996 insertions(+), 19 deletions(-) create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ImmediateByteOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/UnaryOperationBenchmarkV2.java 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/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/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java index a90da960e38..773fce3eca5 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java @@ -518,6 +518,16 @@ public boolean stackHasItems(final int n) { return stackTopV2 >= n; } + /** + * Returns true if the stack has space for at least {@code n} more items. + * + * @param n the number of items to push + * @return true if the stack has enough capacity + */ + public boolean stackHasSpace(final int n) { + return stackTopV2 + n <= txValues.maxStackSize(); + } + // --------------------------------------------------------------------------- // endregion diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java index 6d09453320f..83f2d4f4683 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java @@ -14,8 +14,16 @@ */ package org.hyperledger.besu.evm.v2; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.UInt256; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.math.BigInteger; +import java.nio.ByteOrder; +import java.util.Arrays; + /** * Static utility operating directly on the flat {@code long[]} operand stack. Each slot occupies 4 * consecutive longs in big-endian limb order: {@code [u3, u2, u1, u0]} where u3 is the most @@ -26,6 +34,11 @@ */ 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() {} @@ -298,27 +311,400 @@ private static void sarInPlace( // endregion - // region Private Helpers + // region Binary Arithmetic (pop 2, push 1, return top-1) // --------------------------------------------------------------------------- - /** - * 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)); + /** ADD: s[top-2] = s[top-1] + s[top-2], return top-1. */ + 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. */ + 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. */ + 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. */ + 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. */ + 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. */ + 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. */ + 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. */ + 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. */ + 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. */ + 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. */ + 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. */ + 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. */ + 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. */ + 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. */ + 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. */ + 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. */ + 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. */ + 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. */ + 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; } - // region Arithmetic Operations - // -------------------------------------------------------------------------- + /** EQ: s[top-2] = (s[top-1] == s[top-2]) ? 1 : 0, return top-1. */ + 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. */ + 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. */ public static int mulMod(final long[] s, final int top) { @@ -336,6 +722,401 @@ public static int mulMod(final long[] s, final int top) { return top - 2; } - // -------------------------------------------------------------------------- - // end region + // endregion + + // region Stack Manipulation + // --------------------------------------------------------------------------- + + /** DUP: copy slot at depth → new top, return top+1. depth is 1-based (DUP1 = depth 1). */ + 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 ↔ slot at depth, return top. depth is 1-based (SWAP1 = depth 1). */ + 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 ↔ slot at m (both 0-indexed from top), return top. */ + 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. */ + 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. */ + 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. */ + 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. */ + 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. */ + 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. */ + 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. */ + 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. */ + 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. */ + 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. + */ + 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. + */ + 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. */ + 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. */ + 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. */ + 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). */ + 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. */ + 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. */ + 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. */ + 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. */ + 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/operation/AbstractFixedCostOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java index 0480e77b067..b00cb8657d8 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java @@ -29,6 +29,10 @@ abstract class AbstractFixedCostOperationV2 extends AbstractOperation { static final OperationResult UNDERFLOW_RESPONSE = new OperationResult(0L, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + /** Shared overflow response for static operation methods. */ + static final OperationResult OVERFLOW_RESPONSE = + new OperationResult(0L, ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); + /** The Success response. */ protected final OperationResult successResponse; From 1c1c1c401386c3a48f687abc44711798189c46c9 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Sat, 4 Apr 2026 09:54:13 +1000 Subject: [PATCH 12/24] Migrate all EVM operations to V2 flat long[] stack architecture Adds ~90 V2 operation implementations across all EVM opcode categories (arithmetic, bitwise, comparison, stack manipulation, environment push, memory, storage, data copy/hash/account, control flow, calls, creates) plus 25+ JMH benchmarks and two new benchmark base classes (UnaryOperationBenchmarkV2, ImmediateByteOperationBenchmarkV2). All operations follow the staticOperation(MessageFrame, long[]) pattern and are wired into the runToHaltV2() switch in EVM.java with appropriate fork guards (Constantinople, London, Shanghai, Cancun, Amsterdam, Osaka). Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Simon Dudley --- .../v2/AddModOperationBenchmarkV2.java | 172 ++++++++ .../v2/BlockHashOperationBenchmarkV2.java | 128 ++++++ .../v2/CallDataCopyBenchmarkV2.java | 144 +++++++ .../v2/ClzOperationBenchmarkV2.java | 27 ++ .../v2/DivOperationBenchmarkV2.java | 27 ++ .../v2/DupNOperationBenchmarkV2.java | 47 +++ .../operations/v2/EqOperationBenchmarkV2.java | 27 ++ .../v2/ExchangeOperationBenchmarkV2.java | 47 +++ .../v2/ExpOperationBenchmarkV2.java | 42 ++ .../operations/v2/GtOperationBenchmarkV2.java | 27 ++ .../v2/Keccak256OperationBenchmarkV2.java | 103 +++++ .../operations/v2/LtOperationBenchmarkV2.java | 27 ++ .../v2/MemoryOperationBenchmarkV2.java | 105 +++++ .../v2/ModOperationBenchmarkV2.java | 130 ++++++ .../v2/MulOperationBenchmarkV2.java | 27 ++ .../v2/NotOperationBenchmarkV2.java | 27 ++ .../v2/OperandStackBenchmarkV2.java | 80 ++++ .../v2/SDivOperationBenchmarkV2.java | 27 ++ .../v2/SModOperationBenchmarkV2.java | 130 ++++++ .../v2/SelfBalanceOperationBenchmarkV2.java | 95 +++++ .../v2/SgtOperationBenchmarkV2.java | 27 ++ .../v2/SltOperationBenchmarkV2.java | 27 ++ .../v2/SubOperationBenchmarkV2.java | 27 ++ .../v2/SwapNOperationBenchmarkV2.java | 47 +++ .../TransientStorageOperationBenchmarkV2.java | 112 +++++ .../java/org/hyperledger/besu/evm/EVM.java | 340 ++++++++++++++- .../v2/operation/AbstractCallOperationV2.java | 393 ++++++++++++++++++ .../operation/AbstractCreateOperationV2.java | 270 ++++++++++++ .../evm/v2/operation/AddModOperationV2.java | 55 +++ .../besu/evm/v2/operation/AddOperationV2.java | 28 +- .../evm/v2/operation/AddressOperationV2.java | 56 +++ .../evm/v2/operation/BalanceOperationV2.java | 96 +++++ .../evm/v2/operation/BaseFeeOperationV2.java | 65 +++ .../v2/operation/BlobBaseFeeOperationV2.java | 55 +++ .../evm/v2/operation/BlobHashOperationV2.java | 84 ++++ .../v2/operation/BlockHashOperationV2.java | 91 ++++ .../evm/v2/operation/CallCodeOperationV2.java | 104 +++++ .../v2/operation/CallDataCopyOperationV2.java | 78 ++++ .../v2/operation/CallDataLoadOperationV2.java | 89 ++++ .../v2/operation/CallDataSizeOperationV2.java | 56 +++ .../evm/v2/operation/CallOperationV2.java | 110 +++++ .../v2/operation/CallValueOperationV2.java | 55 +++ .../evm/v2/operation/CallerOperationV2.java | 56 +++ .../evm/v2/operation/ChainIdOperationV2.java | 76 ++++ .../besu/evm/v2/operation/ClzOperationV2.java | 56 +++ .../evm/v2/operation/CodeCopyOperationV2.java | 74 ++++ .../evm/v2/operation/CodeSizeOperationV2.java | 55 +++ .../evm/v2/operation/CoinbaseOperationV2.java | 56 +++ .../evm/v2/operation/Create2OperationV2.java | 103 +++++ .../evm/v2/operation/CreateOperationV2.java | 85 ++++ .../v2/operation/DelegateCallOperationV2.java | 112 +++++ .../besu/evm/v2/operation/DivOperationV2.java | 56 +++ .../evm/v2/operation/DupNOperationV2.java | 92 ++++ .../besu/evm/v2/operation/DupOperationV2.java | 76 ++++ .../besu/evm/v2/operation/EqOperationV2.java | 56 +++ .../evm/v2/operation/ExchangeOperationV2.java | 89 ++++ .../besu/evm/v2/operation/ExpOperationV2.java | 62 +++ .../v2/operation/ExtCodeCopyOperationV2.java | 113 +++++ .../v2/operation/ExtCodeHashOperationV2.java | 98 +++++ .../v2/operation/ExtCodeSizeOperationV2.java | 92 ++++ .../evm/v2/operation/GasLimitOperationV2.java | 56 +++ .../besu/evm/v2/operation/GasOperationV2.java | 48 +++ .../evm/v2/operation/GasPriceOperationV2.java | 55 +++ .../besu/evm/v2/operation/GtOperationV2.java | 56 +++ .../evm/v2/operation/InvalidOperationV2.java | 46 ++ .../evm/v2/operation/IsZeroOperationV2.java | 56 +++ .../evm/v2/operation/JumpDestOperationV2.java | 54 +++ .../evm/v2/operation/JumpOperationV2.java | 81 ++++ .../evm/v2/operation/JumpiOperationV2.java | 67 +++ .../v2/operation/Keccak256OperationV2.java | 81 ++++ .../besu/evm/v2/operation/LogOperationV2.java | 99 +++++ .../besu/evm/v2/operation/LtOperationV2.java | 56 +++ .../evm/v2/operation/MCopyOperationV2.java | 69 +++ .../evm/v2/operation/MSizeOperationV2.java | 55 +++ .../evm/v2/operation/MloadOperationV2.java | 67 +++ .../besu/evm/v2/operation/ModOperationV2.java | 56 +++ .../evm/v2/operation/Mstore8OperationV2.java | 68 +++ .../evm/v2/operation/MstoreOperationV2.java | 71 ++++ .../besu/evm/v2/operation/MulOperationV2.java | 56 +++ .../besu/evm/v2/operation/NotOperationV2.java | 56 +++ .../evm/v2/operation/NumberOperationV2.java | 56 +++ .../evm/v2/operation/OriginOperationV2.java | 56 +++ .../besu/evm/v2/operation/PayOperationV2.java | 134 ++++++ .../besu/evm/v2/operation/PcOperationV2.java | 55 +++ .../besu/evm/v2/operation/PopOperationV2.java | 57 +++ .../v2/operation/PrevRandaoOperationV2.java | 59 +++ .../evm/v2/operation/Push0OperationV2.java | 60 +++ .../evm/v2/operation/PushOperationV2.java | 83 ++++ .../operation/ReturnDataCopyOperationV2.java | 95 +++++ .../operation/ReturnDataSizeOperationV2.java | 56 +++ .../evm/v2/operation/ReturnOperationV2.java | 71 ++++ .../evm/v2/operation/RevertOperationV2.java | 75 ++++ .../evm/v2/operation/SDivOperationV2.java | 56 +++ .../evm/v2/operation/SLoadOperationV2.java | 124 ++++++ .../evm/v2/operation/SModOperationV2.java | 56 +++ .../evm/v2/operation/SStoreOperationV2.java | 158 +++++++ .../v2/operation/SelfBalanceOperationV2.java | 65 +++ .../v2/operation/SelfDestructOperationV2.java | 157 +++++++ .../besu/evm/v2/operation/SgtOperationV2.java | 56 +++ .../evm/v2/operation/SlotNumOperationV2.java | 57 +++ .../besu/evm/v2/operation/SltOperationV2.java | 56 +++ .../v2/operation/StaticCallOperationV2.java | 112 +++++ .../evm/v2/operation/StopOperationV2.java | 55 +++ .../besu/evm/v2/operation/SubOperationV2.java | 56 +++ .../evm/v2/operation/SwapNOperationV2.java | 86 ++++ .../evm/v2/operation/SwapOperationV2.java | 75 ++++ .../evm/v2/operation/TLoadOperationV2.java | 82 ++++ .../evm/v2/operation/TStoreOperationV2.java | 91 ++++ .../v2/operation/TimestampOperationV2.java | 56 +++ 109 files changed, 8601 insertions(+), 18 deletions(-) create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AddModOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BlockHashOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/CallDataCopyBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ClzOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/DivOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/DupNOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/EqOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ExchangeOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ExpOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/GtOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/Keccak256OperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/LtOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MemoryOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ModOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/NotOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/OperandStackBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SDivOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SModOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SelfBalanceOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SgtOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SltOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SubOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SwapNOperationBenchmarkV2.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/TransientStorageOperationBenchmarkV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCallOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCreateOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddModOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddressOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobHashOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlockHashOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallCodeOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallDataCopyOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallDataLoadOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallDataSizeOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallerOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ChainIdOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ClzOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CodeCopyOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CodeSizeOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CoinbaseOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Create2OperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CreateOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DelegateCallOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DivOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DupNOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DupOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/EqOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExchangeOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExpOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExtCodeCopyOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExtCodeHashOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExtCodeSizeOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasLimitOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GtOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/InvalidOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/IsZeroOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/JumpDestOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/JumpOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/JumpiOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Keccak256OperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/LogOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/LtOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MCopyOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MSizeOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MloadOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ModOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Mstore8OperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MstoreOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/NotOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/NumberOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/OriginOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PayOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PcOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PopOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PrevRandaoOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Push0OperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PushOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ReturnDataCopyOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ReturnDataSizeOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ReturnOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/RevertOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SDivOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SLoadOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SModOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SStoreOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfDestructOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SgtOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SlotNumOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SltOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/StaticCallOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/StopOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SubOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SwapNOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SwapOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/TLoadOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/TStoreOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/TimestampOperationV2.java 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/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/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/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/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/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/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/evm/src/main/java/org/hyperledger/besu/evm/EVM.java b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java index a1657823bc7..221e58145b6 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; @@ -87,11 +89,91 @@ import org.hyperledger.besu.evm.operation.XorOperation; import org.hyperledger.besu.evm.operation.XorOperationOptimized; 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.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.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.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.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.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 java.util.Optional; import java.util.function.Function; @@ -120,10 +202,16 @@ public class EVM { // Optimized operation flags private final boolean enableConstantinople; + private final boolean enableLondon; 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; /** @@ -147,9 +235,21 @@ public EVM( this.jumpDestOnlyCodeCache = new JumpDestOnlyCodeCache(evmConfiguration); enableConstantinople = EvmSpecVersion.CONSTANTINOPLE.ordinal() <= evmSpecVersion.ordinal(); + enableLondon = EvmSpecVersion.LONDON.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); } /** @@ -489,7 +589,23 @@ private void runToHaltV2(final MessageFrame frame, final OperationTracer tracing result = switch (opcode) { case 0x01 -> AddOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x02 -> MulOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x03 -> SubOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x04 -> DivOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x05 -> SDivOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x06 -> ModOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x07 -> SModOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x08 -> AddModOperationV2.staticOperation(frame, frame.stackDataV2()); case 0x09 -> MulModOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x0a -> + ExpOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0x10 -> LtOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x11 -> GtOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x12 -> SltOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x13 -> SgtOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x14 -> EqOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x15 -> IsZeroOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x19 -> NotOperationV2.staticOperation(frame, frame.stackDataV2()); case 0x1b -> enableConstantinople ? ShlOperationV2.staticOperation(frame, frame.stackDataV2()) @@ -502,7 +618,229 @@ private void runToHaltV2(final MessageFrame frame, final OperationTracer tracing enableConstantinople ? SarOperationV2.staticOperation(frame, frame.stackDataV2()) : InvalidOperation.invalidOperationResult(opcode); - // TODO: implement remaining opcodes in v2; until then fall through to v1 + case 0x1e -> + enableOsaka + ? ClzOperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); + case 0x50 -> PopOperationV2.staticOperation(frame); + case 0x5f -> + enableShanghai + ? Push0OperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); + case 0x60, // PUSH1-32 + 0x61, + 0x62, + 0x63, + 0x64, + 0x65, + 0x66, + 0x67, + 0x68, + 0x69, + 0x6a, + 0x6b, + 0x6c, + 0x6d, + 0x6e, + 0x6f, + 0x70, + 0x71, + 0x72, + 0x73, + 0x74, + 0x75, + 0x76, + 0x77, + 0x78, + 0x79, + 0x7a, + 0x7b, + 0x7c, + 0x7d, + 0x7e, + 0x7f -> + PushOperationV2.staticOperation( + frame, frame.stackDataV2(), code, pc, opcode - PushOperationV2.PUSH_BASE); + case 0x80, // DUP1-16 + 0x81, + 0x82, + 0x83, + 0x84, + 0x85, + 0x86, + 0x87, + 0x88, + 0x89, + 0x8a, + 0x8b, + 0x8c, + 0x8d, + 0x8e, + 0x8f -> + DupOperationV2.staticOperation( + frame, frame.stackDataV2(), opcode - DupOperationV2.DUP_BASE); + case 0x90, // SWAP1-16 + 0x91, + 0x92, + 0x93, + 0x94, + 0x95, + 0x96, + 0x97, + 0x98, + 0x99, + 0x9a, + 0x9b, + 0x9c, + 0x9d, + 0x9e, + 0x9f -> + SwapOperationV2.staticOperation( + frame, frame.stackDataV2(), opcode - SwapOperationV2.SWAP_BASE); + case 0xe6 -> // DUPN (EIP-8024) + enableAmsterdam + ? DupNOperationV2.staticOperation(frame, frame.stackDataV2(), code, pc) + : InvalidOperation.invalidOperationResult(opcode); + case 0xe7 -> // SWAPN (EIP-8024) + enableAmsterdam + ? SwapNOperationV2.staticOperation(frame, frame.stackDataV2(), code, pc) + : InvalidOperation.invalidOperationResult(opcode); + case 0xe8 -> // EXCHANGE (EIP-8024) + enableAmsterdam + ? ExchangeOperationV2.staticOperation(frame, frame.stackDataV2(), code, pc) + : InvalidOperation.invalidOperationResult(opcode); + case 0x51 -> + MloadOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0x52 -> + MstoreOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0x53 -> + Mstore8OperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0x5e -> + enableCancun + ? MCopyOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator) + : InvalidOperation.invalidOperationResult(opcode); + case 0xf0 -> + CreateOperationV2.staticOperation( + frame, frame.stackDataV2(), gasCalculator, this); + case 0xf1 -> + CallOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator, this); + case 0xf2 -> + CallCodeOperationV2.staticOperation( + frame, frame.stackDataV2(), gasCalculator, this); + case 0xf4 -> + DelegateCallOperationV2.staticOperation( + frame, frame.stackDataV2(), gasCalculator, this); + case 0xf5 -> + Create2OperationV2.staticOperation( + frame, frame.stackDataV2(), gasCalculator, this); + case 0xfa -> + StaticCallOperationV2.staticOperation( + frame, frame.stackDataV2(), gasCalculator, this); + case 0x54 -> + SLoadOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0x55 -> + SStoreOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0x5c -> + enableCancun + ? TLoadOperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); + case 0x5d -> + enableCancun + ? TStoreOperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); + // Data copy / hash / account operations + case 0x20 -> + Keccak256OperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0x31 -> + BalanceOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0x35 -> CallDataLoadOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x37 -> + CallDataCopyOperationV2.staticOperation( + frame, frame.stackDataV2(), gasCalculator); + case 0x39 -> + CodeCopyOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0x3b -> + ExtCodeSizeOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0x3c -> + ExtCodeCopyOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0x3e -> + ReturnDataCopyOperationV2.staticOperation( + frame, frame.stackDataV2(), gasCalculator); + case 0x3f -> + enableConstantinople + ? ExtCodeHashOperationV2.staticOperation( + frame, frame.stackDataV2(), gasCalculator) + : InvalidOperation.invalidOperationResult(opcode); + case 0x40 -> BlockHashOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x47 -> + enableConstantinople + ? SelfBalanceOperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); + case 0x49 -> + enableCancun + ? BlobHashOperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); + // Environment push operations (0 → 1) + case 0x30 -> AddressOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x32 -> OriginOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x33 -> CallerOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x34 -> CallValueOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x36 -> CallDataSizeOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x38 -> CodeSizeOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x3a -> GasPriceOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x3d -> ReturnDataSizeOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x41 -> CoinbaseOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x42 -> TimestampOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x43 -> NumberOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x44 -> PrevRandaoOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x45 -> GasLimitOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x46 -> // CHAINID (Istanbul+) + chainIdOperationV2 != null + ? chainIdOperationV2.executeFixedCostOperation(frame, this) + : InvalidOperation.invalidOperationResult(opcode); + case 0x48 -> // BASEFEE (London+) + enableLondon + ? BaseFeeOperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); + case 0x4a -> // BLOBBASEFEE (Cancun+) + enableCancun + ? BlobBaseFeeOperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); + case 0x4b -> // SLOTNUM (Osaka+) + enableOsaka + ? SlotNumOperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); + case 0x58 -> PcOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x59 -> MSizeOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x5a -> gasOperationV2.executeFixedCostOperation(frame, this); + // Control flow operations + case 0x00 -> StopOperationV2.staticOperation(frame); + case 0x56 -> JumpOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x57 -> JumpiOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x5b -> JumpDestOperationV2.staticOperation(frame); + case 0xf3 -> + ReturnOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0xfd -> + RevertOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0xfe -> InvalidOperationV2.INVALID_RESULT; + case 0xa0, 0xa1, 0xa2, 0xa3, 0xa4 -> { + int topicCount = opcode - 0xa0; + yield LogOperationV2.staticOperation( + frame, frame.stackDataV2(), topicCount, gasCalculator); + } + case 0xff -> + SelfDestructOperationV2.staticOperation( + frame, + frame.stackDataV2(), + gasCalculator, + enableCancun, + enableAmsterdam + ? EIP7708TransferLogEmitter.INSTANCE + : TransferLogEmitter.NOOP); + case 0xfc -> // PAY (EIP-7708, Amsterdam+) + enableAmsterdam + ? PayOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator) + : InvalidOperation.invalidOperationResult(opcode); default -> { frame.setCurrentOperation(currentOperation); yield currentOperation.execute(frame, this); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCallOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCallOperationV2.java new file mode 100644 index 00000000000..8fb0cbda66c --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCallOperationV2.java @@ -0,0 +1,393 @@ +/* + * 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.LEGACY_INSUFFICIENT_BALANCE; +import static org.hyperledger.besu.evm.frame.SoftFailureReason.LEGACY_MAX_CALL_DEPTH; +import static org.hyperledger.besu.evm.internal.Words.clampedAdd; +import static org.hyperledger.besu.evm.worldstate.CodeDelegationHelper.getTarget; +import static org.hyperledger.besu.evm.worldstate.CodeDelegationHelper.hasCodeDelegation; + +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.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.frame.MessageFrame.State; +import org.hyperledger.besu.evm.frame.SoftFailureReason; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.AbstractOperation; +import org.hyperledger.besu.evm.v2.StackArithmetic; +import org.hyperledger.besu.evm.worldstate.CodeDelegationHelper; + +import org.apache.tuweni.bytes.Bytes; + +/** + * Base class for EVM v2 CALL-family operations (CALL, CALLCODE, DELEGATECALL, STATICCALL). + * + *

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 AbstractOperation { + + /** 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.stackSize() < 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 gasForChild = gasAvailableForChildCall(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); + + 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..d18cfb18c13 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCreateOperationV2.java @@ -0,0 +1,270 @@ +/* + * 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.operation.AbstractOperation; +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 AbstractOperation { + + /** 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.stackSize() < getStackItemsConsumed()) { + return UNDERFLOW_RESPONSE; + } + + Supplier 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); + 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 haltReason) {} +} 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/v2/operation/AddOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddOperationV2.java index b4109d29ad1..a0a007ff6e0 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddOperationV2.java @@ -18,19 +18,13 @@ 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/BalanceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java new file mode 100644 index 00000000000..2649a59668e --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java @@ -0,0 +1,96 @@ +/* + * 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.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.AbstractOperation; +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 AbstractOperation { + + /** + * 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 org.hyperledger.besu.evm.account.MutableAccount account = + frame.getWorldUpdater().getAccount(address); + // 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 maybeBaseFee = frame.getBlockValues().getBaseFee(); + if (maybeBaseFee.isEmpty()) { + return INVALID_OPERATION_RESPONSE; + } + frame.setTopV2(StackArithmetic.pushWei(stack, frame.stackTopV2(), maybeBaseFee.get())); + return baseFeeSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java new file mode 100644 index 00000000000..cdb470ef63c --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.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 BLOBBASEFEE operation — pushes the blob base fee onto the stack (Cancun+). */ +public class BlobBaseFeeOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult blobBaseFeeSuccess = new OperationResult(2, null); + + /** + * Instantiates a new Blob base fee operation. + * + * @param gasCalculator the gas calculator + */ + public BlobBaseFeeOperationV2(final GasCalculator gasCalculator) { + super(0x4A, "BLOBBASEFEE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs the BLOBBASEFEE 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.getBlobGasPrice())); + return blobBaseFeeSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobHashOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobHashOperationV2.java new file mode 100644 index 00000000000..59c2607b08b --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobHashOperationV2.java @@ -0,0 +1,84 @@ +/* + * 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.VersionedHash; +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.AbstractOperation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +import java.util.List; + +/** + * EVM v2 BLOBHASH operation (0x49, Cancun+). + * + *

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 AbstractOperation { + + /** 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 versionedHashes = frame.getVersionedHashes().get(); + // If index doesn't fit in a positive int, it's out of range + if (!StackArithmetic.fitsInInt(s, top, 0)) { + StackArithmetic.putAt(s, top, 0, 0L, 0L, 0L, 0L); + return new OperationResult(3, null); + } + final int versionedHashIndex = (int) StackArithmetic.longAt(s, top, 0); + if (versionedHashIndex >= 0 && versionedHashIndex < versionedHashes.size()) { + final VersionedHash requested = versionedHashes.get(versionedHashIndex); + final byte[] hashBytes = requested.getBytes().toArrayUnsafe(); + StackArithmetic.fromBytesAt(s, top, 0, hashBytes, 0, hashBytes.length); + } else { + StackArithmetic.putAt(s, top, 0, 0L, 0L, 0L, 0L); + } + } else { + StackArithmetic.putAt(s, top, 0, 0L, 0L, 0L, 0L); + } + return new OperationResult(3, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlockHashOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlockHashOperationV2.java new file mode 100644 index 00000000000..2597fea6566 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlockHashOperationV2.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.Hash; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.blockhash.BlockHashLookup; +import org.hyperledger.besu.evm.frame.BlockValues; +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.AbstractOperation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** + * EVM v2 BLOCKHASH operation (0x40). + * + *

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 AbstractOperation { + + /** + * 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/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..9903ea18a19 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallDataCopyOperationV2.java @@ -0,0 +1,78 @@ +/* + * 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.AbstractOperation; +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 AbstractOperation { + + /** + * 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..cce5be2d2bc --- /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] < 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..824e72872f3 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CodeCopyOperationV2.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.operation.AbstractOperation; +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 AbstractOperation { + + /** + * 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 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/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). + * + *

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..4e97881365e --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExpOperationV2.java @@ -0,0 +1,62 @@ +/* + * 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.AbstractOperation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The Exp operation. */ +public class ExpOperationV2 extends AbstractOperation { + + /** + * 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..fdaecfd63a7 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExtCodeCopyOperationV2.java @@ -0,0 +1,113 @@ +/* + * 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.operation.AbstractOperation; +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 AbstractOperation { + + /** + * 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 = frame.getWorldUpdater().getAccount(address); + 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..c2d9601536d --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExtCodeHashOperationV2.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.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.operation.AbstractOperation; +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 AbstractOperation { + + /** + * 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 = frame.getWorldUpdater().getAccount(address); + + // 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..ed61e7136aa --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExtCodeSizeOperationV2.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.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.operation.AbstractOperation; +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 AbstractOperation { + + /** + * 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 = frame.getWorldUpdater().getAccount(address); + // 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..6793315759a --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/InvalidOperationV2.java @@ -0,0 +1,46 @@ +/* + * 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.AbstractOperation; + +/** EVM v2 INVALID operation. Always halts with INVALID_OPERATION. */ +public class InvalidOperationV2 extends AbstractOperation { + + /** 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..c4b4094ab85 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Keccak256OperationV2.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 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.operation.AbstractOperation; +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 AbstractOperation { + + /** + * 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..d2f7314c02b --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/LogOperationV2.java @@ -0,0 +1,99 @@ +/* + * 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.operation.AbstractOperation; +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 AbstractOperation { + + 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 builder = + ImmutableList.builderWithExpectedSize(numTopics); + for (int i = 0; i < numTopics; i++) { + final byte[] buf = new byte[32]; + StackArithmetic.toBytesAt(s, top, 2 + i, buf); + builder.add(LogTopic.create(Bytes32.wrap(buf))); + } + frame.setTopV2(top - 2 - numTopics); + + frame.addLog(new Log(address, data, builder.build())); + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/LtOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/LtOperationV2.java new file mode 100644 index 00000000000..b44af404fe0 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/LtOperationV2.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 Lt operation. */ +public class LtOperationV2 extends AbstractFixedCostOperationV2 { + + /** The Lt operation success result. */ + static final OperationResult ltSuccess = new OperationResult(3, null); + + /** + * Instantiates a new Lt operation. + * + * @param gasCalculator the gas calculator + */ + public LtOperationV2(final GasCalculator gasCalculator) { + super(0x10, "LT", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs lt 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.lt(stack, frame.stackTopV2())); + return ltSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MCopyOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MCopyOperationV2.java new file mode 100644 index 00000000000..7a7ba89c25c --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MCopyOperationV2.java @@ -0,0 +1,69 @@ +/* + * 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.AbstractOperation; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The MCOPY operation for EVM v2. */ +public class MCopyOperationV2 extends AbstractOperation { + + /** + * Instantiates a new MCOPY operation. + * + * @param gasCalculator the gas calculator + */ + public MCopyOperationV2(final GasCalculator gasCalculator) { + super(0x5e, "MCOPY", 3, 0, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), gasCalculator()); + } + + /** + * Execute the MCOPY opcode on the v2 long[] stack. + * + * @param frame the message frame + * @param stack the stack operands as a long[] array + * @param gasCalculator the gas calculator + * @return the operation result + */ + public static Operation.OperationResult staticOperation( + final MessageFrame frame, final long[] stack, final GasCalculator gasCalculator) { + if (!frame.stackHasItems(3)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final int top = frame.stackTopV2(); + final long dst = StackArithmetic.clampedToLong(stack, top, 0); + final long src = StackArithmetic.clampedToLong(stack, top, 1); + final long length = StackArithmetic.clampedToLong(stack, top, 2); + frame.setTopV2(top - 3); + + final long cost = gasCalculator.dataCopyOperationGasCost(frame, Math.max(src, dst), length); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + frame.copyMemory(dst, src, length, true); + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MSizeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MSizeOperationV2.java new file mode 100644 index 00000000000..c1e474c0c90 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MSizeOperationV2.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 MSIZE operation — pushes the active memory size in bytes onto the stack. */ +public class MSizeOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult mSizeSuccess = new OperationResult(2, null); + + /** + * Instantiates a new MSize operation. + * + * @param gasCalculator the gas calculator + */ + public MSizeOperationV2(final GasCalculator gasCalculator) { + super(0x59, "MSIZE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs the MSIZE 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.memoryByteSize())); + return mSizeSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MloadOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MloadOperationV2.java new file mode 100644 index 00000000000..7b942d18636 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MloadOperationV2.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.AbstractOperation; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The MLOAD operation for EVM v2. */ +public class MloadOperationV2 extends AbstractOperation { + + /** + * Instantiates a new MLOAD operation. + * + * @param gasCalculator the gas calculator + */ + public MloadOperationV2(final GasCalculator gasCalculator) { + super(0x51, "MLOAD", 1, 1, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), gasCalculator()); + } + + /** + * Execute the MLOAD opcode on the v2 long[] stack. + * + * @param frame the message frame + * @param stack the stack operands as a long[] array + * @param gasCalculator the gas calculator + * @return the operation result + */ + public static Operation.OperationResult staticOperation( + final MessageFrame frame, final long[] stack, final GasCalculator gasCalculator) { + if (!frame.stackHasItems(1)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final int top = frame.stackTopV2(); + final long location = StackArithmetic.clampedToLong(stack, top, 0); + + final long cost = gasCalculator.mLoadOperationGasCost(frame, location); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + final byte[] bytes = frame.readMutableMemory(location, 32, true).copy().toArrayUnsafe(); + StackArithmetic.fromBytesAt(stack, top, 0, bytes, 0, 32); + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ModOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ModOperationV2.java new file mode 100644 index 00000000000..2b5f6a6410c --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ModOperationV2.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 Mod operation. */ +public class ModOperationV2 extends AbstractFixedCostOperationV2 { + + /** The Mod operation success result. */ + static final OperationResult modSuccess = new OperationResult(5, null); + + /** + * Instantiates a new Mod operation. + * + * @param gasCalculator the gas calculator + */ + public ModOperationV2(final GasCalculator gasCalculator) { + super(0x06, "MOD", 2, 1, gasCalculator, gasCalculator.getLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs mod 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.mod(stack, frame.stackTopV2())); + return modSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Mstore8OperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Mstore8OperationV2.java new file mode 100644 index 00000000000..b140f645391 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Mstore8OperationV2.java @@ -0,0 +1,68 @@ +/* + * 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.AbstractOperation; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The MSTORE8 operation for EVM v2. */ +public class Mstore8OperationV2 extends AbstractOperation { + + /** + * Instantiates a new MSTORE8 operation. + * + * @param gasCalculator the gas calculator + */ + public Mstore8OperationV2(final GasCalculator gasCalculator) { + super(0x53, "MSTORE8", 2, 0, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), gasCalculator()); + } + + /** + * Execute the MSTORE8 opcode on the v2 long[] stack. + * + * @param frame the message frame + * @param stack the stack operands as a long[] array + * @param gasCalculator the gas calculator + * @return the operation result + */ + public static Operation.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 top = frame.stackTopV2(); + final long location = StackArithmetic.clampedToLong(stack, top, 0); + final byte theByte = (byte) (StackArithmetic.longAt(stack, top, 1) & 0xFF); + + final long cost = gasCalculator.mStore8OperationGasCost(frame, location); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + frame.writeMemory(location, theByte, true); + frame.setTopV2(top - 2); + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MstoreOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MstoreOperationV2.java new file mode 100644 index 00000000000..4709a5d8027 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MstoreOperationV2.java @@ -0,0 +1,71 @@ +/* + * 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.AbstractOperation; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +import org.apache.tuweni.bytes.Bytes; + +/** The MSTORE operation for EVM v2. */ +public class MstoreOperationV2 extends AbstractOperation { + + /** + * Instantiates a new MSTORE operation. + * + * @param gasCalculator the gas calculator + */ + public MstoreOperationV2(final GasCalculator gasCalculator) { + super(0x52, "MSTORE", 2, 0, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), gasCalculator()); + } + + /** + * Execute the MSTORE opcode on the v2 long[] stack. + * + * @param frame the message frame + * @param stack the stack operands as a long[] array + * @param gasCalculator the gas calculator + * @return the operation result + */ + public static Operation.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 top = frame.stackTopV2(); + final long location = StackArithmetic.clampedToLong(stack, top, 0); + + final long cost = gasCalculator.mStoreOperationGasCost(frame, location); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + final byte[] bytes = new byte[32]; + StackArithmetic.toBytesAt(stack, top, 1, bytes); + frame.writeMemoryRightAligned(location, 32, Bytes.wrap(bytes), true); + frame.setTopV2(top - 2); + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2.java new file mode 100644 index 00000000000..5d85573d806 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2.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 Mul operation. */ +public class MulOperationV2 extends AbstractFixedCostOperationV2 { + + /** The Mul operation success result. */ + static final OperationResult mulSuccess = new OperationResult(5, null); + + /** + * Instantiates a new Mul operation. + * + * @param gasCalculator the gas calculator + */ + public MulOperationV2(final GasCalculator gasCalculator) { + super(0x02, "MUL", 2, 1, gasCalculator, gasCalculator.getLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs mul 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.mul(stack, frame.stackTopV2())); + return mulSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/NotOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/NotOperationV2.java new file mode 100644 index 00000000000..af118f93e57 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/NotOperationV2.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 Not operation. */ +public class NotOperationV2 extends AbstractFixedCostOperationV2 { + + /** The Not operation success result. */ + static final OperationResult notSuccess = new OperationResult(3, null); + + /** + * Instantiates a new Not operation. + * + * @param gasCalculator the gas calculator + */ + public NotOperationV2(final GasCalculator gasCalculator) { + super(0x19, "NOT", 1, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs not 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.not(stack, frame.stackTopV2())); + return notSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/NumberOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/NumberOperationV2.java new file mode 100644 index 00000000000..7656015d292 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/NumberOperationV2.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 NUMBER operation — pushes the block number onto the stack. */ +public class NumberOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult numberSuccess = new OperationResult(2, null); + + /** + * Instantiates a new Number operation. + * + * @param gasCalculator the gas calculator + */ + public NumberOperationV2(final GasCalculator gasCalculator) { + super(0x43, "NUMBER", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs the NUMBER 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().getNumber())); + return numberSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/OriginOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/OriginOperationV2.java new file mode 100644 index 00000000000..d3e51584e0c --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/OriginOperationV2.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 ORIGIN operation — pushes the originator address onto the stack. */ +public class OriginOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult originSuccess = new OperationResult(2, null); + + /** + * Instantiates a new Origin operation. + * + * @param gasCalculator the gas calculator + */ + public OriginOperationV2(final GasCalculator gasCalculator) { + super(0x32, "ORIGIN", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs the ORIGIN 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.getOriginatorAddress())); + return originSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PayOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PayOperationV2.java new file mode 100644 index 00000000000..1617ac1b4af --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PayOperationV2.java @@ -0,0 +1,134 @@ +/* + * 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.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.operation.AbstractOperation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +import java.util.Objects; + +import org.apache.tuweni.bytes.Bytes; + +/** EVM v2 PAY operation (EIP-7708) using long[] stack representation. */ +public class PayOperationV2 extends AbstractOperation { + + /** + * Instantiates a new Pay operation. + * + * @param gasCalculator the gas calculator + */ + public PayOperationV2(final GasCalculator gasCalculator) { + super(0xfc, "PAY", 2, 1, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), gasCalculator()); + } + + /** + * Performs PAY 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); + } + if (frame.isStatic()) { + return new OperationResult(0, ExceptionalHaltReason.ILLEGAL_STATE_CHANGE); + } + + final int top = frame.stackTopV2(); + final int addrOff = (top - 1) << 2; + + // Check if address has more than 20 bytes of significant data (u3 must be 0, upper 32 bits of + // u2 must be 0). + if (s[addrOff] != 0 || (s[addrOff + 1] >>> 32) != 0) { + return new OperationResult(0, ExceptionalHaltReason.ADDRESS_OUT_OF_RANGE); + } + + final Address to = StackArithmetic.toAddressAt(s, top, 0); + final Wei value = Wei.wrap(Bytes.wrap(StackArithmetic.getAt(s, top, 1).toBytesBE())); + final boolean hasValue = value.greaterThan(Wei.ZERO); + final Account recipient = frame.getWorldUpdater().get(to); + + final boolean accountIsWarm = frame.warmUpAddress(to); + + final long cost = cost(to, hasValue, recipient, accountIsWarm, gasCalculator); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + if (!hasValue || Objects.equals(frame.getSenderAddress(), to)) { + StackArithmetic.putAt(s, top, 1, 0, 0, 0, 1); + frame.setTopV2(top - 1); + return new OperationResult(cost, null); + } + + final MutableAccount senderAccount = + frame.getWorldUpdater().getOrCreate(frame.getSenderAddress()); + if (value.compareTo(senderAccount.getBalance()) > 0) { + StackArithmetic.putAt(s, top, 1, 0, 0, 0, 0); + frame.setTopV2(top - 1); + return new OperationResult(cost, null); + } + + final MutableAccount recipientAccount = frame.getWorldUpdater().getOrCreate(to); + senderAccount.decrementBalance(value); + recipientAccount.incrementBalance(value); + + StackArithmetic.putAt(s, top, 1, 0, 0, 0, 1); + frame.setTopV2(top - 1); + return new OperationResult(cost, null); + } + + private static long cost( + final Address to, + final boolean hasValue, + final Account recipient, + final boolean accountIsWarm, + final GasCalculator gasCalculator) { + long cost = 0; + if (hasValue) { + cost = gasCalculator.callValueTransferGasCost(); + } + if (accountIsWarm || gasCalculator.isPrecompile(to)) { + return clampedAdd(cost, gasCalculator.getWarmStorageReadCost()); + } + + cost = clampedAdd(cost, gasCalculator.getColdAccountAccessCost()); + + if (recipient == null && hasValue) { + cost = clampedAdd(cost, gasCalculator.newAccountGasCost()); + } + + return cost; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PcOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PcOperationV2.java new file mode 100644 index 00000000000..4d1ab23bb21 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PcOperationV2.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 PC operation — pushes the current program counter onto the stack. */ +public class PcOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult pcSuccess = new OperationResult(2, null); + + /** + * Instantiates a new PC operation. + * + * @param gasCalculator the gas calculator + */ + public PcOperationV2(final GasCalculator gasCalculator) { + super(0x58, "PC", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs the PC 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.getPC())); + return pcSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PopOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PopOperationV2.java new file mode 100644 index 00000000000..8d40d53576f --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PopOperationV2.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; + +/** + * EVM v2 POP operation (0x50). + * + *

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..f805b53e70d --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ReturnDataCopyOperationV2.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.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.AbstractOperation; +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 AbstractOperation { + + 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..27ad0b795ad --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ReturnOperationV2.java @@ -0,0 +1,71 @@ +/* + * 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.AbstractOperation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 RETURN operation using long[] stack representation. */ +public class ReturnOperationV2 extends AbstractOperation { + + /** 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..a155f26151d --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/RevertOperationV2.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.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.AbstractOperation; +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 AbstractOperation { + + /** 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..8a7a342fe92 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SLoadOperationV2.java @@ -0,0 +1,124 @@ +/* + * 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.operation.AbstractOperation; +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 AbstractOperation { + + 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..4fad5159a3d --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SStoreOperationV2.java @@ -0,0 +1,158 @@ +/* + * 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.operation.AbstractOperation; +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 AbstractOperation { + + /** 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); + } + + private 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 currentValueSupplier = + Suppliers.memoize(() -> account.getStorageValue(key)); + final Supplier originalValueSupplier = + Suppliers.memoize(() -> account.getOriginalStorageValue(key)); + + final long cost = + gasCalculator.calculateStorageCost(newValue, currentValueSupplier, originalValueSupplier) + + (slotIsWarm ? 0L : gasCalculator.getColdSloadCost()); + + if (remainingGas < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + // Increment the refund counter + frame.incrementGasRefund( + gasCalculator.calculateStorageRefundAmount( + newValue, currentValueSupplier, originalValueSupplier)); + + account.setStorageValue(key, newValue); + frame.storageWasUpdated(key, Bytes.wrap(newValueBytes)); + frame + .getEip7928AccessList() + .ifPresent(t -> t.addSlotAccessForAccount(frame.getRecipientAddress(), key)); + + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java new file mode 100644 index 00000000000..e378adcd8cc --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.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.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.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** + * EVM v2 SELFBALANCE operation (0x47, Istanbul+). + * + *

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 = frame.getWorldUpdater().getAccount(frame.getRecipientAddress()); + 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..f3c01d59e10 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfDestructOperationV2.java @@ -0,0 +1,157 @@ +/* + * 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.operation.AbstractOperation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 SELFDESTRUCT operation using long[] stack representation. */ +public class SelfDestructOperationV2 extends AbstractOperation { + + 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 = frame.getWorldUpdater().get(beneficiaryAddress); + final Address originatorAddress = frame.getRecipientAddress(); + final MutableAccount originatorAccount = frame.getWorldUpdater().getOrCreate(originatorAddress); + final Wei originatorBalance = originatorAccount.getBalance(); + + final long cost = + gasCalculator.selfDestructOperationGasCost(beneficiaryNullable, originatorBalance) + + beneficiaryAccessCost; + + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + final MutableAccount beneficiaryAccount = + frame.getWorldUpdater().getOrCreate(beneficiaryAddress); + + 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/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..06592b1387b --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/TLoadOperationV2.java @@ -0,0 +1,82 @@ +/* + * 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. Fixed gas cost equal to veryLow tier. + */ +public class TLoadOperationV2 extends AbstractFixedCostOperationV2 { + + private static final long GAS_COST = 3L; + 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(); + + // Extract the 32-byte slot key from the stack + final byte[] keyBytes = new byte[32]; + StackArithmetic.toBytesAt(s, top, 0, keyBytes); + final Bytes32 slot = Bytes32.wrap(keyBytes); + + // Load from transient storage and overwrite top slot in place + 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..d09e2911227 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/TStoreOperationV2.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.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. Fixed + * gas cost equal to veryLow tier. + */ +public class TStoreOperationV2 extends AbstractFixedCostOperationV2 { + + private static final long GAS_COST = 3L; + 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(); + + // Extract key (depth 0) and value (depth 1) as 32-byte arrays + final byte[] keyBytes = new byte[32]; + final byte[] valueBytes = new byte[32]; + StackArithmetic.toBytesAt(s, top, 0, keyBytes); + StackArithmetic.toBytesAt(s, top, 1, valueBytes); + + // Pop 2 + 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; + } +} From 0b4f1dee6405c4cfcc30ba56271dbe9e162e5945 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Sat, 4 Apr 2026 13:55:02 +1000 Subject: [PATCH 13/24] Fix javadoc errors in V2 StackArithmetic and operation classes Expand single-line Javadoc on all 45 public StackArithmetic methods to include @param and @return tags. Fix three HTML entity errors (&, <, Signed-off-by: Simon Dudley --- .../besu/evm/v2/StackArithmetic.java | 381 ++++++++++++++++-- .../v2/operation/ExtCodeHashOperationV2.java | 2 +- .../evm/v2/operation/MulModOperationV2.java | 1 + 3 files changed, 340 insertions(+), 44 deletions(-) diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java index 83f2d4f4683..6843f622da6 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java @@ -314,7 +314,13 @@ private static void sarInPlace( // region Binary Arithmetic (pop 2, push 1, return top-1) // --------------------------------------------------------------------------- - /** ADD: s[top-2] = s[top-1] + s[top-2], 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; @@ -345,7 +351,13 @@ public static int add(final long[] s, final int top) { return top - 1; } - /** SUB: s[top-2] = s[top-1] - s[top-2], 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 @@ -381,7 +393,13 @@ public static int sub(final long[] s, final int top) { return top - 1; } - /** MUL: s[top-2] = s[top-1] * s[top-2], return top-1. Delegates to UInt256 for now. */ + /** + * 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; @@ -395,7 +413,13 @@ public static int mul(final long[] s, final int top) { return top - 1; } - /** DIV: s[top-2] = s[top-1] / s[top-2], return top-1. Delegates to UInt256. */ + /** + * 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; @@ -409,7 +433,13 @@ public static int div(final long[] s, final int top) { return top - 1; } - /** SDIV: s[top-2] = s[top-1] sdiv s[top-2], return top-1. Delegates to UInt256. */ + /** + * 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; @@ -423,7 +453,13 @@ public static int signedDiv(final long[] s, final int top) { return top - 1; } - /** MOD: s[top-2] = s[top-1] mod s[top-2], return top-1. Delegates to UInt256. */ + /** + * 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; @@ -437,7 +473,13 @@ public static int mod(final long[] s, final int top) { return top - 1; } - /** SMOD: s[top-2] = s[top-1] smod s[top-2], return top-1. Delegates to UInt256. */ + /** + * 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; @@ -451,7 +493,13 @@ public static int signedMod(final long[] s, final int top) { return top - 1; } - /** EXP: s[top-2] = s[top-1] ** s[top-2] mod 2^256, 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 @@ -479,7 +527,13 @@ public static int exp(final long[] s, final int top) { // region Bitwise Binary (pop 2, push 1, return top-1) // --------------------------------------------------------------------------- - /** AND: s[top-2] = s[top-1] & s[top-2], 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; @@ -490,7 +544,13 @@ public static int and(final long[] s, final int top) { return top - 1; } - /** OR: s[top-2] = s[top-1] | s[top-2], 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; @@ -501,7 +561,13 @@ public static int or(final long[] s, final int top) { return top - 1; } - /** XOR: s[top-2] = s[top-1] ^ s[top-2], 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; @@ -512,7 +578,13 @@ public static int xor(final long[] s, final int top) { return top - 1; } - /** BYTE: s[top-2] = byte at offset s[top-1] of s[top-2], 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 @@ -538,7 +610,13 @@ public static int byte_(final long[] s, final int top) { return top - 1; } - /** SIGNEXTEND: sign-extend s[top-2] from byte s[top-1], 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 @@ -578,7 +656,13 @@ public static int signExtend(final long[] s, final int top) { // region Unary Operations (pop 1, push 1, return top) // --------------------------------------------------------------------------- - /** NOT: s[top-1] = ~s[top-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]; @@ -588,7 +672,13 @@ public static int not(final long[] s, final int top) { return top; } - /** ISZERO: s[top-1] = (s[top-1] == 0) ? 1 : 0, 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; @@ -599,7 +689,13 @@ public static int isZero(final long[] s, final int top) { return top; } - /** CLZ: s[top-1] = count leading zeros of s[top-1], 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; @@ -624,7 +720,13 @@ public static int clz(final long[] s, final int top) { // 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. */ + /** + * 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; @@ -636,7 +738,13 @@ public static int lt(final long[] s, final int top) { return top - 1; } - /** GT: s[top-2] = (s[top-1] > s[top-2]) ? 1 : 0, return top-1. Unsigned. */ + /** + * 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; @@ -648,7 +756,13 @@ public static int gt(final long[] s, final int top) { return top - 1; } - /** SLT: s[top-2] = (s[top-1] s s[top-2]) ? 1 : 0, return top-1. Signed. */ + /** + * 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; @@ -672,7 +792,13 @@ public static int sgt(final long[] s, final int top) { return top - 1; } - /** EQ: s[top-2] = (s[top-1] == s[top-2]) ? 1 : 0, 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; @@ -690,7 +816,13 @@ public static int eq(final long[] s, final int top) { // 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. */ + /** + * 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; @@ -706,7 +838,13 @@ public static int addMod(final long[] s, final int top) { return top - 2; } - /** MULMOD: s[top-3] = (s[top-1] * s[top-2]) mod s[top-3], 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; @@ -727,7 +865,14 @@ public static int mulMod(final long[] s, final int top) { // region Stack Manipulation // --------------------------------------------------------------------------- - /** DUP: copy slot at depth → new top, return top+1. depth is 1-based (DUP1 = depth 1). */ + /** + * 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; @@ -738,7 +883,14 @@ public static int dup(final long[] s, final int top, final int depth) { return top + 1; } - /** SWAP: swap top ↔ slot at depth, return top. depth is 1-based (SWAP1 = depth 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; @@ -758,7 +910,15 @@ public static int swap(final long[] s, final int top, final int depth) { return top; } - /** EXCHANGE: swap slot at n ↔ slot at m (both 0-indexed from top), 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; @@ -778,7 +938,13 @@ public static int exchange(final long[] s, final int top, final int n, final int return top; } - /** PUSH0: push zero, return top+1. */ + /** + * 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; @@ -788,7 +954,16 @@ public static int pushZero(final long[] s, final int top) { return top + 1; } - /** PUSH1..PUSH32: decode bytes from code into a new top slot, 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; @@ -840,7 +1015,14 @@ public static int pushFromBytes( return top + 1; } - /** Push a long value (GAS, NUMBER, etc.), 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; @@ -850,7 +1032,14 @@ public static int pushLong(final long[] s, final int top, final long value) { return top + 1; } - /** Push a Wei value onto the stack, 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(); @@ -861,7 +1050,14 @@ public static int pushWei(final long[] s, final int top, final Wei value) { return top + 1; } - /** Push an Address (20 bytes), 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(); @@ -878,18 +1074,39 @@ public static int pushAddress(final long[] s, final int top, final Address addr) // region Boundary Helpers (read/write slots without changing top) // --------------------------------------------------------------------------- - /** Extract u0 (LSB limb) of slot at depth from 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. */ + /** + * 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. */ + /** + * 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 @@ -899,7 +1116,14 @@ public static boolean fitsInInt(final long[] s, final int top, final int depth) && s[off + 3] <= Integer.MAX_VALUE; } - /** Check if slot at depth fits in a non-negative long. */ + /** + * 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; @@ -907,6 +1131,11 @@ public static boolean fitsInLong(final long[] s, final int top, final int depth) /** * 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; @@ -919,6 +1148,11 @@ public static long clampedToLong(final long[] s, final int top, final int depth) /** * 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; @@ -932,7 +1166,14 @@ public static int clampedToInt(final long[] s, final int top, final int depth) { return (int) s[off + 3]; } - /** Write 32 big-endian bytes from slot at depth into dst. */ + /** + * 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]); @@ -941,7 +1182,16 @@ public static void toBytesAt(final long[] s, final int top, final int depth, fin longIntoBytes(dst, 24, s[off + 3]); } - /** Read bytes into slot at depth from src[srcOff..srcOff+len). Pads with zeros. */ + /** + * 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, @@ -973,7 +1223,14 @@ public static void fromBytesAt( } } - /** Extract 20-byte Address from slot at depth. */ + /** + * 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]; @@ -984,13 +1241,27 @@ public static Address toAddressAt(final long[] s, final int top, final int depth return Address.wrap(org.apache.tuweni.bytes.Bytes.wrap(bytes)); } - /** Materialize UInt256 record from slot at depth (boundary only). */ + /** + * 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. */ + /** + * 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(); @@ -1000,7 +1271,14 @@ public static void putWeiAt(final long[] s, final int top, final int depth, fina s[off + 3] = getLong(bytes, 24); } - /** Write UInt256 record into slot at depth. */ + /** + * 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(); @@ -1009,7 +1287,17 @@ public static void putAt(final long[] s, final int top, final int depth, final U s[off + 3] = val.u0(); } - /** Write raw limbs into slot at depth. */ + /** + * 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, @@ -1025,7 +1313,14 @@ public static void putAt( s[off + 3] = u0; } - /** Number of significant bytes in slot at depth. Used by EXP gas calculation. */ + /** + * 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]); 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 index c2d9601536d..d4e16b01173 100644 --- 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 @@ -58,7 +58,7 @@ 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 diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2.java index 0ac7602f524..1756d98e7e5 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2.java @@ -44,6 +44,7 @@ public Operation.OperationResult executeFixedCostOperation( * Performs MulMod operation. * * @param frame the frame + * @param s the stack array * @return the operation result */ public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { From 52971c5868c2722839fa0f900f4f443e52a982b9 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Sat, 4 Apr 2026 17:33:19 +1000 Subject: [PATCH 14/24] Fix NPE when V2 dispatch runs frames not built with enableEvmV2(true) runToHalt gated on evmConfiguration.enableEvmV2() globally, but system call frames (and transaction frames) built without .enableEvmV2(true) on the builder had stackDataV2 == null, causing NPE in StackArithmetic. Fix: make stackDataV2 non-final with lazy init via ensureV2Stack(), called from runToHalt before entering runToHaltV2. No frame builder changes needed. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Simon Dudley --- evm/src/main/java/org/hyperledger/besu/evm/EVM.java | 1 + .../org/hyperledger/besu/evm/frame/MessageFrame.java | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) 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 221e58145b6..94160f02955 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java @@ -324,6 +324,7 @@ public Optional getChainId() { // Please benchmark before refactoring. public void runToHalt(final MessageFrame frame, final OperationTracer tracing) { if (evmConfiguration.enableEvmV2()) { + frame.ensureV2Stack(); runToHaltV2(frame, tracing); return; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java index 773fce3eca5..b122a461706 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java @@ -206,7 +206,7 @@ public enum Type { private final OperandStack stack; // EVM v2 stack: 4 longs per 256-bit word (index 0 = most significant, index 3 = least // significant) - private final long[] stackDataV2; + private long[] stackDataV2; private int stackTopV2; private Bytes output = Bytes.EMPTY; private Bytes returnData = Bytes.EMPTY; @@ -528,6 +528,16 @@ public boolean stackHasSpace(final int n) { return stackTopV2 + n <= txValues.maxStackSize(); } + /** + * Ensures the V2 stack array is allocated. Called by the EVM when entering the V2 dispatch path + * for frames that were not built with {@code enableEvmV2(true)}. + */ + public void ensureV2Stack() { + if (stackDataV2 == null) { + stackDataV2 = new long[txValues.maxStackSize() * 4]; + } + } + // --------------------------------------------------------------------------- // endregion From b366fcff0982d0eb976c57d6fbd4b24175d97098 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Sun, 5 Apr 2026 13:03:26 +1000 Subject: [PATCH 15/24] Add missing V2 bitwise operations: AND, OR, XOR, BYTE, SIGNEXTEND MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The batch migration missed Unit 4 (bitwise binary ops). This adds the 5 missing V2 operation files and wires them into the runToHaltV2 switch (opcodes 0x0B, 0x16–0x18, 0x1A). Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Simon Dudley --- .../java/org/hyperledger/besu/evm/EVM.java | 10 ++++ .../besu/evm/v2/operation/AndOperationV2.java | 56 +++++++++++++++++++ .../evm/v2/operation/ByteOperationV2.java | 56 +++++++++++++++++++ .../besu/evm/v2/operation/OrOperationV2.java | 56 +++++++++++++++++++ .../v2/operation/SignExtendOperationV2.java | 56 +++++++++++++++++++ .../besu/evm/v2/operation/XorOperationV2.java | 56 +++++++++++++++++++ 6 files changed, 290 insertions(+) create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AndOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ByteOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/OrOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SignExtendOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/XorOperationV2.java 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 94160f02955..e52d8411a6b 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java @@ -92,11 +92,13 @@ 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; @@ -143,6 +145,7 @@ 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; @@ -164,6 +167,7 @@ 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; @@ -174,6 +178,7 @@ 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; @@ -600,13 +605,18 @@ private void runToHaltV2(final MessageFrame frame, final OperationTracer tracing case 0x09 -> MulModOperationV2.staticOperation(frame, frame.stackDataV2()); case 0x0a -> ExpOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0x0b -> SignExtendOperationV2.staticOperation(frame, frame.stackDataV2()); case 0x10 -> LtOperationV2.staticOperation(frame, frame.stackDataV2()); case 0x11 -> GtOperationV2.staticOperation(frame, frame.stackDataV2()); case 0x12 -> SltOperationV2.staticOperation(frame, frame.stackDataV2()); case 0x13 -> SgtOperationV2.staticOperation(frame, frame.stackDataV2()); case 0x14 -> EqOperationV2.staticOperation(frame, frame.stackDataV2()); case 0x15 -> IsZeroOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x16 -> AndOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x17 -> OrOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x18 -> XorOperationV2.staticOperation(frame, frame.stackDataV2()); case 0x19 -> NotOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x1a -> ByteOperationV2.staticOperation(frame, frame.stackDataV2()); case 0x1b -> enableConstantinople ? ShlOperationV2.staticOperation(frame, frame.stackDataV2()) 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/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/OrOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/OrOperationV2.java new file mode 100644 index 00000000000..b9b0d82cd09 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/OrOperationV2.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 OR operation (0x17). */ +public class OrOperationV2 extends AbstractFixedCostOperationV2 { + + /** The Or operation success result. */ + static final OperationResult orSuccess = new OperationResult(3, null); + + /** + * Instantiates a new Or operation. + * + * @param gasCalculator the gas calculator + */ + public OrOperationV2(final GasCalculator gasCalculator) { + super(0x17, "OR", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs OR 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.or(stack, frame.stackTopV2())); + return orSuccess; + } +} 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/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; + } +} From a55629f9515ee259040338cf946ae35676887078 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Sun, 5 Apr 2026 15:14:15 +1000 Subject: [PATCH 16/24] Introduce AbstractOperationV2 and fix spurious account tracking in read-only V2 ops Add AbstractOperationV2 as a standalone V2 base class that implements Operation directly (no V1 AbstractOperation dependency), enabling future removal of V1 code. Provides protected static helpers (getAccount, getMutableAccount, getOrCreateAccount, getSenderAccount, getStorageValue) callable from both instance and staticOperation() methods. Migrate all 26 V2 operations from extends AbstractOperation to extends AbstractOperationV2 (AbstractFixedCostOperationV2, AbstractCallOperationV2, and AbstractCreateOperationV2 also updated, covering the remaining 61 ops transitively). Fix BALANCE, EXTCODESIZE, EXTCODEHASH, EXTCODECOPY, and SELFBALANCE to use the new getAccount(address, frame) static helper instead of getWorldUpdater().getAccount(), which was incorrectly wrapping cold accounts in UpdateTrackingAccount and adding them to updatedAccounts. This caused empty accounts to be deleted by clearAccountsThatAreEmpty() purely from read operations, producing a different state root and failing EEST benchmark tests with chain header mismatches. Co-Authored-By: Claude Opus 4.6 Signed-off-by: Simon Dudley --- .../v2/operation/AbstractCallOperationV2.java | 3 +- .../operation/AbstractCreateOperationV2.java | 3 +- .../AbstractFixedCostOperationV2.java | 3 +- .../evm/v2/operation/AbstractOperationV2.java | 176 ++++++++++++++++++ .../evm/v2/operation/BalanceOperationV2.java | 7 +- .../evm/v2/operation/BlobHashOperationV2.java | 3 +- .../v2/operation/BlockHashOperationV2.java | 3 +- .../v2/operation/CallDataCopyOperationV2.java | 3 +- .../evm/v2/operation/CodeCopyOperationV2.java | 3 +- .../besu/evm/v2/operation/ExpOperationV2.java | 3 +- .../v2/operation/ExtCodeCopyOperationV2.java | 5 +- .../v2/operation/ExtCodeHashOperationV2.java | 5 +- .../v2/operation/ExtCodeSizeOperationV2.java | 5 +- .../evm/v2/operation/InvalidOperationV2.java | 3 +- .../v2/operation/Keccak256OperationV2.java | 3 +- .../besu/evm/v2/operation/LogOperationV2.java | 3 +- .../evm/v2/operation/MCopyOperationV2.java | 3 +- .../evm/v2/operation/MloadOperationV2.java | 3 +- .../evm/v2/operation/Mstore8OperationV2.java | 3 +- .../evm/v2/operation/MstoreOperationV2.java | 3 +- .../besu/evm/v2/operation/PayOperationV2.java | 3 +- .../operation/ReturnDataCopyOperationV2.java | 3 +- .../evm/v2/operation/ReturnOperationV2.java | 3 +- .../evm/v2/operation/RevertOperationV2.java | 3 +- .../evm/v2/operation/SLoadOperationV2.java | 3 +- .../evm/v2/operation/SStoreOperationV2.java | 3 +- .../v2/operation/SelfBalanceOperationV2.java | 2 +- .../v2/operation/SelfDestructOperationV2.java | 3 +- 28 files changed, 208 insertions(+), 58 deletions(-) create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractOperationV2.java diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCallOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCallOperationV2.java index 8fb0cbda66c..f98cc150cb3 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCallOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCallOperationV2.java @@ -31,7 +31,6 @@ import org.hyperledger.besu.evm.frame.MessageFrame.State; import org.hyperledger.besu.evm.frame.SoftFailureReason; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.operation.AbstractOperation; import org.hyperledger.besu.evm.v2.StackArithmetic; import org.hyperledger.besu.evm.worldstate.CodeDelegationHelper; @@ -44,7 +43,7 @@ * {@code enableEvmV2(true)}, suspends the parent, and writes the result back when the child * completes. */ -public abstract class AbstractCallOperationV2 extends AbstractOperation { +public abstract class AbstractCallOperationV2 extends AbstractOperationV2 { /** Underflow response returned when the stack does not have enough items. */ protected static final OperationResult UNDERFLOW_RESPONSE = 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 index d18cfb18c13..ff7e65c8dc6 100644 --- 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 @@ -27,7 +27,6 @@ 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.operation.AbstractOperation; import org.hyperledger.besu.evm.v2.StackArithmetic; import java.util.Optional; @@ -43,7 +42,7 @@ * {@code enableEvmV2(true)}, suspends the parent, and writes the result back when the child * completes. */ -public abstract class AbstractCreateOperationV2 extends AbstractOperation { +public abstract class AbstractCreateOperationV2 extends AbstractOperationV2 { /** Underflow response returned when the stack does not have enough items. */ protected static final OperationResult UNDERFLOW_RESPONSE = diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java index b00cb8657d8..ae67c92366d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java @@ -20,10 +20,9 @@ import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.internal.OverflowException; import org.hyperledger.besu.evm.internal.UnderflowException; -import org.hyperledger.besu.evm.operation.AbstractOperation; /** The Abstract fixed cost operation. */ -abstract class AbstractFixedCostOperationV2 extends AbstractOperation { +abstract class AbstractFixedCostOperationV2 extends AbstractOperationV2 { /** Shared underflow response for static operation methods. */ static final OperationResult UNDERFLOW_RESPONSE = diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractOperationV2.java new file mode 100644 index 00000000000..7df3117f7a4 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractOperationV2.java @@ -0,0 +1,176 @@ +/* + * 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.account.Account; +import org.hyperledger.besu.evm.account.MutableAccount; +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.units.bigints.UInt256; + +/** + * Base class for all EVM v2 operations using the flat {@code long[]} stack representation. + * + *

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/BalanceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java index 2649a59668e..b5e5a50ba9b 100644 --- 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 @@ -17,10 +17,10 @@ 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.operation.AbstractOperation; import org.hyperledger.besu.evm.v2.StackArithmetic; /** @@ -29,7 +29,7 @@ *

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 AbstractOperation { +public class BalanceOperationV2 extends AbstractOperationV2 { /** * Instantiates a new Balance operation. @@ -83,8 +83,7 @@ public static OperationResult staticOperation( if (frame.getRemainingGas() < cost) { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); } - final org.hyperledger.besu.evm.account.MutableAccount account = - frame.getWorldUpdater().getAccount(address); + final Account account = getAccount(address, frame); // Overwrite in place (pop 1, push 1) if (account == null) { StackArithmetic.putAt(s, top, 0, UInt256.ZERO); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobHashOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobHashOperationV2.java index 59c2607b08b..6e3f6608bf0 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobHashOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobHashOperationV2.java @@ -19,7 +19,6 @@ 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.AbstractOperation; import org.hyperledger.besu.evm.v2.StackArithmetic; import java.util.List; @@ -30,7 +29,7 @@ *

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 AbstractOperation { +public class BlobHashOperationV2 extends AbstractOperationV2 { /** BLOBHASH opcode number */ public static final int OPCODE = 0x49; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlockHashOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlockHashOperationV2.java index 2597fea6566..cb71e224e0f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlockHashOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlockHashOperationV2.java @@ -21,7 +21,6 @@ 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.AbstractOperation; import org.hyperledger.besu.evm.v2.StackArithmetic; /** @@ -30,7 +29,7 @@ *

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 AbstractOperation { +public class BlockHashOperationV2 extends AbstractOperationV2 { /** * Instantiates a new BlockHash operation. 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 index 9903ea18a19..af731bc1a2f 100644 --- 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 @@ -18,7 +18,6 @@ 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.AbstractOperation; import org.hyperledger.besu.evm.v2.StackArithmetic; /** @@ -27,7 +26,7 @@ *

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 AbstractOperation { +public class CallDataCopyOperationV2 extends AbstractOperationV2 { /** * Instantiates a new CallDataCopy operation. 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 index 824e72872f3..e02fca7bdfe 100644 --- 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 @@ -18,7 +18,6 @@ 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.AbstractOperation; import org.hyperledger.besu.evm.v2.StackArithmetic; /** @@ -27,7 +26,7 @@ *

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 AbstractOperation { +public class CodeCopyOperationV2 extends AbstractOperationV2 { /** * Instantiates a new CodeCopy operation. 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 index 4e97881365e..e7e34096b0c 100644 --- 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 @@ -18,11 +18,10 @@ 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.AbstractOperation; import org.hyperledger.besu.evm.v2.StackArithmetic; /** The Exp operation. */ -public class ExpOperationV2 extends AbstractOperation { +public class ExpOperationV2 extends AbstractOperationV2 { /** * Instantiates a new Exp operation. 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 index fdaecfd63a7..41c9a4c44d7 100644 --- 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 @@ -22,7 +22,6 @@ 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.AbstractOperation; import org.hyperledger.besu.evm.v2.StackArithmetic; import org.apache.tuweni.bytes.Bytes; @@ -33,7 +32,7 @@ *

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 AbstractOperation { +public class ExtCodeCopyOperationV2 extends AbstractOperationV2 { /** * Instantiates a new ExtCodeCopy operation. @@ -103,7 +102,7 @@ public static OperationResult staticOperation( return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); } - final Account account = frame.getWorldUpdater().getAccount(address); + final Account account = getAccount(address, frame); final Bytes code = account != null ? account.getCode() : Bytes.EMPTY; frame.writeMemory(memOffset, sourceOffset, numBytes, code); 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 index d4e16b01173..759ffb49b9a 100644 --- 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 @@ -20,7 +20,6 @@ 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.AbstractOperation; import org.hyperledger.besu.evm.v2.StackArithmetic; /** @@ -29,7 +28,7 @@ *

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 AbstractOperation { +public class ExtCodeHashOperationV2 extends AbstractOperationV2 { /** * Instantiates a new ExtCodeHash operation. @@ -84,7 +83,7 @@ public static OperationResult staticOperation( return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); } - final Account account = frame.getWorldUpdater().getAccount(address); + final Account account = getAccount(address, frame); // Overwrite in place (pop 1, push 1) if (account == null || account.isEmpty()) { 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 index ed61e7136aa..f0708bfc954 100644 --- 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 @@ -20,7 +20,6 @@ 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.AbstractOperation; import org.hyperledger.besu.evm.v2.StackArithmetic; /** @@ -29,7 +28,7 @@ *

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 AbstractOperation { +public class ExtCodeSizeOperationV2 extends AbstractOperationV2 { /** * Instantiates a new ExtCodeSize operation. @@ -83,7 +82,7 @@ public static OperationResult staticOperation( if (frame.getRemainingGas() < cost) { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); } - final Account account = frame.getWorldUpdater().getAccount(address); + 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); 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 index 6793315759a..98df3487d21 100644 --- 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 @@ -18,10 +18,9 @@ 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.AbstractOperation; /** EVM v2 INVALID operation. Always halts with INVALID_OPERATION. */ -public class InvalidOperationV2 extends AbstractOperation { +public class InvalidOperationV2 extends AbstractOperationV2 { /** The constant OPCODE. */ public static final int OPCODE = 0xFE; 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 index c4b4094ab85..3ccc0349601 100644 --- 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 @@ -20,7 +20,6 @@ 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.AbstractOperation; import org.hyperledger.besu.evm.v2.StackArithmetic; import org.apache.tuweni.bytes.Bytes; @@ -31,7 +30,7 @@ *

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 AbstractOperation { +public class Keccak256OperationV2 extends AbstractOperationV2 { /** * Instantiates a new Keccak256 operation. 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 index d2f7314c02b..11c9d10f696 100644 --- 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 @@ -21,7 +21,6 @@ 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.AbstractOperation; import org.hyperledger.besu.evm.v2.StackArithmetic; import com.google.common.collect.ImmutableList; @@ -31,7 +30,7 @@ /** * EVM v2 LOG operation (LOG0-LOG4) using long[] stack representation. Parameterized by topic count. */ -public class LogOperationV2 extends AbstractOperation { +public class LogOperationV2 extends AbstractOperationV2 { private final int numTopics; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MCopyOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MCopyOperationV2.java index 7a7ba89c25c..dcfc8e9f43b 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MCopyOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MCopyOperationV2.java @@ -18,12 +18,11 @@ 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.AbstractOperation; import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.v2.StackArithmetic; /** The MCOPY operation for EVM v2. */ -public class MCopyOperationV2 extends AbstractOperation { +public class MCopyOperationV2 extends AbstractOperationV2 { /** * Instantiates a new MCOPY operation. diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MloadOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MloadOperationV2.java index 7b942d18636..f3ff75247df 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MloadOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MloadOperationV2.java @@ -18,12 +18,11 @@ 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.AbstractOperation; import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.v2.StackArithmetic; /** The MLOAD operation for EVM v2. */ -public class MloadOperationV2 extends AbstractOperation { +public class MloadOperationV2 extends AbstractOperationV2 { /** * Instantiates a new MLOAD operation. diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Mstore8OperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Mstore8OperationV2.java index b140f645391..71840993ba5 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Mstore8OperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Mstore8OperationV2.java @@ -18,12 +18,11 @@ 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.AbstractOperation; import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.v2.StackArithmetic; /** The MSTORE8 operation for EVM v2. */ -public class Mstore8OperationV2 extends AbstractOperation { +public class Mstore8OperationV2 extends AbstractOperationV2 { /** * Instantiates a new MSTORE8 operation. diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MstoreOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MstoreOperationV2.java index 4709a5d8027..6d05e1c444e 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MstoreOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MstoreOperationV2.java @@ -18,14 +18,13 @@ 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.AbstractOperation; import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.v2.StackArithmetic; import org.apache.tuweni.bytes.Bytes; /** The MSTORE operation for EVM v2. */ -public class MstoreOperationV2 extends AbstractOperation { +public class MstoreOperationV2 extends AbstractOperationV2 { /** * Instantiates a new MSTORE operation. diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PayOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PayOperationV2.java index 1617ac1b4af..b48923c4dfd 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PayOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PayOperationV2.java @@ -24,7 +24,6 @@ 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.AbstractOperation; import org.hyperledger.besu.evm.v2.StackArithmetic; import java.util.Objects; @@ -32,7 +31,7 @@ import org.apache.tuweni.bytes.Bytes; /** EVM v2 PAY operation (EIP-7708) using long[] stack representation. */ -public class PayOperationV2 extends AbstractOperation { +public class PayOperationV2 extends AbstractOperationV2 { /** * Instantiates a new Pay operation. 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 index f805b53e70d..dcedc0b8d28 100644 --- 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 @@ -18,7 +18,6 @@ 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.AbstractOperation; import org.hyperledger.besu.evm.v2.StackArithmetic; import org.apache.tuweni.bytes.Bytes; @@ -30,7 +29,7 @@ * 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 AbstractOperation { +public class ReturnDataCopyOperationV2 extends AbstractOperationV2 { private static final OperationResult INVALID_RETURN_DATA_BUFFER_ACCESS = new OperationResult(0L, ExceptionalHaltReason.INVALID_RETURN_DATA_BUFFER_ACCESS); 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 index 27ad0b795ad..4fd802b7a86 100644 --- 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 @@ -18,11 +18,10 @@ 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.AbstractOperation; import org.hyperledger.besu.evm.v2.StackArithmetic; /** EVM v2 RETURN operation using long[] stack representation. */ -public class ReturnOperationV2 extends AbstractOperation { +public class ReturnOperationV2 extends AbstractOperationV2 { /** RETURN opcode number */ public static final int OPCODE = 0xF3; 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 index a155f26151d..41cb2206ed5 100644 --- 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 @@ -18,13 +18,12 @@ 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.AbstractOperation; 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 AbstractOperation { +public class RevertOperationV2 extends AbstractOperationV2 { /** REVERT opcode number */ public static final int OPCODE = 0xFD; 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 index 8a7a342fe92..482d4269f8e 100644 --- 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 @@ -19,7 +19,6 @@ 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.AbstractOperation; import org.hyperledger.besu.evm.v2.StackArithmetic; import org.apache.tuweni.bytes.Bytes32; @@ -31,7 +30,7 @@ *

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 AbstractOperation { +public class SLoadOperationV2 extends AbstractOperationV2 { private final long warmCost; private final long coldCost; 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 index 4fad5159a3d..0e9bf17fe6a 100644 --- 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 @@ -19,7 +19,6 @@ 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.AbstractOperation; import org.hyperledger.besu.evm.v2.StackArithmetic; import java.util.function.Supplier; @@ -35,7 +34,7 @@ *

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 AbstractOperation { +public class SStoreOperationV2 extends AbstractOperationV2 { /** Minimum gas remaining for Frontier (no minimum). */ public static final long FRONTIER_MINIMUM = 0L; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java index e378adcd8cc..dc7d51cf6f0 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java @@ -54,7 +54,7 @@ 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 = frame.getWorldUpdater().getAccount(frame.getRecipientAddress()); + final Account account = getAccount(frame.getRecipientAddress(), frame); if (account == null) { frame.setTopV2(StackArithmetic.pushZero(s, top)); } else { 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 index f3c01d59e10..4d4ec708667 100644 --- 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 @@ -23,11 +23,10 @@ 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.operation.AbstractOperation; import org.hyperledger.besu.evm.v2.StackArithmetic; /** EVM v2 SELFDESTRUCT operation using long[] stack representation. */ -public class SelfDestructOperationV2 extends AbstractOperation { +public class SelfDestructOperationV2 extends AbstractOperationV2 { private final boolean eip6780Semantics; private final TransferLogEmitter transferLogEmitter; From 4a094d923065afe3ff72c0e27027a72e1fab9af3 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Sun, 5 Apr 2026 19:55:18 +1000 Subject: [PATCH 17/24] Fix V2 CALL/CREATE stack check and SSTORE minimum gas - AbstractCallOperationV2/AbstractCreateOperationV2: replace frame.stackSize() (reads V1 OperandStack, always 0 in V2 mode) with frame.stackHasItems() (checks V2 stackTopV2). This was causing every CALL, STATICCALL, CALLCODE, DELEGATECALL, CREATE, and CREATE2 to immediately return UNDERFLOW_RESPONSE. - SStoreOperationV2: make 4-arg staticOperation() public so callers can pass an explicit minimumGasRemaining. - EVM.java V2 SSTORE dispatch: pass EIP_1706_MINIMUM (2300L) instead of FRONTIER_MINIMUM (0L); V2 only targets Istanbul+ forks. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Simon Dudley --- evm/src/main/java/org/hyperledger/besu/evm/EVM.java | 6 +++++- .../evm/v2/operation/AbstractCallOperationV2.java | 2 +- .../evm/v2/operation/AbstractCreateOperationV2.java | 2 +- .../besu/evm/v2/operation/SStoreOperationV2.java | 11 ++++++++++- 4 files changed, 17 insertions(+), 4 deletions(-) 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 e52d8411a6b..9f4080ba514 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java @@ -750,7 +750,11 @@ private void runToHaltV2(final MessageFrame frame, final OperationTracer tracing case 0x54 -> SLoadOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); case 0x55 -> - SStoreOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + SStoreOperationV2.staticOperation( + frame, + frame.stackDataV2(), + gasCalculator, + SStoreOperationV2.EIP_1706_MINIMUM); case 0x5c -> enableCancun ? TLoadOperationV2.staticOperation(frame, frame.stackDataV2()) diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCallOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCallOperationV2.java index f98cc150cb3..d04490d433b 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCallOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCallOperationV2.java @@ -191,7 +191,7 @@ protected boolean isDelegate() { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - if (frame.stackSize() < getStackItemsConsumed()) { + if (!frame.stackHasItems(getStackItemsConsumed())) { return UNDERFLOW_RESPONSE; } 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 index ff7e65c8dc6..4594cad4514 100644 --- 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 @@ -68,7 +68,7 @@ protected AbstractCreateOperationV2( @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - if (frame.stackSize() < getStackItemsConsumed()) { + if (!frame.stackHasItems(getStackItemsConsumed())) { return UNDERFLOW_RESPONSE; } 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 index 0e9bf17fe6a..76c4d0523f9 100644 --- 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 @@ -86,7 +86,16 @@ public static OperationResult staticOperation( return staticOperation(frame, s, gasCalculator, FRONTIER_MINIMUM); } - private static OperationResult staticOperation( + /** + * 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, From b173b7a76302cd43b55ed23aec348aa2d71f7e04 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Sun, 5 Apr 2026 20:07:39 +1000 Subject: [PATCH 18/24] Fix SelfDestructOperationV2 to use AbstractOperationV2 account helpers Replace direct frame.getWorldUpdater().get/getOrCreate() calls with the static helpers from AbstractOperationV2: - beneficiary read: getAccount() (no side effects, EIP-7928 tracking) - originator mutable: getMutableAccount() (matches V1 behaviour; returns null for deleted accounts rather than creating a ghost entry) - beneficiary mutable: getOrCreateAccount() (must create if absent to receive balance transfer) Using getOrCreate() for the originator was the likely cause of the two remaining test_selfdestruct_existing failures: it manufactured a phantom zero-balance account and then called decrementBalance on it, producing incorrect world-state. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Simon Dudley --- .../besu/evm/v2/operation/SelfDestructOperationV2.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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 index 4d4ec708667..07b5c5feafe 100644 --- 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 @@ -113,9 +113,9 @@ public static OperationResult staticOperation( return new OperationResult(staticCost, ExceptionalHaltReason.INSUFFICIENT_GAS); } - final Account beneficiaryNullable = frame.getWorldUpdater().get(beneficiaryAddress); + final Account beneficiaryNullable = getAccount(beneficiaryAddress, frame); final Address originatorAddress = frame.getRecipientAddress(); - final MutableAccount originatorAccount = frame.getWorldUpdater().getOrCreate(originatorAddress); + final MutableAccount originatorAccount = getMutableAccount(originatorAddress, frame); final Wei originatorBalance = originatorAccount.getBalance(); final long cost = @@ -126,8 +126,7 @@ public static OperationResult staticOperation( return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); } - final MutableAccount beneficiaryAccount = - frame.getWorldUpdater().getOrCreate(beneficiaryAddress); + final MutableAccount beneficiaryAccount = getOrCreateAccount(beneficiaryAddress, frame); final boolean willBeDestroyed = !eip6780Semantics || frame.wasCreatedInTransaction(originatorAccount.getAddress()); From 3926718721c87f63f0117c2acebeb35990d5d18b Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Sun, 5 Apr 2026 21:40:49 +1000 Subject: [PATCH 19/24] Fix gasAvailableForChildCall ordering in AbstractCallOperationV2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gasAvailableForChildCall() in TangerineWhistleGasCalculator mutates the frame by calling frame.decrementRemainingGas(gasCap) internally. V2 was calling it before gas cost checks, so all subsequent checks saw remainingGas - gasCap instead of the full remainingGas. This caused incorrect gas accounting near loop boundaries with cold addresses (e.g. the selfdestruct_existing benchmark's CREATE2→CALL→SELFDESTRUCT loop where every CALL hits a cold address at 2600 gas cost). Fix mirrors V1 AbstractCallOperation: call gasAvailableForChildCall after frame.decrementRemainingGas(cost), so gas checks see the correct remaining gas before the child cap is deducted. This fixes the 2 remaining test_selfdestruct_existing failures, bringing the EEST benchmark suite to 940/940 with --Xevm-go-fast=true. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Simon Dudley --- .../besu/evm/v2/operation/AbstractCallOperationV2.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCallOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCallOperationV2.java index d04490d433b..15271fdcfc1 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCallOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCallOperationV2.java @@ -207,7 +207,6 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { final long outputLength = outputDataLength(s, top); final Wei transferValue = value(s, top); final Address recipientAddress = address(frame, s, top); - final long gasForChild = gasAvailableForChildCall(frame, s, top); final long staticCost = gasCalculator() @@ -250,6 +249,7 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); } frame.decrementRemainingGas(cost); + final long gasForChild = gasAvailableForChildCall(frame, s, top); frame.clearReturnData(); From a1e3add97999420c2282bb6671cbbcb55d46b762 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Sun, 5 Apr 2026 22:07:02 +1000 Subject: [PATCH 20/24] Add test.evm.v2 system property to enable EVM v2 in reference tests Allows running reference tests with the experimental EVM v2 (long[] stack) via: ./gradlew referenceTests -Dtest.evm.v2=true Also forwards test.ethereum.state.eips, test.ethereum.blockchain.eips, and test.ethereum.include to the forked test JVM, which previously were read by Java code but never propagated. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Simon Dudley --- ethereum/referencetests/build.gradle | 10 ++++++++++ .../ReferenceTestProtocolSchedules.java | 11 ++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/ethereum/referencetests/build.gradle b/ethereum/referencetests/build.gradle index 3aba4c6cbee..0ec8ffcccee 100644 --- a/ethereum/referencetests/build.gradle +++ b/ethereum/referencetests/build.gradle @@ -330,6 +330,11 @@ 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 +347,11 @@ 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) { From cb5d2c9b3dfd61eb1ccc0e7174981fd5f36d65d3 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Mon, 6 Apr 2026 08:10:50 +1000 Subject: [PATCH 21/24] Fix EVM V2 reference test failures: gas costs, fork gates, state gas - Fix TLOAD/TSTORE gas cost from 3 to 100 (EIP-1153 WARM_STORAGE_READ_COST) - Add DifficultyOperationV2 for pre-Paris DIFFICULTY opcode - Gate RETURNDATASIZE/RETURNDATACOPY on enableByzantium in V2 dispatch - Fix SELFBALANCE gate from enableConstantinople to enableIstanbul - Gate DIFFICULTY/PREVRANDAO on enableParis in V2 dispatch - Add EIP-8037 state gas charging to AbstractCallOperationV2, AbstractCreateOperationV2, and SelfDestructOperationV2 Resolves ~1305 of 1524 reference test failures when running with -Dtest.evm.v2=true. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Simon Dudley --- ethereum/referencetests/build.gradle | 14 ++++- .../java/org/hyperledger/besu/evm/EVM.java | 25 ++++++-- .../v2/operation/AbstractCallOperationV2.java | 8 +++ .../operation/AbstractCreateOperationV2.java | 6 ++ .../v2/operation/DifficultyOperationV2.java | 59 +++++++++++++++++++ .../v2/operation/SelfDestructOperationV2.java | 13 ++++ .../evm/v2/operation/TLoadOperationV2.java | 6 +- .../evm/v2/operation/TStoreOperationV2.java | 8 +-- 8 files changed, 123 insertions(+), 16 deletions(-) create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DifficultyOperationV2.java diff --git a/ethereum/referencetests/build.gradle b/ethereum/referencetests/build.gradle index 0ec8ffcccee..820374d8203 100644 --- a/ethereum/referencetests/build.gradle +++ b/ethereum/referencetests/build.gradle @@ -330,7 +330,12 @@ dependencies { tasks.register('referenceTests', Test) { useJUnitPlatform() - for (String name : ['test.evm.v2', 'test.ethereum.state.eips', 'test.ethereum.blockchain.eips', 'test.ethereum.include']) { + 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) } @@ -347,7 +352,12 @@ 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']) { + 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) } 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 9f4080ba514..d54073e3d05 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java @@ -114,6 +114,7 @@ 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; @@ -206,8 +207,11 @@ 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; @@ -239,8 +243,11 @@ 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(); @@ -779,8 +786,10 @@ private void runToHaltV2(final MessageFrame frame, final OperationTracer tracing case 0x3c -> ExtCodeCopyOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); case 0x3e -> - ReturnDataCopyOperationV2.staticOperation( - frame, frame.stackDataV2(), gasCalculator); + enableByzantium + ? ReturnDataCopyOperationV2.staticOperation( + frame, frame.stackDataV2(), gasCalculator) + : InvalidOperation.invalidOperationResult(opcode); case 0x3f -> enableConstantinople ? ExtCodeHashOperationV2.staticOperation( @@ -788,7 +797,7 @@ private void runToHaltV2(final MessageFrame frame, final OperationTracer tracing : InvalidOperation.invalidOperationResult(opcode); case 0x40 -> BlockHashOperationV2.staticOperation(frame, frame.stackDataV2()); case 0x47 -> - enableConstantinople + enableIstanbul ? SelfBalanceOperationV2.staticOperation(frame, frame.stackDataV2()) : InvalidOperation.invalidOperationResult(opcode); case 0x49 -> @@ -803,11 +812,17 @@ private void runToHaltV2(final MessageFrame frame, final OperationTracer tracing case 0x36 -> CallDataSizeOperationV2.staticOperation(frame, frame.stackDataV2()); case 0x38 -> CodeSizeOperationV2.staticOperation(frame, frame.stackDataV2()); case 0x3a -> GasPriceOperationV2.staticOperation(frame, frame.stackDataV2()); - case 0x3d -> ReturnDataSizeOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x3d -> + enableByzantium + ? ReturnDataSizeOperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); case 0x41 -> CoinbaseOperationV2.staticOperation(frame, frame.stackDataV2()); case 0x42 -> TimestampOperationV2.staticOperation(frame, frame.stackDataV2()); case 0x43 -> NumberOperationV2.staticOperation(frame, frame.stackDataV2()); - case 0x44 -> PrevRandaoOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x44 -> + enableParis + ? PrevRandaoOperationV2.staticOperation(frame, frame.stackDataV2()) + : DifficultyOperationV2.staticOperation(frame, frame.stackDataV2()); case 0x45 -> GasLimitOperationV2.staticOperation(frame, frame.stackDataV2()); case 0x46 -> // CHAINID (Istanbul+) chainIdOperationV2 != null diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCallOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCallOperationV2.java index 15271fdcfc1..b3b0703356e 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCallOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCallOperationV2.java @@ -249,6 +249,14 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { 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(); 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 index 4594cad4514..c9a990fbc0d 100644 --- 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 @@ -116,6 +116,12 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { 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); 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/SelfDestructOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfDestructOperationV2.java index 07b5c5feafe..2de0cec99b8 100644 --- 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 @@ -126,6 +126,19 @@ public static OperationResult staticOperation( 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 = 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 index 06592b1387b..ff91e65a1c9 100644 --- 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 @@ -26,11 +26,11 @@ * 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. Fixed gas cost equal to veryLow tier. + * back. Gas cost is WARM_STORAGE_READ_COST (100) per EIP-1153. */ public class TLoadOperationV2 extends AbstractFixedCostOperationV2 { - private static final long GAS_COST = 3L; + 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); @@ -67,12 +67,10 @@ public static OperationResult staticOperation(final MessageFrame frame, final lo final int top = frame.stackTopV2(); - // Extract the 32-byte slot key from the stack final byte[] keyBytes = new byte[32]; StackArithmetic.toBytesAt(s, top, 0, keyBytes); final Bytes32 slot = Bytes32.wrap(keyBytes); - // Load from transient storage and overwrite top slot in place final byte[] result = frame.getTransientStorageValue(frame.getRecipientAddress(), slot).toArrayUnsafe(); StackArithmetic.fromBytesAt(s, top, 0, result, 0, result.length); 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 index d09e2911227..71e9cea9d13 100644 --- 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 @@ -25,12 +25,12 @@ /** * 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. Fixed - * gas cost equal to veryLow tier. + *

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 = 3L; + 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); @@ -63,13 +63,11 @@ public static OperationResult staticOperation(final MessageFrame frame, final lo final int top = frame.stackTopV2(); - // Extract key (depth 0) and value (depth 1) as 32-byte arrays final byte[] keyBytes = new byte[32]; final byte[] valueBytes = new byte[32]; StackArithmetic.toBytesAt(s, top, 0, keyBytes); StackArithmetic.toBytesAt(s, top, 1, valueBytes); - // Pop 2 frame.setTopV2(top - 2); final long remainingGas = frame.getRemainingGas(); From f4d7757c26decc3bcef8ce54f32f7238b15a0f2b Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Mon, 6 Apr 2026 08:39:36 +1000 Subject: [PATCH 22/24] Fix CallDataLoadOperationV2 int-overflow when offset > Integer.MAX_VALUE When s[off+3] is a large positive long (e.g. 0x80000000L), the old check 's[off+3] < 0' does not trigger because the value is positive as a long. Casting it to int then wraps to a negative int, making 'data.size() - offset' overflow to a large positive value, and tuweni's slice() throws IndexOutOfBoundsException. Replace the check with '(s[off+3] >>> 31) != 0' which catches both negative longs and longs > Integer.MAX_VALUE, correctly treating any offset that doesn't fit in a non-negative int as "beyond calldata = zero". Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Simon Dudley --- .../besu/evm/v2/operation/CallDataLoadOperationV2.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index cce5be2d2bc..4c225d91aea 100644 --- 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 @@ -62,7 +62,7 @@ public static Operation.OperationResult staticOperation( 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] < 0) { + 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; From a9a4d977cec7119fae9ac204ab4bee2fe8939aec Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Mon, 6 Apr 2026 15:14:39 +1000 Subject: [PATCH 23/24] Fix EVM V2 fork gates: REVERT (Byzantium+) and SLOTNUM (Amsterdam+) - Gate REVERT (0xFD) on enableByzantium in V2 dispatch - REVERT was introduced in EIP-140 (Byzantium) but was dispatched unconditionally, causing Frontier/Homestead reference test failures - Fix SLOTNUM (0x4B) gate from enableOsaka to enableAmsterdam - SLOTNUM is registered in registerAmsterdamOperations, not registerOsakaOperations, causing Osaka reference test failures Resolves 11 remaining reference test failures with -Dtest.evm.v2=true. Co-Authored-By: Claude Opus 4.6 Signed-off-by: Simon Dudley --- evm/src/main/java/org/hyperledger/besu/evm/EVM.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 d54073e3d05..47b93809356 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java @@ -836,8 +836,8 @@ private void runToHaltV2(final MessageFrame frame, final OperationTracer tracing enableCancun ? BlobBaseFeeOperationV2.staticOperation(frame, frame.stackDataV2()) : InvalidOperation.invalidOperationResult(opcode); - case 0x4b -> // SLOTNUM (Osaka+) - enableOsaka + case 0x4b -> // SLOTNUM (Amsterdam+) + enableAmsterdam ? SlotNumOperationV2.staticOperation(frame, frame.stackDataV2()) : InvalidOperation.invalidOperationResult(opcode); case 0x58 -> PcOperationV2.staticOperation(frame, frame.stackDataV2()); @@ -850,8 +850,10 @@ private void runToHaltV2(final MessageFrame frame, final OperationTracer tracing case 0x5b -> JumpDestOperationV2.staticOperation(frame); case 0xf3 -> ReturnOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); - case 0xfd -> - RevertOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0xfd -> // REVERT (Byzantium+) + enableByzantium + ? RevertOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator) + : InvalidOperation.invalidOperationResult(opcode); case 0xfe -> InvalidOperationV2.INVALID_RESULT; case 0xa0, 0xa1, 0xa2, 0xa3, 0xa4 -> { int topicCount = opcode - 0xa0; From 31b756a0f4fae60773b80274790911eccb7e266c Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Sun, 12 Apr 2026 07:13:36 +1000 Subject: [PATCH 24/24] Add StackPool Signed-off-by: Simon Dudley --- .../besu/evm/frame/MessageFrame.java | 14 +- .../processor/AbstractMessageProcessor.java | 2 + .../hyperledger/besu/evm/v2/StackPool.java | 125 ++++++++++++++++++ 3 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/StackPool.java diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java index b122a461706..e17ae5eb451 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java @@ -29,9 +29,11 @@ import org.hyperledger.besu.evm.internal.StorageEntry; import org.hyperledger.besu.evm.internal.UnderflowException; import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackPool; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.ArrayList; +import java.util.Arrays; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; @@ -276,7 +278,7 @@ private MessageFrame( this.worldUpdater = worldUpdater; this.gasRemaining = initialGas; this.stack = new OperandStack(txValues.maxStackSize()); - this.stackDataV2 = enableEvmV2 ? new long[txValues.maxStackSize() * 4] : null; + this.stackDataV2 = enableEvmV2 ? StackPool.borrow(txValues.maxStackSize()) : null; this.stackTopV2 = 0; this.pc = 0; this.recipient = recipient; @@ -538,6 +540,16 @@ public void ensureV2Stack() { } } + /** Returns this frame's operand stack to the thread-local pool for reuse. */ + public void returnStackToPool() { + if (stackTopV2 > 0) { + Arrays.fill(stackDataV2, 0, stackTopV2 << 2, 0L); + } + stackTopV2 = 0; + StackPool.release(stackDataV2, txValues.maxStackSize()); + stackDataV2 = null; + } + // --------------------------------------------------------------------------- // endregion diff --git a/evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java b/evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java index b56dad19c1f..1d00b71b024 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java @@ -193,6 +193,7 @@ protected void revert(final MessageFrame frame) { private void completedSuccess(final MessageFrame frame) { frame.getWorldUpdater().commit(); frame.getMessageFrameStack().removeFirst(); + frame.returnStackToPool(); frame.notifyCompletion(); } @@ -203,6 +204,7 @@ private void completedSuccess(final MessageFrame frame) { */ private void completedFailed(final MessageFrame frame) { frame.getMessageFrameStack().removeFirst(); + frame.returnStackToPool(); frame.notifyCompletion(); } 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 POOL = ThreadLocal.withInitial(StackPool::new); + + long[][] stacks; + int size; // available stacks in array + int capacity; + int outstanding; // currently borrowed (borrows - releases) + int peakThisCycle; // max(outstanding) since last maintenance + int peakEmaX16; // EMA of peak, fixed-point <<4 + int idleCount; // times outstanding hit 0 since last maintenance + + StackPool() { + capacity = INITIAL_CAPACITY; + stacks = new long[INITIAL_CAPACITY][]; + for (int i = 0; i < INITIAL_CAPACITY; i++) { + stacks[i] = new long[DEFAULT_MAX_SIZE << 2]; + } + size = INITIAL_CAPACITY; + } + + /** + * Borrows a {@code long[]} stack array from the thread-local pool, or creates a new one if the + * pool is empty. + * + * @param maxSize the max stack size (number of UInt256 entries) + * @return a zeroed long[] array of size maxSize << 2 + */ + public static long[] borrow(final int maxSize) { + if (maxSize == DEFAULT_MAX_SIZE) { + return POOL.get().borrowInternal(); + } + return new long[maxSize << 2]; + } + + /** + * Returns a {@code long[]} stack array to the thread-local pool for reuse. + * + * @param data the long[] array to return + * @param maxSize the max stack size used when borrowing + */ + public static void release(final long[] data, final int maxSize) { + if (maxSize == DEFAULT_MAX_SIZE) { + POOL.get().releaseInternal(data); + } + } + + private long[] borrowInternal() { + outstanding++; + if (outstanding > peakThisCycle) { + peakThisCycle = outstanding; + } + if (size > 0) { + return stacks[--size]; + } + return new long[DEFAULT_MAX_SIZE << 2]; + } + + private void releaseInternal(final long[] data) { + outstanding--; + if (size < capacity) { + stacks[size++] = data; + } + // else: pool full, discard (GC reclaims) + + if (outstanding == 0) { + if (++idleCount >= MAINTENANCE_INTERVAL) { + maintain(); + } + } + } + + void maintain() { + // Update EMA: alpha = 1/4 -> peakEma = 3/4 * old + 1/4 * new + peakEmaX16 = (peakEmaX16 * 3 + (peakThisCycle << 4) + 2) >> 2; + peakThisCycle = 0; + idleCount = 0; + + int smoothedPeak = (peakEmaX16 + 8) >> 4; + int target = nextPowerOf2(Math.max(smoothedPeak * 2, INITIAL_CAPACITY)); + + if (target != capacity) { + long[][] newArr = new long[target][]; + int keep = Math.min(size, target); + System.arraycopy(stacks, 0, newArr, 0, keep); + stacks = newArr; + size = keep; + capacity = target; + } + } + + private static int nextPowerOf2(final int n) { + if (n <= 1) { + return 1; + } + return Integer.highestOneBit(n - 1) << 1; + } +}