From 8d0d4dbc37241cba3771fa9f4e9af4d14d88390f Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Tue, 21 Apr 2026 16:34:10 +1000 Subject: [PATCH 1/7] Add MulOperationV2 Uses UInt256.mul same as MulOperationOptimized and stack_long_array Signed-off-by: Simon Dudley --- .../v2/MulOperationBenchmarkV2.java | 27 ++++ .../java/org/hyperledger/besu/evm/EVM.java | 2 + .../besu/evm/v2/operation/MulOperationV2.java | 76 +++++++++ .../evm/v2/operation/MulOperationV2Test.java | 144 ++++++++++++++++++ 4 files changed, 249 insertions(+) create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulOperationBenchmarkV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2.java create mode 100644 evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2Test.java diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulOperationBenchmarkV2.java new file mode 100644 index 00000000000..07a1d39fa5f --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulOperationBenchmarkV2.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.MulOperationV2; + +public class MulOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return MulOperationV2.staticOperation(frame); + } +} 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 bed9b8602e3..9ef2a97405b 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java @@ -89,6 +89,7 @@ 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.MulOperationV2; import org.hyperledger.besu.evm.v2.operation.SarOperationV2; import org.hyperledger.besu.evm.v2.operation.ShlOperationV2; import org.hyperledger.besu.evm.v2.operation.ShrOperationV2; @@ -489,6 +490,7 @@ private void runToHaltV2(final MessageFrame frame, final OperationTracer tracing result = switch (opcode) { case 0x01 -> AddOperationV2.staticOperation(frame); + case 0x02 -> MulOperationV2.staticOperation(frame); case 0x09 -> MulModOperationV2.staticOperation(frame); case 0x1b -> enableConstantinople diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2.java new file mode 100644 index 00000000000..b46348e9431 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2.java @@ -0,0 +1,76 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +/** + * EVM v2 MUL operation using long[] stack representation. + * + *

Each 256-bit word is stored as four longs: index 0 = most significant 64 bits, index 3 = least + * significant 64 bits. + */ +public class MulOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult MUL_SUCCESS = + new OperationResult(5, null); + + /** + * Instantiates a new Mul operation. + * + * @param gasCalculator the gas calculator + */ + public MulOperationV2(final GasCalculator gasCalculator) { + super(0x01, "MUL", 2, 1, gasCalculator, gasCalculator.getLowTierGasCost()); + } + + @Override + public OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame); + } + + /** + * Execute the MUL opcode on the v2 long[] stack. + * MUL: stack[top-2] = stack[top-1] * stack[top-2], return top-1 + * + * @param frame the message frame + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame) { + if (!frame.stackHasItemsV2(2)) return UNDERFLOW_RESPONSE; + long[] stack = frame.stackDataV2(); + int top = frame.stackTopV2(); + final int aOffset = (top - 1) << 2; + final int bOffset = (top - 2) << 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 r = valueA.mul(valueB); + + stack[bOffset] = r.u3(); + stack[bOffset + 1] = r.u2(); + stack[bOffset + 2] = r.u1(); + stack[bOffset + 3] = r.u0(); + + frame.setTopV2(top - 1); + return MUL_SUCCESS; + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2Test.java new file mode 100644 index 00000000000..f0040f8bbad --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2Test.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 static org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2.getV2StackItem; + +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.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 MulOperationV2Test { + private final GasCalculator gasCalculator = new FrontierGasCalculator(); + private final MulOperationV2 operation = new MulOperationV2(gasCalculator); + + /** + * Structural test data for MUL(a, b) = expected. Arithmetic correctness is covered by + * UInt256PropertyBasedTest; these cases verify stack arity, limb-level read/write wiring, and + * 256-bit wrap handling. + * + *

Push order when building the frame: b first (deepest), then a (top). + */ + static Iterable data() { + return List.of( + // (a, b, expected) + // Happy path: low-limb only. + Arguments.of("0x02", "0x03", "0x06"), + // Zero identity with operand ordering: confirms b is read from the deeper slot. + Arguments.of("0x00", "0x03", "0x00"), + // 256-bit wrap: all 4 result limbs written as zero. + Arguments.of( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0x01", "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + // All 4 limbs populated on both inputs and the result: verifies every limb is read and + // written through stackDataV2(). Four distinct limb values on `a` catch any + // limb-index/offset mistakes in either input or the output. + Arguments.of( + "0x1000000000000001200000000000000230000000000000034000000000000004", + "0x1000000000000001100000000000000110000000000000011000000000000001", + "0x490000000000000b2700000000000009e4000000000000078000000000000004")); + } + + @ParameterizedTest(name = "{index}: MUL({0}, {1}) = {2}") + @MethodSource("data") + void mulOperation(final String a, final String b, final String expectedResult) { + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .pushStackItem(Bytes32.fromHexString(b)) // pushed first → deepest (top-2) + .pushStackItem(Bytes32.fromHexString(a)) // pushed last → top (top-1) + .build(); + assertThat(frame.stackTopV2()).isEqualTo(2); + + final Operation.OperationResult result = operation.execute(frame, null); + + assertThat(result.getHaltReason()).isNull(); + // MUL consumes 2 items and produces 1: net stack change is -1. + assertThat(frame.stackTopV2()).isEqualTo(1); + + final UInt256 expected = UInt256.fromBytesBE(Bytes32.fromHexString(expectedResult).toArrayUnsafe()); + assertThat(getV2StackItem(frame, 0)).isEqualTo(expected); + } + + @Test + void mulOperationUnderflowNoItems() { + final MessageFrame frame = new TestMessageFrameBuilderV2().build(); + assertThat(frame.stackTopV2()).isEqualTo(0); + + final Operation.OperationResult result = MulOperationV2.staticOperation(frame); + + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + assertThat(frame.stackTopV2()).isEqualTo(0); + } + + @Test + void mulOperationUnderflowOnlyOneItem() { + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .pushStackItem(Bytes32.fromHexString("0x01")) // top-2, missing top-1 + .build(); + assertThat(frame.stackTopV2()).isEqualTo(1); + + final Operation.OperationResult result = MulOperationV2.staticOperation(frame); + + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + assertThat(frame.stackTopV2()).isEqualTo(1); + } + + @Test + void mulOperationPreservesDeeperStackItems() { + // Use a distinctive 4-limb value for the untouched deep slot so any accidental write is + // detectable in any limb. + final Bytes32 untouched = + Bytes32.fromHexString("0xdeadbeefdeadbeefcafebabecafebabe0123456789abcdeff1e2d3c4b5a69788"); + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .pushStackItem(untouched) // top-3 (untouched by MUL) + .pushStackItem(Bytes32.fromHexString("0x02")) // top-2 (b) + .pushStackItem(Bytes32.fromHexString("0x04")) // top-1 (a) + .build(); + assertThat(frame.stackTopV2()).isEqualTo(3); + + operation.execute(frame, null); + + assertThat(frame.stackTopV2()).isEqualTo(2); + assertThat(getV2StackItem(frame, 0)).isEqualTo(UInt256.fromInt(8)); // 4 * 2 + assertThat(getV2StackItem(frame, 1)).isEqualTo(UInt256.fromBytesBE(untouched.toArrayUnsafe())); + } + + @Test + void mulOperationGasCostIsLowTier() { + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .pushStackItem(Bytes32.fromHexString("0x01")) + .pushStackItem(Bytes32.fromHexString("0x02")) + .build(); + + final Operation.OperationResult result = operation.execute(frame, null); + + assertThat(result.getGasCost()).isEqualTo(gasCalculator.getLowTierGasCost()); + } +} \ No newline at end of file From 5cd9f95289797e8dec5e158d9b426ca7afe6be0e Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Tue, 21 Apr 2026 21:39:08 +1000 Subject: [PATCH 2/7] spotless Signed-off-by: Simon Dudley --- .../besu/evm/v2/operation/MulOperationV2.java | 19 +++-- .../evm/v2/operation/MulOperationV2Test.java | 71 ++++++++++--------- 2 files changed, 46 insertions(+), 44 deletions(-) diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2.java index b46348e9431..7b1a68189fa 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2.java @@ -27,8 +27,7 @@ */ public class MulOperationV2 extends AbstractFixedCostOperationV2 { - private static final OperationResult MUL_SUCCESS = - new OperationResult(5, null); + private static final OperationResult MUL_SUCCESS = new OperationResult(5, null); /** * Instantiates a new Mul operation. @@ -40,28 +39,28 @@ public MulOperationV2(final GasCalculator gasCalculator) { } @Override - public OperationResult executeFixedCostOperation( - final MessageFrame frame, final EVM evm) { + public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { return staticOperation(frame); } /** - * Execute the MUL opcode on the v2 long[] stack. - * MUL: stack[top-2] = stack[top-1] * stack[top-2], return top-1 + * Execute the MUL opcode on the v2 long[] stack. MUL: stack[top-2] = stack[top-1] * stack[top-2], + * return top-1 * * @param frame the message frame * @return the operation result */ - public static OperationResult staticOperation( - final MessageFrame frame) { + public static OperationResult staticOperation(final MessageFrame frame) { if (!frame.stackHasItemsV2(2)) return UNDERFLOW_RESPONSE; long[] stack = frame.stackDataV2(); int top = frame.stackTopV2(); final int aOffset = (top - 1) << 2; final int bOffset = (top - 2) << 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 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 r = valueA.mul(valueB); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2Test.java index f0040f8bbad..9554aa63b51 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2Test.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2Test.java @@ -46,31 +46,33 @@ class MulOperationV2Test { */ static Iterable data() { return List.of( - // (a, b, expected) - // Happy path: low-limb only. - Arguments.of("0x02", "0x03", "0x06"), - // Zero identity with operand ordering: confirms b is read from the deeper slot. - Arguments.of("0x00", "0x03", "0x00"), - // 256-bit wrap: all 4 result limbs written as zero. - Arguments.of( - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0x01", "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), - // All 4 limbs populated on both inputs and the result: verifies every limb is read and - // written through stackDataV2(). Four distinct limb values on `a` catch any - // limb-index/offset mistakes in either input or the output. - Arguments.of( - "0x1000000000000001200000000000000230000000000000034000000000000004", - "0x1000000000000001100000000000000110000000000000011000000000000001", - "0x490000000000000b2700000000000009e4000000000000078000000000000004")); + // (a, b, expected) + // Happy path: low-limb only. + Arguments.of("0x02", "0x03", "0x06"), + // Zero identity with operand ordering: confirms b is read from the deeper slot. + Arguments.of("0x00", "0x03", "0x00"), + // 256-bit wrap: all 4 result limbs written as zero. + Arguments.of( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x01", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + // All 4 limbs populated on both inputs and the result: verifies every limb is read and + // written through stackDataV2(). Four distinct limb values on `a` catch any + // limb-index/offset mistakes in either input or the output. + Arguments.of( + "0x1000000000000001200000000000000230000000000000034000000000000004", + "0x1000000000000001100000000000000110000000000000011000000000000001", + "0x490000000000000b2700000000000009e4000000000000078000000000000004")); } @ParameterizedTest(name = "{index}: MUL({0}, {1}) = {2}") @MethodSource("data") void mulOperation(final String a, final String b, final String expectedResult) { final MessageFrame frame = - new TestMessageFrameBuilderV2() - .pushStackItem(Bytes32.fromHexString(b)) // pushed first → deepest (top-2) - .pushStackItem(Bytes32.fromHexString(a)) // pushed last → top (top-1) - .build(); + new TestMessageFrameBuilderV2() + .pushStackItem(Bytes32.fromHexString(b)) // pushed first → deepest (top-2) + .pushStackItem(Bytes32.fromHexString(a)) // pushed last → top (top-1) + .build(); assertThat(frame.stackTopV2()).isEqualTo(2); final Operation.OperationResult result = operation.execute(frame, null); @@ -79,7 +81,8 @@ void mulOperation(final String a, final String b, final String expectedResult) { // MUL consumes 2 items and produces 1: net stack change is -1. assertThat(frame.stackTopV2()).isEqualTo(1); - final UInt256 expected = UInt256.fromBytesBE(Bytes32.fromHexString(expectedResult).toArrayUnsafe()); + final UInt256 expected = + UInt256.fromBytesBE(Bytes32.fromHexString(expectedResult).toArrayUnsafe()); assertThat(getV2StackItem(frame, 0)).isEqualTo(expected); } @@ -97,9 +100,9 @@ void mulOperationUnderflowNoItems() { @Test void mulOperationUnderflowOnlyOneItem() { final MessageFrame frame = - new TestMessageFrameBuilderV2() - .pushStackItem(Bytes32.fromHexString("0x01")) // top-2, missing top-1 - .build(); + new TestMessageFrameBuilderV2() + .pushStackItem(Bytes32.fromHexString("0x01")) // top-2, missing top-1 + .build(); assertThat(frame.stackTopV2()).isEqualTo(1); final Operation.OperationResult result = MulOperationV2.staticOperation(frame); @@ -113,13 +116,13 @@ void mulOperationPreservesDeeperStackItems() { // Use a distinctive 4-limb value for the untouched deep slot so any accidental write is // detectable in any limb. final Bytes32 untouched = - Bytes32.fromHexString("0xdeadbeefdeadbeefcafebabecafebabe0123456789abcdeff1e2d3c4b5a69788"); + Bytes32.fromHexString("0xdeadbeefdeadbeefcafebabecafebabe0123456789abcdeff1e2d3c4b5a69788"); final MessageFrame frame = - new TestMessageFrameBuilderV2() - .pushStackItem(untouched) // top-3 (untouched by MUL) - .pushStackItem(Bytes32.fromHexString("0x02")) // top-2 (b) - .pushStackItem(Bytes32.fromHexString("0x04")) // top-1 (a) - .build(); + new TestMessageFrameBuilderV2() + .pushStackItem(untouched) // top-3 (untouched by MUL) + .pushStackItem(Bytes32.fromHexString("0x02")) // top-2 (b) + .pushStackItem(Bytes32.fromHexString("0x04")) // top-1 (a) + .build(); assertThat(frame.stackTopV2()).isEqualTo(3); operation.execute(frame, null); @@ -132,13 +135,13 @@ void mulOperationPreservesDeeperStackItems() { @Test void mulOperationGasCostIsLowTier() { final MessageFrame frame = - new TestMessageFrameBuilderV2() - .pushStackItem(Bytes32.fromHexString("0x01")) - .pushStackItem(Bytes32.fromHexString("0x02")) - .build(); + new TestMessageFrameBuilderV2() + .pushStackItem(Bytes32.fromHexString("0x01")) + .pushStackItem(Bytes32.fromHexString("0x02")) + .build(); final Operation.OperationResult result = operation.execute(frame, null); assertThat(result.getGasCost()).isEqualTo(gasCalculator.getLowTierGasCost()); } -} \ No newline at end of file +} From f1ab58f16179e841bee9bafa5977829e61dff42c Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Tue, 21 Apr 2026 22:04:32 +1000 Subject: [PATCH 3/7] Wrong opcdoe byte Signed-off-by: Simon Dudley --- .../org/hyperledger/besu/evm/v2/operation/MulOperationV2.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2.java index 7b1a68189fa..efad2f7eab2 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2.java @@ -35,7 +35,7 @@ public class MulOperationV2 extends AbstractFixedCostOperationV2 { * @param gasCalculator the gas calculator */ public MulOperationV2(final GasCalculator gasCalculator) { - super(0x01, "MUL", 2, 1, gasCalculator, gasCalculator.getLowTierGasCost()); + super(0x02, "MUL", 2, 1, gasCalculator, gasCalculator.getLowTierGasCost()); } @Override From 8467d91f57973a20b706a32ddcc3e141adba373d Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Tue, 21 Apr 2026 22:09:35 +1000 Subject: [PATCH 4/7] Fixup test cases Signed-off-by: Simon Dudley --- .../besu/evm/v2/operation/MulOperationV2Test.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2Test.java index 9554aa63b51..2982506f83c 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2Test.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2Test.java @@ -49,13 +49,12 @@ static Iterable data() { // (a, b, expected) // Happy path: low-limb only. Arguments.of("0x02", "0x03", "0x06"), - // Zero identity with operand ordering: confirms b is read from the deeper slot. + // Zero absorbing element: 0 × b = 0, verifies MUL yields zero when a multiplicand is zero. Arguments.of("0x00", "0x03", "0x00"), - // 256-bit wrap: all 4 result limbs written as zero. + // 256-bit wrap: 2^255 × 2 = 2^256 ≡ 0 (mod 2^256). All 4 result limbs must be written as + // zero — catches bugs where high limbs are left stale after an overflowing product. Arguments.of( - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "0x01", - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + "0x8000000000000000000000000000000000000000000000000000000000000000", "0x02", "0x00"), // All 4 limbs populated on both inputs and the result: verifies every limb is read and // written through stackDataV2(). Four distinct limb values on `a` catch any // limb-index/offset mistakes in either input or the output. From 9723474b3915c1da42119edd45cabad81f842910 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Thu, 23 Apr 2026 14:03:54 +1000 Subject: [PATCH 5/7] Normalise with other binaryop unit tests Signed-off-by: Simon Dudley --- .../evm/v2/operation/MulOperationV2Test.java | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2Test.java index 2982506f83c..6ab79ac5869 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2Test.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2Test.java @@ -86,8 +86,8 @@ void mulOperation(final String a, final String b, final String expectedResult) { } @Test - void mulOperationUnderflowNoItems() { - final MessageFrame frame = new TestMessageFrameBuilderV2().build(); + void shouldUnderflowNoItemsEvenOOG() { + final MessageFrame frame = new TestMessageFrameBuilderV2().initialGas(1L).build(); assertThat(frame.stackTopV2()).isEqualTo(0); final Operation.OperationResult result = MulOperationV2.staticOperation(frame); @@ -97,7 +97,7 @@ void mulOperationUnderflowNoItems() { } @Test - void mulOperationUnderflowOnlyOneItem() { + void shouldUnderflowOnlyOneItem() { final MessageFrame frame = new TestMessageFrameBuilderV2() .pushStackItem(Bytes32.fromHexString("0x01")) // top-2, missing top-1 @@ -111,7 +111,7 @@ void mulOperationUnderflowOnlyOneItem() { } @Test - void mulOperationPreservesDeeperStackItems() { + void shouldPreservesDeeperStackItems() { // Use a distinctive 4-limb value for the untouched deep slot so any accidental write is // detectable in any limb. final Bytes32 untouched = @@ -143,4 +143,20 @@ void mulOperationGasCostIsLowTier() { assertThat(result.getGasCost()).isEqualTo(gasCalculator.getLowTierGasCost()); } + + @Test + void shouldHaltOnInsufficientGas() { + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .pushStackItem(Bytes32.ZERO) + .pushStackItem(Bytes32.ZERO) + .initialGas(1L) + .build(); + assertThat(frame.stackTopV2()).isEqualTo(2); + + final Operation.OperationResult result = operation.execute(frame, null); + + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INSUFFICIENT_GAS); + assertThat(frame.stackTopV2()).isEqualTo(2); + } } From bf35ee99bd65f15409a0040e8bdd8fa21cf082ca Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Thu, 23 Apr 2026 14:07:31 +1000 Subject: [PATCH 6/7] Fixup benchmark Signed-off-by: Simon Dudley --- .../v2/BinaryArithmeticOperationBenchmarkV2.java | 3 +-- .../operations/v2/MulOperationBenchmarkV2.java | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BinaryArithmeticOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BinaryArithmeticOperationBenchmarkV2.java index fd622f25077..97a49106495 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BinaryArithmeticOperationBenchmarkV2.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BinaryArithmeticOperationBenchmarkV2.java @@ -14,7 +14,6 @@ */ package org.hyperledger.besu.ethereum.vm.operations.v2; -import org.hyperledger.besu.ethereum.vm.operations.BenchmarkHelper; import org.hyperledger.besu.evm.UInt256; import java.math.BigInteger; @@ -56,7 +55,7 @@ private static int parseSizeBytes(final String s) { @Setup(Level.Iteration) @Override public void setUp() { - frame = BenchmarkHelper.createMessageCallFrame(); + frame = BenchmarkHelperV2.createMessageCallFrame(); Case scenario = Case.fromString(opCode(), caseName()); aPool = new UInt256[SAMPLE_SIZE]; diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulOperationBenchmarkV2.java index 07a1d39fa5f..50b1d49ea1b 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulOperationBenchmarkV2.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulOperationBenchmarkV2.java @@ -18,10 +18,24 @@ import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.v2.operation.MulOperationV2; -public class MulOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { +import org.openjdk.jmh.annotations.Param; + +public class MulOperationBenchmarkV2 extends BinaryArithmeticOperationBenchmarkV2 { + @Param("MUL_RANDOM_RANDOM") + private String caseName; @Override protected Operation.OperationResult invoke(final MessageFrame frame) { return MulOperationV2.staticOperation(frame); } + + @Override + protected String caseName() { + return caseName; + } + + @Override + protected String opCode() { + return "MOD"; + } } From 5575f275ea1bb02e35f297176a9f0c9a779654f9 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Thu, 23 Apr 2026 14:56:27 +1000 Subject: [PATCH 7/7] typo Signed-off-by: Simon Dudley --- .../besu/ethereum/vm/operations/v2/MulOperationBenchmarkV2.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulOperationBenchmarkV2.java index 50b1d49ea1b..e00f4106d8d 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulOperationBenchmarkV2.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulOperationBenchmarkV2.java @@ -36,6 +36,6 @@ protected String caseName() { @Override protected String opCode() { - return "MOD"; + return "MUL"; } }