Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,6 @@ public void setUp() {

@Override
protected Operation.OperationResult invoke(final MessageFrame frame) {
return MulModOperationV2.staticOperation(frame, frame.stackDataV2());
return MulModOperationV2.staticOperation(frame);
}
}
2 changes: 1 addition & 1 deletion evm/src/main/java/org/hyperledger/besu/evm/EVM.java
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +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 0x09 -> MulModOperationV2.staticOperation(frame);
case 0x1b ->
enableConstantinople
? ShlOperationV2.staticOperation(frame)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
/** The Mul mod operation. */
public class MulModOperationV2 extends AbstractFixedCostOperationV2 {

private static final OperationResult mulModSuccess = new OperationResult(8, null);
private static final OperationResult MUL_MOD_SUCCESS = new OperationResult(8, null);

/**
* Instantiates a new Mul mod operation.
Expand All @@ -37,49 +37,40 @@ public MulModOperationV2(final GasCalculator gasCalculator) {
@Override
public Operation.OperationResult executeFixedCostOperation(
final MessageFrame frame, final EVM evm) {
return staticOperation(frame, frame.stackDataV2());
return staticOperation(frame);
}

/**
* Performs MulMod operation.
* Performs MULMOD operation.
*
* <p>mulmod(a, b, m) = (a * b) mod m
*
* @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) {
public static OperationResult staticOperation(final MessageFrame frame) {
if (!frame.stackHasItems(3)) return UNDERFLOW_RESPONSE;
int top = frame.stackTopV2();
mulMod(stack, top);
// consumed three items and produced one item
frame.setTopV2(top - 2);
return mulModSuccess;
}
final int aOffset = (--top) << 2;
final int bOffset = (--top) << 2;
final int mOffset = (--top) << 2;

/**
* Performs EVM MULMOD (modular multiplication) on the three top stack items.
*
* <p>MULMOD: mulmod(a,b,m) = (a * b) mod m
*
* <p>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)
*/
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;
final long[] stack = frame.stackDataV2();
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();

frame.setTopV2(++top);
return MUL_MOD_SUCCESS;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
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;
Expand Down Expand Up @@ -88,16 +89,15 @@ void mulModOperation(
expectedResult.equals("0x") || expectedResult.equals("0x0")
? UInt256.ZERO
: UInt256.fromBytesBE(Bytes32.fromHexStringLenient(expectedResult).toArrayUnsafe());
assertThat(getStackItem(frame, 0)).isEqualTo(expected);
assertThat(getV2StackItem(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());
final Operation.OperationResult result = MulModOperationV2.staticOperation(frame);

assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS);
assertThat(frame.stackTopV2()).isEqualTo(0);
Expand All @@ -112,16 +112,9 @@ void mulModOperationUnderflowOnlyTwoItems() {
.build();
assertThat(frame.stackTopV2()).isEqualTo(2);

final Operation.OperationResult result =
MulModOperationV2.staticOperation(frame, frame.stackDataV2());
final Operation.OperationResult result = MulModOperationV2.staticOperation(frame);

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]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,19 @@ public MessageFrame build() {
return frame;
}

/**
* Reads a 256-bit word from the V2 stack at the given depth below the current top.
*
* @param frame the message frame with a V2 stack
* @param offset 0 for the topmost item, 1 for the item below, etc.
* @return the value as a {@link UInt256}
*/
public static UInt256 getV2StackItem(final MessageFrame frame, final int offset) {
final long[] s = frame.stackDataV2();
final int idx = (frame.stackTopV2() - 1 - offset) << 2;
Comment on lines +195 to +196
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getV2StackItem will currently throw an ArrayIndexOutOfBoundsException (or compute a negative index) if offset is negative or offset >= frame.stackTopV2(). Since this is a shared test utility, it’s worth failing fast with a clear IllegalArgumentException (or an assertion) that validates offset >= 0 and offset < frame.stackTopV2() to make test failures easier to diagnose.

Suggested change
final long[] s = frame.stackDataV2();
final int idx = (frame.stackTopV2() - 1 - offset) << 2;
final int stackTop = frame.stackTopV2();
if (offset < 0 || offset >= stackTop) {
throw new IllegalArgumentException(
"Offset must be between 0 (inclusive) and stack size (exclusive): offset="
+ offset
+ ", stackTop="
+ stackTop);
}
final long[] s = frame.stackDataV2();
final int idx = (stackTop - 1 - offset) << 2;

Copilot uses AI. Check for mistakes.
return new UInt256(s[idx], s[idx + 1], s[idx + 2], s[idx + 3]);
}

private WorldUpdater createDefaultWorldUpdater() {
return new ToyWorld();
}
Expand Down
Loading