From d63e96e8f0e968e0f43a7ad4702a65f415a7bacf Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Mon, 13 Apr 2026 15:00:35 +0200 Subject: [PATCH 01/19] Migrate wei operations to EVM v2 (first commit) Signed-off-by: Ameziane H. --- .../org/hyperledger/besu/datatypes/Wei.java | 79 ++++++++++++ .../SelfBalanceOperationBenchmark.java | 2 +- .../v2/SelfBalanceOperationBenchmarkV2.java | 116 ++++++++++++++++++ .../ethereum/core/ProcessableBlockHeader.java | 8 +- .../besu/evm/frame/MessageFrame.java | 12 ++ .../besu/evm/operation/AbstractOperation.java | 67 ++++++++++ .../AbstractFixedCostOperationV2.java | 4 - .../evm/v2/operation/BalanceOperationV2.java | 71 +++++++++++ .../evm/v2/operation/BaseFeeOperationV2.java | 52 ++++++++ .../v2/operation/BlobBaseFeeOperationV2.java | 42 +++++++ .../v2/operation/CallValueOperationV2.java | 44 +++++++ .../evm/v2/operation/GasPriceOperationV2.java | 44 +++++++ .../v2/operation/SelfBalanceOperationV2.java | 49 ++++++++ 13 files changed, 583 insertions(+), 7 deletions(-) create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SelfBalanceOperationBenchmarkV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java index 962640fc53d..bd9cae8800d 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java @@ -14,7 +14,10 @@ */ package org.hyperledger.besu.datatypes; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; import java.math.BigInteger; +import java.nio.ByteOrder; import java.util.Arrays; import java.util.Locale; @@ -25,6 +28,14 @@ /** A particular quantity of Wei, the Ethereum currency. */ public final class Wei extends BaseUInt256Value implements Quantity { + private static final VarHandle LONG_BE = + MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN); + + private final long u3; + private final long u2; + private final long u1; + private final long u0; + /** The constant ZERO. */ public static final Wei ZERO = of(0); @@ -41,6 +52,11 @@ public final class Wei extends BaseUInt256Value implements Quantity { */ Wei(final UInt256 value) { super(value, Wei::new); + final byte[] b = value.toArray(); + this.u3 = (long) LONG_BE.get(b, 0); + this.u2 = (long) LONG_BE.get(b, 8); + this.u1 = (long) LONG_BE.get(b, 16); + this.u0 = (long) LONG_BE.get(b, 24); } private Wei(final long v) { @@ -125,6 +141,69 @@ public static Wei fromEth(final long eth) { return Wei.of(BigInteger.valueOf(eth).multiply(BigInteger.TEN.pow(18))); } + /** + * Returns the most-significant limb (bits 255..192). + * + * @return the u3 limb + */ + public long u3() { + return u3; + } + + /** + * Returns limb 2 (bits 191..128). + * + * @return the u2 limb + */ + public long u2() { + return u2; + } + + /** + * Returns limb 1 (bits 127..64). + * + * @return the u1 limb + */ + public long u1() { + return u1; + } + + /** + * Returns the least-significant limb (bits 63..0). + * + * @return the u0 limb + */ + public long u0() { + return u0; + } + + /** + * Writes the 4 big-endian long limbs into the target array at the given offset. This enables + * zero-allocation transfer to the EVM operand stack. + * + * @param target the target long array (typically the EVM stack) + * @param off the offset into the array where u3 should be written + */ + public void writeLimbs(final long[] target, final int off) { + target[off] = u3; + target[off + 1] = u2; + target[off + 2] = u1; + target[off + 3] = u0; + } + + @Override + public boolean fitsLong() { + return u3 == 0 && u2 == 0 && u1 == 0 && u0 >= 0; + } + + @Override + public long toLong() { + if (!fitsLong()) { + throw new ArithmeticException("Value does not fit a 8 byte long"); + } + return u0; + } + @Override public BigInteger getAsBigInteger() { return toBigInteger(); diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SelfBalanceOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SelfBalanceOperationBenchmark.java index cf1d71ee357..d353d146f4c 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SelfBalanceOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SelfBalanceOperationBenchmark.java @@ -102,7 +102,7 @@ public void prepare() throws Exception { ExecutionContextTestFixture.create(); for (int i = 0; i < NUMBER_ADDRESSES; i++) { frames[i] = - createMessageFrame(blockchain, worldUpdater, executionContextTestFixture, addresses[0]); + createMessageFrame(blockchain, worldUpdater, executionContextTestFixture, addresses[i]); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SelfBalanceOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SelfBalanceOperationBenchmarkV2.java new file mode 100644 index 00000000000..a69c54e848e --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SelfBalanceOperationBenchmarkV2.java @@ -0,0 +1,116 @@ +/* + * 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.core.InMemoryKeyValueStorageProvider.createBonsaiInMemoryWorldStateArchive; +import static org.mockito.Mockito.mock; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.frame.BlockValues; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.v2.operation.SelfBalanceOperationV2; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.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 = 3, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(value = TimeUnit.NANOSECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@BenchmarkMode(Mode.AverageTime) +public class SelfBalanceOperationBenchmarkV2 { + + private static final int NUMBER_ADDRESSES = 20_000; + private static final Random RANDOM = new Random(); + + private SelfBalanceOperationV2 operation; + private MessageFrame[] frames; + private int index = 0; + + @Setup + public void setup() throws Exception { + operation = new SelfBalanceOperationV2(mock(GasCalculator.class)); + final Blockchain blockchain = mock(Blockchain.class); + + final Address[] addresses = new Address[NUMBER_ADDRESSES]; + for (int i = 0; i < addresses.length; i++) { + final byte[] raw = new byte[Address.SIZE]; + RANDOM.nextBytes(raw); + addresses[i] = Address.wrap(Bytes.wrap(raw)); + } + + final WorldUpdater worldUpdater; + try (WorldStateArchive archive = createBonsaiInMemoryWorldStateArchive(blockchain)) { + worldUpdater = archive.getWorldState().updater(); + } + for (Address addr : addresses) { + worldUpdater.getOrCreate(addr).setBalance(Wei.of(1)); + } + worldUpdater.commit(); + + frames = new MessageFrame[NUMBER_ADDRESSES]; + for (int i = 0; i < NUMBER_ADDRESSES; i++) { + frames[i] = + MessageFrame.builder() + .enableEvmV2(true) + .worldUpdater(worldUpdater) + .originator(Address.ZERO) + .gasPrice(Wei.ONE) + .blobGasPrice(Wei.ONE) + .blockValues(mock(BlockValues.class)) + .miningBeneficiary(Address.ZERO) + .blockHashLookup((__, ___) -> Hash.ZERO) + .type(MessageFrame.Type.MESSAGE_CALL) + .initialGas(Long.MAX_VALUE) + .address(addresses[i]) + .contract(addresses[i]) + .inputData(Bytes32.ZERO) + .sender(Address.ZERO) + .value(Wei.ZERO) + .apparentValue(Wei.ZERO) + .code(Code.EMPTY_CODE) + .completer(__ -> {}) + .build(); + } + } + + @Benchmark + public void executeOperation(final Blackhole blackhole) { + final MessageFrame frame = frames[index]; + blackhole.consume(operation.execute(frame, null)); + frame.setTopV2(frame.stackTopV2() - 1); + index = (index + 1) % NUMBER_ADDRESSES; + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java index e06faac0a72..a9356801f7a 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java @@ -42,6 +42,8 @@ public class ProcessableBlockHeader protected final long timestamp; // base fee is included for post EIP-1559 blocks protected final Wei baseFee; + // store optional base fee to compute it at build time + protected final Optional optionalBaseFee; // prevRandao is included for post-merge blocks protected final Bytes32 mixHashOrPrevRandao; // parentBeaconBlockRoot is included for Cancun @@ -67,6 +69,7 @@ protected ProcessableBlockHeader( this.gasLimit = gasLimit; this.timestamp = timestamp; this.baseFee = baseFee; + this.optionalBaseFee = Optional.ofNullable(baseFee); this.mixHashOrPrevRandao = mixHashOrPrevRandao; this.parentBeaconBlockRoot = parentBeaconBlockRoot; this.slotNumber = slotNumber; @@ -149,7 +152,7 @@ public long getTimestamp() { */ @Override public Optional getBaseFee() { - return Optional.ofNullable(baseFee); + return optionalBaseFee; } /** @@ -216,7 +219,8 @@ public String toString() { sb.append("difficulty=").append(difficulty).append(", "); sb.append("gasLimit=").append(gasLimit).append(", "); sb.append("timestamp=").append(timestamp).append(", "); - sb.append("baseFee=").append(baseFee).append(", "); + if (optionalBaseFee.isPresent()) + sb.append("baseFee=").append(optionalBaseFee.get()).append(", "); sb.append("mixHashOrPrevRandao=").append(mixHashOrPrevRandao).append(", "); if (parentBeaconBlockRoot != null) { sb.append("parentBeaconBlockRoot=").append(parentBeaconBlockRoot).append(", "); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java index a90da960e38..326b163e028 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java @@ -208,6 +208,7 @@ public enum Type { // significant) private final long[] stackDataV2; private int stackTopV2; + private final int stackMaxSize; private Bytes output = Bytes.EMPTY; private Bytes returnData = Bytes.EMPTY; private Code createdCode = null; @@ -278,6 +279,7 @@ private MessageFrame( this.stack = new OperandStack(txValues.maxStackSize()); this.stackDataV2 = enableEvmV2 ? new long[txValues.maxStackSize() * 4] : null; this.stackTopV2 = 0; + this.stackMaxSize = txValues.maxStackSize(); this.pc = 0; this.recipient = recipient; this.contract = contract; @@ -518,6 +520,16 @@ public boolean stackHasItems(final int n) { return stackTopV2 >= n; } + /** + * Returns true if the stack has space for {@code n} more items. + * + * @param n the number of additional items + * @return true if the stack can accommodate n more items + */ + public boolean stackHasSpace(final int n) { + return stackTopV2 + n <= stackMaxSize; + } + // --------------------------------------------------------------------------- // endregion diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java index 3b5c0939995..271d2cc0414 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java @@ -15,11 +15,17 @@ package org.hyperledger.besu.evm.operation; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.nio.ByteOrder; + import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; @@ -29,6 +35,19 @@ */ public abstract class AbstractOperation implements Operation { + protected static final VarHandle LONG_BE = + MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN); + protected static final VarHandle INT_BE = + MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.BIG_ENDIAN); + + /** Shared underflow response for static operation methods. */ + protected static final OperationResult UNDERFLOW_RESPONSE = + new OperationResult(0L, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + + /** Shared overflow response for static operation methods. */ + protected static final OperationResult OVERFLOW_RESPONSE = + new OperationResult(0L, ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); + static final Bytes BYTES_ONE = Bytes.of(1); private final int opcode; @@ -166,4 +185,52 @@ protected UInt256 getStorageValue( .ifPresent(t -> t.addSlotAccessForAccount(account.getAddress(), slotKey)); return slotValue; } + + /** + * Writes a zero-valued 256-bit word at the given stack slot. + * + * @param stack the flat limb array (4 longs per 256-bit word) + * @param top the slot index to write to + */ + protected static void pushZero(final long[] stack, final int top) { + final int offset = top << 2; + stack[offset] = 0; + stack[offset + 1] = 0; + stack[offset + 2] = 0; + stack[offset + 3] = 0; + } + + /** + * Writes a {@link Wei} value as four big-endian limbs at the given stack slot. + * + * @param wei the Wei value to write + * @param stack the flat limb array + * @param top the slot index to write to + */ + protected static void pushWei(final Wei wei, final long[] stack, final int top) { + final int offset = top << 2; + stack[offset] = wei.u3(); + stack[offset + 1] = wei.u2(); + stack[offset + 2] = wei.u1(); + stack[offset + 3] = wei.u0(); + } + + /** + * Extracts a 160-bit {@link Address} from a 256-bit stack word at the given depth below the top + * of stack. + * + * @param stack the flat limb array + * @param top current stack-top (item count) + * @param depth 0 for the topmost item, 1 for the item below, etc. + * @return the address formed from the lower 160 bits of the stack word + */ + protected static Address toAddressAt(final long[] stack, final int top, final int depth) { + final int off = (top - 1 - depth) << 2; + byte[] bytes = new byte[20]; + // u2 has top 4 bytes, u1 has next 8, u0 has last 8 + INT_BE.set(bytes, 0, (int) stack[off + 1]); + LONG_BE.set(bytes, 4, stack[off + 2]); + LONG_BE.set(bytes, 12, stack[off + 3]); + return Address.wrap(org.apache.tuweni.bytes.Bytes.wrap(bytes)); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java index 0480e77b067..cc21ffe881d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java @@ -25,10 +25,6 @@ /** The Abstract fixed cost operation. */ abstract class AbstractFixedCostOperationV2 extends AbstractOperation { - /** Shared underflow response for static operation methods. */ - static final OperationResult UNDERFLOW_RESPONSE = - new OperationResult(0L, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); - /** The Success response. */ protected final OperationResult successResponse; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java new file mode 100644 index 00000000000..1dacc2249b8 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java @@ -0,0 +1,71 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.AbstractOperation; + +public class BalanceOperationV2 extends AbstractOperation { + + /** + * Instantiates a new Balance operation. + * + * @param gasCalculator the gas calculator + */ + public BalanceOperationV2(final GasCalculator gasCalculator) { + super(0x31, "BALANCE", 1, 1, gasCalculator); + } + + /** + * Gets Balance operation Gas Cost plus warm storage read cost or cold account access cost. + * + * @param accountIsWarm true to add warm storage read cost, false to add cold account access cost + * @return the long + */ + protected long cost(final boolean accountIsWarm) { + return gasCalculator().getBalanceOperationGasCost() + + (accountIsWarm + ? gasCalculator().getWarmStorageReadCost() + : gasCalculator().getColdAccountAccessCost()); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + if (!frame.stackHasItems(1)) return UNDERFLOW_RESPONSE; + + final long[] stack = frame.stackDataV2(); + final int top = frame.stackTopV2(); + Address address = toAddressAt(stack, top, 0); + final boolean accountIsWarm = + frame.warmUpAddress(address) || gasCalculator().isPrecompile(address); + final long cost = cost(accountIsWarm); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + final Account account = getAccount(address, frame); + if (account == null) { + pushZero(stack, top - 1); + } else { + pushWei(account.getBalance(), stack, top - 1); + } + // no setTopV2 needed -- pop 1 + push 1 = net 0 + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java new file mode 100644 index 00000000000..6025663d330 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java @@ -0,0 +1,52 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; + +import java.util.Optional; + +/** The Base fee operation. */ +public class BaseFeeOperationV2 extends AbstractFixedCostOperationV2 { + + /** + * Instantiates a new Base fee operation. + * + * @param gasCalculator the gas calculator + */ + public BaseFeeOperationV2(final GasCalculator gasCalculator) { + super(0x48, "BASEFEE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + final Optional maybeBaseFee = frame.getBlockValues().getBaseFee(); + if (maybeBaseFee.isEmpty()) { + return new Operation.OperationResult(gasCost, ExceptionalHaltReason.INVALID_OPERATION); + } + final long[] stack = frame.stackDataV2(); + final int top = frame.stackTopV2(); + pushWei(maybeBaseFee.get(), stack, top); + frame.setTopV2(top + 1); + return successResponse; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java new file mode 100644 index 00000000000..17b16971920 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java @@ -0,0 +1,42 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +/** The Blob Base fee operation. */ +public class BlobBaseFeeOperationV2 extends AbstractFixedCostOperationV2 { + + /** + * Instantiates a new Blob Base fee operation. + * + * @param gasCalculator the gas calculator + */ + public BlobBaseFeeOperationV2(final GasCalculator gasCalculator) { + super(0x4a, "BLOBBASEFEE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + final long[] stack = frame.stackDataV2(); + final int top = frame.stackTopV2(); + pushWei(frame.getBlobGasPrice(), stack, top); + frame.setTopV2(top + 1); + return successResponse; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java new file mode 100644 index 00000000000..9319c29e7ef --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java @@ -0,0 +1,44 @@ +/* + * 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; + +/** The Call value operation. */ +public class CallValueOperationV2 extends AbstractFixedCostOperationV2 { + + /** + * Instantiates a new Call value operation. + * + * @param gasCalculator the gas calculator + */ + public CallValueOperationV2(final GasCalculator gasCalculator) { + super(0x34, "CALLVALUE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + final long[] stack = frame.stackDataV2(); + final int top = frame.stackTopV2(); + pushWei(frame.getApparentValue(), stack, top); + frame.setTopV2(top + 1); + return successResponse; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java new file mode 100644 index 00000000000..a841a6f9a56 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java @@ -0,0 +1,44 @@ +/* + * 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; + +/** The Gas price operation. */ +public class GasPriceOperationV2 extends AbstractFixedCostOperationV2 { + + /** + * Instantiates a new Gas price operation. + * + * @param gasCalculator the gas calculator + */ + public GasPriceOperationV2(final GasCalculator gasCalculator) { + super(0x3A, "GASPRICE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + final long[] stack = frame.stackDataV2(); + final int top = frame.stackTopV2(); + pushWei(frame.getGasPrice(), stack, top); + frame.setTopV2(top + 1); + return successResponse; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java new file mode 100644 index 00000000000..0d64bece2b0 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java @@ -0,0 +1,49 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; + +public class SelfBalanceOperationV2 extends AbstractFixedCostOperationV2 { + + /** + * Instantiates a new Self balance operation. + * + * @param gasCalculator the gas calculator + */ + public SelfBalanceOperationV2(final GasCalculator gasCalculator) { + super(0x47, "SELFBALANCE", 0, 1, gasCalculator, gasCalculator.getLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + final long[] s = frame.stackDataV2(); + final int top = frame.stackTopV2(); + final Account account = getAccount(frame.getRecipientAddress(), frame); + if (account == null) { + pushZero(s, top); + } else { + pushWei(account.getBalance(), s, top); + } + frame.setTopV2(top + 1); + return successResponse; + } +} From ff6d03e0d09975ad472bfd691fba053579768c64 Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Mon, 13 Apr 2026 17:13:15 +0200 Subject: [PATCH 02/19] Update SelfBalance benchmarks Signed-off-by: Ameziane H. --- .../SelfBalanceOperationBenchmark.java | 79 ++++++------------- .../v2/SelfBalanceOperationBenchmarkV2.java | 71 +++++++---------- 2 files changed, 53 insertions(+), 97 deletions(-) diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SelfBalanceOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SelfBalanceOperationBenchmark.java index d353d146f4c..0e02d0da47e 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SelfBalanceOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SelfBalanceOperationBenchmark.java @@ -30,11 +30,8 @@ import org.hyperledger.besu.evm.operation.SelfBalanceOperation; import org.hyperledger.besu.evm.worldstate.WorldUpdater; -import java.io.IOException; -import java.util.Random; import java.util.concurrent.TimeUnit; -import org.apache.tuweni.bytes.Bytes; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Measurement; @@ -44,6 +41,7 @@ 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 = 3, timeUnit = TimeUnit.SECONDS) @@ -52,65 +50,40 @@ @BenchmarkMode(Mode.AverageTime) public class SelfBalanceOperationBenchmark { - private static final int NUMBER_ADDRESSES = 20000; - private static final Random RANDOM_GENERATOR = new Random(); private SelfBalanceOperation operation; - private MessageFrame[] frames; - private int executingFrameIndex = 0; - - private MessageFrame createMessageFrame( - final Blockchain blockchain, - final WorldUpdater worldUpdater, - final ExecutionContextTestFixture executionContextTestFixture, - final Address address) { - final BlockHeader blockHeader = new BlockHeaderTestFixture().buildHeader(); - return new MessageFrameTestFixture() - .address(address) - .worldUpdater(worldUpdater) - .blockHeader(blockHeader) - .executionContextTestFixture(executionContextTestFixture) - .blockchain(blockchain) - .build(); - } - - private WorldUpdater createWorldUpdater(final Blockchain blockchain, final Address[] addresses) - throws IOException { - final WorldUpdater worldStateUpdater; - try (WorldStateArchive worldStateArchive = createBonsaiInMemoryWorldStateArchive(blockchain)) { - worldStateUpdater = worldStateArchive.getWorldState().updater(); - } - for (Address address : addresses) { - worldStateUpdater.getOrCreate(address).setBalance(Wei.of(1)); - } - worldStateUpdater.commit(); - return worldStateUpdater; - } + private MessageFrame frame; @Setup public void prepare() throws Exception { operation = new SelfBalanceOperation(mock(GasCalculator.class)); final Blockchain blockchain = mock(Blockchain.class); - final Address[] addresses = new Address[NUMBER_ADDRESSES]; - for (int j = 0; j < addresses.length; j++) { - final byte[] address = new byte[Address.SIZE]; - RANDOM_GENERATOR.nextBytes(address); - addresses[j] = Address.wrap(Bytes.wrap(address)); + final Address address = Address.fromHexString("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + + final WorldUpdater worldUpdater; + try (WorldStateArchive archive = createBonsaiInMemoryWorldStateArchive(blockchain)) { + worldUpdater = archive.getWorldState().updater(); } - frames = new MessageFrame[NUMBER_ADDRESSES]; - final WorldUpdater worldUpdater = createWorldUpdater(blockchain, addresses); + worldUpdater.getOrCreate(address).setBalance(Wei.of(1)); + worldUpdater.commit(); + final ExecutionContextTestFixture executionContextTestFixture = - ExecutionContextTestFixture.create(); - for (int i = 0; i < NUMBER_ADDRESSES; i++) { - frames[i] = - createMessageFrame(blockchain, worldUpdater, executionContextTestFixture, addresses[i]); - } + ExecutionContextTestFixture.create(); + final BlockHeader blockHeader = new BlockHeaderTestFixture().buildHeader(); + frame = new MessageFrameTestFixture() + .address(address) + .worldUpdater(worldUpdater) + .blockHeader(blockHeader) + .executionContextTestFixture(executionContextTestFixture) + .blockchain(blockchain) + .build(); + + // Pre-warm: force trie path into memory + worldUpdater.get(address); } @Benchmark - public void executeOperation() { - final MessageFrame executingFrame = frames[executingFrameIndex++]; - operation.execute(executingFrame, null); - executingFrame.popStackItem(); - executingFrameIndex = executingFrameIndex % NUMBER_ADDRESSES; + public void executeOperation(final Blackhole blackhole) { + blackhole.consume(operation.execute(frame, null)); + frame.popStackItem(); } -} +} \ No newline at end of file diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SelfBalanceOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SelfBalanceOperationBenchmarkV2.java index a69c54e848e..8b4ae6b5d1f 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SelfBalanceOperationBenchmarkV2.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SelfBalanceOperationBenchmarkV2.java @@ -29,10 +29,8 @@ import org.hyperledger.besu.evm.v2.operation.SelfBalanceOperationV2; import org.hyperledger.besu.evm.worldstate.WorldUpdater; -import java.util.Random; import java.util.concurrent.TimeUnit; -import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; @@ -52,65 +50,50 @@ @BenchmarkMode(Mode.AverageTime) public class SelfBalanceOperationBenchmarkV2 { - private static final int NUMBER_ADDRESSES = 20_000; - private static final Random RANDOM = new Random(); - private SelfBalanceOperationV2 operation; - private MessageFrame[] frames; - private int index = 0; + private MessageFrame frame; @Setup public void setup() throws Exception { operation = new SelfBalanceOperationV2(mock(GasCalculator.class)); final Blockchain blockchain = mock(Blockchain.class); - - final Address[] addresses = new Address[NUMBER_ADDRESSES]; - for (int i = 0; i < addresses.length; i++) { - final byte[] raw = new byte[Address.SIZE]; - RANDOM.nextBytes(raw); - addresses[i] = Address.wrap(Bytes.wrap(raw)); - } + final Address address = Address.fromHexString("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); final WorldUpdater worldUpdater; try (WorldStateArchive archive = createBonsaiInMemoryWorldStateArchive(blockchain)) { worldUpdater = archive.getWorldState().updater(); } - for (Address addr : addresses) { - worldUpdater.getOrCreate(addr).setBalance(Wei.of(1)); - } + worldUpdater.getOrCreate(address).setBalance(Wei.of(1)); worldUpdater.commit(); - frames = new MessageFrame[NUMBER_ADDRESSES]; - for (int i = 0; i < NUMBER_ADDRESSES; i++) { - frames[i] = - MessageFrame.builder() - .enableEvmV2(true) - .worldUpdater(worldUpdater) - .originator(Address.ZERO) - .gasPrice(Wei.ONE) - .blobGasPrice(Wei.ONE) - .blockValues(mock(BlockValues.class)) - .miningBeneficiary(Address.ZERO) - .blockHashLookup((__, ___) -> Hash.ZERO) - .type(MessageFrame.Type.MESSAGE_CALL) - .initialGas(Long.MAX_VALUE) - .address(addresses[i]) - .contract(addresses[i]) - .inputData(Bytes32.ZERO) - .sender(Address.ZERO) - .value(Wei.ZERO) - .apparentValue(Wei.ZERO) - .code(Code.EMPTY_CODE) - .completer(__ -> {}) - .build(); - } + frame = MessageFrame.builder() + .enableEvmV2(true) + .worldUpdater(worldUpdater) + .originator(Address.ZERO) + .gasPrice(Wei.ONE) + .blobGasPrice(Wei.ONE) + .blockValues(mock(BlockValues.class)) + .miningBeneficiary(Address.ZERO) + .blockHashLookup((__, ___) -> Hash.ZERO) + .type(MessageFrame.Type.MESSAGE_CALL) + .initialGas(Long.MAX_VALUE) + .address(address) + .contract(address) + .inputData(Bytes32.ZERO) + .sender(Address.ZERO) + .value(Wei.ZERO) + .apparentValue(Wei.ZERO) + .code(Code.EMPTY_CODE) + .completer(__ -> {}) + .build(); + + // Pre-warm: force trie path into memory + worldUpdater.get(address); } @Benchmark public void executeOperation(final Blackhole blackhole) { - final MessageFrame frame = frames[index]; blackhole.consume(operation.execute(frame, null)); frame.setTopV2(frame.stackTopV2() - 1); - index = (index + 1) % NUMBER_ADDRESSES; } -} +} \ No newline at end of file From de6a840aa981d50003ea5c77d6522c3151b000bd Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Mon, 13 Apr 2026 18:37:04 +0200 Subject: [PATCH 03/19] spotless Signed-off-by: Ameziane H. --- .../vm/operations/SelfBalanceOperationBenchmark.java | 7 ++++--- .../vm/operations/v2/SelfBalanceOperationBenchmarkV2.java | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SelfBalanceOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SelfBalanceOperationBenchmark.java index 0e02d0da47e..73b86cf4dd1 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SelfBalanceOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SelfBalanceOperationBenchmark.java @@ -67,9 +67,10 @@ public void prepare() throws Exception { worldUpdater.commit(); final ExecutionContextTestFixture executionContextTestFixture = - ExecutionContextTestFixture.create(); + ExecutionContextTestFixture.create(); final BlockHeader blockHeader = new BlockHeaderTestFixture().buildHeader(); - frame = new MessageFrameTestFixture() + frame = + new MessageFrameTestFixture() .address(address) .worldUpdater(worldUpdater) .blockHeader(blockHeader) @@ -86,4 +87,4 @@ public void executeOperation(final Blackhole blackhole) { blackhole.consume(operation.execute(frame, null)); frame.popStackItem(); } -} \ No newline at end of file +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SelfBalanceOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SelfBalanceOperationBenchmarkV2.java index 8b4ae6b5d1f..beb33076f0c 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SelfBalanceOperationBenchmarkV2.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SelfBalanceOperationBenchmarkV2.java @@ -66,7 +66,8 @@ public void setup() throws Exception { worldUpdater.getOrCreate(address).setBalance(Wei.of(1)); worldUpdater.commit(); - frame = MessageFrame.builder() + frame = + MessageFrame.builder() .enableEvmV2(true) .worldUpdater(worldUpdater) .originator(Address.ZERO) @@ -96,4 +97,4 @@ public void executeOperation(final Blackhole blackhole) { blackhole.consume(operation.execute(frame, null)); frame.setTopV2(frame.stackTopV2() - 1); } -} \ No newline at end of file +} From 436c308e566bb2b1c84bfcb7f7bcbe8267669a22 Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Wed, 15 Apr 2026 10:31:29 +0200 Subject: [PATCH 04/19] Address comments. Signed-off-by: Ameziane H. --- .../org/hyperledger/besu/datatypes/Wei.java | 68 ++------------ .../besu/evm/frame/MessageFrame.java | 6 +- .../besu/evm/operation/AbstractOperation.java | 67 -------------- .../hyperledger/besu/evm/v2/StackUtil.java | 90 +++++++++++++++++++ .../AbstractFixedCostOperationV2.java | 23 +---- .../evm/v2/operation/BalanceOperationV2.java | 5 ++ .../evm/v2/operation/BaseFeeOperationV2.java | 3 + .../v2/operation/BlobBaseFeeOperationV2.java | 3 + .../v2/operation/CallValueOperationV2.java | 3 + .../evm/v2/operation/GasPriceOperationV2.java | 3 + .../besu/evm/v2/operation/SarOperationV2.java | 2 + .../v2/operation/SelfBalanceOperationV2.java | 4 + .../besu/evm/v2/operation/ShlOperationV2.java | 2 + .../besu/evm/v2/operation/ShrOperationV2.java | 2 + 14 files changed, 129 insertions(+), 152 deletions(-) create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/StackUtil.java diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java index bd9cae8800d..843568192f7 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java @@ -31,11 +31,6 @@ public final class Wei extends BaseUInt256Value implements Quantity { private static final VarHandle LONG_BE = MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN); - private final long u3; - private final long u2; - private final long u1; - private final long u0; - /** The constant ZERO. */ public static final Wei ZERO = of(0); @@ -52,11 +47,6 @@ public final class Wei extends BaseUInt256Value implements Quantity { */ Wei(final UInt256 value) { super(value, Wei::new); - final byte[] b = value.toArray(); - this.u3 = (long) LONG_BE.get(b, 0); - this.u2 = (long) LONG_BE.get(b, 8); - this.u1 = (long) LONG_BE.get(b, 16); - this.u0 = (long) LONG_BE.get(b, 24); } private Wei(final long v) { @@ -141,42 +131,6 @@ public static Wei fromEth(final long eth) { return Wei.of(BigInteger.valueOf(eth).multiply(BigInteger.TEN.pow(18))); } - /** - * Returns the most-significant limb (bits 255..192). - * - * @return the u3 limb - */ - public long u3() { - return u3; - } - - /** - * Returns limb 2 (bits 191..128). - * - * @return the u2 limb - */ - public long u2() { - return u2; - } - - /** - * Returns limb 1 (bits 127..64). - * - * @return the u1 limb - */ - public long u1() { - return u1; - } - - /** - * Returns the least-significant limb (bits 63..0). - * - * @return the u0 limb - */ - public long u0() { - return u0; - } - /** * Writes the 4 big-endian long limbs into the target array at the given offset. This enables * zero-allocation transfer to the EVM operand stack. @@ -185,23 +139,11 @@ public long u0() { * @param off the offset into the array where u3 should be written */ public void writeLimbs(final long[] target, final int off) { - target[off] = u3; - target[off + 1] = u2; - target[off + 2] = u1; - target[off + 3] = u0; - } - - @Override - public boolean fitsLong() { - return u3 == 0 && u2 == 0 && u1 == 0 && u0 >= 0; - } - - @Override - public long toLong() { - if (!fitsLong()) { - throw new ArithmeticException("Value does not fit a 8 byte long"); - } - return u0; + final byte[] b = toArrayUnsafe(); + target[off] = (long) LONG_BE.get(b, 0); + target[off + 1] = (long) LONG_BE.get(b, 8); + target[off + 2] = (long) LONG_BE.get(b, 16); + target[off + 3] = (long) LONG_BE.get(b, 24); } @Override diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java index 326b163e028..61cc1039d2c 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java @@ -208,7 +208,7 @@ public enum Type { // significant) private final long[] stackDataV2; private int stackTopV2; - private final int stackMaxSize; + private final int stackMaxSizeV2; private Bytes output = Bytes.EMPTY; private Bytes returnData = Bytes.EMPTY; private Code createdCode = null; @@ -279,7 +279,7 @@ private MessageFrame( this.stack = new OperandStack(txValues.maxStackSize()); this.stackDataV2 = enableEvmV2 ? new long[txValues.maxStackSize() * 4] : null; this.stackTopV2 = 0; - this.stackMaxSize = txValues.maxStackSize(); + this.stackMaxSizeV2 = txValues.maxStackSize(); this.pc = 0; this.recipient = recipient; this.contract = contract; @@ -527,7 +527,7 @@ public boolean stackHasItems(final int n) { * @return true if the stack can accommodate n more items */ public boolean stackHasSpace(final int n) { - return stackTopV2 + n <= stackMaxSize; + return stackTopV2 + n <= stackMaxSizeV2; } // --------------------------------------------------------------------------- diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java index 271d2cc0414..3b5c0939995 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java @@ -15,17 +15,11 @@ package org.hyperledger.besu.evm.operation; import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.account.MutableAccount; -import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.VarHandle; -import java.nio.ByteOrder; - import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; @@ -35,19 +29,6 @@ */ public abstract class AbstractOperation implements Operation { - protected static final VarHandle LONG_BE = - MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN); - protected static final VarHandle INT_BE = - MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.BIG_ENDIAN); - - /** Shared underflow response for static operation methods. */ - protected static final OperationResult UNDERFLOW_RESPONSE = - new OperationResult(0L, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); - - /** Shared overflow response for static operation methods. */ - protected static final OperationResult OVERFLOW_RESPONSE = - new OperationResult(0L, ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); - static final Bytes BYTES_ONE = Bytes.of(1); private final int opcode; @@ -185,52 +166,4 @@ protected UInt256 getStorageValue( .ifPresent(t -> t.addSlotAccessForAccount(account.getAddress(), slotKey)); return slotValue; } - - /** - * Writes a zero-valued 256-bit word at the given stack slot. - * - * @param stack the flat limb array (4 longs per 256-bit word) - * @param top the slot index to write to - */ - protected static void pushZero(final long[] stack, final int top) { - final int offset = top << 2; - stack[offset] = 0; - stack[offset + 1] = 0; - stack[offset + 2] = 0; - stack[offset + 3] = 0; - } - - /** - * Writes a {@link Wei} value as four big-endian limbs at the given stack slot. - * - * @param wei the Wei value to write - * @param stack the flat limb array - * @param top the slot index to write to - */ - protected static void pushWei(final Wei wei, final long[] stack, final int top) { - final int offset = top << 2; - stack[offset] = wei.u3(); - stack[offset + 1] = wei.u2(); - stack[offset + 2] = wei.u1(); - stack[offset + 3] = wei.u0(); - } - - /** - * Extracts a 160-bit {@link Address} from a 256-bit stack word at the given depth below the top - * of stack. - * - * @param stack the flat limb array - * @param top current stack-top (item count) - * @param depth 0 for the topmost item, 1 for the item below, etc. - * @return the address formed from the lower 160 bits of the stack word - */ - protected static Address toAddressAt(final long[] stack, final int top, final int depth) { - final int off = (top - 1 - depth) << 2; - byte[] bytes = new byte[20]; - // u2 has top 4 bytes, u1 has next 8, u0 has last 8 - INT_BE.set(bytes, 0, (int) stack[off + 1]); - LONG_BE.set(bytes, 4, stack[off + 2]); - LONG_BE.set(bytes, 12, stack[off + 3]); - return Address.wrap(org.apache.tuweni.bytes.Bytes.wrap(bytes)); - } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/StackUtil.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/StackUtil.java new file mode 100644 index 00000000000..d7a7f1fcb28 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/StackUtil.java @@ -0,0 +1,90 @@ +/* + * 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; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.operation.Operation; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.nio.ByteOrder; + +/** + * Static utility for reading/writing typed values on the flat {@code long[]} V2 operand stack. Each + * 256-bit word occupies 4 consecutive longs in big-endian limb order: {@code [u3, u2, u1, u0]} + * where u3 is the most-significant limb. + */ +public final class StackUtil { + + private static final VarHandle LONG_BE = + MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN); + private static final VarHandle INT_BE = + MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.BIG_ENDIAN); + + /** Shared underflow response (zero gas cost). */ + public static final Operation.OperationResult UNDERFLOW_RESPONSE = + new Operation.OperationResult(0L, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + + /** Shared overflow response (zero gas cost). */ + public static final Operation.OperationResult OVERFLOW_RESPONSE = + new Operation.OperationResult(0L, ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); + + private StackUtil() {} + + /** + * Writes a zero-valued 256-bit word at the given stack slot. + * + * @param stack the flat limb array (4 longs per 256-bit word) + * @param top the slot index to write to + */ + public static void pushZero(final long[] stack, final int top) { + final int offset = top << 2; + stack[offset] = 0; + stack[offset + 1] = 0; + stack[offset + 2] = 0; + stack[offset + 3] = 0; + } + + /** + * Writes a {@link Wei} value as four big-endian limbs at the given stack slot. + * + * @param wei the Wei value to write + * @param stack the flat limb array + * @param top the slot index to write to + */ + public static void pushWei(final Wei wei, final long[] stack, final int top) { + wei.writeLimbs(stack, top << 2); + } + + /** + * Extracts a 160-bit {@link Address} from a 256-bit stack word at the given depth below the top + * of stack. + * + * @param stack the flat limb array + * @param top current stack-top (item count) + * @param depth 0 for the topmost item, 1 for the item below, etc. + * @return the address formed from the lower 160 bits of the stack word + */ + public static Address toAddressAt(final long[] stack, final int top, final int depth) { + final int off = (top - 1 - depth) << 2; + byte[] bytes = new byte[20]; + INT_BE.set(bytes, 0, (int) stack[off + 1]); + LONG_BE.set(bytes, 4, stack[off + 2]); + LONG_BE.set(bytes, 12, stack[off + 3]); + return Address.wrap(org.apache.tuweni.bytes.Bytes.wrap(bytes)); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java index cc21ffe881d..3677d31c5dc 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java @@ -18,11 +18,9 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.OverflowException; -import org.hyperledger.besu.evm.internal.UnderflowException; import org.hyperledger.besu.evm.operation.AbstractOperation; -/** The Abstract fixed cost operation. */ +/** The Abstract fixed cost operation for V2 (long[] stack) operations. */ abstract class AbstractFixedCostOperationV2 extends AbstractOperation { /** The Success response. */ @@ -31,9 +29,6 @@ abstract class AbstractFixedCostOperationV2 extends AbstractOperation { /** The Out of gas response. */ protected final OperationResult outOfGasResponse; - private final OperationResult underflowResponse; - private final OperationResult overflowResponse; - /** The Gas cost. */ protected final long gasCost; @@ -58,24 +53,14 @@ protected AbstractFixedCostOperationV2( gasCost = fixedCost; successResponse = new OperationResult(gasCost, null); outOfGasResponse = new OperationResult(gasCost, ExceptionalHaltReason.INSUFFICIENT_GAS); - underflowResponse = - new OperationResult(gasCost, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); - overflowResponse = new OperationResult(gasCost, ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); } @Override public final OperationResult execute(final MessageFrame frame, final EVM evm) { - try { - if (frame.getRemainingGas() < gasCost) { - return outOfGasResponse; - } else { - return executeFixedCostOperation(frame, evm); - } - } catch (final UnderflowException ufe) { - return underflowResponse; - } catch (final OverflowException ofe) { - return overflowResponse; + if (frame.getRemainingGas() < gasCost) { + return outOfGasResponse; } + return executeFixedCostOperation(frame, evm); } /** diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java index 1dacc2249b8..3eac723e7c9 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java @@ -14,6 +14,11 @@ */ package org.hyperledger.besu.evm.v2.operation; +import static org.hyperledger.besu.evm.v2.StackUtil.UNDERFLOW_RESPONSE; +import static org.hyperledger.besu.evm.v2.StackUtil.pushWei; +import static org.hyperledger.besu.evm.v2.StackUtil.pushZero; +import static org.hyperledger.besu.evm.v2.StackUtil.toAddressAt; + import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.account.Account; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java index 6025663d330..4da9c784c5c 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java @@ -14,6 +14,9 @@ */ package org.hyperledger.besu.evm.v2.operation; +import static org.hyperledger.besu.evm.v2.StackUtil.OVERFLOW_RESPONSE; +import static org.hyperledger.besu.evm.v2.StackUtil.pushWei; + import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java index 17b16971920..58af2c5b20f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java @@ -14,6 +14,9 @@ */ package org.hyperledger.besu.evm.v2.operation; +import static org.hyperledger.besu.evm.v2.StackUtil.OVERFLOW_RESPONSE; +import static org.hyperledger.besu.evm.v2.StackUtil.pushWei; + import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java index 9319c29e7ef..907ab2fec4c 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java @@ -14,6 +14,9 @@ */ package org.hyperledger.besu.evm.v2.operation; +import static org.hyperledger.besu.evm.v2.StackUtil.OVERFLOW_RESPONSE; +import static org.hyperledger.besu.evm.v2.StackUtil.pushWei; + import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java index a841a6f9a56..44de819c19f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java @@ -14,6 +14,9 @@ */ package org.hyperledger.besu.evm.v2.operation; +import static org.hyperledger.besu.evm.v2.StackUtil.OVERFLOW_RESPONSE; +import static org.hyperledger.besu.evm.v2.StackUtil.pushWei; + import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; 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 d488886c5df..0f63f0682d4 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 @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.evm.v2.operation; +import static org.hyperledger.besu.evm.v2.StackUtil.UNDERFLOW_RESPONSE; + import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java index 0d64bece2b0..1a45232af1f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java @@ -14,6 +14,10 @@ */ package org.hyperledger.besu.evm.v2.operation; +import static org.hyperledger.besu.evm.v2.StackUtil.OVERFLOW_RESPONSE; +import static org.hyperledger.besu.evm.v2.StackUtil.pushWei; +import static org.hyperledger.besu.evm.v2.StackUtil.pushZero; + import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.frame.MessageFrame; 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 2f765e94269..6358537b2fd 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 @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.evm.v2.operation; +import static org.hyperledger.besu.evm.v2.StackUtil.UNDERFLOW_RESPONSE; + import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; 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 71c412a04ab..00c48583ac3 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 @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.evm.v2.operation; +import static org.hyperledger.besu.evm.v2.StackUtil.UNDERFLOW_RESPONSE; + import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; From edcc53193a04139526d2ef00526beedc31a0cd6b Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Wed, 15 Apr 2026 10:53:54 +0200 Subject: [PATCH 05/19] Add Javadoc Signed-off-by: Ameziane H. --- .../hyperledger/besu/evm/v2/operation/BalanceOperationV2.java | 1 + .../besu/evm/v2/operation/SelfBalanceOperationV2.java | 1 + 2 files changed, 2 insertions(+) diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java index 3eac723e7c9..71a0a1dab1e 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java @@ -27,6 +27,7 @@ import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.operation.AbstractOperation; +/** The Balance operation. */ public class BalanceOperationV2 extends AbstractOperation { /** diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java index 1a45232af1f..ee4b7da95e2 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java @@ -24,6 +24,7 @@ import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.operation.Operation; +/** The Self balance operation. */ public class SelfBalanceOperationV2 extends AbstractFixedCostOperationV2 { /** From f9695161de89419a0270ed9ca62165e3e3cbf0eb Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Thu, 16 Apr 2026 16:02:59 +0200 Subject: [PATCH 06/19] Add unit tests Signed-off-by: Ameziane H. --- .../org/hyperledger/besu/datatypes/Wei.java | 1 + .../ethereum/core/ProcessableBlockHeader.java | 11 +- .../v2/operation/BalanceOperationV2Test.java | 143 ++++++++++++++++++ .../v2/operation/BaseFeeOperationV2Test.java | 100 ++++++++++++ .../operation/BlobBaseFeeOperationV2Test.java | 90 +++++++++++ .../operation/CallValueOperationV2Test.java | 90 +++++++++++ .../v2/operation/GasPriceOperationV2Test.java | 75 +++++++++ .../evm/v2/operation/SarOperationV2Test.java | 7 +- .../operation/SelfBalanceOperationV2Test.java | 113 ++++++++++++++ .../evm/v2/operation/ShlOperationV2Test.java | 7 +- .../evm/v2/operation/ShrOperationV2Test.java | 7 +- .../testutils/TestMessageFrameBuilderV2.java | 13 ++ 12 files changed, 634 insertions(+), 23 deletions(-) create mode 100644 evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2Test.java create mode 100644 evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2Test.java create mode 100644 evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2Test.java create mode 100644 evm/src/test/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2Test.java create mode 100644 evm/src/test/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2Test.java create mode 100644 evm/src/test/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2Test.java diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java index 843568192f7..d182d934873 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java @@ -139,6 +139,7 @@ public static Wei fromEth(final long eth) { * @param off the offset into the array where u3 should be written */ public void writeLimbs(final long[] target, final int off) { + // TODO store this representation at build time when switching from v1 to v2 final byte[] b = toArrayUnsafe(); target[off] = (long) LONG_BE.get(b, 0); target[off + 1] = (long) LONG_BE.get(b, 8); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java index a9356801f7a..93b42bb3662 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java @@ -43,7 +43,7 @@ public class ProcessableBlockHeader // base fee is included for post EIP-1559 blocks protected final Wei baseFee; // store optional base fee to compute it at build time - protected final Optional optionalBaseFee; + protected final Optional maybeBaseFee; // prevRandao is included for post-merge blocks protected final Bytes32 mixHashOrPrevRandao; // parentBeaconBlockRoot is included for Cancun @@ -69,7 +69,7 @@ protected ProcessableBlockHeader( this.gasLimit = gasLimit; this.timestamp = timestamp; this.baseFee = baseFee; - this.optionalBaseFee = Optional.ofNullable(baseFee); + this.maybeBaseFee = Optional.ofNullable(baseFee); this.mixHashOrPrevRandao = mixHashOrPrevRandao; this.parentBeaconBlockRoot = parentBeaconBlockRoot; this.slotNumber = slotNumber; @@ -152,7 +152,7 @@ public long getTimestamp() { */ @Override public Optional getBaseFee() { - return optionalBaseFee; + return maybeBaseFee; } /** @@ -219,8 +219,9 @@ public String toString() { sb.append("difficulty=").append(difficulty).append(", "); sb.append("gasLimit=").append(gasLimit).append(", "); sb.append("timestamp=").append(timestamp).append(", "); - if (optionalBaseFee.isPresent()) - sb.append("baseFee=").append(optionalBaseFee.get()).append(", "); + if (maybeBaseFee.isPresent()) { + sb.append("baseFee=").append(maybeBaseFee.get()).append(", "); + } sb.append("mixHashOrPrevRandao=").append(mixHashOrPrevRandao).append(", "); if (parentBeaconBlockRoot != null) { sb.append("parentBeaconBlockRoot=").append(parentBeaconBlockRoot).append(", "); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2Test.java new file mode 100644 index 00000000000..57efd63d100 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2Test.java @@ -0,0 +1,143 @@ +/* + * 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.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +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.BerlinGasCalculator; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation.OperationResult; +import org.hyperledger.besu.evm.toy.ToyWorld; +import org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2; + +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.Test; + +class BalanceOperationV2Test { + + private static final Address TARGET = Address.fromHexString("0xABCDEF0123456789ABCD"); + private final GasCalculator gasCalculator = new BerlinGasCalculator(); + private final BalanceOperationV2 operation = new BalanceOperationV2(gasCalculator); + + @Test + void shouldPushBalanceToStack() { + final ToyWorld world = new ToyWorld(); + world.createAccount(TARGET, 0, Wei.of(99999L)); + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .worldUpdater(world) + .pushStackItem(Bytes32.leftPad(TARGET.getBytes())) + .build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)).isEqualTo(new UInt256(0, 0, 0, 99999L)); + } + + @Test + void shouldPushZeroForNonExistentAccount() { + final ToyWorld world = new ToyWorld(); + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .worldUpdater(world) + .pushStackItem(Bytes32.leftPad(TARGET.getBytes())) + .build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)).isEqualTo(UInt256.ZERO); + } + + @Test + void shouldPushLargeBalance() { + final Wei largeBalance = + Wei.fromHexString("0x00000000000000010000000000000002000000000000000300000000000000FF"); + final ToyWorld world = new ToyWorld(); + world.createAccount(TARGET, 0, largeBalance); + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .worldUpdater(world) + .pushStackItem(Bytes32.leftPad(TARGET.getBytes())) + .build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)) + .isEqualTo( + new UInt256( + 0x0000000000000001L, + 0x0000000000000002L, + 0x0000000000000003L, + 0x00000000000000FFL)); + } + + @Test + void shouldChargeColdAccessCost() { + final ToyWorld world = new ToyWorld(); + world.createAccount(TARGET, 0, Wei.of(1L)); + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .worldUpdater(world) + .pushStackItem(Bytes32.leftPad(TARGET.getBytes())) + .build(); + final OperationResult result = operation.execute(frame, null); + final long expectedCost = + gasCalculator.getBalanceOperationGasCost() + gasCalculator.getColdAccountAccessCost(); + assertThat(result.getGasCost()).isEqualTo(expectedCost); + assertThat(result.getHaltReason()).isNull(); + } + + @Test + void shouldChargeWarmAccessCost() { + final ToyWorld world = new ToyWorld(); + world.createAccount(TARGET, 0, Wei.of(1L)); + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .worldUpdater(world) + .pushStackItem(Bytes32.leftPad(TARGET.getBytes())) + .build(); + frame.warmUpAddress(TARGET); + final OperationResult result = operation.execute(frame, null); + final long expectedCost = + gasCalculator.getBalanceOperationGasCost() + gasCalculator.getWarmStorageReadCost(); + assertThat(result.getGasCost()).isEqualTo(expectedCost); + assertThat(result.getHaltReason()).isNull(); + } + + @Test + void shouldHaltOnInsufficientGas() { + final ToyWorld world = new ToyWorld(); + world.createAccount(TARGET, 0, Wei.of(1L)); + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .initialGas(1) + .worldUpdater(world) + .pushStackItem(Bytes32.leftPad(TARGET.getBytes())) + .build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + @Test + void shouldHaltOnStackUnderflow() { + final ToyWorld world = new ToyWorld(); + final MessageFrame frame = new TestMessageFrameBuilderV2().worldUpdater(world).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2Test.java new file mode 100644 index 00000000000..2b2d9072044 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2Test.java @@ -0,0 +1,100 @@ +/* + * 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.datatypes.Wei; +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.BerlinGasCalculator; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation.OperationResult; +import org.hyperledger.besu.evm.testutils.FakeBlockValues; +import org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; + +class BaseFeeOperationV2Test { + + private final GasCalculator gasCalculator = new BerlinGasCalculator(); + private final BaseFeeOperationV2 operation = new BaseFeeOperationV2(gasCalculator); + + @Test + void shouldReturnGasCost() { + final MessageFrame frame = createFrame(100, Optional.of(Wei.of(5L))); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getGasCost()).isEqualTo(gasCalculator.getBaseTierGasCost()); + assertThat(result.getHaltReason()).isNull(); + } + + @Test + void shouldWriteBaseFeeToStack() { + final MessageFrame frame = createFrame(100, Optional.of(Wei.of(5L))); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)).isEqualTo(new UInt256(0, 0, 0, 5L)); + } + + @Test + void shouldWriteLargeBaseFeeToStack() { + final Wei largeFee = + Wei.fromHexString("0x00000000000000010000000000000002000000000000000300000000000000FF"); + final MessageFrame frame = createFrame(100, Optional.of(largeFee)); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + final UInt256 stackVal = getV2StackItem(frame, 0); + assertThat(stackVal) + .isEqualTo( + new UInt256( + 0x0000000000000001L, + 0x0000000000000002L, + 0x0000000000000003L, + 0x00000000000000FFL)); + } + + @Test + void shouldHaltIfNoBaseFeeInBlockHeader() { + final MessageFrame frame = createFrame(100, Optional.empty()); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INVALID_OPERATION); + } + + @Test + void shouldHaltOnInsufficientGas() { + final MessageFrame frame = createFrame(1, Optional.of(Wei.of(5L))); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + @Test + void shouldHaltOnStackOverflow() { + final MessageFrame frame = createFrame(100, Optional.of(Wei.of(5L))); + frame.setTopV2(MessageFrame.DEFAULT_MAX_STACK_SIZE); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); + } + + private MessageFrame createFrame(final long initialGas, final Optional baseFee) { + return new TestMessageFrameBuilderV2() + .initialGas(initialGas) + .blockValues(new FakeBlockValues(baseFee)) + .build(); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2Test.java new file mode 100644 index 00000000000..2245d7cb8b9 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2Test.java @@ -0,0 +1,90 @@ +/* + * 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.datatypes.Wei; +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.BerlinGasCalculator; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation.OperationResult; +import org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2; + +import org.junit.jupiter.api.Test; + +class BlobBaseFeeOperationV2Test { + + private final GasCalculator gasCalculator = new BerlinGasCalculator(); + private final BlobBaseFeeOperationV2 operation = new BlobBaseFeeOperationV2(gasCalculator); + + @Test + void shouldPushBlobGasPriceToStack() { + final MessageFrame frame = new TestMessageFrameBuilderV2().blobGasPrice(Wei.of(42L)).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)).isEqualTo(new UInt256(0, 0, 0, 42L)); + } + + @Test + void shouldPushZeroBlobGasPrice() { + final MessageFrame frame = new TestMessageFrameBuilderV2().blobGasPrice(Wei.ZERO).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)).isEqualTo(UInt256.ZERO); + } + + @Test + void shouldPushLargeBlobGasPrice() { + final Wei largePrice = + Wei.fromHexString("0x00000000000000010000000000000002000000000000000300000000000000FF"); + final MessageFrame frame = new TestMessageFrameBuilderV2().blobGasPrice(largePrice).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)) + .isEqualTo( + new UInt256( + 0x0000000000000001L, + 0x0000000000000002L, + 0x0000000000000003L, + 0x00000000000000FFL)); + } + + @Test + void shouldReturnCorrectGasCost() { + final MessageFrame frame = new TestMessageFrameBuilderV2().blobGasPrice(Wei.of(1L)).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getGasCost()).isEqualTo(gasCalculator.getBaseTierGasCost()); + } + + @Test + void shouldHaltOnInsufficientGas() { + final MessageFrame frame = + new TestMessageFrameBuilderV2().initialGas(1).blobGasPrice(Wei.of(1L)).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + @Test + void shouldHaltOnStackOverflow() { + final MessageFrame frame = new TestMessageFrameBuilderV2().blobGasPrice(Wei.of(1L)).build(); + frame.setTopV2(MessageFrame.DEFAULT_MAX_STACK_SIZE); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2Test.java new file mode 100644 index 00000000000..f819541f435 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2Test.java @@ -0,0 +1,90 @@ +/* + * 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.datatypes.Wei; +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.BerlinGasCalculator; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation.OperationResult; +import org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2; + +import org.junit.jupiter.api.Test; + +class CallValueOperationV2Test { + + private final GasCalculator gasCalculator = new BerlinGasCalculator(); + private final CallValueOperationV2 operation = new CallValueOperationV2(gasCalculator); + + @Test + void shouldPushCallValueToStack() { + final MessageFrame frame = new TestMessageFrameBuilderV2().value(Wei.of(1_000L)).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)).isEqualTo(new UInt256(0, 0, 0, 1_000L)); + } + + @Test + void shouldPushZeroCallValue() { + final MessageFrame frame = new TestMessageFrameBuilderV2().value(Wei.ZERO).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)).isEqualTo(UInt256.ZERO); + } + + @Test + void shouldPushLargeCallValue() { + final Wei largeValue = + Wei.fromHexString("0x00000000000000010000000000000002000000000000000300000000000000FF"); + final MessageFrame frame = new TestMessageFrameBuilderV2().value(largeValue).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)) + .isEqualTo( + new UInt256( + 0x0000000000000001L, + 0x0000000000000002L, + 0x0000000000000003L, + 0x00000000000000FFL)); + } + + @Test + void shouldReturnCorrectGasCost() { + final MessageFrame frame = new TestMessageFrameBuilderV2().value(Wei.of(1L)).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getGasCost()).isEqualTo(gasCalculator.getBaseTierGasCost()); + } + + @Test + void shouldHaltOnInsufficientGas() { + final MessageFrame frame = + new TestMessageFrameBuilderV2().initialGas(0).value(Wei.of(1L)).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + @Test + void shouldHaltOnStackOverflow() { + final MessageFrame frame = new TestMessageFrameBuilderV2().value(Wei.of(1L)).build(); + frame.setTopV2(MessageFrame.DEFAULT_MAX_STACK_SIZE); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2Test.java new file mode 100644 index 00000000000..c95b5a929bb --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2Test.java @@ -0,0 +1,75 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2.getV2StackItem; + +import org.hyperledger.besu.datatypes.Wei; +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.BerlinGasCalculator; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation.OperationResult; +import org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2; + +import org.junit.jupiter.api.Test; + +class GasPriceOperationV2Test { + + private final GasCalculator gasCalculator = new BerlinGasCalculator(); + private final GasPriceOperationV2 operation = new GasPriceOperationV2(gasCalculator); + + @Test + void shouldPushGasPriceToStack() { + final MessageFrame frame = + new TestMessageFrameBuilderV2().gasPrice(Wei.of(20_000_000_000L)).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)).isEqualTo(new UInt256(0, 0, 0, 20_000_000_000L)); + } + + @Test + void shouldPushZeroGasPrice() { + final MessageFrame frame = new TestMessageFrameBuilderV2().gasPrice(Wei.ZERO).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)).isEqualTo(UInt256.ZERO); + } + + @Test + void shouldReturnCorrectGasCost() { + final MessageFrame frame = new TestMessageFrameBuilderV2().gasPrice(Wei.of(1L)).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getGasCost()).isEqualTo(gasCalculator.getBaseTierGasCost()); + } + + @Test + void shouldHaltOnInsufficientGas() { + final MessageFrame frame = + new TestMessageFrameBuilderV2().initialGas(1L).gasPrice(Wei.of(10L)).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + @Test + void shouldHaltOnStackOverflow() { + final MessageFrame frame = new TestMessageFrameBuilderV2().gasPrice(Wei.of(1L)).build(); + frame.setTopV2(MessageFrame.DEFAULT_MAX_STACK_SIZE); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); + } +} 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 index f48f58c794f..d3f1223f950 100644 --- 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 @@ -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.MessageFrame; @@ -179,10 +180,4 @@ void shiftOperation(final String number, final String shift, final String expect } assertThat(getV2StackItem(frame, 0)).isEqualTo(expected); } - - 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/SelfBalanceOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2Test.java new file mode 100644 index 00000000000..4680e730b75 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2Test.java @@ -0,0 +1,113 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2.getV2StackItem; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +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.BerlinGasCalculator; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation.OperationResult; +import org.hyperledger.besu.evm.toy.ToyWorld; +import org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2; + +import org.junit.jupiter.api.Test; + +class SelfBalanceOperationV2Test { + + private static final Address RECIPIENT = Address.fromHexString("0xdeadbeef"); + private final GasCalculator gasCalculator = new BerlinGasCalculator(); + private final SelfBalanceOperationV2 operation = new SelfBalanceOperationV2(gasCalculator); + + @Test + void shouldPushSelfBalanceToStack() { + final ToyWorld world = new ToyWorld(); + world.createAccount(RECIPIENT, 0, Wei.of(12345L)); + final MessageFrame frame = + new TestMessageFrameBuilderV2().worldUpdater(world).address(RECIPIENT).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)).isEqualTo(new UInt256(0, 0, 0, 12345L)); + } + + @Test + void shouldPushZeroForNullAccount() { + final ToyWorld world = new ToyWorld(); + final MessageFrame frame = + new TestMessageFrameBuilderV2().worldUpdater(world).address(RECIPIENT).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)).isEqualTo(UInt256.ZERO); + } + + @Test + void shouldPushLargeBalance() { + final Wei largeBalance = + Wei.fromHexString("0x00000000000000010000000000000002000000000000000300000000000000FF"); + final ToyWorld world = new ToyWorld(); + world.createAccount(RECIPIENT, 0, largeBalance); + final MessageFrame frame = + new TestMessageFrameBuilderV2().worldUpdater(world).address(RECIPIENT).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)) + .isEqualTo( + new UInt256( + 0x0000000000000001L, + 0x0000000000000002L, + 0x0000000000000003L, + 0x00000000000000FFL)); + } + + @Test + void shouldReturnCorrectGasCost() { + final ToyWorld world = new ToyWorld(); + world.createAccount(RECIPIENT, 0, Wei.of(1L)); + final MessageFrame frame = + new TestMessageFrameBuilderV2().worldUpdater(world).address(RECIPIENT).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getGasCost()).isEqualTo(gasCalculator.getLowTierGasCost()); + } + + @Test + void shouldHaltOnInsufficientGas() { + final ToyWorld world = new ToyWorld(); + world.createAccount(RECIPIENT, 0, Wei.of(1L)); + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .initialGas(4) + .worldUpdater(world) + .address(RECIPIENT) + .build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + @Test + void shouldHaltOnStackOverflow() { + final ToyWorld world = new ToyWorld(); + world.createAccount(RECIPIENT, 0, Wei.of(1L)); + final MessageFrame frame = + new TestMessageFrameBuilderV2().worldUpdater(world).address(RECIPIENT).build(); + frame.setTopV2(MessageFrame.DEFAULT_MAX_STACK_SIZE); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); + } +} 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 index d60910e1a3c..5c8a119e641 100644 --- 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 @@ -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.MessageFrame; @@ -115,10 +116,4 @@ void shiftOperation(final String number, final String shift, final String expect } assertThat(getV2StackItem(frame, 0)).isEqualTo(expected); } - - 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 index 398acafb3b9..19e59251d73 100644 --- 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 @@ -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.MessageFrame; @@ -127,10 +128,4 @@ void shiftOperation(final String number, final String shift, final String expect } assertThat(getV2StackItem(frame, 0)).isEqualTo(expected); } - - 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 index ebf852e768a..516ab48b168 100644 --- 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 @@ -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; + return new UInt256(s[idx], s[idx + 1], s[idx + 2], s[idx + 3]); + } + private WorldUpdater createDefaultWorldUpdater() { return new ToyWorld(); } From 3ecd7d3153dca9a56d2ff4f7259c126f4cfbf79d Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Thu, 16 Apr 2026 17:35:37 +0200 Subject: [PATCH 07/19] spotless Signed-off-by: Ameziane H. --- .../besu/evm/v2/operation/BalanceOperationV2.java | 8 ++++---- .../besu/evm/v2/operation/BaseFeeOperationV2.java | 4 ++-- .../evm/v2/operation/BlobBaseFeeOperationV2.java | 4 ++-- .../besu/evm/v2/operation/CallValueOperationV2.java | 4 ++-- .../besu/evm/v2/operation/GasPriceOperationV2.java | 4 ++-- .../besu/evm/v2/operation/SarOperationV2.java | 2 +- .../evm/v2/operation/SelfBalanceOperationV2.java | 6 +++--- .../besu/evm/v2/operation/ShlOperationV2.java | 2 +- .../besu/evm/v2/operation/ShrOperationV2.java | 2 +- .../besu/evm/v2/{ => operation}/StackUtil.java | 13 +++++++------ .../besu/evm/v2/operation/SarOperationV2Test.java | 4 ++-- .../besu/evm/v2/operation/ShlOperationV2Test.java | 4 ++-- .../besu/evm/v2/operation/ShrOperationV2Test.java | 4 ++-- 13 files changed, 31 insertions(+), 30 deletions(-) rename evm/src/main/java/org/hyperledger/besu/evm/v2/{ => operation}/StackUtil.java (87%) diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java index 71a0a1dab1e..a2aa7e8d34f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java @@ -14,10 +14,10 @@ */ package org.hyperledger.besu.evm.v2.operation; -import static org.hyperledger.besu.evm.v2.StackUtil.UNDERFLOW_RESPONSE; -import static org.hyperledger.besu.evm.v2.StackUtil.pushWei; -import static org.hyperledger.besu.evm.v2.StackUtil.pushZero; -import static org.hyperledger.besu.evm.v2.StackUtil.toAddressAt; +import static org.hyperledger.besu.evm.v2.operation.StackUtil.UNDERFLOW_RESPONSE; +import static org.hyperledger.besu.evm.v2.operation.StackUtil.pushWei; +import static org.hyperledger.besu.evm.v2.operation.StackUtil.pushZero; +import static org.hyperledger.besu.evm.v2.operation.StackUtil.toAddressAt; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.EVM; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java index 4da9c784c5c..22172c27f7f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java @@ -14,8 +14,8 @@ */ package org.hyperledger.besu.evm.v2.operation; -import static org.hyperledger.besu.evm.v2.StackUtil.OVERFLOW_RESPONSE; -import static org.hyperledger.besu.evm.v2.StackUtil.pushWei; +import static org.hyperledger.besu.evm.v2.operation.StackUtil.OVERFLOW_RESPONSE; +import static org.hyperledger.besu.evm.v2.operation.StackUtil.pushWei; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.EVM; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java index 58af2c5b20f..716ac6e4c88 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java @@ -14,8 +14,8 @@ */ package org.hyperledger.besu.evm.v2.operation; -import static org.hyperledger.besu.evm.v2.StackUtil.OVERFLOW_RESPONSE; -import static org.hyperledger.besu.evm.v2.StackUtil.pushWei; +import static org.hyperledger.besu.evm.v2.operation.StackUtil.OVERFLOW_RESPONSE; +import static org.hyperledger.besu.evm.v2.operation.StackUtil.pushWei; 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/CallValueOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java index 907ab2fec4c..c550cd1ce08 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java @@ -14,8 +14,8 @@ */ package org.hyperledger.besu.evm.v2.operation; -import static org.hyperledger.besu.evm.v2.StackUtil.OVERFLOW_RESPONSE; -import static org.hyperledger.besu.evm.v2.StackUtil.pushWei; +import static org.hyperledger.besu.evm.v2.operation.StackUtil.OVERFLOW_RESPONSE; +import static org.hyperledger.besu.evm.v2.operation.StackUtil.pushWei; 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/GasPriceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java index 44de819c19f..813787d4bf7 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java @@ -14,8 +14,8 @@ */ package org.hyperledger.besu.evm.v2.operation; -import static org.hyperledger.besu.evm.v2.StackUtil.OVERFLOW_RESPONSE; -import static org.hyperledger.besu.evm.v2.StackUtil.pushWei; +import static org.hyperledger.besu.evm.v2.operation.StackUtil.OVERFLOW_RESPONSE; +import static org.hyperledger.besu.evm.v2.operation.StackUtil.pushWei; 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 index eb3fbc4d717..d909f5e3fdd 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 @@ -14,7 +14,7 @@ */ package org.hyperledger.besu.evm.v2.operation; -import static org.hyperledger.besu.evm.v2.StackUtil.UNDERFLOW_RESPONSE; +import static org.hyperledger.besu.evm.v2.operation.StackUtil.UNDERFLOW_RESPONSE; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.UInt256; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java index ee4b7da95e2..d1e3b0b0e61 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java @@ -14,9 +14,9 @@ */ package org.hyperledger.besu.evm.v2.operation; -import static org.hyperledger.besu.evm.v2.StackUtil.OVERFLOW_RESPONSE; -import static org.hyperledger.besu.evm.v2.StackUtil.pushWei; -import static org.hyperledger.besu.evm.v2.StackUtil.pushZero; +import static org.hyperledger.besu.evm.v2.operation.StackUtil.OVERFLOW_RESPONSE; +import static org.hyperledger.besu.evm.v2.operation.StackUtil.pushWei; +import static org.hyperledger.besu.evm.v2.operation.StackUtil.pushZero; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.account.Account; 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 07dc08915ee..79e1939b725 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 @@ -14,7 +14,7 @@ */ package org.hyperledger.besu.evm.v2.operation; -import static org.hyperledger.besu.evm.v2.StackUtil.UNDERFLOW_RESPONSE; +import static org.hyperledger.besu.evm.v2.operation.StackUtil.UNDERFLOW_RESPONSE; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.UInt256; 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 a5377946e34..e79740a95c9 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 @@ -14,7 +14,7 @@ */ package org.hyperledger.besu.evm.v2.operation; -import static org.hyperledger.besu.evm.v2.StackUtil.UNDERFLOW_RESPONSE; +import static org.hyperledger.besu.evm.v2.operation.StackUtil.UNDERFLOW_RESPONSE; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.UInt256; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/StackUtil.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/StackUtil.java similarity index 87% rename from evm/src/main/java/org/hyperledger/besu/evm/v2/StackUtil.java rename to evm/src/main/java/org/hyperledger/besu/evm/v2/operation/StackUtil.java index d7a7f1fcb28..83fc5f1bdd3 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/StackUtil.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/StackUtil.java @@ -12,7 +12,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.evm.v2; +package org.hyperledger.besu.evm.v2.operation; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; @@ -26,9 +26,10 @@ /** * Static utility for reading/writing typed values on the flat {@code long[]} V2 operand stack. Each * 256-bit word occupies 4 consecutive longs in big-endian limb order: {@code [u3, u2, u1, u0]} - * where u3 is the most-significant limb. + * where u3 is the most-significant limb. This class has a default modifier (package-private) + * because it shouldn't be used outside the EVM operations */ -public final class StackUtil { +final class StackUtil { private static final VarHandle LONG_BE = MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN); @@ -51,7 +52,7 @@ private StackUtil() {} * @param stack the flat limb array (4 longs per 256-bit word) * @param top the slot index to write to */ - public static void pushZero(final long[] stack, final int top) { + static void pushZero(final long[] stack, final int top) { final int offset = top << 2; stack[offset] = 0; stack[offset + 1] = 0; @@ -66,7 +67,7 @@ public static void pushZero(final long[] stack, final int top) { * @param stack the flat limb array * @param top the slot index to write to */ - public static void pushWei(final Wei wei, final long[] stack, final int top) { + static void pushWei(final Wei wei, final long[] stack, final int top) { wei.writeLimbs(stack, top << 2); } @@ -79,7 +80,7 @@ public static void pushWei(final Wei wei, final long[] stack, final int top) { * @param depth 0 for the topmost item, 1 for the item below, etc. * @return the address formed from the lower 160 bits of the stack word */ - public static Address toAddressAt(final long[] stack, final int top, final int depth) { + static Address toAddressAt(final long[] stack, final int top, final int depth) { final int off = (top - 1 - depth) << 2; byte[] bytes = new byte[20]; INT_BE.set(bytes, 0, (int) stack[off + 1]); 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 index 2a869b478a9..31af52c4f65 100644 --- 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 @@ -174,7 +174,7 @@ void shiftOperation(final String number, final String shift, final String expect .build(); operation.execute(frame, null); UInt256 expected = UInt256.fromBytesBE(Bytes32.fromHexString(expectedResult).toArrayUnsafe()); - assertThat(getStackItem(frame, 0)).isEqualTo(expected); + assertThat(getV2StackItem(frame, 0)).isEqualTo(expected); assertThat(frame.stackTopV2()).isEqualTo(1); } @@ -194,7 +194,7 @@ void stackTopUpdated() { .build(); operation.execute(frame, null); assertThat(frame.stackTopV2()).isEqualTo(4); - assertThat(getStackItem(frame, 0)) + assertThat(getV2StackItem(frame, 0)) .isEqualTo( UInt256.fromBytesBE( Bytes32.fromHexString( 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 index a3e6fdb0f4d..5d345bfb61c 100644 --- 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 @@ -110,7 +110,7 @@ void shiftOperation(final String number, final String shift, final String expect .build(); operation.execute(frame, null); UInt256 expected = UInt256.fromBytesBE(Bytes32.fromHexString(expectedResult).toArrayUnsafe()); - assertThat(getStackItem(frame, 0)).isEqualTo(expected); + assertThat(getV2StackItem(frame, 0)).isEqualTo(expected); assertThat(frame.stackTopV2()).isEqualTo(1); } @@ -130,7 +130,7 @@ void stackTopUpdated() { .build(); operation.execute(frame, null); assertThat(frame.stackTopV2()).isEqualTo(4); - assertThat(getStackItem(frame, 0)) + assertThat(getV2StackItem(frame, 0)) .isEqualTo(UInt256.fromBytesBE(Bytes32.fromHexString("0x00").toArrayUnsafe())); } } 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 index 6d6931dc994..9e7c7be0ae2 100644 --- 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 @@ -122,7 +122,7 @@ void shiftOperation(final String number, final String shift, final String expect .build(); operation.execute(frame, null); UInt256 expected = UInt256.fromBytesBE(Bytes32.fromHexString(expectedResult).toArrayUnsafe()); - assertThat(getStackItem(frame, 0)).isEqualTo(expected); + assertThat(getV2StackItem(frame, 0)).isEqualTo(expected); assertThat(frame.stackTopV2()).isEqualTo(1); } @@ -142,7 +142,7 @@ void stackTopUpdated() { .build(); operation.execute(frame, null); assertThat(frame.stackTopV2()).isEqualTo(4); - assertThat(getStackItem(frame, 0)) + assertThat(getV2StackItem(frame, 0)) .isEqualTo(UInt256.fromBytesBE(Bytes32.fromHexString("0x00").toArrayUnsafe())); } } From 8009532e167d017a6c2ad33cf97b8c867318fdfd Mon Sep 17 00:00:00 2001 From: ahamlat Date: Fri, 17 Apr 2026 09:08:52 +0200 Subject: [PATCH 08/19] Update datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java Co-authored-by: Simon Dudley Signed-off-by: ahamlat --- datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java index d182d934873..c7c3977aba4 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java @@ -139,7 +139,7 @@ public static Wei fromEth(final long eth) { * @param off the offset into the array where u3 should be written */ public void writeLimbs(final long[] target, final int off) { - // TODO store this representation at build time when switching from v1 to v2 + // TODO EVMv2 store this representation at Wei object construction time when switching from v2 to v1 final byte[] b = toArrayUnsafe(); target[off] = (long) LONG_BE.get(b, 0); target[off + 1] = (long) LONG_BE.get(b, 8); From 3cdf3af59a62ea79626fd675e864211c736ee616 Mon Sep 17 00:00:00 2001 From: ahamlat Date: Fri, 17 Apr 2026 09:15:01 +0200 Subject: [PATCH 09/19] Update evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java Co-authored-by: Simon Dudley Signed-off-by: ahamlat --- .../main/java/org/hyperledger/besu/evm/frame/MessageFrame.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java index 54dee8b0460..6ce9c54b9d4 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java @@ -520,7 +520,7 @@ public void setTopV2(final int newTop) { * @param n the number of items required * @return true if the stack contains at least n items */ - public boolean stackHasItems(final int n) { + public boolean stackHasItemsV2(final int n) { return stackTopV2 >= n; } From 310ffbfbf3cfbb33666cf790bc08308454caeb3f Mon Sep 17 00:00:00 2001 From: ahamlat Date: Fri, 17 Apr 2026 09:15:15 +0200 Subject: [PATCH 10/19] Update evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java Co-authored-by: Simon Dudley Signed-off-by: ahamlat --- .../main/java/org/hyperledger/besu/evm/frame/MessageFrame.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java index 6ce9c54b9d4..94216289733 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java @@ -530,7 +530,7 @@ public boolean stackHasItemsV2(final int n) { * @param n the number of additional items * @return true if the stack can accommodate n more items */ - public boolean stackHasSpace(final int n) { + public boolean stackHasSpaceV2(final int n) { return stackTopV2 + n <= stackMaxSizeV2; } From e38f455b7a2f92f67246033c75fcb9c8ae218142 Mon Sep 17 00:00:00 2001 From: ahamlat Date: Fri, 17 Apr 2026 09:23:27 +0200 Subject: [PATCH 11/19] Update datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java Co-authored-by: Simon Dudley Signed-off-by: ahamlat --- datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java index c7c3977aba4..4c671a37de7 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java @@ -133,7 +133,7 @@ public static Wei fromEth(final long eth) { /** * Writes the 4 big-endian long limbs into the target array at the given offset. This enables - * zero-allocation transfer to the EVM operand stack. + * zero-allocation writes to the EVM operand stack; reads may allocate typed wrappers. * * @param target the target long array (typically the EVM stack) * @param off the offset into the array where u3 should be written From c4066464355b7cbf2be254916e49364abf6f0fa0 Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Fri, 17 Apr 2026 09:47:47 +0200 Subject: [PATCH 12/19] Apply refactoring changes Signed-off-by: Ameziane H. --- .../src/main/java/org/hyperledger/besu/datatypes/Wei.java | 3 ++- .../hyperledger/besu/evm/v2/operation/BalanceOperationV2.java | 3 +-- .../hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java | 2 +- .../besu/evm/v2/operation/BlobBaseFeeOperationV2.java | 2 +- .../besu/evm/v2/operation/CallValueOperationV2.java | 2 +- .../besu/evm/v2/operation/GasPriceOperationV2.java | 2 +- .../hyperledger/besu/evm/v2/operation/MulModOperationV2.java | 4 +++- .../org/hyperledger/besu/evm/v2/operation/SarOperationV2.java | 2 +- .../besu/evm/v2/operation/SelfBalanceOperationV2.java | 2 +- .../org/hyperledger/besu/evm/v2/operation/ShlOperationV2.java | 2 +- .../org/hyperledger/besu/evm/v2/operation/ShrOperationV2.java | 2 +- 11 files changed, 14 insertions(+), 12 deletions(-) diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java index 4c671a37de7..9d930d6240e 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java @@ -139,7 +139,8 @@ public static Wei fromEth(final long eth) { * @param off the offset into the array where u3 should be written */ public void writeLimbs(final long[] target, final int off) { - // TODO EVMv2 store this representation at Wei object construction time when switching from v2 to v1 + // TODO EVMv2 store this representation at Wei object construction time when switching from v2 + // to v1 final byte[] b = toArrayUnsafe(); target[off] = (long) LONG_BE.get(b, 0); target[off + 1] = (long) LONG_BE.get(b, 8); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java index a2aa7e8d34f..636f06a8a52 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java @@ -54,8 +54,7 @@ protected long cost(final boolean accountIsWarm) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - if (!frame.stackHasItems(1)) return UNDERFLOW_RESPONSE; - + if (!frame.stackHasItemsV2(1)) return UNDERFLOW_RESPONSE; final long[] stack = frame.stackDataV2(); final int top = frame.stackTopV2(); Address address = toAddressAt(stack, top, 0); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java index 22172c27f7f..cec98e75138 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java @@ -41,7 +41,7 @@ public BaseFeeOperationV2(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + if (!frame.stackHasSpaceV2(1)) return OVERFLOW_RESPONSE; final Optional maybeBaseFee = frame.getBlockValues().getBaseFee(); if (maybeBaseFee.isEmpty()) { return new Operation.OperationResult(gasCost, ExceptionalHaltReason.INVALID_OPERATION); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java index 716ac6e4c88..877054e7ff5 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java @@ -35,7 +35,7 @@ public BlobBaseFeeOperationV2(final GasCalculator gasCalculator) { @Override public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { - if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + if (!frame.stackHasSpaceV2(1)) return OVERFLOW_RESPONSE; final long[] stack = frame.stackDataV2(); final int top = frame.stackTopV2(); pushWei(frame.getBlobGasPrice(), stack, top); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java index c550cd1ce08..35b9234183d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java @@ -37,7 +37,7 @@ public CallValueOperationV2(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + if (!frame.stackHasSpaceV2(1)) return OVERFLOW_RESPONSE; final long[] stack = frame.stackDataV2(); final int top = frame.stackTopV2(); pushWei(frame.getApparentValue(), stack, top); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java index 813787d4bf7..da450936dc4 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java @@ -37,7 +37,7 @@ public GasPriceOperationV2(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + if (!frame.stackHasSpaceV2(1)) return OVERFLOW_RESPONSE; final long[] stack = frame.stackDataV2(); final int top = frame.stackTopV2(); pushWei(frame.getGasPrice(), stack, top); 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 203601408db..b13f664f4d6 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 @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.evm.v2.operation; +import static org.hyperledger.besu.evm.v2.operation.StackUtil.UNDERFLOW_RESPONSE; + import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.UInt256; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -48,7 +50,7 @@ public Operation.OperationResult executeFixedCostOperation( * @return the operation result */ public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { - if (!frame.stackHasItems(3)) return UNDERFLOW_RESPONSE; + if (!frame.stackHasItemsV2(3)) return UNDERFLOW_RESPONSE; int top = frame.stackTopV2(); mulMod(stack, top); // consumed three items and produced one item 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 d909f5e3fdd..53c721279fb 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 @@ -50,7 +50,7 @@ public Operation.OperationResult executeFixedCostOperation( * @return the operation result */ public static OperationResult staticOperation(final MessageFrame frame) { - if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + if (!frame.stackHasItemsV2(2)) return UNDERFLOW_RESPONSE; long[] stack = frame.stackDataV2(); int top = frame.stackTopV2(); final int shiftOffset = (--top) << 2; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java index d1e3b0b0e61..af371683386 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java @@ -39,7 +39,7 @@ public SelfBalanceOperationV2(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + if (!frame.stackHasSpaceV2(1)) return OVERFLOW_RESPONSE; final long[] s = frame.stackDataV2(); final int top = frame.stackTopV2(); final Account account = getAccount(frame.getRecipientAddress(), frame); 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 79e1939b725..24024278dbc 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 @@ -50,7 +50,7 @@ public Operation.OperationResult executeFixedCostOperation( * @return the operation result */ public static OperationResult staticOperation(final MessageFrame frame) { - if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + if (!frame.stackHasItemsV2(2)) return UNDERFLOW_RESPONSE; long[] stack = frame.stackDataV2(); int top = frame.stackTopV2(); final int shiftOffset = (--top) << 2; 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 e79740a95c9..f6f97b73e05 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 @@ -50,7 +50,7 @@ public Operation.OperationResult executeFixedCostOperation( * @return the operation result */ public static OperationResult staticOperation(final MessageFrame frame) { - if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + if (!frame.stackHasItemsV2(2)) return UNDERFLOW_RESPONSE; long[] stack = frame.stackDataV2(); int top = frame.stackTopV2(); final int shiftOffset = (--top) << 2; From 08201927b7320559efe791edba76dc056f2f171b Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Mon, 20 Apr 2026 10:31:25 +0200 Subject: [PATCH 13/19] remove basefee field Signed-off-by: Ameziane H. --- .../org/hyperledger/besu/ethereum/core/BlockHeader.java | 6 +++--- .../besu/ethereum/core/ProcessableBlockHeader.java | 2 -- .../besu/ethereum/referencetests/ReferenceTestEnv.java | 3 ++- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java index eb0edd52e33..42084f5db6b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java @@ -293,8 +293,8 @@ public void writeTo(final RLPOutput out) { out.writeBytes(mixHashOrPrevRandao); out.writeLong(nonce); do { - if (baseFee == null) break; - out.writeUInt256Scalar(baseFee); + if (maybeBaseFee.isEmpty()) break; + out.writeUInt256Scalar(maybeBaseFee.get()); if (withdrawalsRoot == null) break; out.writeBytes(withdrawalsRoot.getBytes()); @@ -488,7 +488,7 @@ public String toString() { sb.append("gasUsed=").append(gasUsed).append(", "); sb.append("timestamp=").append(timestamp).append(", "); sb.append("extraData=").append(extraData).append(", "); - sb.append("baseFee=").append(baseFee).append(", "); + sb.append("baseFee=").append(maybeBaseFee.orElse(null)).append(", "); sb.append("mixHashOrPrevRandao=").append(mixHashOrPrevRandao).append(", "); sb.append("nonce=").append(nonce).append(", "); if (withdrawalsRoot != null) { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java index 93b42bb3662..4293fe6ce88 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java @@ -41,7 +41,6 @@ public class ProcessableBlockHeader // The block creation timestamp (seconds since the unix epoch) protected final long timestamp; // base fee is included for post EIP-1559 blocks - protected final Wei baseFee; // store optional base fee to compute it at build time protected final Optional maybeBaseFee; // prevRandao is included for post-merge blocks @@ -68,7 +67,6 @@ protected ProcessableBlockHeader( this.number = number; this.gasLimit = gasLimit; this.timestamp = timestamp; - this.baseFee = baseFee; this.maybeBaseFee = Optional.ofNullable(baseFee); this.mixHashOrPrevRandao = mixHashOrPrevRandao; this.parentBeaconBlockRoot = parentBeaconBlockRoot; diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestEnv.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestEnv.java index 2ed8174fa65..4d094ac114a 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestEnv.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestEnv.java @@ -207,7 +207,8 @@ public BlockHeader parentBlockHeader(final ProtocolSpec protocolSpec) { if (protocolSpec.getWithdrawalsProcessor().isPresent()) { builder.withdrawalsRoot(BodyValidation.withdrawalsRoot(withdrawals)); } - if ((baseFee == null || baseFee.isEmpty()) && protocolSpec.getFeeMarket().implementsBaseFee()) { + if ((maybeBaseFee.isEmpty() || maybeBaseFee.get().isEmpty()) + && protocolSpec.getFeeMarket().implementsBaseFee()) { builder.baseFee( ((BaseFeeMarket) protocolSpec.getFeeMarket()) .computeBaseFee( From 8ed8bfb93ad0a63b061203577674167871178472 Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Mon, 20 Apr 2026 10:51:01 +0200 Subject: [PATCH 14/19] Fix merge issue Signed-off-by: Ameziane H. --- .../hyperledger/besu/evm/v2/operation/MulModOperationV2.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8d6194d9ce5..e85e6703dce 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 @@ -50,7 +50,7 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + public static OperationResult staticOperation(final MessageFrame frame) { if (!frame.stackHasItemsV2(3)) return UNDERFLOW_RESPONSE; int top = frame.stackTopV2(); final int aOffset = (--top) << 2; From 5c79af7aa922bfd2caba2ad55ee27d86ca82c30e Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Mon, 20 Apr 2026 15:53:58 +0200 Subject: [PATCH 15/19] Address more comments Signed-off-by: Ameziane H. --- .../org/hyperledger/besu/datatypes/Wei.java | 17 -- .../AbstractFixedCostOperationV2.java | 3 +- .../evm/v2/operation/AbstractOperationV2.java | 178 ++++++++++++++++++ .../evm/v2/operation/BalanceOperationV2.java | 10 +- .../evm/v2/operation/BaseFeeOperationV2.java | 3 +- .../v2/operation/BlobBaseFeeOperationV2.java | 1 - .../v2/operation/CallValueOperationV2.java | 1 - .../evm/v2/operation/GasPriceOperationV2.java | 1 - .../evm/v2/operation/MulModOperationV2.java | 2 - .../besu/evm/v2/operation/SarOperationV2.java | 2 - .../v2/operation/SelfBalanceOperationV2.java | 1 - .../besu/evm/v2/operation/ShlOperationV2.java | 2 - .../besu/evm/v2/operation/ShrOperationV2.java | 2 - .../besu/evm/v2/operation/StackUtil.java | 25 ++- 14 files changed, 196 insertions(+), 52 deletions(-) create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractOperationV2.java diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java index 9d930d6240e..def5ea21167 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java @@ -131,23 +131,6 @@ public static Wei fromEth(final long eth) { return Wei.of(BigInteger.valueOf(eth).multiply(BigInteger.TEN.pow(18))); } - /** - * Writes the 4 big-endian long limbs into the target array at the given offset. This enables - * zero-allocation writes to the EVM operand stack; reads may allocate typed wrappers. - * - * @param target the target long array (typically the EVM stack) - * @param off the offset into the array where u3 should be written - */ - public void writeLimbs(final long[] target, final int off) { - // TODO EVMv2 store this representation at Wei object construction time when switching from v2 - // to v1 - final byte[] b = toArrayUnsafe(); - target[off] = (long) LONG_BE.get(b, 0); - target[off + 1] = (long) LONG_BE.get(b, 8); - target[off + 2] = (long) LONG_BE.get(b, 16); - target[off + 3] = (long) LONG_BE.get(b, 24); - } - @Override public BigInteger getAsBigInteger() { return toBigInteger(); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java index 3677d31c5dc..3b42ccdbe9b 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java @@ -18,10 +18,9 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.operation.AbstractOperation; /** The Abstract fixed cost operation for V2 (long[] stack) operations. */ -abstract class AbstractFixedCostOperationV2 extends AbstractOperation { +abstract class AbstractFixedCostOperationV2 extends AbstractOperationV2 { /** The Success response. */ protected final OperationResult successResponse; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractOperationV2.java new file mode 100644 index 00000000000..1eb06678128 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractOperationV2.java @@ -0,0 +1,178 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt256; + +/** + * All V2 {@link Operation} implementations should inherit from this class to get the setting of + * some members for free. + */ +public abstract class AbstractOperationV2 implements Operation { + static final Bytes BYTES_ONE = Bytes.of(1); + + /** Shared underflow response (zero gas cost). */ + public static final Operation.OperationResult UNDERFLOW_RESPONSE = + new Operation.OperationResult(0L, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + + /** Shared overflow response (zero gas cost). */ + public static final Operation.OperationResult OVERFLOW_RESPONSE = + new Operation.OperationResult(0L, ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); + + private final int opcode; + private final String name; + private final int stackItemsConsumed; + private final int stackItemsProduced; + private final GasCalculator gasCalculator; + + /** + * Instantiates a new Abstract operation. + * + * @param opcode the opcode + * @param name the name + * @param stackItemsConsumed the stack items consumed + * @param stackItemsProduced the stack items produced + * @param gasCalculator the gas calculator + */ + protected AbstractOperationV2( + final int opcode, + final String name, + final int stackItemsConsumed, + final int stackItemsProduced, + final GasCalculator gasCalculator) { + this.opcode = opcode & 0xff; + this.name = name; + this.stackItemsConsumed = stackItemsConsumed; + this.stackItemsProduced = stackItemsProduced; + this.gasCalculator = gasCalculator; + } + + /** + * Gets Gas calculator. + * + * @return the gas calculator + */ + protected GasCalculator gasCalculator() { + return gasCalculator; + } + + @Override + public int getOpcode() { + return opcode; + } + + @Override + public String getName() { + return name; + } + + @Override + public int getStackItemsConsumed() { + return stackItemsConsumed; + } + + @Override + public int getStackItemsProduced() { + return stackItemsProduced; + } + + /** + * Retrieves the {@link Account} at the specified address. + * + *

If an EIP-7928 Block Access List is active, the address is added to the access list. + * + * @param address the account address + * @param frame the current message execution frame + * @return the {@link Account}, or {@code null} if it does not exist + */ + protected Account getAccount(final Address address, final MessageFrame frame) { + final Account account = frame.getWorldUpdater().get(address); + frame.getEip7928AccessList().ifPresent(t -> t.addTouchedAccount(address)); + return account; + } + + /** + * Retrieves a mutable view of the account at the specified address. + * + *

If an EIP-7928 Block Access List is active, the address is added to the access list. + * + * @param address the account address + * @param frame the current message execution frame + * @return the {@link MutableAccount}, or {@code null} if it does not exist + */ + protected MutableAccount getMutableAccount(final Address address, final MessageFrame frame) { + final MutableAccount account = frame.getWorldUpdater().getAccount(address); + frame.getEip7928AccessList().ifPresent(t -> t.addTouchedAccount(address)); + return account; + } + + /** + * Retrieves a mutable view of the account at the specified address, creating it if it does not + * exist. + * + *

If an EIP-7928 Block Access List is active, the address is added to the access list. + * + * @param address the account address + * @param frame the current message execution frame + * @return the existing or newly created {@link MutableAccount} + */ + protected MutableAccount getOrCreateAccount(final Address address, final MessageFrame frame) { + final MutableAccount account = frame.getWorldUpdater().getOrCreate(address); + frame.getEip7928AccessList().ifPresent(t -> t.addTouchedAccount(address)); + return account; + } + + /** + * Retrieves a mutable view of the sender's account. + * + *

If an EIP-7928 Block Access List is active, the sender address is added to the access list. + * + * @param frame the current message execution frame + * @return the {@link MutableAccount} for the sender + */ + protected MutableAccount getSenderAccount(final MessageFrame frame) { + final MutableAccount account = frame.getWorldUpdater().getSenderAccount(frame); + frame.getEip7928AccessList().ifPresent(t -> t.addTouchedAccount(account.getAddress())); + return account; + } + + /** + * Reads a storage slot from the specified account. + * + *

If an EIP-7928 Block Access List is active, the slot access is recorded for the account. + * + * @param account the account whose storage is being accessed + * @param slotKey the key of the storage slot + * @param frame the current message execution frame + * @return the value stored at the specified key + */ + protected UInt256 getStorageValue( + final Account account, final UInt256 slotKey, final MessageFrame frame) { + final UInt256 slotValue = account.getStorageValue(slotKey); + frame + .getEip7928AccessList() + .ifPresent(t -> t.addSlotAccessForAccount(account.getAddress(), slotKey)); + return slotValue; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java index 636f06a8a52..a3fa19bc30e 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java @@ -14,10 +14,9 @@ */ package org.hyperledger.besu.evm.v2.operation; -import static org.hyperledger.besu.evm.v2.operation.StackUtil.UNDERFLOW_RESPONSE; import static org.hyperledger.besu.evm.v2.operation.StackUtil.pushWei; import static org.hyperledger.besu.evm.v2.operation.StackUtil.pushZero; -import static org.hyperledger.besu.evm.v2.operation.StackUtil.toAddressAt; +import static org.hyperledger.besu.evm.v2.operation.StackUtil.readAddressAt; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.EVM; @@ -25,10 +24,9 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.operation.AbstractOperation; /** The Balance operation. */ -public class BalanceOperationV2 extends AbstractOperation { +public class BalanceOperationV2 extends AbstractOperationV2 { /** * Instantiates a new Balance operation. @@ -54,10 +52,9 @@ protected long cost(final boolean accountIsWarm) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - if (!frame.stackHasItemsV2(1)) return UNDERFLOW_RESPONSE; final long[] stack = frame.stackDataV2(); final int top = frame.stackTopV2(); - Address address = toAddressAt(stack, top, 0); + final Address address = readAddressAt(stack, top, 0); final boolean accountIsWarm = frame.warmUpAddress(address) || gasCalculator().isPrecompile(address); final long cost = cost(accountIsWarm); @@ -65,6 +62,7 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); } final Account account = getAccount(address, frame); + if (!frame.stackHasItemsV2(1)) return UNDERFLOW_RESPONSE; if (account == null) { pushZero(stack, top - 1); } else { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java index cec98e75138..58821149a32 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java @@ -14,7 +14,6 @@ */ package org.hyperledger.besu.evm.v2.operation; -import static org.hyperledger.besu.evm.v2.operation.StackUtil.OVERFLOW_RESPONSE; import static org.hyperledger.besu.evm.v2.operation.StackUtil.pushWei; import org.hyperledger.besu.datatypes.Wei; @@ -41,11 +40,11 @@ public BaseFeeOperationV2(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - if (!frame.stackHasSpaceV2(1)) return OVERFLOW_RESPONSE; final Optional maybeBaseFee = frame.getBlockValues().getBaseFee(); if (maybeBaseFee.isEmpty()) { return new Operation.OperationResult(gasCost, ExceptionalHaltReason.INVALID_OPERATION); } + if (!frame.stackHasSpaceV2(1)) return OVERFLOW_RESPONSE; final long[] stack = frame.stackDataV2(); final int top = frame.stackTopV2(); pushWei(maybeBaseFee.get(), stack, top); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java index 877054e7ff5..69b60f7ba84 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java @@ -14,7 +14,6 @@ */ package org.hyperledger.besu.evm.v2.operation; -import static org.hyperledger.besu.evm.v2.operation.StackUtil.OVERFLOW_RESPONSE; import static org.hyperledger.besu.evm.v2.operation.StackUtil.pushWei; import org.hyperledger.besu.evm.EVM; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java index 35b9234183d..56bbb1cdc1f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java @@ -14,7 +14,6 @@ */ package org.hyperledger.besu.evm.v2.operation; -import static org.hyperledger.besu.evm.v2.operation.StackUtil.OVERFLOW_RESPONSE; import static org.hyperledger.besu.evm.v2.operation.StackUtil.pushWei; import org.hyperledger.besu.evm.EVM; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java index da450936dc4..278ff41ff34 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java @@ -14,7 +14,6 @@ */ package org.hyperledger.besu.evm.v2.operation; -import static org.hyperledger.besu.evm.v2.operation.StackUtil.OVERFLOW_RESPONSE; import static org.hyperledger.besu.evm.v2.operation.StackUtil.pushWei; import org.hyperledger.besu.evm.EVM; 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 e85e6703dce..b7f090e5ec1 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 @@ -14,8 +14,6 @@ */ package org.hyperledger.besu.evm.v2.operation; -import static org.hyperledger.besu.evm.v2.operation.StackUtil.UNDERFLOW_RESPONSE; - import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.UInt256; 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 index 53c721279fb..aa629ffa509 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 @@ -14,8 +14,6 @@ */ package org.hyperledger.besu.evm.v2.operation; -import static org.hyperledger.besu.evm.v2.operation.StackUtil.UNDERFLOW_RESPONSE; - import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.UInt256; import org.hyperledger.besu.evm.frame.MessageFrame; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java index af371683386..d505a464bac 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java @@ -14,7 +14,6 @@ */ package org.hyperledger.besu.evm.v2.operation; -import static org.hyperledger.besu.evm.v2.operation.StackUtil.OVERFLOW_RESPONSE; import static org.hyperledger.besu.evm.v2.operation.StackUtil.pushWei; import static org.hyperledger.besu.evm.v2.operation.StackUtil.pushZero; 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 24024278dbc..2b17264cc42 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 @@ -14,8 +14,6 @@ */ package org.hyperledger.besu.evm.v2.operation; -import static org.hyperledger.besu.evm.v2.operation.StackUtil.UNDERFLOW_RESPONSE; - import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.UInt256; import org.hyperledger.besu.evm.frame.MessageFrame; 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 f6f97b73e05..7f7b9c000fd 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 @@ -14,8 +14,6 @@ */ package org.hyperledger.besu.evm.v2.operation; -import static org.hyperledger.besu.evm.v2.operation.StackUtil.UNDERFLOW_RESPONSE; - import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.UInt256; import org.hyperledger.besu.evm.frame.MessageFrame; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/StackUtil.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/StackUtil.java index 83fc5f1bdd3..d14d64495d6 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/StackUtil.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/StackUtil.java @@ -16,13 +16,13 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; -import org.hyperledger.besu.evm.operation.Operation; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.nio.ByteOrder; +import org.apache.tuweni.bytes.Bytes; + /** * Static utility for reading/writing typed values on the flat {@code long[]} V2 operand stack. Each * 256-bit word occupies 4 consecutive longs in big-endian limb order: {@code [u3, u2, u1, u0]} @@ -36,14 +36,6 @@ final class StackUtil { private static final VarHandle INT_BE = MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.BIG_ENDIAN); - /** Shared underflow response (zero gas cost). */ - public static final Operation.OperationResult UNDERFLOW_RESPONSE = - new Operation.OperationResult(0L, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); - - /** Shared overflow response (zero gas cost). */ - public static final Operation.OperationResult OVERFLOW_RESPONSE = - new Operation.OperationResult(0L, ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); - private StackUtil() {} /** @@ -68,7 +60,14 @@ static void pushZero(final long[] stack, final int top) { * @param top the slot index to write to */ static void pushWei(final Wei wei, final long[] stack, final int top) { - wei.writeLimbs(stack, top << 2); + // TODO EVMv2 store this representation at Wei object construction time when switching from v2 + // to v1 + int offset = top << 2; + final byte[] b = wei.toArrayUnsafe(); + stack[offset] = (long) LONG_BE.get(b, 0); + stack[offset + 1] = (long) LONG_BE.get(b, 8); + stack[offset + 2] = (long) LONG_BE.get(b, 16); + stack[offset + 3] = (long) LONG_BE.get(b, 24); } /** @@ -80,12 +79,12 @@ static void pushWei(final Wei wei, final long[] stack, final int top) { * @param depth 0 for the topmost item, 1 for the item below, etc. * @return the address formed from the lower 160 bits of the stack word */ - static Address toAddressAt(final long[] stack, final int top, final int depth) { + static Address readAddressAt(final long[] stack, final int top, final int depth) { final int off = (top - 1 - depth) << 2; byte[] bytes = new byte[20]; INT_BE.set(bytes, 0, (int) stack[off + 1]); LONG_BE.set(bytes, 4, stack[off + 2]); LONG_BE.set(bytes, 12, stack[off + 3]); - return Address.wrap(org.apache.tuweni.bytes.Bytes.wrap(bytes)); + return Address.wrap(Bytes.wrap(bytes)); } } From 5acec2501ddca847d5a6b9b8e834dd1851c9eec3 Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Mon, 20 Apr 2026 16:18:13 +0200 Subject: [PATCH 16/19] Remove not used field Signed-off-by: Ameziane H. --- .../src/main/java/org/hyperledger/besu/datatypes/Wei.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java index def5ea21167..f7e36811188 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java @@ -28,9 +28,6 @@ /** A particular quantity of Wei, the Ethereum currency. */ public final class Wei extends BaseUInt256Value implements Quantity { - private static final VarHandle LONG_BE = - MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN); - /** The constant ZERO. */ public static final Wei ZERO = of(0); From 18260100ec7fdc47c1a0def147819bbaed6f734b Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Mon, 20 Apr 2026 21:15:35 +0200 Subject: [PATCH 17/19] spotless Signed-off-by: Ameziane H. --- .../src/main/java/org/hyperledger/besu/datatypes/Wei.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java index f7e36811188..962640fc53d 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java @@ -14,10 +14,7 @@ */ package org.hyperledger.besu.datatypes; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.VarHandle; import java.math.BigInteger; -import java.nio.ByteOrder; import java.util.Arrays; import java.util.Locale; From 98b66d1504391d12b52ffe4f469f1d8fa32df9f3 Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Tue, 21 Apr 2026 11:18:40 +0200 Subject: [PATCH 18/19] Undo Balance operation change and add more unit tests Signed-off-by: Ameziane H. --- .../evm/v2/operation/BalanceOperationV2.java | 2 +- .../evm/v2/operation/BaseFeeOperationV2Test.java | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java index a3fa19bc30e..3834a4e3ccc 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java @@ -52,6 +52,7 @@ protected long cost(final boolean accountIsWarm) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { + if (!frame.stackHasItemsV2(1)) return UNDERFLOW_RESPONSE; final long[] stack = frame.stackDataV2(); final int top = frame.stackTopV2(); final Address address = readAddressAt(stack, top, 0); @@ -62,7 +63,6 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); } final Account account = getAccount(address, frame); - if (!frame.stackHasItemsV2(1)) return UNDERFLOW_RESPONSE; if (account == null) { pushZero(stack, top - 1); } else { diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2Test.java index 2b2d9072044..93bc64c9a45 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2Test.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2Test.java @@ -76,6 +76,14 @@ void shouldHaltIfNoBaseFeeInBlockHeader() { assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INVALID_OPERATION); } + @Test + void shouldTriggerIvalidOperationEvenStackOverflow() { + final MessageFrame frame = createFrame(100, Optional.empty()); + frame.setTopV2(MessageFrame.DEFAULT_MAX_STACK_SIZE); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INVALID_OPERATION); + } + @Test void shouldHaltOnInsufficientGas() { final MessageFrame frame = createFrame(1, Optional.of(Wei.of(5L))); @@ -83,6 +91,14 @@ void shouldHaltOnInsufficientGas() { assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INSUFFICIENT_GAS); } + @Test + void shouldHaltOnInsufficientGasEvenStackOverflow() { + final MessageFrame frame = createFrame(1, Optional.of(Wei.of(5L))); + frame.setTopV2(MessageFrame.DEFAULT_MAX_STACK_SIZE); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INSUFFICIENT_GAS); + } + @Test void shouldHaltOnStackOverflow() { final MessageFrame frame = createFrame(100, Optional.of(Wei.of(5L))); From aec17bcc55d0e2fcbd6dd23cfb0fb2a91945a06d Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Tue, 21 Apr 2026 11:24:07 +0200 Subject: [PATCH 19/19] Update AddOperationV2 after merge with main Signed-off-by: Ameziane H. --- .../org/hyperledger/besu/evm/v2/operation/AddOperationV2.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddOperationV2.java index c544c830610..b0a9ea1de26 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddOperationV2.java @@ -56,7 +56,7 @@ public Operation.OperationResult executeFixedCostOperation( * @return the operation result */ public static Operation.OperationResult staticOperation(final MessageFrame frame) { - if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + if (!frame.stackHasItemsV2(2)) return UNDERFLOW_RESPONSE; long[] stack = frame.stackDataV2(); int top = frame.stackTopV2(); final int aOffset = (top - 1) << 2;