-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add MulOperationV2 #10291
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add MulOperationV2 #10291
Changes from all commits
8d0d4db
5cd9f95
f1ab58f
8467d91
56e82c8
9723474
624a47d
bf35ee9
5575f27
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| /* | ||
| * Copyright contributors to Besu. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with | ||
| * the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on | ||
| * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 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; | ||
|
|
||
| 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 "MUL"; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| /* | ||
| * Copyright contributors to Besu. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with | ||
| * the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on | ||
| * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the | ||
| * specific language governing permissions and limitations under the License. | ||
| * | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
| package org.hyperledger.besu.evm.v2.operation; | ||
|
|
||
| import org.hyperledger.besu.evm.EVM; | ||
| import org.hyperledger.besu.evm.UInt256; | ||
| import org.hyperledger.besu.evm.frame.MessageFrame; | ||
| import org.hyperledger.besu.evm.gascalculator.GasCalculator; | ||
|
|
||
| /** | ||
| * EVM v2 MUL operation using long[] stack representation. | ||
| * | ||
| * <p>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(0x02, "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; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| /* | ||
| * Copyright contributors to Besu. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with | ||
| * the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on | ||
| * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 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. | ||
| * | ||
| * <p>Push order when building the frame: b first (deepest), then a (top). | ||
| */ | ||
| static Iterable<Arguments> data() { | ||
| return List.of( | ||
| // (a, b, expected) | ||
| // Happy path: low-limb only. | ||
| Arguments.of("0x02", "0x03", "0x06"), | ||
| // Zero absorbing element: 0 × b = 0, verifies MUL yields zero when a multiplicand is zero. | ||
| Arguments.of("0x00", "0x03", "0x00"), | ||
| // 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( | ||
| "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. | ||
| Arguments.of( | ||
| "0x1000000000000001200000000000000230000000000000034000000000000004", | ||
| "0x1000000000000001100000000000000110000000000000011000000000000001", | ||
| "0x490000000000000b2700000000000009e4000000000000078000000000000004")); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you add these two cases :
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Already covered by property_mul_identity and property_mul_commutative |
||
| } | ||
|
|
||
| @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 shouldUnderflowNoItemsEvenOOG() { | ||
| final MessageFrame frame = new TestMessageFrameBuilderV2().initialGas(1L).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 shouldUnderflowOnlyOneItem() { | ||
| 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 shouldPreservesDeeperStackItems() { | ||
| // 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())); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
| } | ||
|
|
||
| @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()); | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you include a test for OutOfGas, and an other test for OutOfGas even stackunderflow error, ex. https://github.com/besu-eth/besu/pull/10298/changes#diff-3bee54d9bf3b24b510bf547be393a3a29960b9b273f0a2b8f5a5b07ab2679e20R101-R107
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done 9723474 |
||
|
|
||
| @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); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The operation is constructed with a
GasCalculatorandAbstractFixedCostOperationV2is givengasCalculator.getLowTierGasCost(), butexecuteFixedCostOperation()returns a staticOperationResulthardcoding gas cost5. This makesexecute(...)ignore the provided gas calculator (e.g., custom calculators or future changes). Prefer deriving theOperationResultgas cost from the calculator (e.g., store a per-instanceOperationResultbuilt fromgetLowTierGasCost()), or adjust the API so the static path can receive the intended gas cost.