From 60fdfe0c1ad115a2fba9394b7d011af343824fa7 Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Wed, 1 Apr 2026 16:07:37 +0200 Subject: [PATCH 01/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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 c4c7c3dd8e35ce4f7338b9cb1e63fe5e5dd8bc7d Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Tue, 7 Apr 2026 12:17:52 +0200 Subject: [PATCH 09/22] address comments. Signed-off-by: Ameziane H. --- .../v2/AbstractShiftOperationBenchmarkV2.java | 2 +- .../besu/evm/v2/StackArithmetic.java | 47 ++++++++++--------- 2 files changed, 27 insertions(+), 22 deletions(-) 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 index cd83e28f46f..31e64b2962f 100644 --- 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 @@ -38,7 +38,7 @@ public enum Case { SHIFT_0, /** Small shift by 1 bit. */ SHIFT_1, - /** Medium shift by 128 bits (half word). */ + /** Medium shift by 128 bits. */ SHIFT_128, /** Large shift by 255 bits (max valid). */ SHIFT_255, 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..6a9f808f3dc 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 @@ -33,8 +33,9 @@ private StackArithmetic() {} /** * 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. + *

Reads the shift amount (unsigned) and the value from the top two stack slots, writes {@code + * value << shift} back into the value slot and decrements the top. Shifts >= 256 or a zero value + * produce 0. * * @param stack the flat limb array * @param top current stack-top (item count) @@ -76,7 +77,9 @@ private static void shiftLeftInPlace(final long[] stack, final int valueOffset, w1 = stack[valueOffset + 1], w2 = stack[valueOffset + 2], w3 = stack[valueOffset + 3]; + // Number of whole 64-bit words to shift (shift / 64) final int wordShift = shift >>> 6; + // Remaining intra-word bit shift (shift % 64) final int bitShift = shift & 63; switch (wordShift) { case 0: @@ -132,8 +135,9 @@ private static long shiftLeftWord(final long value, final long nextValue, final /** * 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. + *

Reads the shift amount (unsigned) and the value from the top two stack slots, writes {@code + * value >>> shift} back into the value slot and decrements the top. Shifts >= 256 or a zero value + * produce 0. * * @param stack the flat limb array * @param top current stack-top (item count) @@ -164,17 +168,20 @@ public static int shr(final long[] stack, final int top) { /** * 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 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 shiftRightInPlace(final long[] s, final int valueOffset, final int shift) { + private static void shiftRightInPlace( + final long[] stack, final int valueOffset, final int shift) { if (shift == 0) return; - long w0 = s[valueOffset], - w1 = s[valueOffset + 1], - w2 = s[valueOffset + 2], - w3 = s[valueOffset + 3]; + long w0 = stack[valueOffset], + w1 = stack[valueOffset + 1], + w2 = stack[valueOffset + 2], + w3 = stack[valueOffset + 3]; + // Number of whole 64-bit words to shift (shift / 64) final int wordShift = shift >>> 6; + // Remaining intra-word bit shift (shift % 64) final int bitShift = shift & 63; switch (wordShift) { case 0: @@ -202,10 +209,10 @@ private static void shiftRightInPlace(final long[] s, final int valueOffset, fin w0 = 0; break; } - s[valueOffset] = w0; - s[valueOffset + 1] = w1; - s[valueOffset + 2] = w2; - s[valueOffset + 3] = w3; + stack[valueOffset] = w0; + stack[valueOffset + 1] = w1; + stack[valueOffset + 2] = w2; + stack[valueOffset + 3] = w3; } // endregion @@ -216,8 +223,9 @@ private static void shiftRightInPlace(final long[] s, final int valueOffset, fin /** * 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. + *

Reads the shift amount (unsigned) and the value (signed) from the top two stack slots, + * writes {@code value >> shift} back into the value slot and decrements the top. 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) @@ -260,7 +268,9 @@ private static void sarInPlace( w2 = stack[valueOffset + 2], w3 = stack[valueOffset + 3]; final long fill = negative ? -1L : 0L; + // Number of whole 64-bit words to shift (shift / 64) final int wordShift = shift >>> 6; + // Remaining intra-word bit shift (shift % 64) final int bitShift = shift & 63; switch (wordShift) { case 0: @@ -294,11 +304,6 @@ 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. * From 18bd044e50d64d04b206352d0a9abccc23b4a328 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Thu, 2 Apr 2026 22:09:01 +1000 Subject: [PATCH 10/22] 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 6a9f808f3dc..dea0ed6cf7d 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 @@ -320,5 +322,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 6b743640b521cfc4a00827ba1b9e189d93410254 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Fri, 3 Apr 2026 08:16:05 +1000 Subject: [PATCH 11/22] 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 a0decd28d41d0deb5a65ef4b60c64483e9bf174d Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Wed, 8 Apr 2026 16:56:14 +1000 Subject: [PATCH 12/22] javadoc Signed-off-by: Simon Dudley --- .../besu/evm/v2/StackArithmetic.java | 24 ++++++++++++------- .../evm/v2/operation/MulModOperationV2.java | 5 ++-- 2 files changed, 18 insertions(+), 11 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 707cc013bf5..c9230ec691d 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 @@ -327,19 +327,25 @@ private static long shiftRightWord(final long value, final long prevValue, final // 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) { + /** Performs EVM MULMOD (modular multiplication) on the three top stack items. + * @param stack the flat limb array + * @param top current stack-top (item count) + * @return the new stack-top after consuming three items and producing one item + * + * MULMOD: stack[top-3] = (stack[top-1] * stack[top-2]) mod stack[top-3], return top-2. + */ + public static int mulMod(final long[] stack, 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 va = new UInt256(stack[a], stack[a + 1], stack[a + 2], stack[a + 3]); + UInt256 vb = new UInt256(stack[b], stack[b + 1], stack[b + 2], stack[b + 3]); + UInt256 vc = new UInt256(stack[c], stack[c + 1], stack[c + 2], stack[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(); + stack[c] = r.u3(); + stack[c + 1] = r.u2(); + stack[c + 2] = r.u1(); + stack[c + 3] = r.u0(); return top - 2; } 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..96109fe6e9d 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,11 +44,12 @@ public Operation.OperationResult executeFixedCostOperation( * Performs MulMod 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(3)) return UNDERFLOW_RESPONSE; - frame.setTopV2(StackArithmetic.mulMod(s, frame.stackTopV2())); + frame.setTopV2(StackArithmetic.mulMod(stack, frame.stackTopV2())); return mulModSuccess; } } From 9f4fc6473000c0b7754ac1c65d61a345a63322a6 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Wed, 8 Apr 2026 16:59:49 +1000 Subject: [PATCH 13/22] Review comments - fixup benchmark param and comments Signed-off-by: Simon Dudley --- .../besu/ethereum/vm/operations/MulModOperationBenchmark.java | 4 ++-- .../ethereum/vm/operations/v2/MulModOperationBenchmarkV2.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/MulModOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/MulModOperationBenchmark.java index 5821f57e3d6..e25244b495d 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/MulModOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/MulModOperationBenchmark.java @@ -27,7 +27,7 @@ public class MulModOperationBenchmark extends TernaryOperationBenchmark { - // Benches for (a + b) % c + // Benches for (a * b) % c // Define available scenarios public enum Case { @@ -40,7 +40,7 @@ public enum Case { 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_128_128_128(4, 4, 4), MULMOD_192_32_32(6, 1, 1), MULMOD_192_64_32(6, 2, 1), MULMOD_192_64_64(6, 2, 2), 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 d635f404a62..d0905f77324 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 @@ -27,7 +27,7 @@ public class MulModOperationBenchmarkV2 extends TernaryOperationBenchmarkV2 { - // Benches for (a + b) % c + // Benches for (a * b) % c // Define available scenarios public enum Case { @@ -40,7 +40,7 @@ public enum Case { 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_128_128_128(4, 4, 4), MULMOD_192_32_32(6, 1, 1), MULMOD_192_64_32(6, 2, 1), MULMOD_192_64_64(6, 2, 2), From 0b9adc16f507a647d0869c0de200f6fad55da880 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Wed, 8 Apr 2026 17:10:33 +1000 Subject: [PATCH 14/22] review comments - var names Also update c -> m in benchmarks following (a * b) % m formula Signed-off-by: Simon Dudley --- .../operations/AddModOperationBenchmark.java | 4 ++-- .../operations/MulModOperationBenchmark.java | 22 +++++++++--------- .../operations/TernaryOperationBenchmark.java | 8 +++---- .../v2/MulModOperationBenchmarkV2.java | 22 +++++++++--------- .../v2/TernaryOperationBenchmarkV2.java | 8 +++---- .../besu/evm/v2/StackArithmetic.java | 23 ++++++++++--------- 6 files changed, 44 insertions(+), 43 deletions(-) diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AddModOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AddModOperationBenchmark.java index ac042cdee15..7f0722b9e41 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AddModOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AddModOperationBenchmark.java @@ -133,7 +133,7 @@ public void setUp() { Case scenario = Case.valueOf(caseName); aPool = new Bytes[SAMPLE_SIZE]; bPool = new Bytes[SAMPLE_SIZE]; - cPool = new Bytes[SAMPLE_SIZE]; + mPool = new Bytes[SAMPLE_SIZE]; final ThreadLocalRandom random = ThreadLocalRandom.current(); int aSize; @@ -156,7 +156,7 @@ public void setUp() { random.nextBytes(c); aPool[i] = Bytes.wrap(a); bPool[i] = Bytes.wrap(b); - cPool[i] = Bytes.wrap(c); + mPool[i] = Bytes.wrap(c); } index = 0; } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/MulModOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/MulModOperationBenchmark.java index e25244b495d..bf696196ccb 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/MulModOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/MulModOperationBenchmark.java @@ -27,7 +27,7 @@ public class MulModOperationBenchmark extends TernaryOperationBenchmark { - // Benches for (a * b) % c + // Benches for (a * b) % m // Define available scenarios public enum Case { @@ -75,12 +75,12 @@ public enum Case { final int aSize; final int bSize; - final int cSize; + final int mSize; - Case(final int aSize, final int bSize, final int cSize) { + Case(final int aSize, final int bSize, final int mSize) { this.aSize = aSize; this.bSize = bSize; - this.cSize = cSize; + this.mSize = mSize; } } @@ -137,30 +137,30 @@ public void setUp() { Case scenario = Case.valueOf(caseName); aPool = new Bytes[SAMPLE_SIZE]; bPool = new Bytes[SAMPLE_SIZE]; - cPool = new Bytes[SAMPLE_SIZE]; + mPool = new Bytes[SAMPLE_SIZE]; final ThreadLocalRandom random = ThreadLocalRandom.current(); int aSize; int bSize; - int cSize; + int mSize; 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; + if (scenario.mSize < 0) mSize = random.nextInt(1, 33); + else mSize = scenario.mSize * 4; final byte[] a = new byte[aSize]; final byte[] b = new byte[bSize]; - final byte[] c = new byte[cSize]; + final byte[] m = new byte[mSize]; random.nextBytes(a); random.nextBytes(b); - random.nextBytes(c); + random.nextBytes(m); aPool[i] = Bytes.wrap(a); bPool[i] = Bytes.wrap(b); - cPool[i] = Bytes.wrap(c); + mPool[i] = Bytes.wrap(m); } index = 0; } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/TernaryOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/TernaryOperationBenchmark.java index 6b300f97e7b..842d4fb9de8 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/TernaryOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/TernaryOperationBenchmark.java @@ -42,7 +42,7 @@ public abstract class TernaryOperationBenchmark { protected Bytes[] aPool; protected Bytes[] bPool; - protected Bytes[] cPool; + protected Bytes[] mPool; protected int index; protected MessageFrame frame; @@ -51,16 +51,16 @@ public void setUp() { frame = BenchmarkHelper.createMessageCallFrame(); aPool = new Bytes[SAMPLE_SIZE]; bPool = new Bytes[SAMPLE_SIZE]; - cPool = new Bytes[SAMPLE_SIZE]; + mPool = new Bytes[SAMPLE_SIZE]; BenchmarkHelper.fillPool(aPool); BenchmarkHelper.fillPool(bPool); - BenchmarkHelper.fillPool(cPool); + BenchmarkHelper.fillPool(mPool); index = 0; } @Benchmark public void executeOperation(final Blackhole blackhole) { - frame.pushStackItem(cPool[index]); + frame.pushStackItem(mPool[index]); frame.pushStackItem(bPool[index]); frame.pushStackItem(aPool[index]); 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 d0905f77324..179cf51f686 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 @@ -27,7 +27,7 @@ public class MulModOperationBenchmarkV2 extends TernaryOperationBenchmarkV2 { - // Benches for (a * b) % c + // Benches for (a * b) % m // Define available scenarios public enum Case { @@ -75,12 +75,12 @@ public enum Case { final int aSize; final int bSize; - final int cSize; + final int mSize; - Case(final int aSize, final int bSize, final int cSize) { + Case(final int aSize, final int bSize, final int mSize) { this.aSize = aSize; this.bSize = bSize; - this.cSize = cSize; + this.mSize = mSize; } } @@ -137,30 +137,30 @@ public void setUp() { MulModOperationBenchmarkV2.Case scenario = MulModOperationBenchmarkV2.Case.valueOf(caseName); aPool = new UInt256[SAMPLE_SIZE]; bPool = new UInt256[SAMPLE_SIZE]; - cPool = new UInt256[SAMPLE_SIZE]; + mPool = new UInt256[SAMPLE_SIZE]; final ThreadLocalRandom random = ThreadLocalRandom.current(); int aSize; int bSize; - int cSize; + int mSize; 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; + if (scenario.mSize < 0) mSize = random.nextInt(1, 33); + else mSize = scenario.mSize * 4; final byte[] a = new byte[aSize]; final byte[] b = new byte[bSize]; - final byte[] c = new byte[cSize]; + final byte[] m = new byte[mSize]; random.nextBytes(a); random.nextBytes(b); - random.nextBytes(c); + random.nextBytes(m); aPool[i] = BenchmarkHelperV2.bytesToUInt256(a); bPool[i] = BenchmarkHelperV2.bytesToUInt256(b); - cPool[i] = BenchmarkHelperV2.bytesToUInt256(c); + mPool[i] = BenchmarkHelperV2.bytesToUInt256(m); } index = 0; } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/TernaryOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/TernaryOperationBenchmarkV2.java index 857402a31c0..16a5d63203d 100644 --- 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 @@ -42,7 +42,7 @@ public abstract class TernaryOperationBenchmarkV2 { protected UInt256[] aPool; protected UInt256[] bPool; - protected UInt256[] cPool; + protected UInt256[] mPool; protected int index; protected MessageFrame frame; @@ -51,16 +51,16 @@ public void setUp() { frame = BenchmarkHelperV2.createMessageCallFrame(); aPool = new UInt256[SAMPLE_SIZE]; bPool = new UInt256[SAMPLE_SIZE]; - cPool = new UInt256[SAMPLE_SIZE]; + mPool = new UInt256[SAMPLE_SIZE]; BenchmarkHelperV2.fillUInt256Pool(aPool); BenchmarkHelperV2.fillUInt256Pool(bPool); - BenchmarkHelperV2.fillUInt256Pool(cPool); + BenchmarkHelperV2.fillUInt256Pool(mPool); index = 0; } @Benchmark public void executeOperation(final Blackhole blackhole) { - BenchmarkHelperV2.pushUInt256(frame, cPool[index]); + BenchmarkHelperV2.pushUInt256(frame, mPool[index]); BenchmarkHelperV2.pushUInt256(frame, bPool[index]); BenchmarkHelperV2.pushUInt256(frame, aPool[index]); 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 c9230ec691d..f243c0975c3 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 @@ -332,20 +332,21 @@ private static long shiftRightWord(final long value, final long prevValue, final * @param top current stack-top (item count) * @return the new stack-top after consuming three items and producing one item * + * MULMOD: mulmod(a,b,m) = (a * b) mod m * MULMOD: stack[top-3] = (stack[top-1] * stack[top-2]) mod stack[top-3], return top-2. */ public static int mulMod(final long[] stack, 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(stack[a], stack[a + 1], stack[a + 2], stack[a + 3]); - UInt256 vb = new UInt256(stack[b], stack[b + 1], stack[b + 2], stack[b + 3]); - UInt256 vc = new UInt256(stack[c], stack[c + 1], stack[c + 2], stack[c + 3]); - UInt256 r = vc.isZero() ? UInt256.ZERO : va.mulMod(vb, vc); - stack[c] = r.u3(); - stack[c + 1] = r.u2(); - stack[c + 2] = r.u1(); - stack[c + 3] = r.u0(); + final int aOffset = (top - 1) << 2; + final int bOffset = (top - 2) << 2; + final int mOffset = (top - 3) << 2; + UInt256 valueA = new UInt256(stack[aOffset], stack[aOffset + 1], stack[aOffset + 2], stack[aOffset + 3]); + UInt256 valueB = new UInt256(stack[bOffset], stack[bOffset + 1], stack[bOffset + 2], stack[bOffset + 3]); + UInt256 modulus = new UInt256(stack[mOffset], stack[mOffset + 1], stack[mOffset + 2], stack[mOffset + 3]); + UInt256 r = modulus.isZero() ? UInt256.ZERO : valueA.mulMod(valueB, modulus); + stack[mOffset] = r.u3(); + stack[mOffset + 1] = r.u2(); + stack[mOffset + 2] = r.u1(); + stack[mOffset + 3] = r.u0(); return top - 2; } From 5ef4135463d135923417ffb8593e185d3a2a6958 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Wed, 8 Apr 2026 17:14:50 +1000 Subject: [PATCH 15/22] spotless Signed-off-by: Simon Dudley --- .../besu/evm/v2/StackArithmetic.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 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 f243c0975c3..fab095544ca 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 @@ -327,21 +327,25 @@ private static long shiftRightWord(final long value, final long prevValue, final // region Arithmetic Operations // -------------------------------------------------------------------------- - /** Performs EVM MULMOD (modular multiplication) on the three top stack items. + /** + * Performs EVM MULMOD (modular multiplication) on the three top stack items. + * * @param stack the flat limb array * @param top current stack-top (item count) * @return the new stack-top after consuming three items and producing one item - * - * MULMOD: mulmod(a,b,m) = (a * b) mod m - * MULMOD: stack[top-3] = (stack[top-1] * stack[top-2]) mod stack[top-3], return top-2. + *

MULMOD: mulmod(a,b,m) = (a * b) mod m MULMOD: stack[top-3] = (stack[top-1] * + * stack[top-2]) mod stack[top-3], return top-2. */ public static int mulMod(final long[] stack, final int top) { final int aOffset = (top - 1) << 2; final int bOffset = (top - 2) << 2; final int mOffset = (top - 3) << 2; - UInt256 valueA = new UInt256(stack[aOffset], stack[aOffset + 1], stack[aOffset + 2], stack[aOffset + 3]); - UInt256 valueB = new UInt256(stack[bOffset], stack[bOffset + 1], stack[bOffset + 2], stack[bOffset + 3]); - UInt256 modulus = new UInt256(stack[mOffset], stack[mOffset + 1], stack[mOffset + 2], stack[mOffset + 3]); + UInt256 valueA = + new UInt256(stack[aOffset], stack[aOffset + 1], stack[aOffset + 2], stack[aOffset + 3]); + UInt256 valueB = + new UInt256(stack[bOffset], stack[bOffset + 1], stack[bOffset + 2], stack[bOffset + 3]); + UInt256 modulus = + new UInt256(stack[mOffset], stack[mOffset + 1], stack[mOffset + 2], stack[mOffset + 3]); UInt256 r = modulus.isZero() ? UInt256.ZERO : valueA.mulMod(valueB, modulus); stack[mOffset] = r.u3(); stack[mOffset + 1] = r.u2(); From 7278b9cb7f71fe1bef9eff1c13fb2b5d41ccd2ba Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Thu, 9 Apr 2026 13:09:59 +1000 Subject: [PATCH 16/22] Add MulModOperationV2Test covering stack management and underflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tests verify correct stack depth reduction (3→1), result placement, zero-modulus special case, cross-limb arithmetic, and that underflow with 0 or 2 items returns INSUFFICIENT_STACK_ITEMS without mutating the stack. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Simon Dudley --- .../v2/operation/MulModOperationV2Test.java | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2Test.java diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2Test.java new file mode 100644 index 00000000000..462e3e38f39 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2Test.java @@ -0,0 +1,126 @@ +/* + * 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.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.FrontierGasCalculator; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.gascalculator.SpuriousDragonGasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +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 MulModOperationV2Test { + + private final GasCalculator gasCalculator = new FrontierGasCalculator(); + private final MulModOperationV2 operation = new MulModOperationV2(gasCalculator); + + /** + * Test data for mulmod(a, b, m) = expected. + * + *

Push order when building the frame: m first (deepest), then b, then a (top). + */ + static Iterable data() { + return List.of( + // (a, b, m, expected) + Arguments.of("0x02", "0x03", "0x05", "0x01"), // 2*3=6, 6 mod 5 = 1 + Arguments.of("0x02", "0x03", "0x06", "0x"), // 2*3=6, 6 mod 6 = 0 (exact multiple) + Arguments.of("0x00", "0xff", "0x07", "0x"), // 0 * anything = 0 + Arguments.of("0xff", "0xff", "0x01", "0x"), // anything mod 1 = 0 + Arguments.of("0x02", "0x03", "0x00", "0x"), // zero modulus → 0 (special EVM rule) + // Large value spanning all four 64-bit limbs: MAX * 1 mod 7. + // 2^256 ≡ 2 (mod 7), so MAX = 2^256-1 ≡ 1 (mod 7). + Arguments.of( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x01", + "0x07", + "0x01"), + // Cross-limb: (2^128) * (2^128) mod (2^128 + 1) = 1. + // 2^128 ≡ -1 (mod 2^128+1), so 2^256 = (2^128)^2 ≡ 1 (mod 2^128+1). + Arguments.of( + "0x00000000000000000000000000000001" + "00000000000000000000000000000000", // 2^128 + "0x00000000000000000000000000000001" + "00000000000000000000000000000000", // 2^128 + "0x00000000000000000000000000000001" + "00000000000000000000000000000001", // 2^128+1 + "0x01")); + } + + @ParameterizedTest(name = "{index}: mulmod({0}, {1}, {2}) = {3}") + @MethodSource("data") + void mulModOperation( + final String a, final String b, final String m, final String expectedResult) { + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .pushStackItem(Bytes32.fromHexStringLenient(m)) // pushed first → deepest (top-3) + .pushStackItem(Bytes32.fromHexStringLenient(b)) // top-2 + .pushStackItem(Bytes32.fromHexStringLenient(a)) // pushed last → top (top-1) + .build(); + assertThat(frame.stackTopV2()).isEqualTo(3); + + final Operation.OperationResult result = operation.execute(frame, null); + + assertThat(result.getHaltReason()).isNull(); + // MULMOD consumes 3 items and produces 1: net stack change is -2. + assertThat(frame.stackTopV2()).isEqualTo(1); + + final UInt256 expected = + expectedResult.equals("0x") || expectedResult.equals("0x0") + ? UInt256.ZERO + : UInt256.fromBytesBE(Bytes32.fromHexStringLenient(expectedResult).toArrayUnsafe()); + assertThat(getStackItem(frame, 0)).isEqualTo(expected); + } + + @Test + void mulModOperationUnderflowNoItems() { + final MessageFrame frame = new TestMessageFrameBuilderV2().build(); + assertThat(frame.stackTopV2()).isEqualTo(0); + final Operation.OperationResult result = + MulModOperationV2.staticOperation(frame, frame.stackDataV2()); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + assertThat(frame.stackTopV2()).isEqualTo(0); + } + + @Test + void mulModOperationUnderflowOnlyTwoItems() { + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .pushStackItem(Bytes32.fromHexStringLenient("0x1")) // deepest (top-3) + .pushStackItem(Bytes32.fromHexStringLenient("0x2")) // top-2, missing top-1 + .build(); + assertThat(frame.stackTopV2()).isEqualTo(2); + + final Operation.OperationResult result = + MulModOperationV2.staticOperation(frame, frame.stackDataV2()); + + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + assertThat(frame.stackTopV2()).isEqualTo(2); + } + + private static UInt256 getStackItem(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]); + } +} From 995d5e6abc363dc1e8052b0ed42c69adb49ed954 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Thu, 9 Apr 2026 13:13:14 +1000 Subject: [PATCH 17/22] spotless Signed-off-by: Simon Dudley --- .../hyperledger/besu/evm/v2/operation/MulModOperationV2Test.java | 1 - 1 file changed, 1 deletion(-) diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2Test.java index 462e3e38f39..bea55190ec9 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2Test.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2Test.java @@ -21,7 +21,6 @@ import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.FrontierGasCalculator; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.gascalculator.SpuriousDragonGasCalculator; import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2; From dd580d12ba2544b39b4aca5084e3a09b7f0dcf8b Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Thu, 9 Apr 2026 14:04:12 +1000 Subject: [PATCH 18/22] Javadoc formatting Signed-off-by: Simon Dudley --- .../java/org/hyperledger/besu/evm/v2/StackArithmetic.java | 6 +++--- 1 file changed, 3 insertions(+), 3 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 fab095544ca..80ec1ae7115 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 @@ -329,12 +329,12 @@ private static long shiftRightWord(final long value, final long prevValue, final /** * Performs EVM MULMOD (modular multiplication) on the three top stack items. - * + *

MULMOD: mulmod(a,b,m) = (a * b) mod m + *

MULMOD: stack[top-3] = (stack[top-1] * stack[top-2]) mod stack[top-3], return top-2. + * * @param stack the flat limb array * @param top current stack-top (item count) * @return the new stack-top after consuming three items and producing one item - *

MULMOD: mulmod(a,b,m) = (a * b) mod m MULMOD: stack[top-3] = (stack[top-1] * - * stack[top-2]) mod stack[top-3], return top-2. */ public static int mulMod(final long[] stack, final int top) { final int aOffset = (top - 1) << 2; From 5456d98c13762714c85a1bbc15f20907efc1a728 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Thu, 9 Apr 2026 20:57:48 +1000 Subject: [PATCH 19/22] spotless Signed-off-by: Simon Dudley --- .../java/org/hyperledger/besu/evm/v2/StackArithmetic.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 80ec1ae7115..ac89d762ba2 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 @@ -329,9 +329,11 @@ private static long shiftRightWord(final long value, final long prevValue, final /** * Performs EVM MULMOD (modular multiplication) on the three top stack items. + * *

MULMOD: mulmod(a,b,m) = (a * b) mod m + * *

MULMOD: stack[top-3] = (stack[top-1] * stack[top-2]) mod stack[top-3], return top-2. - * + * * @param stack the flat limb array * @param top current stack-top (item count) * @return the new stack-top after consuming three items and producing one item From 2a6b47628353d707e8a2f035bbbe03796f76ea84 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Fri, 10 Apr 2026 12:42:03 +1000 Subject: [PATCH 20/22] Add finals Signed-off-by: Simon Dudley --- .../java/org/hyperledger/besu/evm/v2/StackArithmetic.java | 8 ++++---- .../besu/evm/v2/operation/MulModOperationV2Test.java | 2 ++ 2 files changed, 6 insertions(+), 4 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 ac89d762ba2..d0948fa108a 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 @@ -342,13 +342,13 @@ public static int mulMod(final long[] stack, final int top) { final int aOffset = (top - 1) << 2; final int bOffset = (top - 2) << 2; final int mOffset = (top - 3) << 2; - UInt256 valueA = + final UInt256 valueA = new UInt256(stack[aOffset], stack[aOffset + 1], stack[aOffset + 2], stack[aOffset + 3]); - UInt256 valueB = + final UInt256 valueB = new UInt256(stack[bOffset], stack[bOffset + 1], stack[bOffset + 2], stack[bOffset + 3]); - UInt256 modulus = + final UInt256 modulus = new UInt256(stack[mOffset], stack[mOffset + 1], stack[mOffset + 2], stack[mOffset + 3]); - UInt256 r = modulus.isZero() ? UInt256.ZERO : valueA.mulMod(valueB, modulus); + final UInt256 r = modulus.isZero() ? UInt256.ZERO : valueA.mulMod(valueB, modulus); stack[mOffset] = r.u3(); stack[mOffset + 1] = r.u2(); stack[mOffset + 2] = r.u1(); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2Test.java index bea55190ec9..de8bddd6e58 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2Test.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2Test.java @@ -95,8 +95,10 @@ void mulModOperation( void mulModOperationUnderflowNoItems() { final MessageFrame frame = new TestMessageFrameBuilderV2().build(); assertThat(frame.stackTopV2()).isEqualTo(0); + final Operation.OperationResult result = MulModOperationV2.staticOperation(frame, frame.stackDataV2()); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); assertThat(frame.stackTopV2()).isEqualTo(0); } From e11a9cc45c50ba774f2f334e0ff79719e23ad096 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Mon, 13 Apr 2026 07:19:12 +1000 Subject: [PATCH 21/22] Move StackArithmetic.mulMod to MulModOperationV2 Signed-off-by: Simon Dudley --- .../besu/evm/v2/StackArithmetic.java | 37 ------------------- .../evm/v2/operation/MulModOperationV2.java | 33 ++++++++++++++++- 2 files changed, 31 insertions(+), 39 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 d0948fa108a..6a9f808f3dc 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,6 @@ */ 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 @@ -323,39 +321,4 @@ private static long shiftRightWord(final long value, final long prevValue, final } // endregion - - // region Arithmetic Operations - // -------------------------------------------------------------------------- - - /** - * Performs EVM MULMOD (modular multiplication) on the three top stack items. - * - *

MULMOD: mulmod(a,b,m) = (a * b) mod m - * - *

MULMOD: stack[top-3] = (stack[top-1] * stack[top-2]) mod stack[top-3], return top-2. - * - * @param stack the flat limb array - * @param top current stack-top (item count) - * @return the new stack-top after consuming three items and producing one item - */ - public static int mulMod(final long[] stack, final int top) { - final int aOffset = (top - 1) << 2; - final int bOffset = (top - 2) << 2; - final int mOffset = (top - 3) << 2; - final UInt256 valueA = - new UInt256(stack[aOffset], stack[aOffset + 1], stack[aOffset + 2], stack[aOffset + 3]); - final UInt256 valueB = - new UInt256(stack[bOffset], stack[bOffset + 1], stack[bOffset + 2], stack[bOffset + 3]); - final UInt256 modulus = - new UInt256(stack[mOffset], stack[mOffset + 1], stack[mOffset + 2], stack[mOffset + 3]); - final UInt256 r = modulus.isZero() ? UInt256.ZERO : valueA.mulMod(valueB, modulus); - stack[mOffset] = r.u3(); - stack[mOffset + 1] = r.u2(); - stack[mOffset + 2] = r.u1(); - stack[mOffset + 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 index 96109fe6e9d..0bcc13864ae 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 @@ -15,10 +15,10 @@ package org.hyperledger.besu.evm.v2.operation; import org.hyperledger.besu.evm.EVM; +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.operation.Operation; -import org.hyperledger.besu.evm.v2.StackArithmetic; /** The Mul mod operation. */ public class MulModOperationV2 extends AbstractFixedCostOperationV2 { @@ -49,7 +49,36 @@ public Operation.OperationResult executeFixedCostOperation( */ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { if (!frame.stackHasItems(3)) return UNDERFLOW_RESPONSE; - frame.setTopV2(StackArithmetic.mulMod(stack, frame.stackTopV2())); + frame.setTopV2(mulMod(stack, frame.stackTopV2())); return mulModSuccess; } + + /** + * Performs EVM MULMOD (modular multiplication) on the three top stack items. + * + *

MULMOD: mulmod(a,b,m) = (a * b) mod m + * + *

MULMOD: stack[top-3] = (stack[top-1] * stack[top-2]) mod stack[top-3], return top-2. + * + * @param stack the flat limb array + * @param top current stack-top (item count) + * @return the new stack-top after consuming three items and producing one item + */ + private static int mulMod(final long[] stack, final int top) { + final int aOffset = (top - 1) << 2; + final int bOffset = (top - 2) << 2; + final int mOffset = (top - 3) << 2; + final UInt256 valueA = + new UInt256(stack[aOffset], stack[aOffset + 1], stack[aOffset + 2], stack[aOffset + 3]); + final UInt256 valueB = + new UInt256(stack[bOffset], stack[bOffset + 1], stack[bOffset + 2], stack[bOffset + 3]); + final UInt256 modulus = + new UInt256(stack[mOffset], stack[mOffset + 1], stack[mOffset + 2], stack[mOffset + 3]); + final UInt256 r = modulus.isZero() ? UInt256.ZERO : valueA.mulMod(valueB, modulus); + stack[mOffset] = r.u3(); + stack[mOffset + 1] = r.u2(); + stack[mOffset + 2] = r.u1(); + stack[mOffset + 3] = r.u0(); + return top - 2; + } } From f67ceae3c18cc6a2b47ab002e9281ae289199587 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Thu, 16 Apr 2026 06:48:05 +1000 Subject: [PATCH 22/22] Use internal void method Signed-off-by: Simon Dudley --- .../besu/evm/v2/operation/MulModOperationV2.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) 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 0bcc13864ae..203601408db 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 @@ -49,7 +49,10 @@ public Operation.OperationResult executeFixedCostOperation( */ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { if (!frame.stackHasItems(3)) return UNDERFLOW_RESPONSE; - frame.setTopV2(mulMod(stack, frame.stackTopV2())); + int top = frame.stackTopV2(); + mulMod(stack, top); + // consumed three items and produced one item + frame.setTopV2(top - 2); return mulModSuccess; } @@ -58,13 +61,12 @@ public static OperationResult staticOperation(final MessageFrame frame, final lo * *

MULMOD: mulmod(a,b,m) = (a * b) mod m * - *

MULMOD: stack[top-3] = (stack[top-1] * stack[top-2]) mod stack[top-3], return top-2. + *

MULMOD: stack[top-3] = (stack[top-1] * stack[top-2]) mod stack[top-3]. * * @param stack the flat limb array * @param top current stack-top (item count) - * @return the new stack-top after consuming three items and producing one item */ - private static int mulMod(final long[] stack, final int top) { + private static void mulMod(final long[] stack, final int top) { final int aOffset = (top - 1) << 2; final int bOffset = (top - 2) << 2; final int mOffset = (top - 3) << 2; @@ -79,6 +81,5 @@ private static int mulMod(final long[] stack, final int top) { stack[mOffset + 1] = r.u2(); stack[mOffset + 2] = r.u1(); stack[mOffset + 3] = r.u0(); - return top - 2; } }