diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AbstractShiftOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AbstractShiftOperationBenchmarkV2.java new file mode 100644 index 00000000000..cd83e28f46f --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AbstractShiftOperationBenchmarkV2.java @@ -0,0 +1,120 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import static org.hyperledger.besu.ethereum.vm.operations.v2.BenchmarkHelperV2.bytesToUInt256; +import static org.hyperledger.besu.ethereum.vm.operations.v2.BenchmarkHelperV2.randomUInt256Value; + +import org.hyperledger.besu.evm.UInt256; + +import java.util.concurrent.ThreadLocalRandom; + +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Setup; + +/** + * Abstract base class for shift operation benchmarks (SHL, SHR, SAR). + * + *

Provides shared test case definitions and setup logic. + */ +public abstract class AbstractShiftOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + /** Test cases covering different execution paths for shift operations. */ + public enum Case { + /** Shift by 0 - no shift needed. */ + SHIFT_0, + /** Small shift by 1 bit. */ + SHIFT_1, + /** Medium shift by 128 bits (half word). */ + SHIFT_128, + /** Large shift by 255 bits (max valid). */ + SHIFT_255, + /** Overflow: shift of exactly 256. */ + OVERFLOW_SHIFT_256, + /** Overflow: shift amount > 4 bytes. */ + OVERFLOW_LARGE_SHIFT, + /** Random values (original behavior). */ + FULL_RANDOM + } + + @Param({ + "SHIFT_0", + "SHIFT_1", + "SHIFT_128", + "SHIFT_255", + "OVERFLOW_SHIFT_256", + "OVERFLOW_LARGE_SHIFT", + "FULL_RANDOM" + }) + protected String caseName; + + @Setup(Level.Iteration) + @Override + public void setUp() { + frame = BenchmarkHelperV2.createMessageCallFrame(); + + final Case scenario = Case.valueOf(caseName); + aPool = new UInt256[SAMPLE_SIZE]; // shift amount (pushed second, popped first) + bPool = new UInt256[SAMPLE_SIZE]; // value (pushed first, popped second) + + final ThreadLocalRandom random = ThreadLocalRandom.current(); + + for (int i = 0; i < SAMPLE_SIZE; i++) { + switch (scenario) { + case SHIFT_0: + aPool[i] = UInt256.ZERO; + bPool[i] = randomUInt256Value(random); + break; + + case SHIFT_1: + aPool[i] = UInt256.ONE; + bPool[i] = randomUInt256Value(random); + break; + + case SHIFT_128: + aPool[i] = UInt256.fromInt(128); + bPool[i] = randomUInt256Value(random); + break; + + case SHIFT_255: + aPool[i] = UInt256.fromInt(255); + bPool[i] = randomUInt256Value(random); + break; + + case OVERFLOW_SHIFT_256: + aPool[i] = UInt256.fromInt(256); // 256 + bPool[i] = randomUInt256Value(random); + break; + + case OVERFLOW_LARGE_SHIFT: + aPool[i] = bytesToUInt256(new byte[] {0x01, 0x00, 0x00, 0x00, 0x00, 0x00}); + bPool[i] = randomUInt256Value(random); + break; + + case FULL_RANDOM: + default: + final byte[] shift = new byte[1 + random.nextInt(4)]; + final byte[] value = new byte[1 + random.nextInt(32)]; + random.nextBytes(shift); + random.nextBytes(value); + aPool[i] = bytesToUInt256(shift); + bPool[i] = bytesToUInt256(value); + break; + } + } + index = 0; + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AddModOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AddModOperationBenchmarkV2.java new file mode 100644 index 00000000000..01cf8d09683 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AddModOperationBenchmarkV2.java @@ -0,0 +1,172 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.AddModOperationV2; + +import java.util.concurrent.ThreadLocalRandom; + +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Setup; + +public class AddModOperationBenchmarkV2 extends TernaryOperationBenchmarkV2 { + + // Benches for (a + b) % c + + // Define available scenarios + public enum Case { + MULMOD_32_32_32(1, 1, 1), + MULMOD_64_32_32(2, 1, 1), + MULMOD_64_64_32(2, 2, 1), + MULMOD_64_64_64(2, 2, 2), + MULMOD_128_32_32(4, 1, 1), + MULMOD_128_64_32(4, 2, 1), + MULMOD_128_64_64(4, 2, 2), + MULMOD_128_128_32(4, 4, 1), + MULMOD_128_128_64(4, 4, 2), + MULMOD_128_128_128(4, 4, 3), + MULMOD_192_32_32(6, 1, 1), + MULMOD_192_64_32(6, 2, 1), + MULMOD_192_64_64(6, 2, 2), + MULMOD_192_128_32(6, 4, 1), + MULMOD_192_128_64(6, 4, 2), + MULMOD_192_128_128(6, 4, 4), + MULMOD_192_192_32(6, 6, 1), + MULMOD_192_192_64(6, 6, 2), + MULMOD_192_192_128(6, 6, 4), + MULMOD_192_192_192(6, 6, 6), + MULMOD_256_32_32(8, 1, 1), + MULMOD_256_64_32(8, 2, 1), + MULMOD_256_64_64(8, 2, 2), + MULMOD_256_64_128(8, 2, 4), + MULMOD_256_64_192(8, 2, 6), + MULMOD_256_128_32(8, 4, 1), + MULMOD_256_128_64(8, 4, 2), + MULMOD_256_128_128(8, 4, 4), + MULMOD_256_192_32(8, 6, 1), + MULMOD_256_192_64(8, 6, 2), + MULMOD_256_192_128(8, 6, 4), + MULMOD_256_192_192(8, 6, 6), + MULMOD_256_256_32(8, 8, 1), + MULMOD_256_256_64(8, 8, 2), + MULMOD_256_256_128(8, 8, 4), + MULMOD_256_256_192(8, 8, 6), + MULMOD_256_256_256(8, 8, 8), + LARGER_MULMOD_64_64_128(2, 2, 4), + LARGER_MULMOD_192_192_256(6, 6, 8), + ZERO_MULMOD_128_256_0(4, 8, 0), + FULL_RANDOM(-1, -1, -1); + + final int aSize; + final int bSize; + final int cSize; + + Case(final int aSize, final int bSize, final int cSize) { + this.aSize = aSize; + this.bSize = bSize; + this.cSize = cSize; + } + } + + @Param({ + "MULMOD_32_32_32", + "MULMOD_64_32_32", + "MULMOD_64_64_32", + "MULMOD_64_64_64", + "MULMOD_128_32_32", + "MULMOD_128_64_32", + "MULMOD_128_64_64", + "MULMOD_128_128_32", + "MULMOD_128_128_64", + "MULMOD_128_128_128", + "MULMOD_192_32_32", + "MULMOD_192_64_32", + "MULMOD_192_64_64", + "MULMOD_192_128_32", + "MULMOD_192_128_64", + "MULMOD_192_128_128", + "MULMOD_192_192_32", + "MULMOD_192_192_64", + "MULMOD_192_192_128", + "MULMOD_192_192_192", + "MULMOD_256_32_32", + "MULMOD_256_64_32", + "MULMOD_256_64_64", + "MULMOD_256_64_128", + "MULMOD_256_64_192", + "MULMOD_256_128_32", + "MULMOD_256_128_64", + "MULMOD_256_128_128", + "MULMOD_256_192_32", + "MULMOD_256_192_64", + "MULMOD_256_192_128", + "MULMOD_256_192_192", + "MULMOD_256_256_32", + "MULMOD_256_256_64", + "MULMOD_256_256_128", + "MULMOD_256_256_192", + "MULMOD_256_256_256", + "LARGER_MULMOD_64_64_128", + "LARGER_MULMOD_192_192_256", + "ZERO_MULMOD_128_256_0", + "FULL_RANDOM" + }) + private String caseName; + + @Setup(Level.Iteration) + @Override + public void setUp() { + frame = BenchmarkHelperV2.createMessageCallFrame(); + + AddModOperationBenchmarkV2.Case scenario = AddModOperationBenchmarkV2.Case.valueOf(caseName); + aPool = new UInt256[SAMPLE_SIZE]; + bPool = new UInt256[SAMPLE_SIZE]; + cPool = new UInt256[SAMPLE_SIZE]; + + final ThreadLocalRandom random = ThreadLocalRandom.current(); + int aSize; + int bSize; + int cSize; + + for (int i = 0; i < SAMPLE_SIZE; i++) { + if (scenario.aSize < 0) aSize = random.nextInt(1, 33); + else aSize = scenario.aSize * 4; + if (scenario.bSize < 0) bSize = random.nextInt(1, 33); + else bSize = scenario.bSize * 4; + if (scenario.cSize < 0) cSize = random.nextInt(1, 33); + else cSize = scenario.cSize * 4; + + final byte[] a = new byte[aSize]; + final byte[] b = new byte[bSize]; + final byte[] c = new byte[cSize]; + random.nextBytes(a); + random.nextBytes(b); + random.nextBytes(c); + aPool[i] = BenchmarkHelperV2.bytesToUInt256(a); + bPool[i] = BenchmarkHelperV2.bytesToUInt256(b); + cPool[i] = BenchmarkHelperV2.bytesToUInt256(c); + } + index = 0; + } + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return AddModOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AddOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AddOperationBenchmarkV2.java index c9e16edbd0e..97b7fa76aed 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AddOperationBenchmarkV2.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AddOperationBenchmarkV2.java @@ -16,7 +16,7 @@ import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.operation.Operation; -import org.hyperledger.besu.evm.operation.v2.AddOperationV2; +import org.hyperledger.besu.evm.v2.operation.AddOperationV2; public class AddOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BenchmarkHelperV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BenchmarkHelperV2.java index fa062fd5fb0..8afcbb92194 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BenchmarkHelperV2.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BenchmarkHelperV2.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; import org.apache.tuweni.bytes.Bytes32; @@ -101,4 +102,44 @@ static void pushUInt256(final MessageFrame frame, final org.hyperledger.besu.evm s[dst + 3] = value.u0(); frame.setTopV2(top + 1); } + + /** + * Generates a random UInt256 value. + * + * @param random thread-local random source + * @return random UInt256 value + */ + static org.hyperledger.besu.evm.UInt256 randomUInt256Value(final ThreadLocalRandom random) { + final byte[] value = new byte[32]; + random.nextBytes(value); + return org.hyperledger.besu.evm.UInt256.fromBytesBE(value); + } + + /** + * Generates a random positive signed 256-bit UInt256 value (sign bit cleared). + * + * @param random thread-local random source + * @return random positive UInt256 value + */ + static org.hyperledger.besu.evm.UInt256 randomPositiveUInt256Value( + final ThreadLocalRandom random) { + final byte[] value = new byte[32]; + random.nextBytes(value); + value[0] = (byte) (value[0] & 0x7F); + return org.hyperledger.besu.evm.UInt256.fromBytesBE(value); + } + + /** + * Generates a random negative signed 256-bit UInt256 value (sign bit set). + * + * @param random thread-local random source + * @return random negative UInt256 value + */ + static org.hyperledger.besu.evm.UInt256 randomNegativeUInt256Value( + final ThreadLocalRandom random) { + final byte[] value = new byte[32]; + random.nextBytes(value); + value[0] = (byte) (value[0] | 0x80); + return org.hyperledger.besu.evm.UInt256.fromBytesBE(value); + } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BlockHashOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BlockHashOperationBenchmarkV2.java new file mode 100644 index 00000000000..29066045135 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BlockHashOperationBenchmarkV2.java @@ -0,0 +1,128 @@ +/* + * 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.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.frame.BlockValues; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.v2.operation.BlockHashOperationV2; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +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.Param; +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; + +/** Benchmark for BLOCKHASH EVM v2 operation. */ +@State(Scope.Thread) +@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(value = TimeUnit.NANOSECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@BenchmarkMode(Mode.AverageTime) +public class BlockHashOperationBenchmarkV2 { + + protected static final int SAMPLE_SIZE = 30_000; + private static final long CURRENT_BLOCK_NUMBER = 1_000L; + + @Param({"IN_RANGE", "OUT_OF_RANGE", "FUTURE"}) + public String blockCase; + + private long[] blockNumberPool; + private int index; + private MessageFrame frame; + + @Setup + public void setUp() { + final BlockValues blockValues = mock(BlockValues.class); + when(blockValues.getNumber()).thenReturn(CURRENT_BLOCK_NUMBER); + + frame = + MessageFrame.builder() + .enableEvmV2(true) + .worldUpdater(mock(WorldUpdater.class)) + .originator(Address.ZERO) + .gasPrice(Wei.ONE) + .blobGasPrice(Wei.ONE) + .blockValues(blockValues) + .miningBeneficiary(Address.ZERO) + .blockHashLookup((__, ___) -> Hash.ZERO) + .type(MessageFrame.Type.MESSAGE_CALL) + .initialGas(Long.MAX_VALUE) + .address(Address.ZERO) + .contract(Address.ZERO) + .inputData(Bytes32.ZERO) + .sender(Address.ZERO) + .value(Wei.ZERO) + .apparentValue(Wei.ZERO) + .code(Code.EMPTY_CODE) + .completer(__ -> {}) + .build(); + + blockNumberPool = new long[SAMPLE_SIZE]; + final Random random = new Random(42); + for (int i = 0; i < SAMPLE_SIZE; i++) { + blockNumberPool[i] = + switch (blockCase) { + case "IN_RANGE" -> + // Valid block in the lookback window + CURRENT_BLOCK_NUMBER - 1 - (random.nextInt(256)); + case "OUT_OF_RANGE" -> + // Outside lookback window + CURRENT_BLOCK_NUMBER - 257 - random.nextInt(100); + default -> + // Future block + CURRENT_BLOCK_NUMBER + random.nextInt(100); + }; + } + index = 0; + } + + @Benchmark + public void blockHash(final Blackhole blackhole) { + pushLong(blockNumberPool[index]); + blackhole.consume(BlockHashOperationV2.staticOperation(frame, frame.stackDataV2())); + // Pop the resulting hash + frame.setTopV2(frame.stackTopV2() - 1); + index = (index + 1) % SAMPLE_SIZE; + } + + private void pushLong(final long value) { + final long[] s = frame.stackDataV2(); + final int top = frame.stackTopV2(); + final int dst = top << 2; + s[dst] = 0L; + s[dst + 1] = 0L; + s[dst + 2] = 0L; + s[dst + 3] = value; + frame.setTopV2(top + 1); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/CallDataCopyBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/CallDataCopyBenchmarkV2.java new file mode 100644 index 00000000000..7d7eb98a42b --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/CallDataCopyBenchmarkV2.java @@ -0,0 +1,144 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +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.evm.Code; +import org.hyperledger.besu.evm.frame.BlockValues; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator; +import org.hyperledger.besu.evm.v2.operation.CallDataCopyOperationV2; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +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; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +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; + +/** Benchmark for CALLDATACOPY EVM v2 operation. */ +@State(Scope.Thread) +@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(value = TimeUnit.NANOSECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@BenchmarkMode(Mode.AverageTime) +public class CallDataCopyBenchmarkV2 { + + protected static final int SAMPLE_SIZE = 30_000; + + @Param({"0", "100", "10240", "1048576"}) // 0 bytes, 100 bytes, 10KiB, 1MiB + public int dataSize; + + @Param({"false", "true"}) + public boolean fixedSrcDst; + + private CancunGasCalculator gasCalculator; + private long[] destOffsetPool; + private long[] srcOffsetPool; + private long[] sizePool; + private int index; + private MessageFrame frame; + + @Setup + public void setUp() { + gasCalculator = new CancunGasCalculator(); + + // Build call data payload + final byte[] data = new byte[dataSize]; + for (int i = 0; i < dataSize; i++) { + data[i] = (byte) (i % 256); + } + final Bytes callData = Bytes.wrap(data); + + frame = + MessageFrame.builder() + .enableEvmV2(true) + .worldUpdater(mock(WorldUpdater.class)) + .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.ZERO) + .contract(Address.ZERO) + .inputData(callData) + .sender(Address.ZERO) + .value(Wei.ZERO) + .apparentValue(Wei.ZERO) + .code(Code.EMPTY_CODE) + .completer(__ -> {}) + .build(); + + destOffsetPool = new long[SAMPLE_SIZE]; + srcOffsetPool = new long[SAMPLE_SIZE]; + sizePool = new long[SAMPLE_SIZE]; + + for (int i = 0; i < SAMPLE_SIZE; i++) { + sizePool[i] = dataSize; + if (fixedSrcDst) { + destOffsetPool[i] = 0L; + srcOffsetPool[i] = 0L; + } else { + destOffsetPool[i] = (long) ((i * 32) % 1024); + srcOffsetPool[i] = (long) (i % Math.max(1, dataSize)); + } + } + index = 0; + + // Pre-warm destination memory to avoid expansion costs during benchmark + final long maxDstOffset = 1024L + 32L; + for (long off = 0; off < maxDstOffset; off += 32) { + frame.writeMemory(off, 32, Bytes.EMPTY); + } + } + + @Benchmark + public void callDataCopy(final Blackhole blackhole) { + // Push: size, srcOffset, destOffset (stack order: destOffset is top) + pushLong(sizePool[index]); + pushLong(srcOffsetPool[index]); + pushLong(destOffsetPool[index]); + blackhole.consume( + CallDataCopyOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator)); + index = (index + 1) % SAMPLE_SIZE; + } + + private void pushLong(final long value) { + final long[] s = frame.stackDataV2(); + final int top = frame.stackTopV2(); + final int dst = top << 2; + s[dst] = 0L; + s[dst + 1] = 0L; + s[dst + 2] = 0L; + s[dst + 3] = value; + frame.setTopV2(top + 1); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ClzOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ClzOperationBenchmarkV2.java new file mode 100644 index 00000000000..5e7eededca6 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ClzOperationBenchmarkV2.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.ClzOperationV2; + +public class ClzOperationBenchmarkV2 extends UnaryOperationBenchmarkV2 { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return ClzOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/DivOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/DivOperationBenchmarkV2.java new file mode 100644 index 00000000000..16ed9ac6070 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/DivOperationBenchmarkV2.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.DivOperationV2; + +public class DivOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return DivOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/DupNOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/DupNOperationBenchmarkV2.java new file mode 100644 index 00000000000..097c3d364ea --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/DupNOperationBenchmarkV2.java @@ -0,0 +1,47 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.DupNOperation; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.DupNOperationV2; + +/** JMH benchmark for the EVM v2 DUPN operation (EIP-8024). */ +public class DupNOperationBenchmarkV2 extends ImmediateByteOperationBenchmarkV2 { + + @Override + protected int getOpcode() { + return DupNOperation.OPCODE; + } + + @Override + protected byte getImmediate() { + // Immediate 0x00 decodes to n=17 (duplicate 17th stack item) + return 0x00; + } + + @Override + protected Operation.OperationResult invoke( + final MessageFrame frame, final byte[] code, final int pc) { + return DupNOperationV2.staticOperation(frame, frame.stackDataV2(), code, pc); + } + + @Override + protected int getStackDelta() { + // DUPN adds one item to the stack + return 1; + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/EqOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/EqOperationBenchmarkV2.java new file mode 100644 index 00000000000..1f3baa470d8 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/EqOperationBenchmarkV2.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.EqOperationV2; + +public class EqOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return EqOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ExchangeOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ExchangeOperationBenchmarkV2.java new file mode 100644 index 00000000000..f2cd59bd00a --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ExchangeOperationBenchmarkV2.java @@ -0,0 +1,47 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.ExchangeOperation; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.ExchangeOperationV2; + +/** JMH benchmark for the EVM v2 EXCHANGE operation (EIP-8024). */ +public class ExchangeOperationBenchmarkV2 extends ImmediateByteOperationBenchmarkV2 { + + @Override + protected int getOpcode() { + return ExchangeOperation.OPCODE; + } + + @Override + protected byte getImmediate() { + // Immediate 0x01 gives n=1, m=2 (swap 2nd with 3rd stack item) + return 0x01; + } + + @Override + protected Operation.OperationResult invoke( + final MessageFrame frame, final byte[] code, final int pc) { + return ExchangeOperationV2.staticOperation(frame, frame.stackDataV2(), code, pc); + } + + @Override + protected int getStackDelta() { + // EXCHANGE does not change stack size + return 0; + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ExpOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ExpOperationBenchmarkV2.java new file mode 100644 index 00000000000..f312170b412 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ExpOperationBenchmarkV2.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.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.FrontierGasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.ExpOperationV2; + +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Setup; + +public class ExpOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + private static final FrontierGasCalculator FRONTIER_GAS_CALCULATOR = new FrontierGasCalculator(); + + private ExpOperationV2 op; + + @Setup(Level.Iteration) + @Override + public void setUp() { + super.setUp(); + op = new ExpOperationV2(FRONTIER_GAS_CALCULATOR); + } + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return op.execute(frame, null); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/GtOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/GtOperationBenchmarkV2.java new file mode 100644 index 00000000000..fde5cd22831 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/GtOperationBenchmarkV2.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.GtOperationV2; + +public class GtOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return GtOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ImmediateByteOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ImmediateByteOperationBenchmarkV2.java new file mode 100644 index 00000000000..1c80f6e59b5 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ImmediateByteOperationBenchmarkV2.java @@ -0,0 +1,115 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; + +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +/** + * Base benchmark class for EVM V2 operations that use immediate byte operands. + * + *

This class provides common setup for operations like DUPN, SWAPN, and EXCHANGE (EIP-8024) + * which read an immediate byte following the opcode to determine their behaviour. + */ +@State(Scope.Thread) +@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(value = TimeUnit.NANOSECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@BenchmarkMode(Mode.AverageTime) +public abstract class ImmediateByteOperationBenchmarkV2 { + + protected static final int SAMPLE_SIZE = 30_000; + protected static final int STACK_DEPTH = 50; + + protected UInt256[] valuePool; + protected int index; + protected MessageFrame frame; + protected byte[] code; + + @Setup + public void setUp() { + frame = BenchmarkHelperV2.createMessageCallFrame(); + valuePool = new UInt256[SAMPLE_SIZE]; + BenchmarkHelperV2.fillUInt256Pool(valuePool); + index = 0; + + code = new byte[] {(byte) getOpcode(), getImmediate()}; + + for (int i = 0; i < STACK_DEPTH; i++) { + BenchmarkHelperV2.pushUInt256(frame, valuePool[i]); + } + } + + /** + * Returns the opcode for this operation. + * + * @return the opcode value + */ + protected abstract int getOpcode(); + + /** + * Returns the immediate byte operand for this operation. + * + * @return the immediate byte value + */ + protected abstract byte getImmediate(); + + /** + * Invokes the operation. + * + * @param frame the message frame + * @param code the bytecode array containing opcode and immediate + * @param pc the program counter (typically 0 for benchmarks) + * @return the operation result + */ + protected abstract Operation.OperationResult invoke(MessageFrame frame, byte[] code, int pc); + + /** + * Returns the stack size change after operation execution. Positive means items were added, + * negative means items were removed. + * + * @return the stack delta + */ + protected abstract int getStackDelta(); + + @Benchmark + public void executeOperation(final Blackhole blackhole) { + blackhole.consume(invoke(frame, code, 0)); + + int delta = getStackDelta(); + if (delta > 0) { + frame.setTopV2(frame.stackTopV2() - delta); + } else if (delta < 0) { + for (int i = 0; i < -delta; i++) { + BenchmarkHelperV2.pushUInt256(frame, valuePool[index]); + index = (index + 1) % SAMPLE_SIZE; + } + } + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/Keccak256OperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/Keccak256OperationBenchmarkV2.java new file mode 100644 index 00000000000..f3056228233 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/Keccak256OperationBenchmarkV2.java @@ -0,0 +1,103 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator; +import org.hyperledger.besu.evm.v2.operation.Keccak256OperationV2; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +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; + +/** Benchmark for KECCAK256 EVM v2 operation. */ +@State(Scope.Thread) +@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(value = TimeUnit.NANOSECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@BenchmarkMode(Mode.AverageTime) +public class Keccak256OperationBenchmarkV2 { + + protected static final int SAMPLE_SIZE = 30_000; + + @Param({"32", "64", "128", "256", "512"}) + public int dataSize; + + private CancunGasCalculator gasCalculator; + private long[] offsetPool; + private long[] sizePool; + private int index; + private MessageFrame frame; + + @Setup + public void setUp() { + gasCalculator = new CancunGasCalculator(); + frame = BenchmarkHelperV2.createMessageCallFrame(); + offsetPool = new long[SAMPLE_SIZE]; + sizePool = new long[SAMPLE_SIZE]; + + final Random random = new Random(42); + // Use offsets that keep data within a bounded window to avoid memory expansion cost noise + final long maxOffset = 4096L; + for (int i = 0; i < SAMPLE_SIZE; i++) { + sizePool[i] = dataSize; + offsetPool[i] = (random.nextInt((int) (maxOffset / 32))) * 32L; + } + index = 0; + + // Pre-warm memory so expansion costs don't skew results + final byte[] data = new byte[dataSize]; + random.nextBytes(data); + for (long off = 0; off < maxOffset + dataSize; off += 32) { + frame.writeMemory(off, 32, org.apache.tuweni.bytes.Bytes.EMPTY); + } + // Write benchmark data into the region we'll be hashing + frame.writeMemory(0, dataSize, org.apache.tuweni.bytes.Bytes.wrap(data)); + } + + @Benchmark + public void keccak256(final Blackhole blackhole) { + // Push size then offset; KECCAK256 pops both and pushes hash + pushLong(sizePool[index]); + pushLong(offsetPool[index]); + blackhole.consume( + Keccak256OperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator)); + // Pop the resulting hash + frame.setTopV2(frame.stackTopV2() - 1); + index = (index + 1) % SAMPLE_SIZE; + } + + private void pushLong(final long value) { + final long[] s = frame.stackDataV2(); + final int top = frame.stackTopV2(); + final int dst = top << 2; + s[dst] = 0L; + s[dst + 1] = 0L; + s[dst + 2] = 0L; + s[dst + 3] = value; + frame.setTopV2(top + 1); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/LtOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/LtOperationBenchmarkV2.java new file mode 100644 index 00000000000..d3f6e18c53e --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/LtOperationBenchmarkV2.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.LtOperationV2; + +public class LtOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return LtOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MemoryOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MemoryOperationBenchmarkV2.java new file mode 100644 index 00000000000..3fa1a386467 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MemoryOperationBenchmarkV2.java @@ -0,0 +1,105 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator; +import org.hyperledger.besu.evm.v2.operation.MloadOperationV2; +import org.hyperledger.besu.evm.v2.operation.MstoreOperationV2; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +/** Benchmark for MSTORE and MLOAD V2 operations. */ +@State(Scope.Thread) +@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(value = TimeUnit.NANOSECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@BenchmarkMode(Mode.AverageTime) +public class MemoryOperationBenchmarkV2 { + + protected static final int SAMPLE_SIZE = 30_000; + // Use offsets within a small window to avoid unbounded memory expansion cost + private static final long MAX_OFFSET = 1024; + + private CancunGasCalculator gasCalculator; + protected UInt256[] valuePool; + protected long[] offsetPool; + protected int index; + protected MessageFrame frame; + + @Setup + public void setUp() { + gasCalculator = new CancunGasCalculator(); + frame = BenchmarkHelperV2.createMessageCallFrame(); + valuePool = new UInt256[SAMPLE_SIZE]; + offsetPool = new long[SAMPLE_SIZE]; + + BenchmarkHelperV2.fillUInt256Pool(valuePool); + final Random random = new Random(42); + for (int i = 0; i < SAMPLE_SIZE; i++) { + // Align offsets to 32-byte boundaries within the first 1KiB + offsetPool[i] = (random.nextInt((int) (MAX_OFFSET / 32))) * 32L; + } + index = 0; + + // Pre-warm memory to avoid expansion costs during benchmark + for (long off = 0; off < MAX_OFFSET + 32; off += 32) { + frame.writeMemory(off, 32, org.apache.tuweni.bytes.Bytes.EMPTY); + } + } + + @Benchmark + public void mstore(final Blackhole blackhole) { + // Push offset then value; MSTORE pops both + pushOffset(offsetPool[index]); + BenchmarkHelperV2.pushUInt256(frame, valuePool[index]); + blackhole.consume(MstoreOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator)); + index = (index + 1) % SAMPLE_SIZE; + } + + @Benchmark + public void mload(final Blackhole blackhole) { + // Push offset; MLOAD replaces it with the 32 bytes read from memory + pushOffset(offsetPool[index]); + blackhole.consume(MloadOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator)); + // MLOAD leaves one item on the stack; pop it + frame.setTopV2(frame.stackTopV2() - 1); + index = (index + 1) % SAMPLE_SIZE; + } + + private void pushOffset(final long offset) { + final long[] s = frame.stackDataV2(); + final int t = frame.stackTopV2(); + final int dst = t << 2; + s[dst] = 0; + s[dst + 1] = 0; + s[dst + 2] = 0; + s[dst + 3] = offset; + frame.setTopV2(t + 1); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ModOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ModOperationBenchmarkV2.java new file mode 100644 index 00000000000..dc982b4edbf --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ModOperationBenchmarkV2.java @@ -0,0 +1,130 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.ModOperationV2; + +import java.math.BigInteger; +import java.util.concurrent.ThreadLocalRandom; + +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Setup; + +public class ModOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + public enum Case { + MOD_32_32(1, 1), + MOD_64_32(2, 1), + MOD_64_64(2, 2), + MOD_128_32(4, 1), + MOD_128_64(4, 2), + MOD_128_128(4, 4), + MOD_192_32(6, 1), + MOD_192_64(6, 2), + MOD_192_128(6, 4), + MOD_192_192(6, 6), + MOD_256_32(8, 1), + MOD_256_64(8, 2), + MOD_256_128(8, 4), + MOD_256_192(8, 6), + MOD_256_256(8, 8), + LARGER_MOD_64_128(2, 4), + LARGER_MOD_192_256(6, 8), + ZERO_MOD_128_0(4, 0), + FULL_RANDOM(-1, -1); + + final int divSize; + final int modSize; + + Case(final int divSize, final int modSize) { + this.divSize = divSize; + this.modSize = modSize; + } + } + + @Param({ + "MOD_32_32", + "MOD_64_32", + "MOD_64_64", + "MOD_128_32", + "MOD_128_64", + "MOD_128_128", + "MOD_192_32", + "MOD_192_64", + "MOD_192_128", + "MOD_192_192", + "MOD_256_32", + "MOD_256_64", + "MOD_256_128", + "MOD_256_192", + "MOD_256_256", + "LARGER_MOD_64_128", + "LARGER_MOD_192_256", + "ZERO_MOD_128_0", + "FULL_RANDOM" + }) + private String caseName; + + @Setup(Level.Iteration) + @Override + public void setUp() { + frame = BenchmarkHelperV2.createMessageCallFrame(); + + Case scenario = Case.valueOf(caseName); + aPool = new UInt256[SAMPLE_SIZE]; + bPool = new UInt256[SAMPLE_SIZE]; + + final ThreadLocalRandom random = ThreadLocalRandom.current(); + int aSize; + int bSize; + + for (int i = 0; i < SAMPLE_SIZE; i++) { + if (scenario.divSize < 0) aSize = random.nextInt(1, 33); + else aSize = scenario.divSize * 4; + if (scenario.modSize < 0) bSize = random.nextInt(1, 33); + else bSize = scenario.modSize * 4; + + final byte[] a = new byte[aSize]; + final byte[] b = new byte[bSize]; + random.nextBytes(a); + random.nextBytes(b); + + if (scenario.divSize != scenario.modSize) { + aPool[i] = BenchmarkHelperV2.bytesToUInt256(a); + bPool[i] = BenchmarkHelperV2.bytesToUInt256(b); + } else { + BigInteger aInt = new BigInteger(a); + BigInteger bInt = new BigInteger(b); + if (aInt.compareTo(bInt) < 0) { + aPool[i] = BenchmarkHelperV2.bytesToUInt256(b); + bPool[i] = BenchmarkHelperV2.bytesToUInt256(a); + } else { + aPool[i] = BenchmarkHelperV2.bytesToUInt256(a); + bPool[i] = BenchmarkHelperV2.bytesToUInt256(b); + } + } + } + index = 0; + } + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return ModOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulModOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulModOperationBenchmarkV2.java new file mode 100644 index 00000000000..d635f404a62 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulModOperationBenchmarkV2.java @@ -0,0 +1,172 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.MulModOperationV2; + +import java.util.concurrent.ThreadLocalRandom; + +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Setup; + +public class MulModOperationBenchmarkV2 extends TernaryOperationBenchmarkV2 { + + // Benches for (a + b) % c + + // Define available scenarios + public enum Case { + MULMOD_32_32_32(1, 1, 1), + MULMOD_64_32_32(2, 1, 1), + MULMOD_64_64_32(2, 2, 1), + MULMOD_64_64_64(2, 2, 2), + MULMOD_128_32_32(4, 1, 1), + MULMOD_128_64_32(4, 2, 1), + MULMOD_128_64_64(4, 2, 2), + MULMOD_128_128_32(4, 4, 1), + MULMOD_128_128_64(4, 4, 2), + MULMOD_128_128_128(4, 4, 3), + MULMOD_192_32_32(6, 1, 1), + MULMOD_192_64_32(6, 2, 1), + MULMOD_192_64_64(6, 2, 2), + MULMOD_192_128_32(6, 4, 1), + MULMOD_192_128_64(6, 4, 2), + MULMOD_192_128_128(6, 4, 4), + MULMOD_192_192_32(6, 6, 1), + MULMOD_192_192_64(6, 6, 2), + MULMOD_192_192_128(6, 6, 4), + MULMOD_192_192_192(6, 6, 6), + MULMOD_256_32_32(8, 1, 1), + MULMOD_256_64_32(8, 2, 1), + MULMOD_256_64_64(8, 2, 2), + MULMOD_256_64_128(8, 2, 4), + MULMOD_256_64_192(8, 2, 6), + MULMOD_256_128_32(8, 4, 1), + MULMOD_256_128_64(8, 4, 2), + MULMOD_256_128_128(8, 4, 4), + MULMOD_256_192_32(8, 6, 1), + MULMOD_256_192_64(8, 6, 2), + MULMOD_256_192_128(8, 6, 4), + MULMOD_256_192_192(8, 6, 6), + MULMOD_256_256_32(8, 8, 1), + MULMOD_256_256_64(8, 8, 2), + MULMOD_256_256_128(8, 8, 4), + MULMOD_256_256_192(8, 8, 6), + MULMOD_256_256_256(8, 8, 8), + LARGER_MULMOD_64_64_128(2, 2, 4), + LARGER_MULMOD_192_192_256(6, 6, 8), + ZERO_MULMOD_128_256_0(4, 8, 0), + FULL_RANDOM(-1, -1, -1); + + final int aSize; + final int bSize; + final int cSize; + + Case(final int aSize, final int bSize, final int cSize) { + this.aSize = aSize; + this.bSize = bSize; + this.cSize = cSize; + } + } + + @Param({ + "MULMOD_32_32_32", + "MULMOD_64_32_32", + "MULMOD_64_64_32", + "MULMOD_64_64_64", + "MULMOD_128_32_32", + "MULMOD_128_64_32", + "MULMOD_128_64_64", + "MULMOD_128_128_32", + "MULMOD_128_128_64", + "MULMOD_128_128_128", + "MULMOD_192_32_32", + "MULMOD_192_64_32", + "MULMOD_192_64_64", + "MULMOD_192_128_32", + "MULMOD_192_128_64", + "MULMOD_192_128_128", + "MULMOD_192_192_32", + "MULMOD_192_192_64", + "MULMOD_192_192_128", + "MULMOD_192_192_192", + "MULMOD_256_32_32", + "MULMOD_256_64_32", + "MULMOD_256_64_64", + "MULMOD_256_64_128", + "MULMOD_256_64_192", + "MULMOD_256_128_32", + "MULMOD_256_128_64", + "MULMOD_256_128_128", + "MULMOD_256_192_32", + "MULMOD_256_192_64", + "MULMOD_256_192_128", + "MULMOD_256_192_192", + "MULMOD_256_256_32", + "MULMOD_256_256_64", + "MULMOD_256_256_128", + "MULMOD_256_256_192", + "MULMOD_256_256_256", + "LARGER_MULMOD_64_64_128", + "LARGER_MULMOD_192_192_256", + "ZERO_MULMOD_128_256_0", + "FULL_RANDOM" + }) + private String caseName; + + @Setup(Level.Iteration) + @Override + public void setUp() { + frame = BenchmarkHelperV2.createMessageCallFrame(); + + MulModOperationBenchmarkV2.Case scenario = MulModOperationBenchmarkV2.Case.valueOf(caseName); + aPool = new UInt256[SAMPLE_SIZE]; + bPool = new UInt256[SAMPLE_SIZE]; + cPool = new UInt256[SAMPLE_SIZE]; + + final ThreadLocalRandom random = ThreadLocalRandom.current(); + int aSize; + int bSize; + int cSize; + + for (int i = 0; i < SAMPLE_SIZE; i++) { + if (scenario.aSize < 0) aSize = random.nextInt(1, 33); + else aSize = scenario.aSize * 4; + if (scenario.bSize < 0) bSize = random.nextInt(1, 33); + else bSize = scenario.bSize * 4; + if (scenario.cSize < 0) cSize = random.nextInt(1, 33); + else cSize = scenario.cSize * 4; + + final byte[] a = new byte[aSize]; + final byte[] b = new byte[bSize]; + final byte[] c = new byte[cSize]; + random.nextBytes(a); + random.nextBytes(b); + random.nextBytes(c); + aPool[i] = BenchmarkHelperV2.bytesToUInt256(a); + bPool[i] = BenchmarkHelperV2.bytesToUInt256(b); + cPool[i] = BenchmarkHelperV2.bytesToUInt256(c); + } + index = 0; + } + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return MulModOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulOperationBenchmarkV2.java new file mode 100644 index 00000000000..7138bf83203 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulOperationBenchmarkV2.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.MulOperationV2; + +public class MulOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return MulOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/NotOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/NotOperationBenchmarkV2.java new file mode 100644 index 00000000000..9c94e6d9ece --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/NotOperationBenchmarkV2.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.NotOperationV2; + +public class NotOperationBenchmarkV2 extends UnaryOperationBenchmarkV2 { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return NotOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/OperandStackBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/OperandStackBenchmarkV2.java new file mode 100644 index 00000000000..43c3adffe28 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/OperandStackBenchmarkV2.java @@ -0,0 +1,80 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.v2.StackArithmetic; +import org.hyperledger.besu.evm.v2.operation.PopOperationV2; +import org.hyperledger.besu.evm.v2.operation.Push0OperationV2; + +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +/** + * Benchmarks for EVM v2 stack manipulation (POP and PUSH0) on the flat {@code long[]} stack. + * + *

Measures the cost of filling the stack to a given depth using PUSH0, then popping all items + * back to empty using POP. + */ +@State(Scope.Thread) +@Warmup(iterations = 6, time = 2, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(value = TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +public class OperandStackBenchmarkV2 { + + private static final int OPERATIONS_PER_INVOCATION = 1000; + + @Param({"6", "15", "34", "100", "234", "500", "800", "1024"}) + private int stackDepth; + + @Benchmark + @OperationsPerInvocation(OPERATIONS_PER_INVOCATION) + public void fillUpWithPush0() { + for (int i = 0; i < OPERATIONS_PER_INVOCATION; i++) { + MessageFrame frame = BenchmarkHelperV2.createMessageCallFrame(); + long[] s = frame.stackDataV2(); + for (int j = 0; j < stackDepth; j++) { + frame.setTopV2(StackArithmetic.pushZero(s, frame.stackTopV2())); + } + } + } + + @Benchmark + @OperationsPerInvocation(OPERATIONS_PER_INVOCATION) + public void push0AndPop() { + for (int i = 0; i < OPERATIONS_PER_INVOCATION; i++) { + MessageFrame frame = BenchmarkHelperV2.createMessageCallFrame(); + // Push stackDepth zeros + for (int j = 0; j < stackDepth; j++) { + Push0OperationV2.staticOperation(frame, frame.stackDataV2()); + } + // Pop all of them + for (int j = 0; j < stackDepth; j++) { + PopOperationV2.staticOperation(frame); + } + } + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SDivOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SDivOperationBenchmarkV2.java new file mode 100644 index 00000000000..40a28e4528a --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SDivOperationBenchmarkV2.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.SDivOperationV2; + +public class SDivOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return SDivOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SModOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SModOperationBenchmarkV2.java new file mode 100644 index 00000000000..fdb44ca562a --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SModOperationBenchmarkV2.java @@ -0,0 +1,130 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.SModOperationV2; + +import java.math.BigInteger; +import java.util.concurrent.ThreadLocalRandom; + +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Setup; + +public class SModOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + public enum Case { + SMOD_32_32(1, 1), + SMOD_64_32(2, 1), + SMOD_64_64(2, 2), + SMOD_128_32(4, 1), + SMOD_128_64(4, 2), + SMOD_128_128(4, 4), + SMOD_192_32(6, 1), + SMOD_192_64(6, 2), + SMOD_192_128(6, 4), + SMOD_192_192(6, 6), + SMOD_256_32(8, 1), + SMOD_256_64(8, 2), + SMOD_256_128(8, 4), + SMOD_256_192(8, 6), + SMOD_256_256(8, 8), + LARGER_SMOD_64_128(2, 4), + LARGER_SMOD_192_256(6, 8), + ZERO_SMOD_128_0(4, 0), + FULL_RANDOM(-1, -1); + + final int divSize; + final int modSize; + + Case(final int divSize, final int modSize) { + this.divSize = divSize; + this.modSize = modSize; + } + } + + @Param({ + "SMOD_32_32", + "SMOD_64_32", + "SMOD_64_64", + "SMOD_128_32", + "SMOD_128_64", + "SMOD_128_128", + "SMOD_192_32", + "SMOD_192_64", + "SMOD_192_128", + "SMOD_192_192", + "SMOD_256_32", + "SMOD_256_64", + "SMOD_256_128", + "SMOD_256_192", + "SMOD_256_256", + "LARGER_SMOD_64_128", + "LARGER_SMOD_192_256", + "ZERO_SMOD_128_0", + "FULL_RANDOM" + }) + private String caseName; + + @Setup(Level.Iteration) + @Override + public void setUp() { + frame = BenchmarkHelperV2.createMessageCallFrame(); + + Case scenario = Case.valueOf(caseName); + aPool = new UInt256[SAMPLE_SIZE]; + bPool = new UInt256[SAMPLE_SIZE]; + + final ThreadLocalRandom random = ThreadLocalRandom.current(); + int aSize; + int bSize; + + for (int i = 0; i < SAMPLE_SIZE; i++) { + if (scenario.divSize < 0) aSize = random.nextInt(1, 33); + else aSize = scenario.divSize * 4; + if (scenario.modSize < 0) bSize = random.nextInt(1, 33); + else bSize = scenario.modSize * 4; + + final byte[] a = new byte[aSize]; + final byte[] b = new byte[bSize]; + random.nextBytes(a); + random.nextBytes(b); + + if (scenario.divSize != scenario.modSize) { + aPool[i] = BenchmarkHelperV2.bytesToUInt256(a); + bPool[i] = BenchmarkHelperV2.bytesToUInt256(b); + } else { + BigInteger aInt = new BigInteger(a); + BigInteger bInt = new BigInteger(b); + if (aInt.abs().compareTo(bInt.abs()) < 0) { + aPool[i] = BenchmarkHelperV2.bytesToUInt256(b); + bPool[i] = BenchmarkHelperV2.bytesToUInt256(a); + } else { + aPool[i] = BenchmarkHelperV2.bytesToUInt256(a); + bPool[i] = BenchmarkHelperV2.bytesToUInt256(b); + } + } + } + index = 0; + } + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return SModOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SarOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SarOperationBenchmarkV2.java new file mode 100644 index 00000000000..e617026a236 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SarOperationBenchmarkV2.java @@ -0,0 +1,156 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import static org.hyperledger.besu.ethereum.vm.operations.v2.BenchmarkHelperV2.bytesToUInt256; +import static org.hyperledger.besu.ethereum.vm.operations.v2.BenchmarkHelperV2.randomNegativeUInt256Value; +import static org.hyperledger.besu.ethereum.vm.operations.v2.BenchmarkHelperV2.randomPositiveUInt256Value; +import static org.hyperledger.besu.ethereum.vm.operations.v2.BenchmarkHelperV2.randomUInt256Value; + +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.SarOperationV2; + +import java.util.concurrent.ThreadLocalRandom; + +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Setup; + +public class SarOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + /** Test cases covering different execution paths for SAR operations. */ + public enum Case { + /** Shift by 0 - early return path. */ + SHIFT_0, + /** Negative number with shift=1 - tests sign extension. */ + NEGATIVE_SHIFT_1, + /** Value with all bits set with shift=1. */ + ALL_BITS_SHIFT_1, + /** Positive number with shift=1 - no sign extension needed. */ + POSITIVE_SHIFT_1, + /** Negative number with medium shift. */ + NEGATIVE_SHIFT_128, + /** Negative number with max shift. */ + NEGATIVE_SHIFT_255, + /** Positive number with medium shift. */ + POSITIVE_SHIFT_128, + /** Positive number with max shift. */ + POSITIVE_SHIFT_255, + /** Overflow: shift >= 256. */ + OVERFLOW_SHIFT_256, + /** Overflow: shift amount > 4 bytes. */ + OVERFLOW_LARGE_SHIFT, + /** Random values. */ + FULL_RANDOM + } + + @Param({ + "SHIFT_0", + "NEGATIVE_SHIFT_1", + "POSITIVE_SHIFT_1", + "ALL_BITS_SHIFT_1", + "NEGATIVE_SHIFT_128", + "NEGATIVE_SHIFT_255", + "POSITIVE_SHIFT_128", + "POSITIVE_SHIFT_255", + "OVERFLOW_SHIFT_256", + "OVERFLOW_LARGE_SHIFT", + "FULL_RANDOM" + }) + protected String caseName; + + @Setup(Level.Iteration) + @Override + public void setUp() { + frame = BenchmarkHelperV2.createMessageCallFrame(); + + final Case scenario = Case.valueOf(caseName); + aPool = new UInt256[SAMPLE_SIZE]; + bPool = new UInt256[SAMPLE_SIZE]; + + final ThreadLocalRandom random = ThreadLocalRandom.current(); + + for (int i = 0; i < SAMPLE_SIZE; i++) { + switch (scenario) { + case SHIFT_0: + aPool[i] = UInt256.fromInt(0); + bPool[i] = randomUInt256Value(random); + break; + + case NEGATIVE_SHIFT_1: + aPool[i] = UInt256.fromInt(1); + bPool[i] = randomNegativeUInt256Value(random); + break; + + case ALL_BITS_SHIFT_1: + aPool[i] = UInt256.fromInt(1); + bPool[i] = UInt256.MAX; + break; + + case POSITIVE_SHIFT_1: + aPool[i] = UInt256.fromInt(1); + bPool[i] = randomPositiveUInt256Value(random); + break; + + case NEGATIVE_SHIFT_128: + aPool[i] = UInt256.fromInt(128); + bPool[i] = randomNegativeUInt256Value(random); + break; + + case NEGATIVE_SHIFT_255: + aPool[i] = UInt256.fromInt(255); + bPool[i] = randomNegativeUInt256Value(random); + break; + + case POSITIVE_SHIFT_128: + aPool[i] = UInt256.fromInt(128); + bPool[i] = randomPositiveUInt256Value(random); + break; + case POSITIVE_SHIFT_255: + aPool[i] = UInt256.fromInt(255); + bPool[i] = randomPositiveUInt256Value(random); + break; + + case OVERFLOW_SHIFT_256: + aPool[i] = UInt256.fromInt(256); + bPool[i] = randomUInt256Value(random); + break; + + case OVERFLOW_LARGE_SHIFT: + aPool[i] = bytesToUInt256(new byte[] {0x01, 0x00, 0x00, 0x00, 0x00, 0x00}); + bPool[i] = randomUInt256Value(random); + break; + + case FULL_RANDOM: + default: + final byte[] shift = new byte[1 + random.nextInt(2)]; + final byte[] value = new byte[1 + random.nextInt(32)]; + random.nextBytes(shift); + random.nextBytes(value); + aPool[i] = bytesToUInt256(shift); + bPool[i] = bytesToUInt256(value); + break; + } + } + index = 0; + } + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return SarOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} 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..ce17e9d12e7 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SelfBalanceOperationBenchmarkV2.java @@ -0,0 +1,95 @@ +/* + * 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.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.frame.MessageFrame; +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.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; + +/** Benchmark for SELFBALANCE EVM v2 operation. */ +@State(Scope.Thread) +@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(value = TimeUnit.NANOSECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@BenchmarkMode(Mode.AverageTime) +public class SelfBalanceOperationBenchmarkV2 { + + protected static final int SAMPLE_SIZE = 30_000; + + private MessageFrame frame; + + @Setup + public void setUp() { + // Set up a mock world updater returning a non-null account with a balance + final WorldUpdater worldUpdater = mock(WorldUpdater.class); + final MutableAccount account = mock(MutableAccount.class); + final byte[] balanceBytes = new byte[32]; + new Random(42).nextBytes(balanceBytes); + final Wei balance = Wei.of(new java.math.BigInteger(1, balanceBytes)); + when(account.getBalance()).thenReturn(balance); + + frame = + MessageFrame.builder() + .enableEvmV2(true) + .worldUpdater(worldUpdater) + .originator(Address.ZERO) + .gasPrice(Wei.ONE) + .blobGasPrice(Wei.ONE) + .blockValues(mock(org.hyperledger.besu.evm.frame.BlockValues.class)) + .miningBeneficiary(Address.ZERO) + .blockHashLookup((__, ___) -> org.hyperledger.besu.datatypes.Hash.ZERO) + .type(MessageFrame.Type.MESSAGE_CALL) + .initialGas(Long.MAX_VALUE) + .address(Address.ZERO) + .contract(Address.ZERO) + .inputData(org.apache.tuweni.bytes.Bytes32.ZERO) + .sender(Address.ZERO) + .value(Wei.ZERO) + .apparentValue(Wei.ZERO) + .code(org.hyperledger.besu.evm.Code.EMPTY_CODE) + .completer(__ -> {}) + .build(); + + // Configure world updater to return the account for Address.ZERO (frame's contract address) + when(worldUpdater.getAccount(Address.ZERO)).thenReturn(account); + } + + @Benchmark + public void selfBalance(final Blackhole blackhole) { + blackhole.consume(SelfBalanceOperationV2.staticOperation(frame, frame.stackDataV2())); + // Pop the resulting balance + frame.setTopV2(frame.stackTopV2() - 1); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SgtOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SgtOperationBenchmarkV2.java new file mode 100644 index 00000000000..741b075daea --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SgtOperationBenchmarkV2.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.SgtOperationV2; + +public class SgtOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return SgtOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ShlOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ShlOperationBenchmarkV2.java new file mode 100644 index 00000000000..117f8148036 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ShlOperationBenchmarkV2.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.ShlOperationV2; + +public class ShlOperationBenchmarkV2 extends AbstractShiftOperationBenchmarkV2 { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return ShlOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ShrOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ShrOperationBenchmarkV2.java new file mode 100644 index 00000000000..fa3dd952609 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/ShrOperationBenchmarkV2.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.ShrOperationV2; + +public class ShrOperationBenchmarkV2 extends AbstractShiftOperationBenchmarkV2 { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return ShrOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SltOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SltOperationBenchmarkV2.java new file mode 100644 index 00000000000..85faa762750 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SltOperationBenchmarkV2.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.SltOperationV2; + +public class SltOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return SltOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SubOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SubOperationBenchmarkV2.java new file mode 100644 index 00000000000..bae063fbccd --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SubOperationBenchmarkV2.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.SubOperationV2; + +public class SubOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return SubOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SwapNOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SwapNOperationBenchmarkV2.java new file mode 100644 index 00000000000..b3a9adf0c3a --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SwapNOperationBenchmarkV2.java @@ -0,0 +1,47 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.operation.SwapNOperation; +import org.hyperledger.besu.evm.v2.operation.SwapNOperationV2; + +/** JMH benchmark for the EVM v2 SWAPN operation (EIP-8024). */ +public class SwapNOperationBenchmarkV2 extends ImmediateByteOperationBenchmarkV2 { + + @Override + protected int getOpcode() { + return SwapNOperation.OPCODE; + } + + @Override + protected byte getImmediate() { + // Immediate 0x00 decodes to n=17 (swap top with 18th stack item) + return 0x00; + } + + @Override + protected Operation.OperationResult invoke( + final MessageFrame frame, final byte[] code, final int pc) { + return SwapNOperationV2.staticOperation(frame, frame.stackDataV2(), code, pc); + } + + @Override + protected int getStackDelta() { + // SWAPN does not change stack size + return 0; + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/TernaryOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/TernaryOperationBenchmarkV2.java new file mode 100644 index 00000000000..857402a31c0 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/TernaryOperationBenchmarkV2.java @@ -0,0 +1,75 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; + +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +@State(Scope.Thread) +@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(value = TimeUnit.NANOSECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@BenchmarkMode(Mode.AverageTime) +public abstract class TernaryOperationBenchmarkV2 { + + protected static final int SAMPLE_SIZE = 30_000; + + protected UInt256[] aPool; + protected UInt256[] bPool; + protected UInt256[] cPool; + protected int index; + protected MessageFrame frame; + + @Setup() + public void setUp() { + frame = BenchmarkHelperV2.createMessageCallFrame(); + aPool = new UInt256[SAMPLE_SIZE]; + bPool = new UInt256[SAMPLE_SIZE]; + cPool = new UInt256[SAMPLE_SIZE]; + BenchmarkHelperV2.fillUInt256Pool(aPool); + BenchmarkHelperV2.fillUInt256Pool(bPool); + BenchmarkHelperV2.fillUInt256Pool(cPool); + index = 0; + } + + @Benchmark + public void executeOperation(final Blackhole blackhole) { + BenchmarkHelperV2.pushUInt256(frame, cPool[index]); + BenchmarkHelperV2.pushUInt256(frame, bPool[index]); + BenchmarkHelperV2.pushUInt256(frame, aPool[index]); + + blackhole.consume(invoke(frame)); + + frame.setTopV2(frame.stackTopV2() - 1); + + index = (index + 1) % SAMPLE_SIZE; + } + + protected abstract Operation.OperationResult invoke(MessageFrame frame); +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/TransientStorageOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/TransientStorageOperationBenchmarkV2.java new file mode 100644 index 00000000000..d5f9ada11e5 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/TransientStorageOperationBenchmarkV2.java @@ -0,0 +1,112 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.v2.operation.TLoadOperationV2; +import org.hyperledger.besu.evm.v2.operation.TStoreOperationV2; + +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +/** Benchmarks for TLOAD and TSTORE EVM v2 operations on transient storage. */ +@State(Scope.Thread) +@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(value = TimeUnit.NANOSECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@BenchmarkMode(Mode.AverageTime) +public class TransientStorageOperationBenchmarkV2 { + + private static final int SAMPLE_SIZE = 30_000; + + private UInt256[] keyPool; + private UInt256[] valuePool; + private int index; + private MessageFrame frame; + + @Setup + public void setUp() { + frame = BenchmarkHelperV2.createMessageCallFrame(); + keyPool = new UInt256[SAMPLE_SIZE]; + valuePool = new UInt256[SAMPLE_SIZE]; + BenchmarkHelperV2.fillUInt256Pool(keyPool); + BenchmarkHelperV2.fillUInt256Pool(valuePool); + index = 0; + } + + /** Benchmark TSTORE followed by TLOAD (round-trip). */ + @Benchmark + public void tStoreAndLoad(final Blackhole blackhole) { + final long[] s = frame.stackDataV2(); + + // TSTORE: depth 0 = key, depth 1 = value + BenchmarkHelperV2.pushUInt256(frame, valuePool[index]); + BenchmarkHelperV2.pushUInt256(frame, keyPool[index]); + blackhole.consume(TStoreOperationV2.staticOperation(frame, s)); + + // TLOAD: depth 0 = key; overwrites top slot with loaded value + BenchmarkHelperV2.pushUInt256(frame, keyPool[index]); + blackhole.consume(TLoadOperationV2.staticOperation(frame, s)); + + // Clean up the loaded value left on the stack + frame.setTopV2(frame.stackTopV2() - 1); + + index = (index + 1) % SAMPLE_SIZE; + } + + /** Benchmark TSTORE only. */ + @Benchmark + public void tStoreOnly(final Blackhole blackhole) { + final long[] s = frame.stackDataV2(); + + // TSTORE: depth 0 = key, depth 1 = value + BenchmarkHelperV2.pushUInt256(frame, valuePool[index]); + BenchmarkHelperV2.pushUInt256(frame, keyPool[index]); + blackhole.consume(TStoreOperationV2.staticOperation(frame, s)); + + index = (index + 1) % SAMPLE_SIZE; + } + + /** Benchmark TLOAD only (pre-stores a value to ensure a non-empty result). */ + @Benchmark + public void tLoadOnly(final Blackhole blackhole) { + final long[] s = frame.stackDataV2(); + + // Pre-store a value for the key (not measured by JMH — this is setup work within the method) + BenchmarkHelperV2.pushUInt256(frame, valuePool[index]); + BenchmarkHelperV2.pushUInt256(frame, keyPool[index]); + TStoreOperationV2.staticOperation(frame, s); + + // TLOAD: depth 0 = key; overwrites top slot with loaded value + BenchmarkHelperV2.pushUInt256(frame, keyPool[index]); + blackhole.consume(TLoadOperationV2.staticOperation(frame, s)); + + // Clean up the loaded value left on the stack + frame.setTopV2(frame.stackTopV2() - 1); + + index = (index + 1) % SAMPLE_SIZE; + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/UnaryOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/UnaryOperationBenchmarkV2.java new file mode 100644 index 00000000000..fc0eeb243e8 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/UnaryOperationBenchmarkV2.java @@ -0,0 +1,67 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; + +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +@State(Scope.Thread) +@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(value = TimeUnit.NANOSECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@BenchmarkMode(Mode.AverageTime) +public abstract class UnaryOperationBenchmarkV2 { + + protected static final int SAMPLE_SIZE = 30_000; + + protected UInt256[] aPool; + protected int index; + protected MessageFrame frame; + + @Setup() + public void setUp() { + frame = BenchmarkHelperV2.createMessageCallFrame(); + aPool = new UInt256[SAMPLE_SIZE]; + BenchmarkHelperV2.fillUInt256Pool(aPool); + index = 0; + } + + @Benchmark + public void executeOperation(final Blackhole blackhole) { + BenchmarkHelperV2.pushUInt256(frame, aPool[index]); + + blackhole.consume(invoke(frame)); + + frame.setTopV2(frame.stackTopV2() - 1); + + index = (index + 1) % SAMPLE_SIZE; + } + + protected abstract Operation.OperationResult invoke(MessageFrame frame); +} diff --git a/ethereum/referencetests/build.gradle b/ethereum/referencetests/build.gradle index 3aba4c6cbee..820374d8203 100644 --- a/ethereum/referencetests/build.gradle +++ b/ethereum/referencetests/build.gradle @@ -330,6 +330,16 @@ dependencies { tasks.register('referenceTests', Test) { useJUnitPlatform() + for (String name : [ + 'test.evm.v2', + 'test.ethereum.state.eips', + 'test.ethereum.blockchain.eips', + 'test.ethereum.include' + ]) { + if (System.getProperty(name) != null) { + systemProperty name, System.getProperty(name) + } + } doFirst { if (!file("src/reference-test/external-resources/README.md").exists()) { throw new GradleException("ethereum/referencetest/src/reference-test/external-resources/README.md missing: please clone submodules (git submodule update --init --recursive)") @@ -342,6 +352,16 @@ tasks.register('referenceTests', Test) { tasks.register('referenceTestsDevnet', Test) { useJUnitPlatform() + for (String name : [ + 'test.evm.v2', + 'test.ethereum.state.eips', + 'test.ethereum.blockchain.eips', + 'test.ethereum.include' + ]) { + if (System.getProperty(name) != null) { + systemProperty name, System.getProperty(name) + } + } description = 'Runs execution-spec-tests pre-release (devnet) reference tests.' testClassesDirs = sourceSets.referenceTestDevnet.output.classesDirs classpath = sourceSets.referenceTestDevnet.runtimeClasspath diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java index 74a0abee69f..354d2a50f44 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java @@ -63,7 +63,16 @@ public class ReferenceTestProtocolSchedules { "eip158tobyzantiumat5"); public static ReferenceTestProtocolSchedules create() { - return create(new StubGenesisConfigOptions(), EvmConfiguration.DEFAULT); + final boolean evmV2 = Boolean.getBoolean("test.evm.v2"); + final EvmConfiguration evmConfiguration = + evmV2 + ? new EvmConfiguration( + EvmConfiguration.DEFAULT.jumpDestCacheWeightKB(), + EvmConfiguration.DEFAULT.worldUpdaterMode(), + EvmConfiguration.DEFAULT.enableOptimizedOpcodes(), + true) + : EvmConfiguration.DEFAULT; + return create(new StubGenesisConfigOptions(), evmConfiguration); } public static ReferenceTestProtocolSchedules create(final EvmConfiguration evmConfiguration) { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java index 99c4eb8014b..47b93809356 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java @@ -27,6 +27,8 @@ import org.hyperledger.besu.evm.internal.JumpDestOnlyCodeCache; import org.hyperledger.besu.evm.internal.OverflowException; import org.hyperledger.besu.evm.internal.UnderflowException; +import org.hyperledger.besu.evm.log.EIP7708TransferLogEmitter; +import org.hyperledger.besu.evm.log.TransferLogEmitter; import org.hyperledger.besu.evm.operation.AddModOperation; import org.hyperledger.besu.evm.operation.AddModOperationOptimized; import org.hyperledger.besu.evm.operation.AddOperation; @@ -86,8 +88,98 @@ import org.hyperledger.besu.evm.operation.VirtualOperation; import org.hyperledger.besu.evm.operation.XorOperation; import org.hyperledger.besu.evm.operation.XorOperationOptimized; -import org.hyperledger.besu.evm.operation.v2.AddOperationV2; import org.hyperledger.besu.evm.tracing.OperationTracer; +import org.hyperledger.besu.evm.v2.operation.AddModOperationV2; +import org.hyperledger.besu.evm.v2.operation.AddOperationV2; +import org.hyperledger.besu.evm.v2.operation.AddressOperationV2; +import org.hyperledger.besu.evm.v2.operation.AndOperationV2; +import org.hyperledger.besu.evm.v2.operation.BalanceOperationV2; +import org.hyperledger.besu.evm.v2.operation.BaseFeeOperationV2; +import org.hyperledger.besu.evm.v2.operation.BlobBaseFeeOperationV2; +import org.hyperledger.besu.evm.v2.operation.BlobHashOperationV2; +import org.hyperledger.besu.evm.v2.operation.BlockHashOperationV2; +import org.hyperledger.besu.evm.v2.operation.ByteOperationV2; +import org.hyperledger.besu.evm.v2.operation.CallCodeOperationV2; +import org.hyperledger.besu.evm.v2.operation.CallDataCopyOperationV2; +import org.hyperledger.besu.evm.v2.operation.CallDataLoadOperationV2; +import org.hyperledger.besu.evm.v2.operation.CallDataSizeOperationV2; +import org.hyperledger.besu.evm.v2.operation.CallOperationV2; +import org.hyperledger.besu.evm.v2.operation.CallValueOperationV2; +import org.hyperledger.besu.evm.v2.operation.CallerOperationV2; +import org.hyperledger.besu.evm.v2.operation.ChainIdOperationV2; +import org.hyperledger.besu.evm.v2.operation.ClzOperationV2; +import org.hyperledger.besu.evm.v2.operation.CodeCopyOperationV2; +import org.hyperledger.besu.evm.v2.operation.CodeSizeOperationV2; +import org.hyperledger.besu.evm.v2.operation.CoinbaseOperationV2; +import org.hyperledger.besu.evm.v2.operation.Create2OperationV2; +import org.hyperledger.besu.evm.v2.operation.CreateOperationV2; +import org.hyperledger.besu.evm.v2.operation.DelegateCallOperationV2; +import org.hyperledger.besu.evm.v2.operation.DifficultyOperationV2; +import org.hyperledger.besu.evm.v2.operation.DivOperationV2; +import org.hyperledger.besu.evm.v2.operation.DupNOperationV2; +import org.hyperledger.besu.evm.v2.operation.DupOperationV2; +import org.hyperledger.besu.evm.v2.operation.EqOperationV2; +import org.hyperledger.besu.evm.v2.operation.ExchangeOperationV2; +import org.hyperledger.besu.evm.v2.operation.ExpOperationV2; +import org.hyperledger.besu.evm.v2.operation.ExtCodeCopyOperationV2; +import org.hyperledger.besu.evm.v2.operation.ExtCodeHashOperationV2; +import org.hyperledger.besu.evm.v2.operation.ExtCodeSizeOperationV2; +import org.hyperledger.besu.evm.v2.operation.GasLimitOperationV2; +import org.hyperledger.besu.evm.v2.operation.GasOperationV2; +import org.hyperledger.besu.evm.v2.operation.GasPriceOperationV2; +import org.hyperledger.besu.evm.v2.operation.GtOperationV2; +import org.hyperledger.besu.evm.v2.operation.InvalidOperationV2; +import org.hyperledger.besu.evm.v2.operation.IsZeroOperationV2; +import org.hyperledger.besu.evm.v2.operation.JumpDestOperationV2; +import org.hyperledger.besu.evm.v2.operation.JumpOperationV2; +import org.hyperledger.besu.evm.v2.operation.JumpiOperationV2; +import org.hyperledger.besu.evm.v2.operation.Keccak256OperationV2; +import org.hyperledger.besu.evm.v2.operation.LogOperationV2; +import org.hyperledger.besu.evm.v2.operation.LtOperationV2; +import org.hyperledger.besu.evm.v2.operation.MCopyOperationV2; +import org.hyperledger.besu.evm.v2.operation.MSizeOperationV2; +import org.hyperledger.besu.evm.v2.operation.MloadOperationV2; +import org.hyperledger.besu.evm.v2.operation.ModOperationV2; +import org.hyperledger.besu.evm.v2.operation.Mstore8OperationV2; +import org.hyperledger.besu.evm.v2.operation.MstoreOperationV2; +import org.hyperledger.besu.evm.v2.operation.MulModOperationV2; +import org.hyperledger.besu.evm.v2.operation.MulOperationV2; +import org.hyperledger.besu.evm.v2.operation.NotOperationV2; +import org.hyperledger.besu.evm.v2.operation.NumberOperationV2; +import org.hyperledger.besu.evm.v2.operation.OrOperationV2; +import org.hyperledger.besu.evm.v2.operation.OriginOperationV2; +import org.hyperledger.besu.evm.v2.operation.PayOperationV2; +import org.hyperledger.besu.evm.v2.operation.PcOperationV2; +import org.hyperledger.besu.evm.v2.operation.PopOperationV2; +import org.hyperledger.besu.evm.v2.operation.PrevRandaoOperationV2; +import org.hyperledger.besu.evm.v2.operation.Push0OperationV2; +import org.hyperledger.besu.evm.v2.operation.PushOperationV2; +import org.hyperledger.besu.evm.v2.operation.ReturnDataCopyOperationV2; +import org.hyperledger.besu.evm.v2.operation.ReturnDataSizeOperationV2; +import org.hyperledger.besu.evm.v2.operation.ReturnOperationV2; +import org.hyperledger.besu.evm.v2.operation.RevertOperationV2; +import org.hyperledger.besu.evm.v2.operation.SDivOperationV2; +import org.hyperledger.besu.evm.v2.operation.SLoadOperationV2; +import org.hyperledger.besu.evm.v2.operation.SModOperationV2; +import org.hyperledger.besu.evm.v2.operation.SStoreOperationV2; +import org.hyperledger.besu.evm.v2.operation.SarOperationV2; +import org.hyperledger.besu.evm.v2.operation.SelfBalanceOperationV2; +import org.hyperledger.besu.evm.v2.operation.SelfDestructOperationV2; +import org.hyperledger.besu.evm.v2.operation.SgtOperationV2; +import org.hyperledger.besu.evm.v2.operation.ShlOperationV2; +import org.hyperledger.besu.evm.v2.operation.ShrOperationV2; +import org.hyperledger.besu.evm.v2.operation.SignExtendOperationV2; +import org.hyperledger.besu.evm.v2.operation.SlotNumOperationV2; +import org.hyperledger.besu.evm.v2.operation.SltOperationV2; +import org.hyperledger.besu.evm.v2.operation.StaticCallOperationV2; +import org.hyperledger.besu.evm.v2.operation.StopOperationV2; +import org.hyperledger.besu.evm.v2.operation.SubOperationV2; +import org.hyperledger.besu.evm.v2.operation.SwapNOperationV2; +import org.hyperledger.besu.evm.v2.operation.SwapOperationV2; +import org.hyperledger.besu.evm.v2.operation.TLoadOperationV2; +import org.hyperledger.besu.evm.v2.operation.TStoreOperationV2; +import org.hyperledger.besu.evm.v2.operation.TimestampOperationV2; +import org.hyperledger.besu.evm.v2.operation.XorOperationV2; import java.util.Optional; import java.util.function.Function; @@ -115,11 +207,20 @@ public class EVM { private final EvmSpecVersion evmSpecVersion; // Optimized operation flags + private final boolean enableByzantium; private final boolean enableConstantinople; + private final boolean enableIstanbul; + private final boolean enableLondon; + private final boolean enableParis; private final boolean enableShanghai; + private final boolean enableCancun; private final boolean enableAmsterdam; private final boolean enableOsaka; + // V2 operation instances that require constructor arguments + private final ChainIdOperationV2 chainIdOperationV2; + private final GasOperationV2 gasOperationV2; + private final JumpDestOnlyCodeCache jumpDestOnlyCodeCache; /** @@ -142,10 +243,25 @@ public EVM( this.evmSpecVersion = evmSpecVersion; this.jumpDestOnlyCodeCache = new JumpDestOnlyCodeCache(evmConfiguration); + enableByzantium = EvmSpecVersion.BYZANTIUM.ordinal() <= evmSpecVersion.ordinal(); enableConstantinople = EvmSpecVersion.CONSTANTINOPLE.ordinal() <= evmSpecVersion.ordinal(); + enableIstanbul = EvmSpecVersion.ISTANBUL.ordinal() <= evmSpecVersion.ordinal(); + enableLondon = EvmSpecVersion.LONDON.ordinal() <= evmSpecVersion.ordinal(); + enableParis = EvmSpecVersion.PARIS.ordinal() <= evmSpecVersion.ordinal(); enableShanghai = EvmSpecVersion.SHANGHAI.ordinal() <= evmSpecVersion.ordinal(); + enableCancun = EvmSpecVersion.CANCUN.ordinal() <= evmSpecVersion.ordinal(); enableAmsterdam = EvmSpecVersion.AMSTERDAM.ordinal() <= evmSpecVersion.ordinal(); enableOsaka = EvmSpecVersion.OSAKA.ordinal() <= evmSpecVersion.ordinal(); + + // Pre-compute V2 operation instances that require constructor arguments. + // ChainIdOperation is only registered for Istanbul+, so the instanceof check is the gate. + Operation chainIdOp = operations.get(ChainIdOperation.OPCODE); + if (chainIdOp instanceof ChainIdOperation cid) { + chainIdOperationV2 = new ChainIdOperationV2(gasCalculator, cid.getChainId()); + } else { + chainIdOperationV2 = null; + } + gasOperationV2 = new GasOperationV2(gasCalculator); } /** @@ -220,6 +336,7 @@ public Optional getChainId() { // Please benchmark before refactoring. public void runToHalt(final MessageFrame frame, final OperationTracer tracing) { if (evmConfiguration.enableEvmV2()) { + frame.ensureV2Stack(); runToHaltV2(frame, tracing); return; } @@ -485,7 +602,277 @@ private void runToHaltV2(final MessageFrame frame, final OperationTracer tracing result = switch (opcode) { case 0x01 -> AddOperationV2.staticOperation(frame, frame.stackDataV2()); - // TODO: implement remaining opcodes in v2; until then fall through to v1 + case 0x02 -> MulOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x03 -> SubOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x04 -> DivOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x05 -> SDivOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x06 -> ModOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x07 -> SModOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x08 -> AddModOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x09 -> MulModOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x0a -> + ExpOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0x0b -> SignExtendOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x10 -> LtOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x11 -> GtOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x12 -> SltOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x13 -> SgtOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x14 -> EqOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x15 -> IsZeroOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x16 -> AndOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x17 -> OrOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x18 -> XorOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x19 -> NotOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x1a -> ByteOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x1b -> + enableConstantinople + ? ShlOperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); + case 0x1c -> + enableConstantinople + ? ShrOperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); + case 0x1d -> + enableConstantinople + ? SarOperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); + case 0x1e -> + enableOsaka + ? ClzOperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); + case 0x50 -> PopOperationV2.staticOperation(frame); + case 0x5f -> + enableShanghai + ? Push0OperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); + case 0x60, // PUSH1-32 + 0x61, + 0x62, + 0x63, + 0x64, + 0x65, + 0x66, + 0x67, + 0x68, + 0x69, + 0x6a, + 0x6b, + 0x6c, + 0x6d, + 0x6e, + 0x6f, + 0x70, + 0x71, + 0x72, + 0x73, + 0x74, + 0x75, + 0x76, + 0x77, + 0x78, + 0x79, + 0x7a, + 0x7b, + 0x7c, + 0x7d, + 0x7e, + 0x7f -> + PushOperationV2.staticOperation( + frame, frame.stackDataV2(), code, pc, opcode - PushOperationV2.PUSH_BASE); + case 0x80, // DUP1-16 + 0x81, + 0x82, + 0x83, + 0x84, + 0x85, + 0x86, + 0x87, + 0x88, + 0x89, + 0x8a, + 0x8b, + 0x8c, + 0x8d, + 0x8e, + 0x8f -> + DupOperationV2.staticOperation( + frame, frame.stackDataV2(), opcode - DupOperationV2.DUP_BASE); + case 0x90, // SWAP1-16 + 0x91, + 0x92, + 0x93, + 0x94, + 0x95, + 0x96, + 0x97, + 0x98, + 0x99, + 0x9a, + 0x9b, + 0x9c, + 0x9d, + 0x9e, + 0x9f -> + SwapOperationV2.staticOperation( + frame, frame.stackDataV2(), opcode - SwapOperationV2.SWAP_BASE); + case 0xe6 -> // DUPN (EIP-8024) + enableAmsterdam + ? DupNOperationV2.staticOperation(frame, frame.stackDataV2(), code, pc) + : InvalidOperation.invalidOperationResult(opcode); + case 0xe7 -> // SWAPN (EIP-8024) + enableAmsterdam + ? SwapNOperationV2.staticOperation(frame, frame.stackDataV2(), code, pc) + : InvalidOperation.invalidOperationResult(opcode); + case 0xe8 -> // EXCHANGE (EIP-8024) + enableAmsterdam + ? ExchangeOperationV2.staticOperation(frame, frame.stackDataV2(), code, pc) + : InvalidOperation.invalidOperationResult(opcode); + case 0x51 -> + MloadOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0x52 -> + MstoreOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0x53 -> + Mstore8OperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0x5e -> + enableCancun + ? MCopyOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator) + : InvalidOperation.invalidOperationResult(opcode); + case 0xf0 -> + CreateOperationV2.staticOperation( + frame, frame.stackDataV2(), gasCalculator, this); + case 0xf1 -> + CallOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator, this); + case 0xf2 -> + CallCodeOperationV2.staticOperation( + frame, frame.stackDataV2(), gasCalculator, this); + case 0xf4 -> + DelegateCallOperationV2.staticOperation( + frame, frame.stackDataV2(), gasCalculator, this); + case 0xf5 -> + Create2OperationV2.staticOperation( + frame, frame.stackDataV2(), gasCalculator, this); + case 0xfa -> + StaticCallOperationV2.staticOperation( + frame, frame.stackDataV2(), gasCalculator, this); + case 0x54 -> + SLoadOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0x55 -> + SStoreOperationV2.staticOperation( + frame, + frame.stackDataV2(), + gasCalculator, + SStoreOperationV2.EIP_1706_MINIMUM); + case 0x5c -> + enableCancun + ? TLoadOperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); + case 0x5d -> + enableCancun + ? TStoreOperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); + // Data copy / hash / account operations + case 0x20 -> + Keccak256OperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0x31 -> + BalanceOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0x35 -> CallDataLoadOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x37 -> + CallDataCopyOperationV2.staticOperation( + frame, frame.stackDataV2(), gasCalculator); + case 0x39 -> + CodeCopyOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0x3b -> + ExtCodeSizeOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0x3c -> + ExtCodeCopyOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0x3e -> + enableByzantium + ? ReturnDataCopyOperationV2.staticOperation( + frame, frame.stackDataV2(), gasCalculator) + : InvalidOperation.invalidOperationResult(opcode); + case 0x3f -> + enableConstantinople + ? ExtCodeHashOperationV2.staticOperation( + frame, frame.stackDataV2(), gasCalculator) + : InvalidOperation.invalidOperationResult(opcode); + case 0x40 -> BlockHashOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x47 -> + enableIstanbul + ? SelfBalanceOperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); + case 0x49 -> + enableCancun + ? BlobHashOperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); + // Environment push operations (0 → 1) + case 0x30 -> AddressOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x32 -> OriginOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x33 -> CallerOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x34 -> CallValueOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x36 -> CallDataSizeOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x38 -> CodeSizeOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x3a -> GasPriceOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x3d -> + enableByzantium + ? ReturnDataSizeOperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); + case 0x41 -> CoinbaseOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x42 -> TimestampOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x43 -> NumberOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x44 -> + enableParis + ? PrevRandaoOperationV2.staticOperation(frame, frame.stackDataV2()) + : DifficultyOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x45 -> GasLimitOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x46 -> // CHAINID (Istanbul+) + chainIdOperationV2 != null + ? chainIdOperationV2.executeFixedCostOperation(frame, this) + : InvalidOperation.invalidOperationResult(opcode); + case 0x48 -> // BASEFEE (London+) + enableLondon + ? BaseFeeOperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); + case 0x4a -> // BLOBBASEFEE (Cancun+) + enableCancun + ? BlobBaseFeeOperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); + case 0x4b -> // SLOTNUM (Amsterdam+) + enableAmsterdam + ? SlotNumOperationV2.staticOperation(frame, frame.stackDataV2()) + : InvalidOperation.invalidOperationResult(opcode); + case 0x58 -> PcOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x59 -> MSizeOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x5a -> gasOperationV2.executeFixedCostOperation(frame, this); + // Control flow operations + case 0x00 -> StopOperationV2.staticOperation(frame); + case 0x56 -> JumpOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x57 -> JumpiOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x5b -> JumpDestOperationV2.staticOperation(frame); + case 0xf3 -> + ReturnOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator); + case 0xfd -> // REVERT (Byzantium+) + enableByzantium + ? RevertOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator) + : InvalidOperation.invalidOperationResult(opcode); + case 0xfe -> InvalidOperationV2.INVALID_RESULT; + case 0xa0, 0xa1, 0xa2, 0xa3, 0xa4 -> { + int topicCount = opcode - 0xa0; + yield LogOperationV2.staticOperation( + frame, frame.stackDataV2(), topicCount, gasCalculator); + } + case 0xff -> + SelfDestructOperationV2.staticOperation( + frame, + frame.stackDataV2(), + gasCalculator, + enableCancun, + enableAmsterdam + ? EIP7708TransferLogEmitter.INSTANCE + : TransferLogEmitter.NOOP); + case 0xfc -> // PAY (EIP-7708, Amsterdam+) + enableAmsterdam + ? PayOperationV2.staticOperation(frame, frame.stackDataV2(), gasCalculator) + : InvalidOperation.invalidOperationResult(opcode); default -> { frame.setCurrentOperation(currentOperation); yield currentOperation.execute(frame, this); 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..e17ae5eb451 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 @@ -29,9 +29,11 @@ import org.hyperledger.besu.evm.internal.StorageEntry; import org.hyperledger.besu.evm.internal.UnderflowException; import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackPool; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.ArrayList; +import java.util.Arrays; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; @@ -206,7 +208,7 @@ public enum Type { private final OperandStack stack; // EVM v2 stack: 4 longs per 256-bit word (index 0 = most significant, index 3 = least // significant) - private final long[] stackDataV2; + private long[] stackDataV2; private int stackTopV2; private Bytes output = Bytes.EMPTY; private Bytes returnData = Bytes.EMPTY; @@ -276,7 +278,7 @@ private MessageFrame( this.worldUpdater = worldUpdater; this.gasRemaining = initialGas; this.stack = new OperandStack(txValues.maxStackSize()); - this.stackDataV2 = enableEvmV2 ? new long[txValues.maxStackSize() * 4] : null; + this.stackDataV2 = enableEvmV2 ? StackPool.borrow(txValues.maxStackSize()) : null; this.stackTopV2 = 0; this.pc = 0; this.recipient = recipient; @@ -518,6 +520,36 @@ public boolean stackHasItems(final int n) { return stackTopV2 >= n; } + /** + * Returns true if the stack has space for at least {@code n} more items. + * + * @param n the number of items to push + * @return true if the stack has enough capacity + */ + public boolean stackHasSpace(final int n) { + return stackTopV2 + n <= txValues.maxStackSize(); + } + + /** + * Ensures the V2 stack array is allocated. Called by the EVM when entering the V2 dispatch path + * for frames that were not built with {@code enableEvmV2(true)}. + */ + public void ensureV2Stack() { + if (stackDataV2 == null) { + stackDataV2 = new long[txValues.maxStackSize() * 4]; + } + } + + /** Returns this frame's operand stack to the thread-local pool for reuse. */ + public void returnStackToPool() { + if (stackTopV2 > 0) { + Arrays.fill(stackDataV2, 0, stackTopV2 << 2, 0L); + } + stackTopV2 = 0; + StackPool.release(stackDataV2, txValues.maxStackSize()); + stackDataV2 = null; + } + // --------------------------------------------------------------------------- // endregion diff --git a/evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java b/evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java index b56dad19c1f..1d00b71b024 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java @@ -193,6 +193,7 @@ protected void revert(final MessageFrame frame) { private void completedSuccess(final MessageFrame frame) { frame.getWorldUpdater().commit(); frame.getMessageFrameStack().removeFirst(); + frame.returnStackToPool(); frame.notifyCompletion(); } @@ -203,6 +204,7 @@ private void completedSuccess(final MessageFrame frame) { */ private void completedFailed(final MessageFrame frame) { frame.getMessageFrameStack().removeFirst(); + frame.returnStackToPool(); frame.notifyCompletion(); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java new file mode 100644 index 00000000000..6843f622da6 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/StackArithmetic.java @@ -0,0 +1,1417 @@ +/* + * 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.UInt256; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.math.BigInteger; +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * Static utility operating directly on the flat {@code long[]} operand stack. Each slot occupies 4 + * consecutive longs in big-endian limb order: {@code [u3, u2, u1, u0]} where u3 is the most + * significant limb. + * + *

All methods take {@code (long[] s, int top)} and return the new {@code top}. The caller + * (operation) is responsible for underflow/overflow checks before calling. + */ +public class StackArithmetic { + + 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); + + /** Utility class — not instantiable. */ + private StackArithmetic() {} + + // region SHL (Shift Left) + // --------------------------------------------------------------------------- + + /** + * Performs EVM SHL (shift left) on the two top stack items. + * + *

Pops the shift amount (unsigned) and the value, pushes {@code value << shift}. Shifts >= 256 + * or a zero value produce 0. + * + * @param stack the flat limb array + * @param top current stack-top (item count) + * @return the new stack-top after consuming one item + */ + public static int shl(final long[] stack, final int top) { + final int shiftOffset = (top - 1) << 2; + final int valueOffset = (top - 2) << 2; + // If shift amount > 255 or value is zero, result is zero + if (stack[shiftOffset] != 0 + || stack[shiftOffset + 1] != 0 + || stack[shiftOffset + 2] != 0 + || Long.compareUnsigned(stack[shiftOffset + 3], 256) >= 0 + || (stack[valueOffset] == 0 + && stack[valueOffset + 1] == 0 + && stack[valueOffset + 2] == 0 + && stack[valueOffset + 3] == 0)) { + stack[valueOffset] = 0; + stack[valueOffset + 1] = 0; + stack[valueOffset + 2] = 0; + stack[valueOffset + 3] = 0; + return top - 1; + } + int shift = (int) stack[shiftOffset + 3]; + shiftLeftInPlace(stack, valueOffset, shift); + return top - 1; + } + + /** + * Left-shifts a 256-bit value in place by 1..255 bits, zero-filling from the right. + * + * @param stack the flat limb array + * @param valueOffset index of the value's most-significant limb + * @param shift number of bits to shift (must be in [1, 255]) + */ + private static void shiftLeftInPlace(final long[] stack, final int valueOffset, final int shift) { + if (shift == 0) return; + long w0 = stack[valueOffset], + w1 = stack[valueOffset + 1], + w2 = stack[valueOffset + 2], + w3 = stack[valueOffset + 3]; + final int wordShift = shift >>> 6; + final int bitShift = shift & 63; + switch (wordShift) { + case 0: + w0 = shiftLeftWord(w0, w1, bitShift); + w1 = shiftLeftWord(w1, w2, bitShift); + w2 = shiftLeftWord(w2, w3, bitShift); + w3 = shiftLeftWord(w3, 0, bitShift); + break; + case 1: + w0 = shiftLeftWord(w1, w2, bitShift); + w1 = shiftLeftWord(w2, w3, bitShift); + w2 = shiftLeftWord(w3, 0, bitShift); + w3 = 0; + break; + case 2: + w0 = shiftLeftWord(w2, w3, bitShift); + w1 = shiftLeftWord(w3, 0, bitShift); + w2 = 0; + w3 = 0; + break; + case 3: + w0 = shiftLeftWord(w3, 0, bitShift); + w1 = 0; + w2 = 0; + w3 = 0; + break; + } + stack[valueOffset] = w0; + stack[valueOffset + 1] = w1; + stack[valueOffset + 2] = w2; + stack[valueOffset + 3] = w3; + } + + /** + * Shifts a 64-bit word left and carries in bits from the next less-significant word. + * + * @param value the current word + * @param nextValue the next less-significant word (bits carry in from its top) + * @param bitShift the intra-word shift amount in [0, 63]; 0 returns {@code value} unchanged to + * avoid Java's mod-64 shift semantics on {@code nextValue >>> 64} + * @return the shifted word + */ + private static long shiftLeftWord(final long value, final long nextValue, final int bitShift) { + if (bitShift == 0) return value; + return (value << bitShift) | (nextValue >>> (64 - bitShift)); + } + + // endregion + + // region SHR (Shift Right) + // --------------------------------------------------------------------------- + + /** + * Performs EVM SHR (logical shift right) on the two top stack items. + * + *

Pops the shift amount (unsigned) and the value, pushes {@code value >>> shift}. Shifts >= + * 256 or a zero value produce 0. + * + * @param stack the flat limb array + * @param top current stack-top (item count) + * @return the new stack-top after consuming one item + */ + public static int shr(final long[] stack, final int top) { + final int shiftOffset = (top - 1) << 2; + final int valueOffset = (top - 2) << 2; + if (stack[shiftOffset] != 0 + || stack[shiftOffset + 1] != 0 + || stack[shiftOffset + 2] != 0 + || Long.compareUnsigned(stack[shiftOffset + 3], 256) >= 0 + || (stack[valueOffset] == 0 + && stack[valueOffset + 1] == 0 + && stack[valueOffset + 2] == 0 + && stack[valueOffset + 3] == 0)) { + stack[valueOffset] = 0; + stack[valueOffset + 1] = 0; + stack[valueOffset + 2] = 0; + stack[valueOffset + 3] = 0; + return top - 1; + } + int shift = (int) stack[shiftOffset + 3]; + shiftRightInPlace(stack, valueOffset, shift); + return top - 1; + } + + /** + * Logically right-shifts a 256-bit value in place by 1..255 bits, zero-filling from the left. + * + * @param s the flat limb array + * @param valueOffset index of the value's most-significant limb + * @param shift number of bits to shift (must be in [1, 255]) + */ + private static void shiftRightInPlace(final long[] s, final int valueOffset, final int shift) { + if (shift == 0) return; + long w0 = s[valueOffset], + w1 = s[valueOffset + 1], + w2 = s[valueOffset + 2], + w3 = s[valueOffset + 3]; + final int wordShift = shift >>> 6; + final int bitShift = shift & 63; + switch (wordShift) { + case 0: + w3 = shiftRightWord(w3, w2, bitShift); + w2 = shiftRightWord(w2, w1, bitShift); + w1 = shiftRightWord(w1, w0, bitShift); + w0 = shiftRightWord(w0, 0, bitShift); + break; + case 1: + w3 = shiftRightWord(w2, w1, bitShift); + w2 = shiftRightWord(w1, w0, bitShift); + w1 = shiftRightWord(w0, 0, bitShift); + w0 = 0; + break; + case 2: + w3 = shiftRightWord(w1, w0, bitShift); + w2 = shiftRightWord(w0, 0, bitShift); + w1 = 0; + w0 = 0; + break; + case 3: + w3 = shiftRightWord(w0, 0, bitShift); + w2 = 0; + w1 = 0; + w0 = 0; + break; + } + s[valueOffset] = w0; + s[valueOffset + 1] = w1; + s[valueOffset + 2] = w2; + s[valueOffset + 3] = w3; + } + + // endregion + + // region SAR (Shift Arithmetic Right) + // --------------------------------------------------------------------------- + + /** + * Performs EVM SAR (arithmetic shift right) on the two top stack items. + * + *

Pops the shift amount (unsigned) and the value (signed), pushes {@code value >> shift}. + * Shifts >= 256 produce 0 for positive values and -1 for negative values. + * + * @param stack the flat limb array + * @param top current stack-top (item count) + * @return the new stack-top after consuming one item + */ + public static int sar(final long[] stack, final int top) { + final int shiftOffset = (top - 1) << 2; + final int valueOffset = (top - 2) << 2; + boolean negative = stack[valueOffset] < 0; + if (stack[shiftOffset] != 0 + || stack[shiftOffset + 1] != 0 + || stack[shiftOffset + 2] != 0 + || Long.compareUnsigned(stack[shiftOffset + 3], 256) >= 0) { + long fill = negative ? -1L : 0L; + stack[valueOffset] = fill; + stack[valueOffset + 1] = fill; + stack[valueOffset + 2] = fill; + stack[valueOffset + 3] = fill; + return top - 1; + } + int shift = (int) stack[shiftOffset + 3]; + sarInPlace(stack, valueOffset, shift, negative); + return top - 1; + } + + /** + * Arithmetic right-shifts a 256-bit value in place by 0..255 bits, sign-extending with {@code + * fill}. + * + * @param stack the flat limb array + * @param valueOffset index of the value's most-significant limb + * @param shift number of bits to shift (must be in [0, 255]) + * @param negative true if the original value is negative (fill = -1) + */ + private static void sarInPlace( + final long[] stack, final int valueOffset, final int shift, final boolean negative) { + if (shift == 0) return; + long w0 = stack[valueOffset], + w1 = stack[valueOffset + 1], + w2 = stack[valueOffset + 2], + w3 = stack[valueOffset + 3]; + final long fill = negative ? -1L : 0L; + final int wordShift = shift >>> 6; + final int bitShift = shift & 63; + switch (wordShift) { + case 0: + w3 = shiftRightWord(w3, w2, bitShift); + w2 = shiftRightWord(w2, w1, bitShift); + w1 = shiftRightWord(w1, w0, bitShift); + w0 = shiftRightWord(w0, fill, bitShift); + break; + case 1: + w3 = shiftRightWord(w2, w1, bitShift); + w2 = shiftRightWord(w1, w0, bitShift); + w1 = shiftRightWord(w0, fill, bitShift); + w0 = fill; + break; + case 2: + w3 = shiftRightWord(w1, w0, bitShift); + w2 = shiftRightWord(w0, fill, bitShift); + w1 = fill; + w0 = fill; + break; + case 3: + w3 = shiftRightWord(w0, fill, bitShift); + w2 = fill; + w1 = fill; + w0 = fill; + break; + } + stack[valueOffset] = w0; + stack[valueOffset + 1] = w1; + stack[valueOffset + 2] = w2; + stack[valueOffset + 3] = w3; + } + + // endregion + + // region Binary Arithmetic (pop 2, push 1, return top-1) + // --------------------------------------------------------------------------- + + /** + * ADD: s[top-2] = s[top-1] + s[top-2], return top-1. + * + * @param s the stack array + * @param top the current top index + * @return the new top index + */ + public static int add(final long[] s, final int top) { + final int a = (top - 1) << 2; + final int b = (top - 2) << 2; + // Fast path: both values fit in a single limb (common case) + if ((s[a] | s[a + 1] | s[a + 2] | s[b] | s[b + 1] | s[b + 2]) == 0) { + long z = s[a + 3] + s[b + 3]; + s[b + 2] = ((s[a + 3] & s[b + 3]) | ((s[a + 3] | s[b + 3]) & ~z)) >>> 63; + s[b + 3] = z; + return top - 1; + } + long a0 = s[a + 3], a1 = s[a + 2], a2 = s[a + 1], a3 = s[a]; + long b0 = s[b + 3], b1 = s[b + 2], b2 = s[b + 1], b3 = s[b]; + long z0 = a0 + b0; + long c = ((a0 & b0) | ((a0 | b0) & ~z0)) >>> 63; + long t1 = a1 + b1; + long c1 = ((a1 & b1) | ((a1 | b1) & ~t1)) >>> 63; + long z1 = t1 + c; + c = c1 | (((t1 & c) | ((t1 | c) & ~z1)) >>> 63); + long t2 = a2 + b2; + long c2 = ((a2 & b2) | ((a2 | b2) & ~t2)) >>> 63; + long z2 = t2 + c; + c = c2 | (((t2 & c) | ((t2 | c) & ~z2)) >>> 63); + long z3 = a3 + b3 + c; + s[b] = z3; + s[b + 1] = z2; + s[b + 2] = z1; + s[b + 3] = z0; + return top - 1; + } + + /** + * SUB: s[top-2] = s[top-1] - s[top-2], return top-1. + * + * @param s the stack array + * @param top the current top index + * @return the new top index + */ + public static int sub(final long[] s, final int top) { + final int a = (top - 1) << 2; // first operand (top) + final int b = (top - 2) << 2; // second operand + // Fast path: both values fit in a single limb (common case) + if ((s[a] | s[a + 1] | s[a + 2] | s[b] | s[b + 1] | s[b + 2]) == 0) { + long z = s[a + 3] - s[b + 3]; + // borrow is 0 or 1; negate to get 0 or -1L (0xFFFFFFFFFFFFFFFF) for upper limbs + long fill = -(((~s[a + 3] & s[b + 3]) | (~(s[a + 3] ^ s[b + 3]) & z)) >>> 63); + s[b] = fill; + s[b + 1] = fill; + s[b + 2] = fill; + s[b + 3] = z; + return top - 1; + } + long a0 = s[a + 3], a1 = s[a + 2], a2 = s[a + 1], a3 = s[a]; + long b0 = s[b + 3], b1 = s[b + 2], b2 = s[b + 1], b3 = s[b]; + // a - b with branchless borrow chain + long z0 = a0 - b0; + long w = ((~a0 & b0) | (~(a0 ^ b0) & z0)) >>> 63; + long t1 = a1 - b1; + long w1 = ((~a1 & b1) | (~(a1 ^ b1) & t1)) >>> 63; + long z1 = t1 - w; + w = w1 | (((~t1 & w) | (~(t1 ^ w) & z1)) >>> 63); + long t2 = a2 - b2; + long w2 = ((~a2 & b2) | (~(a2 ^ b2) & t2)) >>> 63; + long z2 = t2 - w; + w = w2 | (((~t2 & w) | (~(t2 ^ w) & z2)) >>> 63); + long z3 = a3 - b3 - w; + s[b] = z3; + s[b + 1] = z2; + s[b + 2] = z1; + s[b + 3] = z0; + return top - 1; + } + + /** + * MUL: s[top-2] = s[top-1] * s[top-2], return top-1. Delegates to UInt256 for now. + * + * @param s the stack array + * @param top the current top index + * @return the new top index + */ + public static int mul(final long[] s, final int top) { + final int a = (top - 1) << 2; + final int b = (top - 2) << 2; + UInt256 va = new UInt256(s[a], s[a + 1], s[a + 2], s[a + 3]); + UInt256 vb = new UInt256(s[b], s[b + 1], s[b + 2], s[b + 3]); + UInt256 r = va.mul(vb); + s[b] = r.u3(); + s[b + 1] = r.u2(); + s[b + 2] = r.u1(); + s[b + 3] = r.u0(); + return top - 1; + } + + /** + * DIV: s[top-2] = s[top-1] / s[top-2], return top-1. Delegates to UInt256. + * + * @param s the stack array + * @param top the current top index + * @return the new top index + */ + public static int div(final long[] s, final int top) { + final int a = (top - 1) << 2; + final int b = (top - 2) << 2; + UInt256 va = new UInt256(s[a], s[a + 1], s[a + 2], s[a + 3]); + UInt256 vb = new UInt256(s[b], s[b + 1], s[b + 2], s[b + 3]); + UInt256 r = va.div(vb); + s[b] = r.u3(); + s[b + 1] = r.u2(); + s[b + 2] = r.u1(); + s[b + 3] = r.u0(); + return top - 1; + } + + /** + * SDIV: s[top-2] = s[top-1] sdiv s[top-2], return top-1. Delegates to UInt256. + * + * @param s the stack array + * @param top the current top index + * @return the new top index + */ + public static int signedDiv(final long[] s, final int top) { + final int a = (top - 1) << 2; + final int b = (top - 2) << 2; + UInt256 va = new UInt256(s[a], s[a + 1], s[a + 2], s[a + 3]); + UInt256 vb = new UInt256(s[b], s[b + 1], s[b + 2], s[b + 3]); + UInt256 r = va.signedDiv(vb); + s[b] = r.u3(); + s[b + 1] = r.u2(); + s[b + 2] = r.u1(); + s[b + 3] = r.u0(); + return top - 1; + } + + /** + * MOD: s[top-2] = s[top-1] mod s[top-2], return top-1. Delegates to UInt256. + * + * @param s the stack array + * @param top the current top index + * @return the new top index + */ + public static int mod(final long[] s, final int top) { + final int a = (top - 1) << 2; + final int b = (top - 2) << 2; + UInt256 va = new UInt256(s[a], s[a + 1], s[a + 2], s[a + 3]); + UInt256 vb = new UInt256(s[b], s[b + 1], s[b + 2], s[b + 3]); + UInt256 r = va.mod(vb); + s[b] = r.u3(); + s[b + 1] = r.u2(); + s[b + 2] = r.u1(); + s[b + 3] = r.u0(); + return top - 1; + } + + /** + * SMOD: s[top-2] = s[top-1] smod s[top-2], return top-1. Delegates to UInt256. + * + * @param s the stack array + * @param top the current top index + * @return the new top index + */ + public static int signedMod(final long[] s, final int top) { + final int a = (top - 1) << 2; + final int b = (top - 2) << 2; + UInt256 va = new UInt256(s[a], s[a + 1], s[a + 2], s[a + 3]); + UInt256 vb = new UInt256(s[b], s[b + 1], s[b + 2], s[b + 3]); + UInt256 r = va.signedMod(vb); + s[b] = r.u3(); + s[b + 1] = r.u2(); + s[b + 2] = r.u1(); + s[b + 3] = r.u0(); + return top - 1; + } + + /** + * EXP: s[top-2] = s[top-1] ** s[top-2] mod 2^256, return top-1. + * + * @param s the stack array + * @param top the current top index + * @return the new top index + */ + public static int exp(final long[] s, final int top) { + final int a = (top - 1) << 2; // base + final int b = (top - 2) << 2; // exponent + UInt256 base = new UInt256(s[a], s[a + 1], s[a + 2], s[a + 3]); + UInt256 power = new UInt256(s[b], s[b + 1], s[b + 2], s[b + 3]); + BigInteger result = base.toBigInteger().modPow(power.toBigInteger(), BigInteger.TWO.pow(256)); + byte[] rBytes = result.toByteArray(); + if (rBytes.length > 32) { + rBytes = Arrays.copyOfRange(rBytes, rBytes.length - 32, rBytes.length); + } else if (rBytes.length < 32) { + byte[] padded = new byte[32]; + System.arraycopy(rBytes, 0, padded, 32 - rBytes.length, rBytes.length); + rBytes = padded; + } + UInt256 r = UInt256.fromBytesBE(rBytes); + s[b] = r.u3(); + s[b + 1] = r.u2(); + s[b + 2] = r.u1(); + s[b + 3] = r.u0(); + return top - 1; + } + + // endregion + + // region Bitwise Binary (pop 2, push 1, return top-1) + // --------------------------------------------------------------------------- + + /** + * AND: s[top-2] = s[top-1] & s[top-2], return top-1. + * + * @param s the stack array + * @param top the current top index + * @return the new top index + */ + public static int and(final long[] s, final int top) { + final int a = (top - 1) << 2; + final int b = (top - 2) << 2; + s[b] = s[a] & s[b]; + s[b + 1] = s[a + 1] & s[b + 1]; + s[b + 2] = s[a + 2] & s[b + 2]; + s[b + 3] = s[a + 3] & s[b + 3]; + return top - 1; + } + + /** + * OR: s[top-2] = s[top-1] | s[top-2], return top-1. + * + * @param s the stack array + * @param top the current top index + * @return the new top index + */ + public static int or(final long[] s, final int top) { + final int a = (top - 1) << 2; + final int b = (top - 2) << 2; + s[b] = s[a] | s[b]; + s[b + 1] = s[a + 1] | s[b + 1]; + s[b + 2] = s[a + 2] | s[b + 2]; + s[b + 3] = s[a + 3] | s[b + 3]; + return top - 1; + } + + /** + * XOR: s[top-2] = s[top-1] ^ s[top-2], return top-1. + * + * @param s the stack array + * @param top the current top index + * @return the new top index + */ + public static int xor(final long[] s, final int top) { + final int a = (top - 1) << 2; + final int b = (top - 2) << 2; + s[b] = s[a] ^ s[b]; + s[b + 1] = s[a + 1] ^ s[b + 1]; + s[b + 2] = s[a + 2] ^ s[b + 2]; + s[b + 3] = s[a + 3] ^ s[b + 3]; + return top - 1; + } + + /** + * BYTE: s[top-2] = byte at offset s[top-1] of s[top-2], return top-1. + * + * @param s the stack array + * @param top the current top index + * @return the new top index + */ + public static int byte_(final long[] s, final int top) { + final int a = (top - 1) << 2; // offset + final int b = (top - 2) << 2; // value + // offset must be 0..31 + if (s[a] != 0 || s[a + 1] != 0 || s[a + 2] != 0 || s[a + 3] < 0 || s[a + 3] >= 32) { + s[b] = 0; + s[b + 1] = 0; + s[b + 2] = 0; + s[b + 3] = 0; + return top - 1; + } + int idx = (int) s[a + 3]; // 0..31, big-endian byte index + // Determine which limb and bit position + // byte 0 is the MSB of u3, byte 31 is the LSB of u0 + int limbIdx = idx >> 3; // which limb offset from base (0=u3, 3=u0) + int byteInLimb = 7 - (idx & 7); // byte position within limb (7=MSB, 0=LSB) + long limb = s[b + limbIdx]; + long result = (limb >>> (byteInLimb << 3)) & 0xFFL; + s[b] = 0; + s[b + 1] = 0; + s[b + 2] = 0; + s[b + 3] = result; + return top - 1; + } + + /** + * SIGNEXTEND: sign-extend s[top-2] from byte s[top-1], return top-1. + * + * @param s the stack array + * @param top the current top index + * @return the new top index + */ + public static int signExtend(final long[] s, final int top) { + final int a = (top - 1) << 2; // byte index b + final int b = (top - 2) << 2; // value + // If b >= 31, no extension needed + if (s[a] != 0 || s[a + 1] != 0 || s[a + 2] != 0 || s[a + 3] >= 31) { + // result is just the value unchanged + return top - 1; + } + int byteIdx = (int) s[a + 3]; // 0..30 + // The sign bit is at bit (byteIdx * 8 + 7) from LSB + int signBit = byteIdx * 8 + 7; + int limbIdx = signBit >> 6; // which limb (0=u0/LSB) + int bitInLimb = signBit & 63; + // Read from the value slot - limbs are stored [u3, u2, u1, u0] at [b, b+1, b+2, b+3] + long limb = s[b + 3 - limbIdx]; + boolean isNeg = ((limb >>> bitInLimb) & 1L) != 0; + if (isNeg) { + // Set all bits above signBit to 1 + s[b + 3 - limbIdx] = limb | (-1L << bitInLimb); + for (int i = limbIdx + 1; i < 4; i++) { + s[b + 3 - i] = -1L; + } + } else { + // Clear all bits above signBit to 0 + if (bitInLimb < 63) { + s[b + 3 - limbIdx] = limb & ((1L << (bitInLimb + 1)) - 1); + } + for (int i = limbIdx + 1; i < 4; i++) { + s[b + 3 - i] = 0; + } + } + return top - 1; + } + + // endregion + + // region Unary Operations (pop 1, push 1, return top) + // --------------------------------------------------------------------------- + + /** + * NOT: s[top-1] = ~s[top-1], return top. + * + * @param s the stack array + * @param top the current top index + * @return the new top index + */ + public static int not(final long[] s, final int top) { + final int a = (top - 1) << 2; + s[a] = ~s[a]; + s[a + 1] = ~s[a + 1]; + s[a + 2] = ~s[a + 2]; + s[a + 3] = ~s[a + 3]; + return top; + } + + /** + * ISZERO: s[top-1] = (s[top-1] == 0) ? 1 : 0, return top. + * + * @param s the stack array + * @param top the current top index + * @return the new top index + */ + public static int isZero(final long[] s, final int top) { + final int a = (top - 1) << 2; + boolean zero = (s[a] | s[a + 1] | s[a + 2] | s[a + 3]) == 0; + s[a] = 0; + s[a + 1] = 0; + s[a + 2] = 0; + s[a + 3] = zero ? 1L : 0L; + return top; + } + + /** + * CLZ: s[top-1] = count leading zeros of s[top-1], return top. + * + * @param s the stack array + * @param top the current top index + * @return the new top index + */ + public static int clz(final long[] s, final int top) { + final int a = (top - 1) << 2; + int result; + if (s[a] != 0) { + result = Long.numberOfLeadingZeros(s[a]); + } else if (s[a + 1] != 0) { + result = 64 + Long.numberOfLeadingZeros(s[a + 1]); + } else if (s[a + 2] != 0) { + result = 128 + Long.numberOfLeadingZeros(s[a + 2]); + } else { + result = 192 + Long.numberOfLeadingZeros(s[a + 3]); + } + s[a] = 0; + s[a + 1] = 0; + s[a + 2] = 0; + s[a + 3] = result; + return top; + } + + // endregion + + // region Comparison Operations (pop 2, push 1, return top-1) + // --------------------------------------------------------------------------- + + /** + * LT: s[top-2] = (s[top-1] < s[top-2]) ? 1 : 0, return top-1. Unsigned. + * + * @param s the stack array + * @param top the current top index + * @return the new top index + */ + public static int lt(final long[] s, final int top) { + final int a = (top - 1) << 2; + final int b = (top - 2) << 2; + boolean less = unsignedLt(s, a, s, b); + s[b] = 0; + s[b + 1] = 0; + s[b + 2] = 0; + s[b + 3] = less ? 1L : 0L; + return top - 1; + } + + /** + * GT: s[top-2] = (s[top-1] > s[top-2]) ? 1 : 0, return top-1. Unsigned. + * + * @param s the stack array + * @param top the current top index + * @return the new top index + */ + public static int gt(final long[] s, final int top) { + final int a = (top - 1) << 2; + final int b = (top - 2) << 2; + boolean greater = unsignedLt(s, b, s, a); + s[b] = 0; + s[b + 1] = 0; + s[b + 2] = 0; + s[b + 3] = greater ? 1L : 0L; + return top - 1; + } + + /** + * SLT: s[top-2] = (s[top-1] <s s[top-2]) ? 1 : 0, return top-1. Signed. + * + * @param s the stack array + * @param top the current top index + * @return the new top index + */ + public static int slt(final long[] s, final int top) { + final int a = (top - 1) << 2; + final int b = (top - 2) << 2; + int cmp = signedCompare(s, a, s, b); + s[b] = 0; + s[b + 1] = 0; + s[b + 2] = 0; + s[b + 3] = cmp < 0 ? 1L : 0L; + return top - 1; + } + + /** + * SGT: s[top-2] = (s[top-1] >s s[top-2]) ? 1 : 0, return top-1. Signed. + * + * @param s the stack array + * @param top the current top index + * @return the new top index + */ + public static int sgt(final long[] s, final int top) { + final int a = (top - 1) << 2; + final int b = (top - 2) << 2; + int cmp = signedCompare(s, a, s, b); + s[b] = 0; + s[b + 1] = 0; + s[b + 2] = 0; + s[b + 3] = cmp > 0 ? 1L : 0L; + return top - 1; + } + + /** + * EQ: s[top-2] = (s[top-1] == s[top-2]) ? 1 : 0, return top-1. + * + * @param s the stack array + * @param top the current top index + * @return the new top index + */ + public static int eq(final long[] s, final int top) { + final int a = (top - 1) << 2; + final int b = (top - 2) << 2; + boolean equal = + s[a] == s[b] && s[a + 1] == s[b + 1] && s[a + 2] == s[b + 2] && s[a + 3] == s[b + 3]; + s[b] = 0; + s[b + 1] = 0; + s[b + 2] = 0; + s[b + 3] = equal ? 1L : 0L; + return top - 1; + } + + // endregion + + // region Ternary Operations (pop 3, push 1, return top-2) + // --------------------------------------------------------------------------- + + /** + * ADDMOD: s[top-3] = (s[top-1] + s[top-2]) mod s[top-3], return top-2. + * + * @param s the stack array + * @param top the current top index + * @return the new top index + */ + public static int addMod(final long[] s, final int top) { + final int a = (top - 1) << 2; + final int b = (top - 2) << 2; + final int c = (top - 3) << 2; + UInt256 va = new UInt256(s[a], s[a + 1], s[a + 2], s[a + 3]); + UInt256 vb = new UInt256(s[b], s[b + 1], s[b + 2], s[b + 3]); + UInt256 vc = new UInt256(s[c], s[c + 1], s[c + 2], s[c + 3]); + UInt256 r = vc.isZero() ? UInt256.ZERO : va.addMod(vb, vc); + s[c] = r.u3(); + s[c + 1] = r.u2(); + s[c + 2] = r.u1(); + s[c + 3] = r.u0(); + return top - 2; + } + + /** + * MULMOD: s[top-3] = (s[top-1] * s[top-2]) mod s[top-3], return top-2. + * + * @param s the stack array + * @param top the current top index + * @return the new top index + */ + public static int mulMod(final long[] s, final int top) { + final int a = (top - 1) << 2; + final int b = (top - 2) << 2; + final int c = (top - 3) << 2; + UInt256 va = new UInt256(s[a], s[a + 1], s[a + 2], s[a + 3]); + UInt256 vb = new UInt256(s[b], s[b + 1], s[b + 2], s[b + 3]); + UInt256 vc = new UInt256(s[c], s[c + 1], s[c + 2], s[c + 3]); + UInt256 r = vc.isZero() ? UInt256.ZERO : va.mulMod(vb, vc); + s[c] = r.u3(); + s[c + 1] = r.u2(); + s[c + 2] = r.u1(); + s[c + 3] = r.u0(); + return top - 2; + } + + // endregion + + // region Stack Manipulation + // --------------------------------------------------------------------------- + + /** + * DUP: copy slot at depth to new top, return top+1. depth is 1-based (DUP1 = depth 1). + * + * @param s the stack array + * @param top the current top index + * @param depth the 1-based depth of the slot to duplicate + * @return the new top index + */ + public static int dup(final long[] s, final int top, final int depth) { + final int src = (top - depth) << 2; + final int dst = top << 2; + s[dst] = s[src]; + s[dst + 1] = s[src + 1]; + s[dst + 2] = s[src + 2]; + s[dst + 3] = s[src + 3]; + return top + 1; + } + + /** + * SWAP: swap top with slot at depth, return top. depth is 1-based (SWAP1 = depth 1). + * + * @param s the stack array + * @param top the current top index + * @param depth the 1-based depth of the slot to swap with the top + * @return the new top index + */ + public static int swap(final long[] s, final int top, final int depth) { + final int a = (top - 1) << 2; + final int b = (top - 1 - depth) << 2; + long t; + t = s[a]; + s[a] = s[b]; + s[b] = t; + t = s[a + 1]; + s[a + 1] = s[b + 1]; + s[b + 1] = t; + t = s[a + 2]; + s[a + 2] = s[b + 2]; + s[b + 2] = t; + t = s[a + 3]; + s[a + 3] = s[b + 3]; + s[b + 3] = t; + return top; + } + + /** + * EXCHANGE: swap slot at n with slot at m (both 0-indexed from top), return top. + * + * @param s the stack array + * @param top the current top index + * @param n the 0-based index of the first slot (from top) + * @param m the 0-based index of the second slot (from top) + * @return the new top index + */ + public static int exchange(final long[] s, final int top, final int n, final int m) { + final int a = (top - 1 - n) << 2; + final int b = (top - 1 - m) << 2; + long t; + t = s[a]; + s[a] = s[b]; + s[b] = t; + t = s[a + 1]; + s[a + 1] = s[b + 1]; + s[b + 1] = t; + t = s[a + 2]; + s[a + 2] = s[b + 2]; + s[b + 2] = t; + t = s[a + 3]; + s[a + 3] = s[b + 3]; + s[b + 3] = t; + return top; + } + + /** + * PUSH0: push zero, return top+1. + * + * @param s the stack array + * @param top the current top index + * @return the new top index + */ + public static int pushZero(final long[] s, final int top) { + final int dst = top << 2; + s[dst] = 0; + s[dst + 1] = 0; + s[dst + 2] = 0; + s[dst + 3] = 0; + return top + 1; + } + + /** + * PUSH1..PUSH32: decode bytes from code into a new top slot, return top+1. + * + * @param s the stack array + * @param top the current top index + * @param code the bytecode array + * @param start the start offset within the code array + * @param len the number of bytes to push (1-32) + * @return the new top index + */ + public static int pushFromBytes( + final long[] s, final int top, final byte[] code, final int start, final int len) { + final int dst = top << 2; + + if (start >= code.length) { + s[dst] = 0; + s[dst + 1] = 0; + s[dst + 2] = 0; + s[dst + 3] = 0; + return top + 1; + } + final int copyLen = Math.min(len, code.length - start); + + s[dst] = 0; + s[dst + 1] = 0; + s[dst + 2] = 0; + s[dst + 3] = 0; + + if (copyLen == len) { + // Fast path: all bytes available (common case — not near end of code) + if (len <= 8) { + s[dst + 3] = buildLong(code, start, len); + } else if (len <= 16) { + final int hiLen = len - 8; + s[dst + 2] = buildLong(code, start, hiLen); + s[dst + 3] = bytesToLong(code, start + hiLen); + } else if (len <= 24) { + final int hiLen = len - 16; + s[dst + 1] = buildLong(code, start, hiLen); + s[dst + 2] = bytesToLong(code, start + hiLen); + s[dst + 3] = bytesToLong(code, start + hiLen + 8); + } else { + final int hiLen = len - 24; + s[dst] = buildLong(code, start, hiLen); + s[dst + 1] = bytesToLong(code, start + hiLen); + s[dst + 2] = bytesToLong(code, start + hiLen + 8); + s[dst + 3] = bytesToLong(code, start + hiLen + 16); + } + } else { + // Truncated push (rare: near end of code). Right-pad with zeros. + int bytePos = len - 1; + for (int i = 0; i < copyLen; i++) { + int limbOffset = 3 - (bytePos >> 3); + int shift = (bytePos & 7) << 3; + s[dst + limbOffset] |= (code[start + i] & 0xFFL) << shift; + bytePos--; + } + } + return top + 1; + } + + /** + * Push a long value (GAS, NUMBER, etc.), return top+1. + * + * @param s the stack array + * @param top the current top index + * @param value the long value to push + * @return the new top index + */ + public static int pushLong(final long[] s, final int top, final long value) { + final int dst = top << 2; + s[dst] = 0; + s[dst + 1] = 0; + s[dst + 2] = 0; + s[dst + 3] = value; + return top + 1; + } + + /** + * Push a Wei value onto the stack, return top+1. + * + * @param s the stack array + * @param top the current top index + * @param value the Wei value to push + * @return the new top index + */ + public static int pushWei(final long[] s, final int top, final Wei value) { + final int dst = top << 2; + final byte[] bytes = value.toArrayUnsafe(); + s[dst] = getLong(bytes, 0); + s[dst + 1] = getLong(bytes, 8); + s[dst + 2] = getLong(bytes, 16); + s[dst + 3] = getLong(bytes, 24); + return top + 1; + } + + /** + * Push an Address (20 bytes), return top+1. + * + * @param s the stack array + * @param top the current top index + * @param addr the address to push + * @return the new top index + */ + public static int pushAddress(final long[] s, final int top, final Address addr) { + final int dst = top << 2; + byte[] bytes = addr.getBytes().toArrayUnsafe(); + // Address is 20 bytes: fits in u2(4 bytes) + u1(8 bytes) + u0(8 bytes) + s[dst] = 0; // u3 + s[dst + 1] = getInt(bytes, 0) & 0xFFFFFFFFL; // u2 (top 4 bytes) + s[dst + 2] = getLong(bytes, 4); // u1 + s[dst + 3] = getLong(bytes, 12); // u0 + return top + 1; + } + + // endregion + + // region Boundary Helpers (read/write slots without changing top) + // --------------------------------------------------------------------------- + + /** + * Extract u0 (LSB limb) of slot at depth from top. + * + * @param s the stack array + * @param top the current top index + * @param depth the 0-based depth from the top + * @return the least-significant limb of the slot + */ + public static long longAt(final long[] s, final int top, final int depth) { + return s[((top - 1 - depth) << 2) + 3]; + } + + /** + * Check if slot at depth is zero. + * + * @param s the stack array + * @param top the current top index + * @param depth the 0-based depth from the top + * @return true if the slot is zero + */ + public static boolean isZeroAt(final long[] s, final int top, final int depth) { + final int off = (top - 1 - depth) << 2; + return (s[off] | s[off + 1] | s[off + 2] | s[off + 3]) == 0; + } + + /** + * Check if slot at depth fits in a non-negative int. + * + * @param s the stack array + * @param top the current top index + * @param depth the 0-based depth from the top + * @return true if the slot value fits in a non-negative int + */ + public static boolean fitsInInt(final long[] s, final int top, final int depth) { + final int off = (top - 1 - depth) << 2; + return s[off] == 0 + && s[off + 1] == 0 + && s[off + 2] == 0 + && s[off + 3] >= 0 + && s[off + 3] <= Integer.MAX_VALUE; + } + + /** + * Check if slot at depth fits in a non-negative long. + * + * @param s the stack array + * @param top the current top index + * @param depth the 0-based depth from the top + * @return true if the slot value fits in a non-negative long + */ + public static boolean fitsInLong(final long[] s, final int top, final int depth) { + final int off = (top - 1 - depth) << 2; + return s[off] == 0 && s[off + 1] == 0 && s[off + 2] == 0 && s[off + 3] >= 0; + } + + /** + * Clamp slot at depth to long, returning Long.MAX_VALUE if it doesn't fit in a non-negative long. + * + * @param s the stack array + * @param top the current top index + * @param depth the 0-based depth from the top + * @return the slot value as a long, or Long.MAX_VALUE if it does not fit + */ + public static long clampedToLong(final long[] s, final int top, final int depth) { + final int off = (top - 1 - depth) << 2; + if (s[off] != 0 || s[off + 1] != 0 || s[off + 2] != 0 || s[off + 3] < 0) { + return Long.MAX_VALUE; + } + return s[off + 3]; + } + + /** + * Clamp slot at depth to int, returning Integer.MAX_VALUE if it doesn't fit in a non-negative + * int. + * + * @param s the stack array + * @param top the current top index + * @param depth the 0-based depth from the top + * @return the slot value as an int, or Integer.MAX_VALUE if it does not fit + */ + public static int clampedToInt(final long[] s, final int top, final int depth) { + final int off = (top - 1 - depth) << 2; + if (s[off] != 0 + || s[off + 1] != 0 + || s[off + 2] != 0 + || s[off + 3] < 0 + || s[off + 3] > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + return (int) s[off + 3]; + } + + /** + * Write 32 big-endian bytes from slot at depth into dst. + * + * @param s the stack array + * @param top the current top index + * @param depth the 0-based depth from the top + * @param dst the destination byte array (must have at least 32 bytes) + */ + public static void toBytesAt(final long[] s, final int top, final int depth, final byte[] dst) { + final int off = (top - 1 - depth) << 2; + longIntoBytes(dst, 0, s[off]); + longIntoBytes(dst, 8, s[off + 1]); + longIntoBytes(dst, 16, s[off + 2]); + longIntoBytes(dst, 24, s[off + 3]); + } + + /** + * Read bytes into slot at depth from src[srcOff..srcOff+len). Pads with zeros. + * + * @param s the stack array + * @param top the current top index + * @param depth the 0-based depth from the top + * @param src the source byte array + * @param srcOff the start offset within the source array + * @param len the number of bytes to read (up to 32) + */ + public static void fromBytesAt( + final long[] s, + final int top, + final int depth, + final byte[] src, + final int srcOff, + final int len) { + final int off = (top - 1 - depth) << 2; + // Fast path: full 32-byte read — decode 8 bytes per limb + if (len >= 32 && srcOff + 32 <= src.length) { + s[off] = bytesToLong(src, srcOff); + s[off + 1] = bytesToLong(src, srcOff + 8); + s[off + 2] = bytesToLong(src, srcOff + 16); + s[off + 3] = bytesToLong(src, srcOff + 24); + return; + } + // Slow path: variable-length, byte-by-byte + s[off] = 0; + s[off + 1] = 0; + s[off + 2] = 0; + s[off + 3] = 0; + int end = srcOff + Math.min(len, 32); + if (end > src.length) end = src.length; + int pos = 0; + for (int i = srcOff; i < end; i++, pos++) { + int limbIdx = pos >> 3; + int shift = (7 - (pos & 7)) << 3; + s[off + limbIdx] |= (src[i] & 0xFFL) << shift; + } + } + + /** + * Extract 20-byte Address from slot at depth. + * + * @param s the stack array + * @param top the current top index + * @param depth the 0-based depth from the top + * @return the address stored in the slot + */ + public static Address toAddressAt(final long[] s, 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 + putInt(bytes, 0, (int) s[off + 1]); + putLong(bytes, 4, s[off + 2]); + putLong(bytes, 12, s[off + 3]); + return Address.wrap(org.apache.tuweni.bytes.Bytes.wrap(bytes)); + } + + /** + * Materialize UInt256 record from slot at depth (boundary only). + * + * @param s the stack array + * @param top the current top index + * @param depth the 0-based depth from the top + * @return the UInt256 value at the slot + */ + public static UInt256 getAt(final long[] s, final int top, final int depth) { + final int off = (top - 1 - depth) << 2; + return new UInt256(s[off], s[off + 1], s[off + 2], s[off + 3]); + } + + /** + * Write a Wei value into slot at depth. + * + * @param s the stack array + * @param top the current top index + * @param depth the 0-based depth from the top + * @param value the Wei value to write + */ + public static void putWeiAt(final long[] s, final int top, final int depth, final Wei value) { + final int off = (top - 1 - depth) << 2; + final byte[] bytes = value.toArrayUnsafe(); + s[off] = getLong(bytes, 0); + s[off + 1] = getLong(bytes, 8); + s[off + 2] = getLong(bytes, 16); + s[off + 3] = getLong(bytes, 24); + } + + /** + * Write UInt256 record into slot at depth. + * + * @param s the stack array + * @param top the current top index + * @param depth the 0-based depth from the top + * @param val the UInt256 value to write + */ + public static void putAt(final long[] s, final int top, final int depth, final UInt256 val) { + final int off = (top - 1 - depth) << 2; + s[off] = val.u3(); + s[off + 1] = val.u2(); + s[off + 2] = val.u1(); + s[off + 3] = val.u0(); + } + + /** + * Write raw limbs into slot at depth. + * + * @param s the stack array + * @param top the current top index + * @param depth the 0-based depth from the top + * @param u3 the most-significant limb + * @param u2 the second limb + * @param u1 the third limb + * @param u0 the least-significant limb + */ + public static void putAt( + final long[] s, + final int top, + final int depth, + final long u3, + final long u2, + final long u1, + final long u0) { + final int off = (top - 1 - depth) << 2; + s[off] = u3; + s[off + 1] = u2; + s[off + 2] = u1; + s[off + 3] = u0; + } + + /** + * Number of significant bytes in slot at depth. Used by EXP gas calculation. + * + * @param s the stack array + * @param top the current top index + * @param depth the 0-based depth from the top + * @return the number of significant bytes in the slot + */ + public static int byteLengthAt(final long[] s, final int top, final int depth) { + final int off = (top - 1 - depth) << 2; + if (s[off] != 0) return 24 + byteLen(s[off]); + if (s[off + 1] != 0) return 16 + byteLen(s[off + 1]); + if (s[off + 2] != 0) return 8 + byteLen(s[off + 2]); + if (s[off + 3] != 0) return byteLen(s[off + 3]); + return 0; + } + + // endregion + + // region Private Helpers + // --------------------------------------------------------------------------- + + private static long getLong(final byte[] b, final int off) { + return (long) LONG_BE.get(b, off); + } + + private static void putLong(final byte[] b, final int off, final long v) { + LONG_BE.set(b, off, v); + } + + private static int getInt(final byte[] b, final int off) { + return (int) INT_BE.get(b, off); + } + + private static void putInt(final byte[] b, final int off, final int v) { + INT_BE.set(b, off, v); + } + + /** Build a long from 1-8 big-endian bytes. */ + private static long buildLong(final byte[] src, final int off, final int len) { + long v = 0; + for (int i = off, end = off + len; i < end; i++) { + v = (v << 8) | (src[i] & 0xFFL); + } + return v; + } + + /** Decode 8 big-endian bytes from src[off] into a long. */ + private static long bytesToLong(final byte[] src, final int off) { + return getLong(src, off); + } + + private static void longIntoBytes(final byte[] bytes, final int offset, final long value) { + putLong(bytes, offset, value); + } + + private static int byteLen(final long v) { + return (64 - Long.numberOfLeadingZeros(v) + 7) / 8; + } + + /** Unsigned less-than comparison of two slots. */ + private static boolean unsignedLt( + final long[] s1, final int off1, final long[] s2, final int off2) { + // Compare u3 first (MSB) + if (s1[off1] != s2[off2]) return Long.compareUnsigned(s1[off1], s2[off2]) < 0; + if (s1[off1 + 1] != s2[off2 + 1]) return Long.compareUnsigned(s1[off1 + 1], s2[off2 + 1]) < 0; + if (s1[off1 + 2] != s2[off2 + 2]) return Long.compareUnsigned(s1[off1 + 2], s2[off2 + 2]) < 0; + return Long.compareUnsigned(s1[off1 + 3], s2[off2 + 3]) < 0; + } + + /** Signed comparison of two slots. */ + private static int signedCompare( + final long[] s1, final int off1, final long[] s2, final int off2) { + boolean aNeg = s1[off1] < 0; + boolean bNeg = s2[off2] < 0; + if (aNeg && !bNeg) return -1; + if (!aNeg && bNeg) return 1; + // Same sign: unsigned compare gives correct result + if (s1[off1] != s2[off2]) return Long.compareUnsigned(s1[off1], s2[off2]); + if (s1[off1 + 1] != s2[off2 + 1]) return Long.compareUnsigned(s1[off1 + 1], s2[off2 + 1]); + if (s1[off1 + 2] != s2[off2 + 2]) return Long.compareUnsigned(s1[off1 + 2], s2[off2 + 2]); + return Long.compareUnsigned(s1[off1 + 3], s2[off2 + 3]); + } + + /** + * Shifts a 64-bit word right and carries in bits from the previous more-significant word. + * + *

The {@code bitShift == 0} fast path avoids Java long-shift masking, where a shift by 64 is + * treated as a shift by 0. + * + * @param value the current word + * @param prevValue the previous more-significant word + * @param bitShift the intra-word shift amount in the range {@code [0..63]} + * @return the shifted word + */ + private static long shiftRightWord(final long value, final long prevValue, final int bitShift) { + if (bitShift == 0) return value; + return (value >>> bitShift) | (prevValue << (64 - bitShift)); + } + + // endregion +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/StackPool.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/StackPool.java new file mode 100644 index 00000000000..63f7ba8bc8e --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/StackPool.java @@ -0,0 +1,125 @@ +/* + * Copyright contributors to Hyperledger 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; + +/** + * Thread-local pool of {@code long[]} arrays used as EVM operand stacks. Replaces the previous + * {@code OperandStack} pooling with direct array pooling to eliminate one level of indirection. + * + *

The pool tracks peak usage with an exponential moving average and periodically shrinks to + * reclaim memory after usage spikes subside. + */ +public final class StackPool { + + private static final int DEFAULT_MAX_SIZE = 1024; + private static final int INITIAL_CAPACITY = 16; + private static final int MAINTENANCE_INTERVAL = 256; + + private static final ThreadLocal POOL = ThreadLocal.withInitial(StackPool::new); + + long[][] stacks; + int size; // available stacks in array + int capacity; + int outstanding; // currently borrowed (borrows - releases) + int peakThisCycle; // max(outstanding) since last maintenance + int peakEmaX16; // EMA of peak, fixed-point <<4 + int idleCount; // times outstanding hit 0 since last maintenance + + StackPool() { + capacity = INITIAL_CAPACITY; + stacks = new long[INITIAL_CAPACITY][]; + for (int i = 0; i < INITIAL_CAPACITY; i++) { + stacks[i] = new long[DEFAULT_MAX_SIZE << 2]; + } + size = INITIAL_CAPACITY; + } + + /** + * Borrows a {@code long[]} stack array from the thread-local pool, or creates a new one if the + * pool is empty. + * + * @param maxSize the max stack size (number of UInt256 entries) + * @return a zeroed long[] array of size maxSize << 2 + */ + public static long[] borrow(final int maxSize) { + if (maxSize == DEFAULT_MAX_SIZE) { + return POOL.get().borrowInternal(); + } + return new long[maxSize << 2]; + } + + /** + * Returns a {@code long[]} stack array to the thread-local pool for reuse. + * + * @param data the long[] array to return + * @param maxSize the max stack size used when borrowing + */ + public static void release(final long[] data, final int maxSize) { + if (maxSize == DEFAULT_MAX_SIZE) { + POOL.get().releaseInternal(data); + } + } + + private long[] borrowInternal() { + outstanding++; + if (outstanding > peakThisCycle) { + peakThisCycle = outstanding; + } + if (size > 0) { + return stacks[--size]; + } + return new long[DEFAULT_MAX_SIZE << 2]; + } + + private void releaseInternal(final long[] data) { + outstanding--; + if (size < capacity) { + stacks[size++] = data; + } + // else: pool full, discard (GC reclaims) + + if (outstanding == 0) { + if (++idleCount >= MAINTENANCE_INTERVAL) { + maintain(); + } + } + } + + void maintain() { + // Update EMA: alpha = 1/4 -> peakEma = 3/4 * old + 1/4 * new + peakEmaX16 = (peakEmaX16 * 3 + (peakThisCycle << 4) + 2) >> 2; + peakThisCycle = 0; + idleCount = 0; + + int smoothedPeak = (peakEmaX16 + 8) >> 4; + int target = nextPowerOf2(Math.max(smoothedPeak * 2, INITIAL_CAPACITY)); + + if (target != capacity) { + long[][] newArr = new long[target][]; + int keep = Math.min(size, target); + System.arraycopy(stacks, 0, newArr, 0, keep); + stacks = newArr; + size = keep; + capacity = target; + } + } + + private static int nextPowerOf2(final int n) { + if (n <= 1) { + return 1; + } + return Integer.highestOneBit(n - 1) << 1; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCallOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCallOperationV2.java new file mode 100644 index 00000000000..b3b0703356e --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCallOperationV2.java @@ -0,0 +1,400 @@ +/* + * 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.hyperledger.besu.evm.frame.SoftFailureReason.LEGACY_INSUFFICIENT_BALANCE; +import static org.hyperledger.besu.evm.frame.SoftFailureReason.LEGACY_MAX_CALL_DEPTH; +import static org.hyperledger.besu.evm.internal.Words.clampedAdd; +import static org.hyperledger.besu.evm.worldstate.CodeDelegationHelper.getTarget; +import static org.hyperledger.besu.evm.worldstate.CodeDelegationHelper.hasCodeDelegation; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.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.frame.MessageFrame.State; +import org.hyperledger.besu.evm.frame.SoftFailureReason; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.v2.StackArithmetic; +import org.hyperledger.besu.evm.worldstate.CodeDelegationHelper; + +import org.apache.tuweni.bytes.Bytes; + +/** + * Base class for EVM v2 CALL-family operations (CALL, CALLCODE, DELEGATECALL, STATICCALL). + * + *

Reads arguments from the v2 {@code long[]} stack, creates a child {@link MessageFrame} with + * {@code enableEvmV2(true)}, suspends the parent, and writes the result back when the child + * completes. + */ +public abstract class AbstractCallOperationV2 extends AbstractOperationV2 { + + /** Underflow response returned when the stack does not have enough items. */ + protected static final OperationResult UNDERFLOW_RESPONSE = + new OperationResult(0L, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + + /** + * Instantiates a new abstract call operation for EVM v2. + * + * @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 + */ + AbstractCallOperationV2( + final int opcode, + final String name, + final int stackItemsConsumed, + final int stackItemsProduced, + final GasCalculator gasCalculator) { + super(opcode, name, stackItemsConsumed, stackItemsProduced, gasCalculator); + } + + /** + * Returns the gas stipend from the v2 stack (depth 0). + * + * @param s the v2 stack array + * @param top the current stack top + * @return the gas stipend, clamped to long + */ + protected long gas(final long[] s, final int top) { + return StackArithmetic.clampedToLong(s, top, 0); + } + + /** + * Returns the target address from the v2 stack. + * + * @param s the v2 stack array + * @param top the current stack top + * @return the target address + */ + protected abstract Address to(long[] s, int top); + + /** + * Returns the value (Wei) to transfer. + * + * @param s the v2 stack array + * @param top the current stack top + * @return the transfer value + */ + protected abstract Wei value(long[] s, int top); + + /** + * Returns the apparent value (Wei) for the child frame. + * + * @param frame the current message frame + * @param s the v2 stack array + * @param top the current stack top + * @return the apparent value + */ + protected abstract Wei apparentValue(MessageFrame frame, long[] s, int top); + + /** + * Returns the input data memory offset. + * + * @param s the v2 stack array + * @param top the current stack top + * @return the input data offset + */ + protected abstract long inputDataOffset(long[] s, int top); + + /** + * Returns the input data length. + * + * @param s the v2 stack array + * @param top the current stack top + * @return the input data length + */ + protected abstract long inputDataLength(long[] s, int top); + + /** + * Returns the output data memory offset. + * + * @param s the v2 stack array + * @param top the current stack top + * @return the output data offset + */ + protected abstract long outputDataOffset(long[] s, int top); + + /** + * Returns the output data length. + * + * @param s the v2 stack array + * @param top the current stack top + * @return the output data length + */ + protected abstract long outputDataLength(long[] s, int top); + + /** + * Returns the address used as the recipient in the child frame. + * + * @param frame the current message frame + * @param s the v2 stack array + * @param top the current stack top + * @return the recipient address + */ + protected abstract Address address(MessageFrame frame, long[] s, int top); + + /** + * Returns the sender address for the child frame. + * + * @param frame the current message frame + * @return the sender address + */ + protected abstract Address sender(MessageFrame frame); + + /** + * Returns the gas available for the child call. + * + * @param frame the current message frame + * @param s the v2 stack array + * @param top the current stack top + * @return the gas available for the child call + */ + public abstract long gasAvailableForChildCall(MessageFrame frame, long[] s, int top); + + /** + * Returns whether the child call should be static. + * + * @param frame the current message frame + * @return {@code true} if the child call should be static + */ + protected boolean isStatic(final MessageFrame frame) { + return frame.isStatic(); + } + + /** + * Returns whether this is a delegate call. + * + * @return {@code true} if this is a delegate call + */ + protected boolean isDelegate() { + return false; + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + if (!frame.stackHasItems(getStackItemsConsumed())) { + return UNDERFLOW_RESPONSE; + } + + final long[] s = frame.stackDataV2(); + final int top = frame.stackTopV2(); + + final Address to = to(s, top); + final boolean accountIsWarm = frame.warmUpAddress(to) || gasCalculator().isPrecompile(to); + final long stipend = gas(s, top); + final long inputOffset = inputDataOffset(s, top); + final long inputLength = inputDataLength(s, top); + final long outputOffset = outputDataOffset(s, top); + final long outputLength = outputDataLength(s, top); + final Wei transferValue = value(s, top); + final Address recipientAddress = address(frame, s, top); + + final long staticCost = + gasCalculator() + .callOperationStaticGasCost( + frame, + stipend, + inputOffset, + inputLength, + outputOffset, + outputLength, + transferValue, + recipientAddress, + accountIsWarm); + + if (frame.getRemainingGas() < staticCost) { + return new OperationResult(staticCost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + long cost = + gasCalculator() + .callOperationGasCost( + frame, + staticCost, + stipend, + inputOffset, + inputLength, + outputOffset, + outputLength, + transferValue, + recipientAddress, + accountIsWarm); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + final Account contract = getAccount(to, frame); + cost = clampedAdd(cost, gasCalculator().calculateCodeDelegationResolutionGas(frame, contract)); + + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + frame.decrementRemainingGas(cost); + + // EIP-8037: Charge state gas for new account creation in CALL + if (!gasCalculator() + .stateGasCostCalculator() + .chargeCallNewAccountStateGas(frame, recipientAddress, transferValue)) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + final long gasForChild = gasAvailableForChildCall(frame, s, top); + + frame.clearReturnData(); + + final Account account = getAccount(frame.getRecipientAddress(), frame); + final Wei balance = account == null ? Wei.ZERO : account.getBalance(); + + final boolean insufficientBalance = transferValue.compareTo(balance) > 0; + final boolean isFrameDepthTooDeep = frame.getDepth() >= 1024; + if (insufficientBalance || isFrameDepthTooDeep) { + frame.expandMemory(inputOffset, inputLength); + frame.expandMemory(outputOffset, outputLength); + frame.incrementRemainingGas(gasForChild + cost); + final int newTop = top - getStackItemsConsumed() + 1; + StackArithmetic.putAt(s, newTop, 0, 0L, 0L, 0L, 0L); + frame.setTopV2(newTop); + final SoftFailureReason softFailureReason = + insufficientBalance ? LEGACY_INSUFFICIENT_BALANCE : LEGACY_MAX_CALL_DEPTH; + return new OperationResult(cost, 1, softFailureReason, gasForChild); + } + + final Bytes inputData = frame.readMutableMemory(inputOffset, inputLength); + final Code code = getCode(evm, frame, contract); + + MessageFrame.Builder builder = + MessageFrame.builder() + .parentMessageFrame(frame) + .type(MessageFrame.Type.MESSAGE_CALL) + .initialGas(gasForChild) + .address(recipientAddress) + .contract(to) + .inputData(inputData) + .sender(sender(frame)) + .value(transferValue) + .apparentValue(apparentValue(frame, s, top)) + .code(code) + .isStatic(isStatic(frame)) + .enableEvmV2(true) + .completer(child -> complete(frame, child)); + + if (frame.getEip7928AccessList().isPresent()) { + builder.eip7928AccessList(frame.getEip7928AccessList().get()); + } + + builder.build(); + frame.incrementRemainingGas(cost); + + frame.setState(MessageFrame.State.CODE_SUSPENDED); + return new OperationResult(cost, null, 0); + } + + /** + * Called when the child frame has finished executing. Restores the parent frame and pushes the + * result onto the v2 stack. + * + * @param frame the parent message frame + * @param childFrame the completed child message frame + */ + public void complete(final MessageFrame frame, final MessageFrame childFrame) { + frame.setState(MessageFrame.State.CODE_EXECUTING); + + final long[] s = frame.stackDataV2(); + final int top = frame.stackTopV2(); + + final long outputOffset = outputDataOffset(s, top); + final long outputSize = outputDataLength(s, top); + final Bytes outputData = childFrame.getOutputData(); + + if (outputSize > outputData.size()) { + frame.expandMemory(outputOffset, outputSize); + frame.writeMemory(outputOffset, outputData.size(), outputData, true); + } else if (outputSize > 0) { + frame.writeMemory(outputOffset, outputSize, outputData, true); + } + + frame.setReturnData(outputData); + if (!childFrame.getLogs().isEmpty()) { + frame.addLogs(childFrame.getLogs()); + } + if (!childFrame.getSelfDestructs().isEmpty()) { + frame.addSelfDestructs(childFrame.getSelfDestructs()); + } + if (!childFrame.getCreates().isEmpty()) { + frame.addCreates(childFrame.getCreates()); + } + + frame.incrementRemainingGas(childFrame.getRemainingGas()); + + final int newTop = top - getStackItemsConsumed() + 1; + final long resultU0 = childFrame.getState() == State.COMPLETED_SUCCESS ? 1L : 0L; + StackArithmetic.putAt(s, newTop, 0, 0L, 0L, 0L, resultU0); + frame.setTopV2(newTop); + + frame.setPC(frame.getPC() + 1); + } + + /** + * Gets the executable code for the given account, resolving EIP-7702 code delegation if present. + * + * @param evm the EVM + * @param frame the current message frame + * @param account the account whose code is needed + * @return the resolved code, or {@link Code#EMPTY_CODE} if none + */ + protected Code getCode(final EVM evm, final MessageFrame frame, final Account account) { + if (account == null) { + return Code.EMPTY_CODE; + } + + final Hash codeHash = account.getCodeHash(); + frame.getEip7928AccessList().ifPresent(t -> t.addTouchedAccount(account.getAddress())); + if (codeHash == null || codeHash.equals(Hash.EMPTY)) { + return Code.EMPTY_CODE; + } + + final boolean accountHasCodeCache = account.getCodeCache() != null; + final Code code; + if (accountHasCodeCache) { + code = account.getOrCreateCachedCode(); + } else { + code = evm.getOrCreateCachedJumpDest(codeHash, account.getCode()); + } + + if (!hasCodeDelegation(code.getBytes())) { + return code; + } + + final CodeDelegationHelper.Target target = + getTarget( + frame.getWorldUpdater(), + evm.getGasCalculator()::isPrecompile, + account, + frame.getEip7928AccessList()); + + if (accountHasCodeCache) { + return target.code(); + } + final Code targetCode = target.code(); + return evm.getOrCreateCachedJumpDest(targetCode.getCodeHash(), targetCode.getBytes()); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCreateOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCreateOperationV2.java new file mode 100644 index 00000000000..c9a990fbc0d --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractCreateOperationV2.java @@ -0,0 +1,275 @@ +/* + * 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.hyperledger.besu.evm.frame.SoftFailureReason.INVALID_STATE; +import static org.hyperledger.besu.evm.frame.SoftFailureReason.LEGACY_INSUFFICIENT_BALANCE; +import static org.hyperledger.besu.evm.frame.SoftFailureReason.LEGACY_MAX_CALL_DEPTH; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +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.frame.SoftFailureReason; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +import java.util.Optional; +import java.util.function.Supplier; + +import com.google.common.base.Suppliers; +import org.apache.tuweni.bytes.Bytes; + +/** + * Base class for EVM v2 CREATE-family operations (CREATE, CREATE2). + * + *

Reads arguments from the v2 {@code long[]} stack, creates a child {@link MessageFrame} with + * {@code enableEvmV2(true)}, suspends the parent, and writes the result back when the child + * completes. + */ +public abstract class AbstractCreateOperationV2 extends AbstractOperationV2 { + + /** Underflow response returned when the stack does not have enough items. */ + protected static final OperationResult UNDERFLOW_RESPONSE = + new OperationResult(0L, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + + /** + * Instantiates a new abstract create operation for EVM v2. + * + * @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 AbstractCreateOperationV2( + final int opcode, + final String name, + final int stackItemsConsumed, + final int stackItemsProduced, + final GasCalculator gasCalculator) { + super(opcode, name, stackItemsConsumed, stackItemsProduced, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + if (!frame.stackHasItems(getStackItemsConsumed())) { + return UNDERFLOW_RESPONSE; + } + + Supplier codeSupplier = Suppliers.memoize(() -> getInitCode(frame, evm)); + + if (frame.isStatic()) { + return new OperationResult(0, ExceptionalHaltReason.ILLEGAL_STATE_CHANGE); + } + + final long[] s = frame.stackDataV2(); + final int top = frame.stackTopV2(); + + final long cost = cost(frame, s, top, codeSupplier); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + final Wei value = Wei.wrap(Bytes.wrap(StackArithmetic.getAt(s, top, 0).toBytesBE())); + + final Address address = frame.getRecipientAddress(); + final MutableAccount account = getMutableAccount(address, frame); + + frame.clearReturnData(); + + Code code = codeSupplier.get(); + + if (code != null && code.getSize() > evm.getMaxInitcodeSize()) { + final int newTop = top - getStackItemsConsumed(); + frame.setTopV2(newTop); + return new OperationResult(cost, ExceptionalHaltReason.CODE_TOO_LARGE); + } + + final boolean insufficientBalance = value.compareTo(account.getBalance()) > 0; + final boolean maxDepthReached = frame.getDepth() >= 1024; + final boolean invalidState = account.getNonce() == -1 || code == null; + + if (insufficientBalance || maxDepthReached || invalidState) { + fail(frame, s, top); + final SoftFailureReason softFailureReason = + insufficientBalance + ? LEGACY_INSUFFICIENT_BALANCE + : (maxDepthReached ? LEGACY_MAX_CALL_DEPTH : INVALID_STATE); + return new OperationResult(cost, getPcIncrement(), softFailureReason); + } + + account.incrementNonce(); + frame.decrementRemainingGas(cost); + + // EIP-8037: Charge state gas for CREATE + if (!gasCalculator().stateGasCostCalculator().chargeCreateStateGas(frame)) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + spawnChildMessage(frame, s, top, value, code); + frame.incrementRemainingGas(cost); + + return new OperationResult(cost, null, getPcIncrement()); + } + + /** + * How many bytes does this operation occupy? + * + * @return the number of bytes the operation and immediate arguments occupy + */ + protected int getPcIncrement() { + return 1; + } + + /** + * Computes the gas cost for this create operation. + * + * @param frame the current message frame + * @param s the v2 stack array + * @param top the current stack top + * @param codeSupplier a supplier for the initcode, if needed for costing + * @return the gas cost + */ + protected abstract long cost(MessageFrame frame, long[] s, int top, Supplier codeSupplier); + + /** + * Generates the target contract address. + * + * @param frame the current message frame + * @param s the v2 stack array + * @param top the current stack top + * @param initcode the initcode + * @return the target address + */ + protected abstract Address generateTargetContractAddress( + MessageFrame frame, long[] s, int top, Code initcode); + + /** + * Gets the initcode to run. + * + * @param frame the current message frame + * @param evm the EVM executing the message frame + * @return the initcode + */ + protected abstract Code getInitCode(MessageFrame frame, EVM evm); + + /** + * Handles stack when the operation fails (insufficient balance, depth limit, invalid state). + * + * @param frame the current execution frame + * @param s the v2 stack array + * @param top the current stack top + */ + protected void fail(final MessageFrame frame, final long[] s, final int top) { + final long inputOffset = StackArithmetic.clampedToLong(s, top, 1); + final long inputSize = StackArithmetic.clampedToLong(s, top, 2); + frame.readMutableMemory(inputOffset, inputSize); + final int newTop = top - getStackItemsConsumed() + 1; + StackArithmetic.putAt(s, newTop, 0, 0L, 0L, 0L, 0L); + frame.setTopV2(newTop); + } + + private void spawnChildMessage( + final MessageFrame parent, final long[] s, final int top, final Wei value, final Code code) { + final Address contractAddress = generateTargetContractAddress(parent, s, top, code); + final Bytes inputData = getInputData(parent); + + final long childGasStipend = + gasCalculator().gasAvailableForChildCreate(parent.getRemainingGas()); + parent.decrementRemainingGas(childGasStipend); + + MessageFrame.Builder builder = + MessageFrame.builder() + .parentMessageFrame(parent) + .type(MessageFrame.Type.CONTRACT_CREATION) + .initialGas(childGasStipend) + .address(contractAddress) + .contract(contractAddress) + .inputData(inputData) + .sender(parent.getRecipientAddress()) + .value(value) + .apparentValue(value) + .code(code) + .enableEvmV2(true) + .completer(child -> complete(parent, child)); + + if (parent.getEip7928AccessList().isPresent()) { + builder.eip7928AccessList(parent.getEip7928AccessList().get()); + } + + builder.build(); + + parent.setState(MessageFrame.State.CODE_SUSPENDED); + } + + /** + * Returns the input data to append for this create operation. Default is empty (for CREATE and + * CREATE2). + * + * @param frame the current message frame + * @return the input data bytes + */ + protected Bytes getInputData(final MessageFrame frame) { + return Bytes.EMPTY; + } + + private void complete(final MessageFrame frame, final MessageFrame childFrame) { + frame.setState(MessageFrame.State.CODE_EXECUTING); + + frame.incrementRemainingGas(childFrame.getRemainingGas()); + frame.addLogs(childFrame.getLogs()); + frame.addSelfDestructs(childFrame.getSelfDestructs()); + frame.addCreates(childFrame.getCreates()); + + final long[] s = frame.stackDataV2(); + final int top = frame.stackTopV2(); + final int newTop = top - getStackItemsConsumed() + 1; + + if (childFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { + final Address createdAddress = childFrame.getContractAddress(); + frame.setTopV2(StackArithmetic.pushAddress(s, newTop - 1, createdAddress)); + frame.setReturnData(Bytes.EMPTY); + onSuccess(frame, createdAddress); + } else { + frame.setReturnData(childFrame.getOutputData()); + StackArithmetic.putAt(s, newTop, 0, 0L, 0L, 0L, 0L); + frame.setTopV2(newTop); + onFailure(frame, childFrame.getExceptionalHaltReason()); + } + + frame.setPC(frame.getPC() + getPcIncrement()); + } + + /** + * Called on successful child contract creation. + * + * @param frame the parent frame + * @param createdAddress the address of the newly created contract + */ + protected void onSuccess(final MessageFrame frame, final Address createdAddress) {} + + /** + * Called on failed child contract creation. + * + * @param frame the parent frame + * @param haltReason the exceptional halt reason + */ + protected void onFailure( + final MessageFrame frame, final Optional haltReason) {} +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/v2/AbstractFixedCostOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java similarity index 92% rename from evm/src/main/java/org/hyperledger/besu/evm/operation/v2/AbstractFixedCostOperationV2.java rename to evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java index 95e059d9605..ae67c92366d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/v2/AbstractFixedCostOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java @@ -12,7 +12,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.evm.operation.v2; +package org.hyperledger.besu.evm.v2.operation; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; @@ -20,15 +20,18 @@ 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. */ -abstract class AbstractFixedCostOperationV2 extends AbstractOperation { +abstract class AbstractFixedCostOperationV2 extends AbstractOperationV2 { /** Shared underflow response for static operation methods. */ static final OperationResult UNDERFLOW_RESPONSE = new OperationResult(0L, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + /** Shared overflow response for static operation methods. */ + static final OperationResult OVERFLOW_RESPONSE = + new OperationResult(0L, ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); + /** 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..7df3117f7a4 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractOperationV2.java @@ -0,0 +1,176 @@ +/* + * 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.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; + +import org.apache.tuweni.units.bigints.UInt256; + +/** + * Base class for all EVM v2 operations using the flat {@code long[]} stack representation. + * + *

Implements {@link Operation} directly (no dependency on V1 {@code AbstractOperation}) so that + * V1 operation code can eventually be removed. Static helpers mirror the instance helpers of V1's + * {@code AbstractOperation} but are callable from {@code staticOperation()} methods used by the v2 + * dispatch loop. + */ +public abstract class AbstractOperationV2 implements Operation { + + private final int opcode; + private final String name; + private final int stackItemsConsumed; + private final int stackItemsProduced; + private final GasCalculator gasCalculator; + + /** + * Instantiates a new v2 operation. + * + * @param opcode the opcode + * @param name the operation name + * @param stackItemsConsumed the number of stack items consumed + * @param stackItemsProduced the number of 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 the 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; + } + + /** + * Reads an account (immutable) and records it in the EIP-7928 block access list if active. + * + *

Use this instead of {@code frame.getWorldUpdater().getAccount(address)} for read-only + * account access. {@code getAccount()} wraps the account in a tracking object that appears in + * {@code getTouchedAccounts()}, which can cause empty accounts to be spuriously deleted by {@code + * clearAccountsThatAreEmpty()}. This method uses the side-effect-free {@code get()} instead. + * + * @param address the account address + * @param frame the current message frame + * @return the account, or {@code null} if it does not exist + */ + protected static 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 frame + * @return the {@link MutableAccount}, or {@code null} if it does not exist + */ + protected static 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 frame + * @return the existing or newly created {@link MutableAccount} + */ + protected static 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 frame + * @return the {@link MutableAccount} for the sender + */ + protected static 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 frame + * @return the value stored at the specified key + */ + protected static 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/AddModOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddModOperationV2.java new file mode 100644 index 00000000000..a8fe33ee6d9 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddModOperationV2.java @@ -0,0 +1,55 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The Add mod operation. */ +public class AddModOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult addModSuccess = new OperationResult(8, null); + + /** + * Instantiates a new Add mod operation. + * + * @param gasCalculator the gas calculator + */ + public AddModOperationV2(final GasCalculator gasCalculator) { + super(0x08, "ADDMOD", 3, 1, gasCalculator, gasCalculator.getMidTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs AddMod operation. + * + * @param frame the frame + * @param s the stack data array + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(3)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.addMod(s, frame.stackTopV2())); + return addModSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/v2/AddOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddOperationV2.java similarity index 60% rename from evm/src/main/java/org/hyperledger/besu/evm/operation/v2/AddOperationV2.java rename to evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddOperationV2.java index 4c866bcf61f..a0a007ff6e0 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/v2/AddOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddOperationV2.java @@ -12,25 +12,19 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.evm.operation.v2; +package org.hyperledger.besu.evm.v2.operation; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; -/** - * EVM v2 ADD operation using long[] stack representation. - * - *

Each 256-bit word is stored as four longs: index 0 = most significant 64 bits, index 3 = least - * significant 64 bits. Addition is performed with carry propagation from least to most significant - * word, with overflow silently truncated (mod 2^256). - */ +/** The Add operation. */ public class AddOperationV2 extends AbstractFixedCostOperationV2 { - @SuppressWarnings("UnusedVariable") - private static final Operation.OperationResult ADD_SUCCESS = - new Operation.OperationResult(3, null); + /** The Add operation success result. */ + static final OperationResult addSuccess = new OperationResult(3, null); /** * Instantiates a new Add operation. @@ -48,15 +42,15 @@ public Operation.OperationResult executeFixedCostOperation( } /** - * Execute the ADD opcode on the v2 long[] stack. + * Performs add operation. * - * @param frame the message frame - * @param stackData the stack operands as a long[] array + * @param frame the frame + * @param stack the v2 operand stack ({@code long[]} in big-endian limb order) * @return the operation result */ - @SuppressWarnings("DoNotCallSuggester") - public static Operation.OperationResult staticOperation( - final MessageFrame frame, final long[] stackData) { - throw new UnsupportedOperationException("ADD operation not yet implemented for evm v2"); + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.add(stack, frame.stackTopV2())); + return addSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddressOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddressOperationV2.java new file mode 100644 index 00000000000..083e5b092ba --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddressOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 ADDRESS operation — pushes the recipient address onto the stack. */ +public class AddressOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult addressSuccess = new OperationResult(2, null); + + /** + * Instantiates a new Address operation. + * + * @param gasCalculator the gas calculator + */ + public AddressOperationV2(final GasCalculator gasCalculator) { + super(0x30, "ADDRESS", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs the ADDRESS operation. + * + * @param frame the message frame + * @param stack the v2 long[] stack + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTopV2( + StackArithmetic.pushAddress(stack, frame.stackTopV2(), frame.getRecipientAddress())); + return addressSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AndOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AndOperationV2.java new file mode 100644 index 00000000000..97210e79895 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AndOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 AND operation (0x16). */ +public class AndOperationV2 extends AbstractFixedCostOperationV2 { + + /** The And operation success result. */ + static final OperationResult andSuccess = new OperationResult(3, null); + + /** + * Instantiates a new And operation. + * + * @param gasCalculator the gas calculator + */ + public AndOperationV2(final GasCalculator gasCalculator) { + super(0x16, "AND", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs AND operation. + * + * @param frame the frame + * @param stack the v2 operand stack ({@code long[]} in big-endian limb order) + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.and(stack, frame.stackTopV2())); + return andSuccess; + } +} 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..b5e5a50ba9b --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java @@ -0,0 +1,95 @@ +/* + * 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.UInt256; +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.v2.StackArithmetic; + +/** + * EVM v2 BALANCE operation (0x31). + * + *

Pops an address from the stack and replaces it with the account's balance in Wei. Applies + * warm/cold account access cost. + */ +public class BalanceOperationV2 extends AbstractOperationV2 { + + /** + * Instantiates a new Balance operation. + * + * @param gasCalculator the gas calculator + */ + public BalanceOperationV2(final GasCalculator gasCalculator) { + super(0x31, "BALANCE", 1, 1, gasCalculator); + } + + /** + * Gas cost including warm/cold access. + * + * @param accountIsWarm whether the account was already warm + * @return the total gas cost + */ + protected long cost(final boolean accountIsWarm) { + return gasCalculator().getBalanceOperationGasCost() + + (accountIsWarm + ? gasCalculator().getWarmStorageReadCost() + : gasCalculator().getColdAccountAccessCost()); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), gasCalculator()); + } + + /** + * Execute BALANCE on the v2 long[] stack. + * + * @param frame the message frame + * @param s the stack data + * @param gasCalculator the gas calculator + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) { + final long warmCost = + gasCalculator.getBalanceOperationGasCost() + gasCalculator.getWarmStorageReadCost(); + final long coldCost = + gasCalculator.getBalanceOperationGasCost() + gasCalculator.getColdAccountAccessCost(); + if (!frame.stackHasItems(1)) { + return new OperationResult(warmCost, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final int top = frame.stackTopV2(); + final Address address = StackArithmetic.toAddressAt(s, top, 0); + final boolean accountIsWarm = + frame.warmUpAddress(address) || gasCalculator.isPrecompile(address); + final long cost = accountIsWarm ? warmCost : coldCost; + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + final Account account = getAccount(address, frame); + // Overwrite in place (pop 1, push 1) + if (account == null) { + StackArithmetic.putAt(s, top, 0, UInt256.ZERO); + } else { + StackArithmetic.putAt(s, top, 0, UInt256.fromBytesBE(account.getBalance().toArrayUnsafe())); + } + 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..f21c8badf35 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java @@ -0,0 +1,65 @@ +/* + * 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 org.hyperledger.besu.evm.v2.StackArithmetic; + +import java.util.Optional; + +/** EVM v2 BASEFEE operation — pushes the block base fee onto the stack (London+). */ +public class BaseFeeOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult baseFeeSuccess = new OperationResult(2, null); + private static final OperationResult INVALID_OPERATION_RESPONSE = + new OperationResult(2, ExceptionalHaltReason.INVALID_OPERATION); + + /** + * 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) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs the BASEFEE operation. + * + * @param frame the message frame + * @param stack the v2 long[] stack + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + final Optional maybeBaseFee = frame.getBlockValues().getBaseFee(); + if (maybeBaseFee.isEmpty()) { + return INVALID_OPERATION_RESPONSE; + } + frame.setTopV2(StackArithmetic.pushWei(stack, frame.stackTopV2(), maybeBaseFee.get())); + return baseFeeSuccess; + } +} 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..cdb470ef63c --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java @@ -0,0 +1,55 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 BLOBBASEFEE operation — pushes the blob base fee onto the stack (Cancun+). */ +public class BlobBaseFeeOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult blobBaseFeeSuccess = new OperationResult(2, null); + + /** + * 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 Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs the BLOBBASEFEE operation. + * + * @param frame the message frame + * @param stack the v2 long[] stack + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.pushWei(stack, frame.stackTopV2(), frame.getBlobGasPrice())); + return blobBaseFeeSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobHashOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobHashOperationV2.java new file mode 100644 index 00000000000..6e3f6608bf0 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobHashOperationV2.java @@ -0,0 +1,83 @@ +/* + * 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.VersionedHash; +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.v2.StackArithmetic; + +import java.util.List; + +/** + * EVM v2 BLOBHASH operation (0x49, Cancun+). + * + *

Reads an index from the top of the stack and replaces it with the versioned hash at that index + * in the transaction's blob versioned hashes list, or zero if the index is out of range. + */ +public class BlobHashOperationV2 extends AbstractOperationV2 { + + /** BLOBHASH opcode number */ + public static final int OPCODE = 0x49; + + /** + * Instantiates a new BlobHash operation. + * + * @param gasCalculator the gas calculator + */ + public BlobHashOperationV2(final GasCalculator gasCalculator) { + super(OPCODE, "BLOBHASH", 1, 1, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Execute BLOBHASH on the v2 long[] stack. + * + * @param frame the message frame + * @param s the stack data + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(1)) { + return new OperationResult(3, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final int top = frame.stackTopV2(); + if (frame.getVersionedHashes().isPresent()) { + final List versionedHashes = frame.getVersionedHashes().get(); + // If index doesn't fit in a positive int, it's out of range + if (!StackArithmetic.fitsInInt(s, top, 0)) { + StackArithmetic.putAt(s, top, 0, 0L, 0L, 0L, 0L); + return new OperationResult(3, null); + } + final int versionedHashIndex = (int) StackArithmetic.longAt(s, top, 0); + if (versionedHashIndex >= 0 && versionedHashIndex < versionedHashes.size()) { + final VersionedHash requested = versionedHashes.get(versionedHashIndex); + final byte[] hashBytes = requested.getBytes().toArrayUnsafe(); + StackArithmetic.fromBytesAt(s, top, 0, hashBytes, 0, hashBytes.length); + } else { + StackArithmetic.putAt(s, top, 0, 0L, 0L, 0L, 0L); + } + } else { + StackArithmetic.putAt(s, top, 0, 0L, 0L, 0L, 0L); + } + return new OperationResult(3, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlockHashOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlockHashOperationV2.java new file mode 100644 index 00000000000..cb71e224e0f --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlockHashOperationV2.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 org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.blockhash.BlockHashLookup; +import org.hyperledger.besu.evm.frame.BlockValues; +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.v2.StackArithmetic; + +/** + * EVM v2 BLOCKHASH operation (0x40). + * + *

Pops a block number from the stack and pushes its hash, or zero if the block is out of the + * lookback window (up to 256 blocks prior to the current block). + */ +public class BlockHashOperationV2 extends AbstractOperationV2 { + + /** + * Instantiates a new BlockHash operation. + * + * @param gasCalculator the gas calculator + */ + public BlockHashOperationV2(final GasCalculator gasCalculator) { + super(0x40, "BLOCKHASH", 1, 1, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Execute BLOCKHASH on the v2 long[] stack. + * + * @param frame the message frame + * @param s the stack data + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + final long cost = 20L; + if (!frame.stackHasItems(1)) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + final int top = frame.stackTopV2(); + // If blockArg doesn't fit in a non-negative long, it's out of range + if (!StackArithmetic.fitsInLong(s, top, 0)) { + StackArithmetic.putAt(s, top, 0, 0L, 0L, 0L, 0L); + return new OperationResult(cost, null); + } + + final long soughtBlock = StackArithmetic.longAt(s, top, 0); + final BlockValues blockValues = frame.getBlockValues(); + final long currentBlockNumber = blockValues.getNumber(); + final BlockHashLookup blockHashLookup = frame.getBlockHashLookup(); + + // If the sought block is in the future, the current block, or outside the lookback window, + // return zero. + if (soughtBlock < 0 + || soughtBlock >= currentBlockNumber + || soughtBlock < (currentBlockNumber - blockHashLookup.getLookback())) { + StackArithmetic.putAt(s, top, 0, 0L, 0L, 0L, 0L); + } else { + final Hash blockHash = blockHashLookup.apply(frame, soughtBlock); + final byte[] hashBytes = blockHash.getBytes().toArrayUnsafe(); + StackArithmetic.fromBytesAt(s, top, 0, hashBytes, 0, hashBytes.length); + } + + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ByteOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ByteOperationV2.java new file mode 100644 index 00000000000..d4024e30ee9 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ByteOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 BYTE operation (0x1A). */ +public class ByteOperationV2 extends AbstractFixedCostOperationV2 { + + /** The Byte operation success result. */ + static final OperationResult byteSuccess = new OperationResult(3, null); + + /** + * Instantiates a new Byte operation. + * + * @param gasCalculator the gas calculator + */ + public ByteOperationV2(final GasCalculator gasCalculator) { + super(0x1A, "BYTE", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs BYTE operation. + * + * @param frame the frame + * @param stack the v2 operand stack ({@code long[]} in big-endian limb order) + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.byte_(stack, frame.stackTopV2())); + return byteSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallCodeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallCodeOperationV2.java new file mode 100644 index 00000000000..53130b4fe49 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallCodeOperationV2.java @@ -0,0 +1,104 @@ +/* + * 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.datatypes.Wei; +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.v2.StackArithmetic; + +import org.apache.tuweni.bytes.Bytes; + +/** + * EVM v2 CALLCODE operation (opcode 0xF2). Stack layout: gas, to, value, inOffset, inSize, + * outOffset, outSize → result. + */ +public class CallCodeOperationV2 extends AbstractCallOperationV2 { + + /** + * Instantiates a new CALLCODE operation for EVM v2. + * + * @param gasCalculator the gas calculator + */ + public CallCodeOperationV2(final GasCalculator gasCalculator) { + super(0xF2, "CALLCODE", 7, 1, gasCalculator); + } + + @Override + protected Address to(final long[] s, final int top) { + return StackArithmetic.toAddressAt(s, top, 1); + } + + @Override + protected Wei value(final long[] s, final int top) { + return Wei.wrap(Bytes.wrap(StackArithmetic.getAt(s, top, 2).toBytesBE())); + } + + @Override + protected Wei apparentValue(final MessageFrame frame, final long[] s, final int top) { + return value(s, top); + } + + @Override + protected long inputDataOffset(final long[] s, final int top) { + return StackArithmetic.clampedToLong(s, top, 3); + } + + @Override + protected long inputDataLength(final long[] s, final int top) { + return StackArithmetic.clampedToLong(s, top, 4); + } + + @Override + protected long outputDataOffset(final long[] s, final int top) { + return StackArithmetic.clampedToLong(s, top, 5); + } + + @Override + protected long outputDataLength(final long[] s, final int top) { + return StackArithmetic.clampedToLong(s, top, 6); + } + + @Override + protected Address address(final MessageFrame frame, final long[] s, final int top) { + return frame.getRecipientAddress(); + } + + @Override + protected Address sender(final MessageFrame frame) { + return frame.getRecipientAddress(); + } + + @Override + public long gasAvailableForChildCall(final MessageFrame frame, final long[] s, final int top) { + return gasCalculator().gasAvailableForChildCall(frame, gas(s, top), !value(s, top).isZero()); + } + + /** + * Performs the CALLCODE operation on the v2 stack. + * + * @param frame the current message frame + * @param s the v2 stack array + * @param gasCalculator the gas calculator + * @param evm the EVM + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final GasCalculator gasCalculator, final EVM evm) { + return new CallCodeOperationV2(gasCalculator).execute(frame, evm); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallDataCopyOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallDataCopyOperationV2.java new file mode 100644 index 00000000000..af731bc1a2f --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallDataCopyOperationV2.java @@ -0,0 +1,77 @@ +/* + * 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.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** + * EVM v2 CALLDATACOPY operation (0x37). + * + *

Pops destOffset, sourceOffset, and size from the stack, then copies {@code size} bytes of + * input data starting at {@code sourceOffset} into memory at {@code destOffset}. + */ +public class CallDataCopyOperationV2 extends AbstractOperationV2 { + + /** + * Instantiates a new CallDataCopy operation. + * + * @param gasCalculator the gas calculator + */ + public CallDataCopyOperationV2(final GasCalculator gasCalculator) { + super(0x37, "CALLDATACOPY", 3, 0, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), gasCalculator()); + } + + /** + * Execute CALLDATACOPY on the v2 long[] stack. + * + * @param frame the message frame + * @param s the stack data + * @param gasCalculator the gas calculator + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) { + if (!frame.stackHasItems(3)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final int top = frame.stackTopV2(); + final long memOffset = StackArithmetic.clampedToLong(s, top, 0); + final long sourceOffset = StackArithmetic.clampedToLong(s, top, 1); + final long numBytes = StackArithmetic.clampedToLong(s, top, 2); + frame.setTopV2(top - 3); + + final long cost = gasCalculator.dataCopyOperationGasCost(frame, memOffset, numBytes); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + if (numBytes == 0) { + return new OperationResult(cost, null); + } + + frame.writeMemory(memOffset, sourceOffset, numBytes, frame.getInputData(), true); + + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallDataLoadOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallDataLoadOperationV2.java new file mode 100644 index 00000000000..4c225d91aea --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallDataLoadOperationV2.java @@ -0,0 +1,89 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +import org.apache.tuweni.bytes.Bytes; + +/** + * EVM v2 CALLDATALOAD operation (0x35). + * + *

Pops an offset from the stack and pushes 32 bytes of input data starting at that offset, + * right-padding with zeros if the offset is beyond the end of the input data. + */ +public class CallDataLoadOperationV2 extends AbstractFixedCostOperationV2 { + + private static final Operation.OperationResult CALLDATALOAD_SUCCESS = + new Operation.OperationResult(3, null); + + /** + * Instantiates a new CallDataLoad operation. + * + * @param gasCalculator the gas calculator + */ + public CallDataLoadOperationV2(final GasCalculator gasCalculator) { + super(0x35, "CALLDATALOAD", 1, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Execute CALLDATALOAD on the v2 long[] stack. + * + * @param frame the message frame + * @param s the stack data + * @return the operation result + */ + public static Operation.OperationResult staticOperation( + final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(1)) return UNDERFLOW_RESPONSE; + final int top = frame.stackTopV2(); + final int off = (top - 1) << 2; + + // If the offset doesn't fit in a non-negative int, result is zero + if (s[off] != 0 || s[off + 1] != 0 || s[off + 2] != 0 || (s[off + 3] >>> 31) != 0) { + s[off] = 0; + s[off + 1] = 0; + s[off + 2] = 0; + s[off + 3] = 0; + return CALLDATALOAD_SUCCESS; + } + + final int offset = (int) s[off + 3]; + final Bytes data = frame.getInputData(); + if (offset < data.size()) { + final byte[] result = new byte[32]; + final int toCopy = Math.min(32, data.size() - offset); + System.arraycopy(data.slice(offset, toCopy).toArrayUnsafe(), 0, result, 0, toCopy); + StackArithmetic.fromBytesAt(s, top, 0, result, 0, 32); + } else { + s[off] = 0; + s[off + 1] = 0; + s[off + 2] = 0; + s[off + 3] = 0; + } + + return CALLDATALOAD_SUCCESS; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallDataSizeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallDataSizeOperationV2.java new file mode 100644 index 00000000000..57958499a15 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallDataSizeOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 CALLDATASIZE operation — pushes the size of the call input data onto the stack. */ +public class CallDataSizeOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult callDataSizeSuccess = new OperationResult(2, null); + + /** + * Instantiates a new Call data size operation. + * + * @param gasCalculator the gas calculator + */ + public CallDataSizeOperationV2(final GasCalculator gasCalculator) { + super(0x36, "CALLDATASIZE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs the CALLDATASIZE operation. + * + * @param frame the message frame + * @param stack the v2 long[] stack + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTopV2( + StackArithmetic.pushLong(stack, frame.stackTopV2(), frame.getInputData().size())); + return callDataSizeSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallOperationV2.java new file mode 100644 index 00000000000..666e3e2ac87 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallOperationV2.java @@ -0,0 +1,110 @@ +/* + * 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.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.v2.StackArithmetic; + +import org.apache.tuweni.bytes.Bytes; + +/** + * EVM v2 CALL operation (opcode 0xF1). Stack layout: gas, to, value, inOffset, inSize, outOffset, + * outSize → result. + */ +public class CallOperationV2 extends AbstractCallOperationV2 { + + /** + * Instantiates a new CALL operation for EVM v2. + * + * @param gasCalculator the gas calculator + */ + public CallOperationV2(final GasCalculator gasCalculator) { + super(0xF1, "CALL", 7, 1, gasCalculator); + } + + @Override + protected Address to(final long[] s, final int top) { + return StackArithmetic.toAddressAt(s, top, 1); + } + + @Override + protected Wei value(final long[] s, final int top) { + return Wei.wrap(Bytes.wrap(StackArithmetic.getAt(s, top, 2).toBytesBE())); + } + + @Override + protected Wei apparentValue(final MessageFrame frame, final long[] s, final int top) { + return value(s, top); + } + + @Override + protected long inputDataOffset(final long[] s, final int top) { + return StackArithmetic.clampedToLong(s, top, 3); + } + + @Override + protected long inputDataLength(final long[] s, final int top) { + return StackArithmetic.clampedToLong(s, top, 4); + } + + @Override + protected long outputDataOffset(final long[] s, final int top) { + return StackArithmetic.clampedToLong(s, top, 5); + } + + @Override + protected long outputDataLength(final long[] s, final int top) { + return StackArithmetic.clampedToLong(s, top, 6); + } + + @Override + protected Address address(final MessageFrame frame, final long[] s, final int top) { + return to(s, top); + } + + @Override + protected Address sender(final MessageFrame frame) { + return frame.getRecipientAddress(); + } + + @Override + public long gasAvailableForChildCall(final MessageFrame frame, final long[] s, final int top) { + return gasCalculator().gasAvailableForChildCall(frame, gas(s, top), !value(s, top).isZero()); + } + + /** + * Performs the CALL operation on the v2 stack. + * + * @param frame the current message frame + * @param s the v2 stack array + * @param gasCalculator the gas calculator + * @param evm the EVM + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final GasCalculator gasCalculator, final EVM evm) { + if (frame.isStatic() + && !Wei.wrap(Bytes.wrap(StackArithmetic.getAt(s, frame.stackTopV2(), 2).toBytesBE())) + .isZero()) { + return new OperationResult(0, ExceptionalHaltReason.ILLEGAL_STATE_CHANGE); + } + return new CallOperationV2(gasCalculator).execute(frame, 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 new file mode 100644 index 00000000000..407b7ff34c7 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java @@ -0,0 +1,55 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 CALLVALUE operation — pushes the apparent call value onto the stack. */ +public class CallValueOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult callValueSuccess = new OperationResult(2, null); + + /** + * 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) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs the CALLVALUE operation. + * + * @param frame the message frame + * @param stack the v2 long[] stack + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.pushWei(stack, frame.stackTopV2(), frame.getApparentValue())); + return callValueSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallerOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallerOperationV2.java new file mode 100644 index 00000000000..c927f172ba9 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallerOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 CALLER operation — pushes the sender address onto the stack. */ +public class CallerOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult callerSuccess = new OperationResult(2, null); + + /** + * Instantiates a new Caller operation. + * + * @param gasCalculator the gas calculator + */ + public CallerOperationV2(final GasCalculator gasCalculator) { + super(0x33, "CALLER", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs the CALLER operation. + * + * @param frame the message frame + * @param stack the v2 long[] stack + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTopV2( + StackArithmetic.pushAddress(stack, frame.stackTopV2(), frame.getSenderAddress())); + return callerSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ChainIdOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ChainIdOperationV2.java new file mode 100644 index 00000000000..11d632da728 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ChainIdOperationV2.java @@ -0,0 +1,76 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +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; +import org.apache.tuweni.bytes.Bytes32; + +/** + * EVM v2 CHAINID operation — pushes the pre-computed chain ID onto the stack (Istanbul+). + * + *

The four 64-bit limbs of the chain ID are computed once at construction time. + */ +public class ChainIdOperationV2 extends AbstractFixedCostOperationV2 { + + private static final VarHandle LONG_BE = + MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN); + + /** CHAINID opcode number. */ + public static final int OPCODE = 0x46; + + private final long chainIdU3; + private final long chainIdU2; + private final long chainIdU1; + private final long chainIdU0; + + /** + * Instantiates a new Chain ID operation. + * + * @param gasCalculator the gas calculator + * @param chainId the chain ID (left-padded to 32 bytes big-endian) + */ + public ChainIdOperationV2(final GasCalculator gasCalculator, final Bytes chainId) { + super(OPCODE, "CHAINID", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + final byte[] b = Bytes32.leftPad(chainId).toArrayUnsafe(); + this.chainIdU3 = (long) LONG_BE.get(b, 0); + this.chainIdU2 = (long) LONG_BE.get(b, 8); + this.chainIdU1 = (long) LONG_BE.get(b, 16); + this.chainIdU0 = (long) LONG_BE.get(b, 24); + } + + @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 int dst = top << 2; + s[dst] = chainIdU3; + s[dst + 1] = chainIdU2; + s[dst + 2] = chainIdU1; + s[dst + 3] = chainIdU0; + frame.setTopV2(top + 1); + return successResponse; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ClzOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ClzOperationV2.java new file mode 100644 index 00000000000..811c949b362 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ClzOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The Clz (Count Leading Zeros) operation. Introduced in Osaka. */ +public class ClzOperationV2 extends AbstractFixedCostOperationV2 { + + /** The Clz operation success result. */ + static final OperationResult clzSuccess = new OperationResult(5, null); + + /** + * Instantiates a new Clz operation. + * + * @param gasCalculator the gas calculator + */ + public ClzOperationV2(final GasCalculator gasCalculator) { + super(0x1e, "CLZ", 1, 1, gasCalculator, gasCalculator.getLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs clz operation. + * + * @param frame the frame + * @param stack the v2 operand stack ({@code long[]} in big-endian limb order) + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasItems(1)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.clz(stack, frame.stackTopV2())); + return clzSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CodeCopyOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CodeCopyOperationV2.java new file mode 100644 index 00000000000..e02fca7bdfe --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CodeCopyOperationV2.java @@ -0,0 +1,73 @@ +/* + * 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.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** + * EVM v2 CODECOPY operation (0x39). + * + *

Pops destOffset, sourceOffset, and size from the stack, then copies {@code size} bytes of the + * current executing contract's code into memory at {@code destOffset}. + */ +public class CodeCopyOperationV2 extends AbstractOperationV2 { + + /** + * Instantiates a new CodeCopy operation. + * + * @param gasCalculator the gas calculator + */ + public CodeCopyOperationV2(final GasCalculator gasCalculator) { + super(0x39, "CODECOPY", 3, 0, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), gasCalculator()); + } + + /** + * Execute CODECOPY on the v2 long[] stack. + * + * @param frame the message frame + * @param s the stack data + * @param gasCalculator the gas calculator + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) { + if (!frame.stackHasItems(3)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final int top = frame.stackTopV2(); + final long memOffset = StackArithmetic.clampedToLong(s, top, 0); + final long sourceOffset = StackArithmetic.clampedToLong(s, top, 1); + final long numBytes = StackArithmetic.clampedToLong(s, top, 2); + frame.setTopV2(top - 3); + + final long cost = gasCalculator.dataCopyOperationGasCost(frame, memOffset, numBytes); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + frame.writeMemory(memOffset, sourceOffset, numBytes, frame.getCode().getBytes(), true); + + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CodeSizeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CodeSizeOperationV2.java new file mode 100644 index 00000000000..c121dd837a8 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CodeSizeOperationV2.java @@ -0,0 +1,55 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 CODESIZE operation — pushes the size of the executing contract code onto the stack. */ +public class CodeSizeOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult codeSizeSuccess = new OperationResult(2, null); + + /** + * Instantiates a new Code size operation. + * + * @param gasCalculator the gas calculator + */ + public CodeSizeOperationV2(final GasCalculator gasCalculator) { + super(0x38, "CODESIZE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs the CODESIZE operation. + * + * @param frame the message frame + * @param stack the v2 long[] stack + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.pushLong(stack, frame.stackTopV2(), frame.getCode().getSize())); + return codeSizeSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CoinbaseOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CoinbaseOperationV2.java new file mode 100644 index 00000000000..c9f5fc5b82f --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CoinbaseOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 COINBASE operation — pushes the block beneficiary address onto the stack. */ +public class CoinbaseOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult coinbaseSuccess = new OperationResult(2, null); + + /** + * Instantiates a new Coinbase operation. + * + * @param gasCalculator the gas calculator + */ + public CoinbaseOperationV2(final GasCalculator gasCalculator) { + super(0x41, "COINBASE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs the COINBASE operation. + * + * @param frame the message frame + * @param stack the v2 long[] stack + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTopV2( + StackArithmetic.pushAddress(stack, frame.stackTopV2(), frame.getMiningBeneficiary())); + return coinbaseSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Create2OperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Create2OperationV2.java new file mode 100644 index 00000000000..01912658b8d --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Create2OperationV2.java @@ -0,0 +1,103 @@ +/* + * 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.hyperledger.besu.crypto.Hash.keccak256; +import static org.hyperledger.besu.evm.internal.Words.clampedAdd; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.EvmSpecVersion; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.InvalidOperation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +import java.util.function.Supplier; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +/** + * EVM v2 CREATE2 operation (opcode 0xF5, Constantinople+). Stack layout: value, offset, size, salt + * → contractAddress. + */ +public class Create2OperationV2 extends AbstractCreateOperationV2 { + + private static final Bytes PREFIX = Bytes.fromHexString("0xFF"); + + /** + * Instantiates a new CREATE2 operation for EVM v2. + * + * @param gasCalculator the gas calculator + */ + public Create2OperationV2(final GasCalculator gasCalculator) { + super(0xF5, "CREATE2", 4, 1, gasCalculator); + } + + @Override + protected long cost( + final MessageFrame frame, final long[] s, final int top, final Supplier unused) { + final int inputOffset = StackArithmetic.clampedToInt(s, top, 1); + final int inputSize = StackArithmetic.clampedToInt(s, top, 2); + return clampedAdd( + clampedAdd( + gasCalculator().txCreateCost(), + gasCalculator().memoryExpansionGasCost(frame, inputOffset, inputSize)), + clampedAdd( + gasCalculator().createKeccakCost(inputSize), gasCalculator().initcodeCost(inputSize))); + } + + @Override + protected Address generateTargetContractAddress( + final MessageFrame frame, final long[] s, final int top, final Code initcode) { + final Address sender = frame.getRecipientAddress(); + final byte[] saltBytes = new byte[32]; + StackArithmetic.toBytesAt(s, top, 3, saltBytes); + final Bytes32 salt = Bytes32.wrap(saltBytes); + final Bytes32 hash = + keccak256( + Bytes.concatenate(PREFIX, sender.getBytes(), salt, initcode.getCodeHash().getBytes())); + return Address.extract(hash); + } + + @Override + protected Code getInitCode(final MessageFrame frame, final EVM evm) { + final long[] s = frame.stackDataV2(); + final int top = frame.stackTopV2(); + final long inputOffset = StackArithmetic.clampedToLong(s, top, 1); + final long inputSize = StackArithmetic.clampedToLong(s, top, 2); + final Bytes inputData = frame.readMemory(inputOffset, inputSize); + return new Code(inputData); + } + + /** + * Performs the CREATE2 operation on the v2 stack. Only available from Constantinople onwards. + * + * @param frame the current message frame + * @param s the v2 stack array + * @param gasCalculator the gas calculator + * @param evm the EVM + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final GasCalculator gasCalculator, final EVM evm) { + if (EvmSpecVersion.CONSTANTINOPLE.ordinal() > evm.getEvmVersion().ordinal()) { + return InvalidOperation.invalidOperationResult(0xF5); + } + return new Create2OperationV2(gasCalculator).execute(frame, evm); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CreateOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CreateOperationV2.java new file mode 100644 index 00000000000..c99dee056b0 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CreateOperationV2.java @@ -0,0 +1,85 @@ +/* + * 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.hyperledger.besu.evm.internal.Words.clampedAdd; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.Code; +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.v2.StackArithmetic; + +import java.util.function.Supplier; + +import org.apache.tuweni.bytes.Bytes; + +/** EVM v2 CREATE operation (opcode 0xF0). Stack layout: value, offset, size → contractAddress. */ +public class CreateOperationV2 extends AbstractCreateOperationV2 { + + /** + * Instantiates a new CREATE operation for EVM v2. + * + * @param gasCalculator the gas calculator + */ + public CreateOperationV2(final GasCalculator gasCalculator) { + super(0xF0, "CREATE", 3, 1, gasCalculator); + } + + @Override + protected long cost( + final MessageFrame frame, final long[] s, final int top, final Supplier unused) { + final int inputOffset = StackArithmetic.clampedToInt(s, top, 1); + final int inputSize = StackArithmetic.clampedToInt(s, top, 2); + return clampedAdd( + clampedAdd( + gasCalculator().txCreateCost(), + gasCalculator().memoryExpansionGasCost(frame, inputOffset, inputSize)), + gasCalculator().initcodeCost(inputSize)); + } + + @Override + protected Address generateTargetContractAddress( + final MessageFrame frame, final long[] s, final int top, final Code initcode) { + final Account sender = getAccount(frame.getRecipientAddress(), frame); + return Address.contractAddress(frame.getRecipientAddress(), sender.getNonce() - 1L); + } + + @Override + protected Code getInitCode(final MessageFrame frame, final EVM evm) { + final long[] s = frame.stackDataV2(); + final int top = frame.stackTopV2(); + final long inputOffset = StackArithmetic.clampedToLong(s, top, 1); + final long inputSize = StackArithmetic.clampedToLong(s, top, 2); + final Bytes inputData = frame.readMemory(inputOffset, inputSize); + return new Code(inputData); + } + + /** + * Performs the CREATE operation on the v2 stack. + * + * @param frame the current message frame + * @param s the v2 stack array + * @param gasCalculator the gas calculator + * @param evm the EVM + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final GasCalculator gasCalculator, final EVM evm) { + return new CreateOperationV2(gasCalculator).execute(frame, evm); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DelegateCallOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DelegateCallOperationV2.java new file mode 100644 index 00000000000..e7b4221c883 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DelegateCallOperationV2.java @@ -0,0 +1,112 @@ +/* + * 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.datatypes.Wei; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.EvmSpecVersion; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.InvalidOperation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** + * EVM v2 DELEGATECALL operation (opcode 0xF4, Homestead+). Stack layout: gas, to, inOffset, inSize, + * outOffset, outSize → result. + */ +public class DelegateCallOperationV2 extends AbstractCallOperationV2 { + + /** + * Instantiates a new DELEGATECALL operation for EVM v2. + * + * @param gasCalculator the gas calculator + */ + public DelegateCallOperationV2(final GasCalculator gasCalculator) { + super(0xF4, "DELEGATECALL", 6, 1, gasCalculator); + } + + @Override + protected Address to(final long[] s, final int top) { + return StackArithmetic.toAddressAt(s, top, 1); + } + + @Override + protected Wei value(final long[] s, final int top) { + return Wei.ZERO; + } + + @Override + protected Wei apparentValue(final MessageFrame frame, final long[] s, final int top) { + return frame.getApparentValue(); + } + + @Override + protected long inputDataOffset(final long[] s, final int top) { + return StackArithmetic.clampedToLong(s, top, 2); + } + + @Override + protected long inputDataLength(final long[] s, final int top) { + return StackArithmetic.clampedToLong(s, top, 3); + } + + @Override + protected long outputDataOffset(final long[] s, final int top) { + return StackArithmetic.clampedToLong(s, top, 4); + } + + @Override + protected long outputDataLength(final long[] s, final int top) { + return StackArithmetic.clampedToLong(s, top, 5); + } + + @Override + protected Address address(final MessageFrame frame, final long[] s, final int top) { + return frame.getRecipientAddress(); + } + + @Override + protected Address sender(final MessageFrame frame) { + return frame.getSenderAddress(); + } + + @Override + public long gasAvailableForChildCall(final MessageFrame frame, final long[] s, final int top) { + return gasCalculator().gasAvailableForChildCall(frame, gas(s, top), false); + } + + @Override + protected boolean isDelegate() { + return true; + } + + /** + * Performs the DELEGATECALL operation on the v2 stack. Only available from Homestead onwards. + * + * @param frame the current message frame + * @param s the v2 stack array + * @param gasCalculator the gas calculator + * @param evm the EVM + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final GasCalculator gasCalculator, final EVM evm) { + if (EvmSpecVersion.HOMESTEAD.ordinal() > evm.getEvmVersion().ordinal()) { + return InvalidOperation.invalidOperationResult(0xF4); + } + return new DelegateCallOperationV2(gasCalculator).execute(frame, evm); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DifficultyOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DifficultyOperationV2.java new file mode 100644 index 00000000000..52a8675f726 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DifficultyOperationV2.java @@ -0,0 +1,59 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** + * EVM v2 DIFFICULTY operation — pushes the block difficulty value onto the stack (pre-Paris forks). + */ +public class DifficultyOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult difficultySuccess = new OperationResult(2, null); + + /** + * Instantiates a new Difficulty operation. + * + * @param gasCalculator the gas calculator + */ + public DifficultyOperationV2(final GasCalculator gasCalculator) { + super(0x44, "DIFFICULTY", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs the DIFFICULTY operation. + * + * @param frame the message frame + * @param stack the v2 long[] stack + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + final byte[] diffBytes = frame.getBlockValues().getDifficultyBytes().toArrayUnsafe(); + frame.setTopV2( + StackArithmetic.pushFromBytes(stack, frame.stackTopV2(), diffBytes, 0, diffBytes.length)); + return difficultySuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DivOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DivOperationV2.java new file mode 100644 index 00000000000..f408d94f113 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DivOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The Div operation. */ +public class DivOperationV2 extends AbstractFixedCostOperationV2 { + + /** The Div operation success result. */ + static final OperationResult divSuccess = new OperationResult(5, null); + + /** + * Instantiates a new Div operation. + * + * @param gasCalculator the gas calculator + */ + public DivOperationV2(final GasCalculator gasCalculator) { + super(0x04, "DIV", 2, 1, gasCalculator, gasCalculator.getLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs div operation. + * + * @param frame the frame + * @param stack the v2 operand stack ({@code long[]} in big-endian limb order) + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.div(stack, frame.stackTopV2())); + return divSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DupNOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DupNOperationV2.java new file mode 100644 index 00000000000..5d23d2e8fe7 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DupNOperationV2.java @@ -0,0 +1,92 @@ +/* + * 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.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Eip8024Decoder; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** + * EVM v2 DUPN operation (0xE6, EIP-8024). + * + *

Duplicates the n'th stack item to the top of the stack, where n is decoded from a 1-byte + * immediate operand. PC increment is 2. Gas cost is veryLow tier (3). Requires Amsterdam fork or + * later. + */ +public class DupNOperationV2 extends AbstractFixedCostOperationV2 { + + /** The DUPN opcode value. */ + public static final int OPCODE = 0xe6; + + private static final OperationResult DUPN_SUCCESS = new OperationResult(3, null, 2); + + private static final OperationResult INVALID_IMMEDIATE = + new OperationResult(3, ExceptionalHaltReason.INVALID_OPERATION, 2); + + private static final OperationResult DUPN_UNDERFLOW = + new OperationResult(3, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS, 2); + + private static final OperationResult DUPN_OVERFLOW = + new OperationResult(3, ExceptionalHaltReason.TOO_MANY_STACK_ITEMS, 2); + + /** + * Instantiates a new DUPN operation. + * + * @param gasCalculator the gas calculator + */ + public DupNOperationV2(final GasCalculator gasCalculator) { + super(OPCODE, "DUPN", 0, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation( + frame, frame.stackDataV2(), frame.getCode().getBytes().toArrayUnsafe(), frame.getPC()); + } + + /** + * Performs DUPN operation. + * + * @param frame the message frame + * @param s the stack data array + * @param code the bytecode array + * @param pc the current program counter + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final byte[] code, final int pc) { + final int imm = (pc + 1 >= code.length) ? 0 : code[pc + 1] & 0xFF; + + if (!Eip8024Decoder.VALID_SINGLE[imm]) { + return INVALID_IMMEDIATE; + } + + final int n = Eip8024Decoder.DECODE_SINGLE[imm]; + + if (!frame.stackHasItems(n)) { + return DUPN_UNDERFLOW; + } + if (!frame.stackHasSpace(1)) { + return DUPN_OVERFLOW; + } + frame.setTopV2(StackArithmetic.dup(s, frame.stackTopV2(), n)); + return DUPN_SUCCESS; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DupOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DupOperationV2.java new file mode 100644 index 00000000000..91fd29fcce7 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/DupOperationV2.java @@ -0,0 +1,76 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** + * EVM v2 DUP1-16 operation (opcodes 0x80–0x8F). + * + *

Duplicates the item at depth {@code index} (1-based) to the top of the stack. Gas cost is + * veryLow tier (3). + */ +public class DupOperationV2 extends AbstractFixedCostOperationV2 { + + /** The DUP opcode base (DUP1 = 0x80, so base = 0x7F). */ + public static final int DUP_BASE = 0x7F; + + static final OperationResult DUP_SUCCESS = new OperationResult(3, null); + + private final int index; + + /** + * Instantiates a new Dup operation. + * + * @param index the 1-based depth to duplicate (1–16) + * @param gasCalculator the gas calculator + */ + public DupOperationV2(final int index, final GasCalculator gasCalculator) { + super( + DUP_BASE + index, + "DUP" + index, + index, + index + 1, + gasCalculator, + gasCalculator.getVeryLowTierGasCost()); + this.index = index; + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), index); + } + + /** + * Performs DUP operation. + * + * @param frame the frame + * @param s the stack data array + * @param index the 1-based depth to duplicate + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final int index) { + if (!frame.stackHasItems(index)) return UNDERFLOW_RESPONSE; + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.dup(s, frame.stackTopV2(), index)); + return DUP_SUCCESS; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/EqOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/EqOperationV2.java new file mode 100644 index 00000000000..d47ec17bd5f --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/EqOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The Eq operation. */ +public class EqOperationV2 extends AbstractFixedCostOperationV2 { + + /** The Eq operation success result. */ + static final OperationResult eqSuccess = new OperationResult(3, null); + + /** + * Instantiates a new Eq operation. + * + * @param gasCalculator the gas calculator + */ + public EqOperationV2(final GasCalculator gasCalculator) { + super(0x14, "EQ", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs eq operation. + * + * @param frame the frame + * @param stack the v2 operand stack ({@code long[]} in big-endian limb order) + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.eq(stack, frame.stackTopV2())); + return eqSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExchangeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExchangeOperationV2.java new file mode 100644 index 00000000000..bc31c421733 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExchangeOperationV2.java @@ -0,0 +1,89 @@ +/* + * 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.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Eip8024Decoder; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** + * EVM v2 EXCHANGE operation (0xE8, EIP-8024). + * + *

Swaps the (n+1)'th stack item with the (m+1)'th stack item, where n and m are decoded from a + * 1-byte immediate operand. PC increment is 2. Gas cost is veryLow tier (3). Requires Amsterdam + * fork or later. + */ +public class ExchangeOperationV2 extends AbstractFixedCostOperationV2 { + + /** The EXCHANGE opcode value. */ + public static final int OPCODE = 0xe8; + + private static final OperationResult EXCHANGE_SUCCESS = new OperationResult(3, null, 2); + + private static final OperationResult INVALID_IMMEDIATE = + new OperationResult(3, ExceptionalHaltReason.INVALID_OPERATION, 2); + + private static final OperationResult EXCHANGE_UNDERFLOW = + new OperationResult(3, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS, 2); + + /** + * Instantiates a new EXCHANGE operation. + * + * @param gasCalculator the gas calculator + */ + public ExchangeOperationV2(final GasCalculator gasCalculator) { + super(OPCODE, "EXCHANGE", 0, 0, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation( + frame, frame.stackDataV2(), frame.getCode().getBytes().toArrayUnsafe(), frame.getPC()); + } + + /** + * Performs EXCHANGE operation. + * + * @param frame the message frame + * @param s the stack data array + * @param code the bytecode array + * @param pc the current program counter + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final byte[] code, final int pc) { + final int imm = (pc + 1 >= code.length) ? 0 : code[pc + 1] & 0xFF; + + final int packed = Eip8024Decoder.DECODE_PAIR_PACKED[imm]; + + if (packed == Eip8024Decoder.INVALID_PAIR) { + return INVALID_IMMEDIATE; + } + + final int n = packed & 0xFF; + final int m = (packed >>> 8) & 0xFF; + + if (!frame.stackHasItems(Math.max(n, m) + 1)) { + return EXCHANGE_UNDERFLOW; + } + StackArithmetic.exchange(s, frame.stackTopV2(), n, m); + return EXCHANGE_SUCCESS; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExpOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExpOperationV2.java new file mode 100644 index 00000000000..e7e34096b0c --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExpOperationV2.java @@ -0,0 +1,61 @@ +/* + * 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.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The Exp operation. */ +public class ExpOperationV2 extends AbstractOperationV2 { + + /** + * Instantiates a new Exp operation. + * + * @param gasCalculator the gas calculator + */ + public ExpOperationV2(final GasCalculator gasCalculator) { + super(0x0A, "EXP", 2, 1, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), gasCalculator()); + } + + /** + * Performs EXP operation. + * + * @param frame the frame + * @param stack the stack data array + * @param gasCalculator the gas calculator + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] stack, final GasCalculator gasCalculator) { + if (!frame.stackHasItems(2)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final int byteCount = StackArithmetic.byteLengthAt(stack, frame.stackTopV2(), 1); + final long gasCost = gasCalculator.expOperationGasCost(byteCount); + if (frame.getRemainingGas() < gasCost) { + return new OperationResult(gasCost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + frame.setTopV2(StackArithmetic.exp(stack, frame.stackTopV2())); + return new OperationResult(gasCost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExtCodeCopyOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExtCodeCopyOperationV2.java new file mode 100644 index 00000000000..41c9a4c44d7 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExtCodeCopyOperationV2.java @@ -0,0 +1,112 @@ +/* + * 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.hyperledger.besu.evm.internal.Words.clampedAdd; + +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.v2.StackArithmetic; + +import org.apache.tuweni.bytes.Bytes; + +/** + * EVM v2 EXTCODECOPY operation (0x3C). + * + *

Pops address, destOffset, sourceOffset, and size from the stack, applies warm/cold account + * access cost, and copies the external contract's code into memory. + */ +public class ExtCodeCopyOperationV2 extends AbstractOperationV2 { + + /** + * Instantiates a new ExtCodeCopy operation. + * + * @param gasCalculator the gas calculator + */ + public ExtCodeCopyOperationV2(final GasCalculator gasCalculator) { + super(0x3C, "EXTCODECOPY", 4, 0, gasCalculator); + } + + /** + * Cost of EXTCODECOPY including warm/cold access. + * + * @param frame the frame + * @param memOffset the memory destination offset + * @param length the number of bytes to copy + * @param accountIsWarm whether the address was already warm + * @return the total gas cost + */ + protected long cost( + final MessageFrame frame, + final long memOffset, + final long length, + final boolean accountIsWarm) { + return clampedAdd( + gasCalculator().extCodeCopyOperationGasCost(frame, memOffset, length), + accountIsWarm + ? gasCalculator().getWarmStorageReadCost() + : gasCalculator().getColdAccountAccessCost()); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), gasCalculator()); + } + + /** + * Execute EXTCODECOPY on the v2 long[] stack. + * + * @param frame the message frame + * @param s the stack data + * @param gasCalculator the gas calculator + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) { + if (!frame.stackHasItems(4)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final int top = frame.stackTopV2(); + final Address address = StackArithmetic.toAddressAt(s, top, 0); + final long memOffset = StackArithmetic.clampedToLong(s, top, 1); + final long sourceOffset = StackArithmetic.clampedToLong(s, top, 2); + final long numBytes = StackArithmetic.clampedToLong(s, top, 3); + frame.setTopV2(top - 4); + + final boolean accountIsWarm = + frame.warmUpAddress(address) || gasCalculator.isPrecompile(address); + final long cost = + clampedAdd( + gasCalculator.extCodeCopyOperationGasCost(frame, memOffset, numBytes), + accountIsWarm + ? gasCalculator.getWarmStorageReadCost() + : gasCalculator.getColdAccountAccessCost()); + + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + final Account account = getAccount(address, frame); + final Bytes code = account != null ? account.getCode() : Bytes.EMPTY; + + frame.writeMemory(memOffset, sourceOffset, numBytes, code); + + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExtCodeHashOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExtCodeHashOperationV2.java new file mode 100644 index 00000000000..759ffb49b9a --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExtCodeHashOperationV2.java @@ -0,0 +1,97 @@ +/* + * 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.v2.StackArithmetic; + +/** + * EVM v2 EXTCODEHASH operation (0x3F, Constantinople+). + * + *

Pops an address from the stack and replaces it with the Keccak-256 hash of the external + * account's code. Returns zero for empty or non-existent accounts. Applies warm/cold access cost. + */ +public class ExtCodeHashOperationV2 extends AbstractOperationV2 { + + /** + * Instantiates a new ExtCodeHash operation. + * + * @param gasCalculator the gas calculator + */ + public ExtCodeHashOperationV2(final GasCalculator gasCalculator) { + super(0x3F, "EXTCODEHASH", 1, 1, gasCalculator); + } + + /** + * Gas cost including warm/cold access. + * + * @param accountIsWarm whether the account was already warm + * @return the total gas cost + */ + protected long cost(final boolean accountIsWarm) { + return gasCalculator().extCodeHashOperationGasCost() + + (accountIsWarm + ? gasCalculator().getWarmStorageReadCost() + : gasCalculator().getColdAccountAccessCost()); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), gasCalculator()); + } + + /** + * Execute EXTCODEHASH on the v2 long[] stack. + * + * @param frame the message frame + * @param s the stack data + * @param gasCalculator the gas calculator + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) { + final long warmCost = + gasCalculator.extCodeHashOperationGasCost() + gasCalculator.getWarmStorageReadCost(); + final long coldCost = + gasCalculator.extCodeHashOperationGasCost() + gasCalculator.getColdAccountAccessCost(); + if (!frame.stackHasItems(1)) { + return new OperationResult(warmCost, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final int top = frame.stackTopV2(); + final Address address = StackArithmetic.toAddressAt(s, top, 0); + final boolean accountIsWarm = + frame.warmUpAddress(address) || gasCalculator.isPrecompile(address); + final long cost = accountIsWarm ? warmCost : coldCost; + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + final Account account = getAccount(address, frame); + + // Overwrite in place (pop 1, push 1) + if (account == null || account.isEmpty()) { + StackArithmetic.putAt(s, top, 0, 0L, 0L, 0L, 0L); + } else { + final byte[] hashBytes = account.getCodeHash().getBytes().toArrayUnsafe(); + StackArithmetic.fromBytesAt(s, top, 0, hashBytes, 0, hashBytes.length); + } + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExtCodeSizeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExtCodeSizeOperationV2.java new file mode 100644 index 00000000000..f0708bfc954 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ExtCodeSizeOperationV2.java @@ -0,0 +1,91 @@ +/* + * 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.v2.StackArithmetic; + +/** + * EVM v2 EXTCODESIZE operation (0x3B). + * + *

Pops an address from the stack and replaces it with the external account's code size. Applies + * warm/cold account access cost. + */ +public class ExtCodeSizeOperationV2 extends AbstractOperationV2 { + + /** + * Instantiates a new ExtCodeSize operation. + * + * @param gasCalculator the gas calculator + */ + public ExtCodeSizeOperationV2(final GasCalculator gasCalculator) { + super(0x3B, "EXTCODESIZE", 1, 1, gasCalculator); + } + + /** + * Gas cost including warm/cold access. + * + * @param accountIsWarm whether the account was already warm + * @return the total gas cost + */ + protected long cost(final boolean accountIsWarm) { + return gasCalculator().getExtCodeSizeOperationGasCost() + + (accountIsWarm + ? gasCalculator().getWarmStorageReadCost() + : gasCalculator().getColdAccountAccessCost()); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), gasCalculator()); + } + + /** + * Execute EXTCODESIZE on the v2 long[] stack. + * + * @param frame the message frame + * @param s the stack data + * @param gasCalculator the gas calculator + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) { + final long warmCost = + gasCalculator.getExtCodeSizeOperationGasCost() + gasCalculator.getWarmStorageReadCost(); + final long coldCost = + gasCalculator.getExtCodeSizeOperationGasCost() + gasCalculator.getColdAccountAccessCost(); + if (!frame.stackHasItems(1)) { + return new OperationResult(warmCost, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final int top = frame.stackTopV2(); + final Address address = StackArithmetic.toAddressAt(s, top, 0); + final boolean accountIsWarm = + frame.warmUpAddress(address) || gasCalculator.isPrecompile(address); + final long cost = accountIsWarm ? warmCost : coldCost; + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + final Account account = getAccount(address, frame); + // Overwrite in place (pop 1, push 1) + final long codeSize = account == null ? 0L : account.getCode().size(); + StackArithmetic.putAt(s, top, 0, 0L, 0L, 0L, codeSize); + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasLimitOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasLimitOperationV2.java new file mode 100644 index 00000000000..65cb9590c3b --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasLimitOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 GASLIMIT operation — pushes the block gas limit onto the stack. */ +public class GasLimitOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult gasLimitSuccess = new OperationResult(2, null); + + /** + * Instantiates a new Gas limit operation. + * + * @param gasCalculator the gas calculator + */ + public GasLimitOperationV2(final GasCalculator gasCalculator) { + super(0x45, "GASLIMIT", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs the GASLIMIT operation. + * + * @param frame the message frame + * @param stack the v2 long[] stack + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTopV2( + StackArithmetic.pushLong(stack, frame.stackTopV2(), frame.getBlockValues().getGasLimit())); + return gasLimitSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasOperationV2.java new file mode 100644 index 00000000000..af48a72eca7 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasOperationV2.java @@ -0,0 +1,48 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** + * EVM v2 GAS operation — pushes the amount of available gas remaining (after this instruction's + * cost) onto the stack. + */ +public class GasOperationV2 extends AbstractFixedCostOperationV2 { + + /** + * Instantiates a new Gas operation. + * + * @param gasCalculator the gas calculator + */ + public GasOperationV2(final GasCalculator gasCalculator) { + super(0x5A, "GAS", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + // Gas remaining after deducting this instruction's cost + frame.setTopV2( + StackArithmetic.pushLong( + frame.stackDataV2(), frame.stackTopV2(), frame.getRemainingGas() - gasCost)); + 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..2197637499e --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java @@ -0,0 +1,55 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 GASPRICE operation — pushes the gas price onto the stack. */ +public class GasPriceOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult gasPriceSuccess = new OperationResult(2, null); + + /** + * 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) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs the GASPRICE operation. + * + * @param frame the message frame + * @param stack the v2 long[] stack + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.pushWei(stack, frame.stackTopV2(), frame.getGasPrice())); + return gasPriceSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GtOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GtOperationV2.java new file mode 100644 index 00000000000..2588e14fac1 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GtOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The Gt operation. */ +public class GtOperationV2 extends AbstractFixedCostOperationV2 { + + /** The Gt operation success result. */ + static final OperationResult gtSuccess = new OperationResult(3, null); + + /** + * Instantiates a new Gt operation. + * + * @param gasCalculator the gas calculator + */ + public GtOperationV2(final GasCalculator gasCalculator) { + super(0x11, "GT", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs gt operation. + * + * @param frame the frame + * @param stack the v2 operand stack ({@code long[]} in big-endian limb order) + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.gt(stack, frame.stackTopV2())); + return gtSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/InvalidOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/InvalidOperationV2.java new file mode 100644 index 00000000000..98df3487d21 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/InvalidOperationV2.java @@ -0,0 +1,45 @@ +/* + * 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.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +/** EVM v2 INVALID operation. Always halts with INVALID_OPERATION. */ +public class InvalidOperationV2 extends AbstractOperationV2 { + + /** The constant OPCODE. */ + public static final int OPCODE = 0xFE; + + /** The constant INVALID_RESULT. */ + public static final OperationResult INVALID_RESULT = + new OperationResult(0, ExceptionalHaltReason.INVALID_OPERATION); + + /** + * Instantiates a new Invalid operation. + * + * @param gasCalculator the gas calculator + */ + public InvalidOperationV2(final GasCalculator gasCalculator) { + super(OPCODE, "INVALID", -1, -1, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return INVALID_RESULT; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/IsZeroOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/IsZeroOperationV2.java new file mode 100644 index 00000000000..6ddddeba055 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/IsZeroOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The IsZero operation. */ +public class IsZeroOperationV2 extends AbstractFixedCostOperationV2 { + + /** The IsZero operation success result. */ + static final OperationResult isZeroSuccess = new OperationResult(3, null); + + /** + * Instantiates a new IsZero operation. + * + * @param gasCalculator the gas calculator + */ + public IsZeroOperationV2(final GasCalculator gasCalculator) { + super(0x15, "ISZERO", 1, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs iszero operation. + * + * @param frame the frame + * @param stack the v2 operand stack ({@code long[]} in big-endian limb order) + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasItems(1)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.isZero(stack, frame.stackTopV2())); + return isZeroSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/JumpDestOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/JumpDestOperationV2.java new file mode 100644 index 00000000000..f2f4aec7557 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/JumpDestOperationV2.java @@ -0,0 +1,54 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; + +/** EVM v2 JUMPDEST operation. A marker opcode that is a no-op at runtime. */ +public class JumpDestOperationV2 extends AbstractFixedCostOperationV2 { + + /** The constant OPCODE. */ + public static final int OPCODE = 0x5B; + + private static final OperationResult JUMPDEST_SUCCESS = new OperationResult(1L, null); + + /** + * Instantiates a new Jump dest operation. + * + * @param gasCalculator the gas calculator + */ + public JumpDestOperationV2(final GasCalculator gasCalculator) { + super(OPCODE, "JUMPDEST", 0, 0, gasCalculator, gasCalculator.getJumpDestOperationGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame); + } + + /** + * Performs JUMPDEST operation (no-op marker). + * + * @param frame the frame + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame) { + return JUMPDEST_SUCCESS; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/JumpOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/JumpOperationV2.java new file mode 100644 index 00000000000..0a0ca80e3b8 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/JumpOperationV2.java @@ -0,0 +1,81 @@ +/* + * 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.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; + +/** EVM v2 JUMP operation using long[] stack representation. */ +public class JumpOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult INVALID_JUMP_RESPONSE = + new OperationResult(8L, ExceptionalHaltReason.INVALID_JUMP_DESTINATION); + private static final OperationResult JUMP_RESPONSE = new OperationResult(8L, null, 0); + + /** + * Instantiates a new Jump operation. + * + * @param gasCalculator the gas calculator + */ + public JumpOperationV2(final GasCalculator gasCalculator) { + super(0x56, "JUMP", 1, 0, gasCalculator, gasCalculator.getMidTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs Jump operation. + * + * @param frame the frame + * @param s the stack data array + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(1)) return UNDERFLOW_RESPONSE; + final int top = frame.stackTopV2(); + final int destOff = (top - 1) << 2; + frame.setTopV2(top - 1); + return performJump(frame, s, destOff, JUMP_RESPONSE, INVALID_JUMP_RESPONSE); + } + + static OperationResult performJump( + final MessageFrame frame, + final long[] s, + final int off, + final OperationResult validResponse, + final OperationResult invalidResponse) { + // Destination must fit in a non-negative int (code size is bounded by int). + if (s[off] != 0 + || s[off + 1] != 0 + || s[off + 2] != 0 + || s[off + 3] < 0 + || s[off + 3] > Integer.MAX_VALUE) { + return invalidResponse; + } + final int jumpDestination = (int) s[off + 3]; + if (frame.getCode().isJumpDestInvalid(jumpDestination)) { + return invalidResponse; + } + frame.setPC(jumpDestination); + return validResponse; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/JumpiOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/JumpiOperationV2.java new file mode 100644 index 00000000000..36ab1bed872 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/JumpiOperationV2.java @@ -0,0 +1,67 @@ +/* + * 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.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; + +/** EVM v2 JUMPI (conditional jump) operation using long[] stack representation. */ +public class JumpiOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult INVALID_JUMP_RESPONSE = + new OperationResult(10L, ExceptionalHaltReason.INVALID_JUMP_DESTINATION); + private static final OperationResult JUMPI_RESPONSE = new OperationResult(10L, null, 0); + private static final OperationResult NOJUMP_RESPONSE = new OperationResult(10L, null); + + /** + * Instantiates a new JUMPI operation. + * + * @param gasCalculator the gas calculator + */ + public JumpiOperationV2(final GasCalculator gasCalculator) { + super(0x57, "JUMPI", 2, 0, gasCalculator, gasCalculator.getHighTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs JUMPI operation. + * + * @param frame the frame + * @param s the stack data array + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + final int top = frame.stackTopV2(); + final int destOff = (top - 1) << 2; + final int condOff = (top - 2) << 2; + frame.setTopV2(top - 2); + + // If condition is zero (false), no jump will be performed. + if (s[condOff] == 0 && s[condOff + 1] == 0 && s[condOff + 2] == 0 && s[condOff + 3] == 0) { + return NOJUMP_RESPONSE; + } + + return JumpOperationV2.performJump(frame, s, destOff, JUMPI_RESPONSE, INVALID_JUMP_RESPONSE); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Keccak256OperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Keccak256OperationV2.java new file mode 100644 index 00000000000..3ccc0349601 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Keccak256OperationV2.java @@ -0,0 +1,80 @@ +/* + * 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.hyperledger.besu.crypto.Hash.keccak256; + +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.v2.StackArithmetic; + +import org.apache.tuweni.bytes.Bytes; + +/** + * EVM v2 KECCAK256 operation (0x20). + * + *

Pops offset and size from the stack, hashes the corresponding memory region with Keccak-256, + * and pushes the 32-byte hash result. Net stack effect: 2 popped, 1 pushed (top - 1). + */ +public class Keccak256OperationV2 extends AbstractOperationV2 { + + /** + * Instantiates a new Keccak256 operation. + * + * @param gasCalculator the gas calculator + */ + public Keccak256OperationV2(final GasCalculator gasCalculator) { + super(0x20, "KECCAK256", 2, 1, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), gasCalculator()); + } + + /** + * Execute KECCAK256 on the v2 long[] stack. + * + * @param frame the message frame + * @param s the stack data + * @param gasCalculator the gas calculator + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) { + if (!frame.stackHasItems(2)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final int top = frame.stackTopV2(); + final long from = StackArithmetic.clampedToLong(s, top, 0); + final long length = StackArithmetic.clampedToLong(s, top, 1); + + final long cost = gasCalculator.keccak256OperationGasCost(frame, from, length); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + final Bytes bytes = frame.readMutableMemory(from, length); + final byte[] hashBytes = keccak256(bytes).toArrayUnsafe(); + // Pop 2, push 1: net effect is top - 1 + final int newTop = top - 1; + frame.setTopV2(newTop); + StackArithmetic.fromBytesAt(s, newTop, 0, hashBytes, 0, hashBytes.length); + + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/LogOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/LogOperationV2.java new file mode 100644 index 00000000000..11c9d10f696 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/LogOperationV2.java @@ -0,0 +1,98 @@ +/* + * 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.datatypes.Log; +import org.hyperledger.besu.datatypes.LogTopic; +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.v2.StackArithmetic; + +import com.google.common.collect.ImmutableList; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +/** + * EVM v2 LOG operation (LOG0-LOG4) using long[] stack representation. Parameterized by topic count. + */ +public class LogOperationV2 extends AbstractOperationV2 { + + private final int numTopics; + + /** + * Instantiates a new Log operation. + * + * @param numTopics the num topics (0-4) + * @param gasCalculator the gas calculator + */ + public LogOperationV2(final int numTopics, final GasCalculator gasCalculator) { + super(0xA0 + numTopics, "LOG" + numTopics, numTopics + 2, 0, gasCalculator); + this.numTopics = numTopics; + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), numTopics, gasCalculator()); + } + + /** + * Performs LOG operation. + * + * @param frame the frame + * @param s the stack data array + * @param numTopics the number of topics to pop + * @param gasCalculator the gas calculator + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, + final long[] s, + final int numTopics, + final GasCalculator gasCalculator) { + if (!frame.stackHasItems(2 + numTopics)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final int top = frame.stackTopV2(); + final long dataLocation = StackArithmetic.clampedToLong(s, top, 0); + final long numBytes = StackArithmetic.clampedToLong(s, top, 1); + + if (frame.isStatic()) { + return new OperationResult(0, ExceptionalHaltReason.ILLEGAL_STATE_CHANGE); + } + + final long cost = gasCalculator.logOperationGasCost(frame, dataLocation, numBytes, numTopics); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + final Address address = frame.getRecipientAddress(); + final Bytes data = frame.readMemory(dataLocation, numBytes); + + final ImmutableList.Builder builder = + ImmutableList.builderWithExpectedSize(numTopics); + for (int i = 0; i < numTopics; i++) { + final byte[] buf = new byte[32]; + StackArithmetic.toBytesAt(s, top, 2 + i, buf); + builder.add(LogTopic.create(Bytes32.wrap(buf))); + } + frame.setTopV2(top - 2 - numTopics); + + frame.addLog(new Log(address, data, builder.build())); + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/LtOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/LtOperationV2.java new file mode 100644 index 00000000000..b44af404fe0 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/LtOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The Lt operation. */ +public class LtOperationV2 extends AbstractFixedCostOperationV2 { + + /** The Lt operation success result. */ + static final OperationResult ltSuccess = new OperationResult(3, null); + + /** + * Instantiates a new Lt operation. + * + * @param gasCalculator the gas calculator + */ + public LtOperationV2(final GasCalculator gasCalculator) { + super(0x10, "LT", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs lt operation. + * + * @param frame the frame + * @param stack the v2 operand stack ({@code long[]} in big-endian limb order) + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.lt(stack, frame.stackTopV2())); + return ltSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MCopyOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MCopyOperationV2.java new file mode 100644 index 00000000000..dcfc8e9f43b --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MCopyOperationV2.java @@ -0,0 +1,68 @@ +/* + * 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.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The MCOPY operation for EVM v2. */ +public class MCopyOperationV2 extends AbstractOperationV2 { + + /** + * Instantiates a new MCOPY operation. + * + * @param gasCalculator the gas calculator + */ + public MCopyOperationV2(final GasCalculator gasCalculator) { + super(0x5e, "MCOPY", 3, 0, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), gasCalculator()); + } + + /** + * Execute the MCOPY opcode on the v2 long[] stack. + * + * @param frame the message frame + * @param stack the stack operands as a long[] array + * @param gasCalculator the gas calculator + * @return the operation result + */ + public static Operation.OperationResult staticOperation( + final MessageFrame frame, final long[] stack, final GasCalculator gasCalculator) { + if (!frame.stackHasItems(3)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final int top = frame.stackTopV2(); + final long dst = StackArithmetic.clampedToLong(stack, top, 0); + final long src = StackArithmetic.clampedToLong(stack, top, 1); + final long length = StackArithmetic.clampedToLong(stack, top, 2); + frame.setTopV2(top - 3); + + final long cost = gasCalculator.dataCopyOperationGasCost(frame, Math.max(src, dst), length); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + frame.copyMemory(dst, src, length, true); + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MSizeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MSizeOperationV2.java new file mode 100644 index 00000000000..c1e474c0c90 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MSizeOperationV2.java @@ -0,0 +1,55 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 MSIZE operation — pushes the active memory size in bytes onto the stack. */ +public class MSizeOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult mSizeSuccess = new OperationResult(2, null); + + /** + * Instantiates a new MSize operation. + * + * @param gasCalculator the gas calculator + */ + public MSizeOperationV2(final GasCalculator gasCalculator) { + super(0x59, "MSIZE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs the MSIZE operation. + * + * @param frame the message frame + * @param stack the v2 long[] stack + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.pushLong(stack, frame.stackTopV2(), frame.memoryByteSize())); + return mSizeSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MloadOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MloadOperationV2.java new file mode 100644 index 00000000000..f3ff75247df --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MloadOperationV2.java @@ -0,0 +1,66 @@ +/* + * 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.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The MLOAD operation for EVM v2. */ +public class MloadOperationV2 extends AbstractOperationV2 { + + /** + * Instantiates a new MLOAD operation. + * + * @param gasCalculator the gas calculator + */ + public MloadOperationV2(final GasCalculator gasCalculator) { + super(0x51, "MLOAD", 1, 1, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), gasCalculator()); + } + + /** + * Execute the MLOAD opcode on the v2 long[] stack. + * + * @param frame the message frame + * @param stack the stack operands as a long[] array + * @param gasCalculator the gas calculator + * @return the operation result + */ + public static Operation.OperationResult staticOperation( + final MessageFrame frame, final long[] stack, final GasCalculator gasCalculator) { + if (!frame.stackHasItems(1)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final int top = frame.stackTopV2(); + final long location = StackArithmetic.clampedToLong(stack, top, 0); + + final long cost = gasCalculator.mLoadOperationGasCost(frame, location); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + final byte[] bytes = frame.readMutableMemory(location, 32, true).copy().toArrayUnsafe(); + StackArithmetic.fromBytesAt(stack, top, 0, bytes, 0, 32); + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ModOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ModOperationV2.java new file mode 100644 index 00000000000..2b5f6a6410c --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ModOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The Mod operation. */ +public class ModOperationV2 extends AbstractFixedCostOperationV2 { + + /** The Mod operation success result. */ + static final OperationResult modSuccess = new OperationResult(5, null); + + /** + * Instantiates a new Mod operation. + * + * @param gasCalculator the gas calculator + */ + public ModOperationV2(final GasCalculator gasCalculator) { + super(0x06, "MOD", 2, 1, gasCalculator, gasCalculator.getLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs mod operation. + * + * @param frame the frame + * @param stack the v2 operand stack ({@code long[]} in big-endian limb order) + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.mod(stack, frame.stackTopV2())); + return modSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Mstore8OperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Mstore8OperationV2.java new file mode 100644 index 00000000000..71840993ba5 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Mstore8OperationV2.java @@ -0,0 +1,67 @@ +/* + * 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.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The MSTORE8 operation for EVM v2. */ +public class Mstore8OperationV2 extends AbstractOperationV2 { + + /** + * Instantiates a new MSTORE8 operation. + * + * @param gasCalculator the gas calculator + */ + public Mstore8OperationV2(final GasCalculator gasCalculator) { + super(0x53, "MSTORE8", 2, 0, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), gasCalculator()); + } + + /** + * Execute the MSTORE8 opcode on the v2 long[] stack. + * + * @param frame the message frame + * @param stack the stack operands as a long[] array + * @param gasCalculator the gas calculator + * @return the operation result + */ + public static Operation.OperationResult staticOperation( + final MessageFrame frame, final long[] stack, final GasCalculator gasCalculator) { + if (!frame.stackHasItems(2)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final int top = frame.stackTopV2(); + final long location = StackArithmetic.clampedToLong(stack, top, 0); + final byte theByte = (byte) (StackArithmetic.longAt(stack, top, 1) & 0xFF); + + final long cost = gasCalculator.mStore8OperationGasCost(frame, location); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + frame.writeMemory(location, theByte, true); + frame.setTopV2(top - 2); + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MstoreOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MstoreOperationV2.java new file mode 100644 index 00000000000..6d05e1c444e --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MstoreOperationV2.java @@ -0,0 +1,70 @@ +/* + * 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.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +import org.apache.tuweni.bytes.Bytes; + +/** The MSTORE operation for EVM v2. */ +public class MstoreOperationV2 extends AbstractOperationV2 { + + /** + * Instantiates a new MSTORE operation. + * + * @param gasCalculator the gas calculator + */ + public MstoreOperationV2(final GasCalculator gasCalculator) { + super(0x52, "MSTORE", 2, 0, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), gasCalculator()); + } + + /** + * Execute the MSTORE opcode on the v2 long[] stack. + * + * @param frame the message frame + * @param stack the stack operands as a long[] array + * @param gasCalculator the gas calculator + * @return the operation result + */ + public static Operation.OperationResult staticOperation( + final MessageFrame frame, final long[] stack, final GasCalculator gasCalculator) { + if (!frame.stackHasItems(2)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final int top = frame.stackTopV2(); + final long location = StackArithmetic.clampedToLong(stack, top, 0); + + final long cost = gasCalculator.mStoreOperationGasCost(frame, location); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + final byte[] bytes = new byte[32]; + StackArithmetic.toBytesAt(stack, top, 1, bytes); + frame.writeMemoryRightAligned(location, 32, Bytes.wrap(bytes), true); + frame.setTopV2(top - 2); + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2.java new file mode 100644 index 00000000000..1756d98e7e5 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2.java @@ -0,0 +1,55 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The Mul mod operation. */ +public class MulModOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult mulModSuccess = new OperationResult(8, null); + + /** + * Instantiates a new Mul mod operation. + * + * @param gasCalculator the gas calculator + */ + public MulModOperationV2(final GasCalculator gasCalculator) { + super(0x09, "MULMOD", 3, 1, gasCalculator, gasCalculator.getMidTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs MulMod operation. + * + * @param frame the frame + * @param s the stack array + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(3)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.mulMod(s, frame.stackTopV2())); + return mulModSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2.java new file mode 100644 index 00000000000..5d85573d806 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The Mul operation. */ +public class MulOperationV2 extends AbstractFixedCostOperationV2 { + + /** The Mul operation success result. */ + static final OperationResult mulSuccess = new OperationResult(5, null); + + /** + * Instantiates a new Mul operation. + * + * @param gasCalculator the gas calculator + */ + public MulOperationV2(final GasCalculator gasCalculator) { + super(0x02, "MUL", 2, 1, gasCalculator, gasCalculator.getLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs mul operation. + * + * @param frame the frame + * @param stack the v2 operand stack ({@code long[]} in big-endian limb order) + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.mul(stack, frame.stackTopV2())); + return mulSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/NotOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/NotOperationV2.java new file mode 100644 index 00000000000..af118f93e57 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/NotOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The Not operation. */ +public class NotOperationV2 extends AbstractFixedCostOperationV2 { + + /** The Not operation success result. */ + static final OperationResult notSuccess = new OperationResult(3, null); + + /** + * Instantiates a new Not operation. + * + * @param gasCalculator the gas calculator + */ + public NotOperationV2(final GasCalculator gasCalculator) { + super(0x19, "NOT", 1, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs not operation. + * + * @param frame the frame + * @param stack the v2 operand stack ({@code long[]} in big-endian limb order) + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasItems(1)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.not(stack, frame.stackTopV2())); + return notSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/NumberOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/NumberOperationV2.java new file mode 100644 index 00000000000..7656015d292 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/NumberOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 NUMBER operation — pushes the block number onto the stack. */ +public class NumberOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult numberSuccess = new OperationResult(2, null); + + /** + * Instantiates a new Number operation. + * + * @param gasCalculator the gas calculator + */ + public NumberOperationV2(final GasCalculator gasCalculator) { + super(0x43, "NUMBER", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs the NUMBER operation. + * + * @param frame the message frame + * @param stack the v2 long[] stack + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTopV2( + StackArithmetic.pushLong(stack, frame.stackTopV2(), frame.getBlockValues().getNumber())); + return numberSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/OrOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/OrOperationV2.java new file mode 100644 index 00000000000..b9b0d82cd09 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/OrOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 OR operation (0x17). */ +public class OrOperationV2 extends AbstractFixedCostOperationV2 { + + /** The Or operation success result. */ + static final OperationResult orSuccess = new OperationResult(3, null); + + /** + * Instantiates a new Or operation. + * + * @param gasCalculator the gas calculator + */ + public OrOperationV2(final GasCalculator gasCalculator) { + super(0x17, "OR", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs OR operation. + * + * @param frame the frame + * @param stack the v2 operand stack ({@code long[]} in big-endian limb order) + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.or(stack, frame.stackTopV2())); + return orSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/OriginOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/OriginOperationV2.java new file mode 100644 index 00000000000..d3e51584e0c --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/OriginOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 ORIGIN operation — pushes the originator address onto the stack. */ +public class OriginOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult originSuccess = new OperationResult(2, null); + + /** + * Instantiates a new Origin operation. + * + * @param gasCalculator the gas calculator + */ + public OriginOperationV2(final GasCalculator gasCalculator) { + super(0x32, "ORIGIN", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs the ORIGIN operation. + * + * @param frame the message frame + * @param stack the v2 long[] stack + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTopV2( + StackArithmetic.pushAddress(stack, frame.stackTopV2(), frame.getOriginatorAddress())); + return originSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PayOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PayOperationV2.java new file mode 100644 index 00000000000..b48923c4dfd --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PayOperationV2.java @@ -0,0 +1,133 @@ +/* + * 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.hyperledger.besu.evm.internal.Words.clampedAdd; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.EVM; +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.v2.StackArithmetic; + +import java.util.Objects; + +import org.apache.tuweni.bytes.Bytes; + +/** EVM v2 PAY operation (EIP-7708) using long[] stack representation. */ +public class PayOperationV2 extends AbstractOperationV2 { + + /** + * Instantiates a new Pay operation. + * + * @param gasCalculator the gas calculator + */ + public PayOperationV2(final GasCalculator gasCalculator) { + super(0xfc, "PAY", 2, 1, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), gasCalculator()); + } + + /** + * Performs PAY operation. + * + * @param frame the frame + * @param s the stack data array + * @param gasCalculator the gas calculator + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) { + if (!frame.stackHasItems(2)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + if (frame.isStatic()) { + return new OperationResult(0, ExceptionalHaltReason.ILLEGAL_STATE_CHANGE); + } + + final int top = frame.stackTopV2(); + final int addrOff = (top - 1) << 2; + + // Check if address has more than 20 bytes of significant data (u3 must be 0, upper 32 bits of + // u2 must be 0). + if (s[addrOff] != 0 || (s[addrOff + 1] >>> 32) != 0) { + return new OperationResult(0, ExceptionalHaltReason.ADDRESS_OUT_OF_RANGE); + } + + final Address to = StackArithmetic.toAddressAt(s, top, 0); + final Wei value = Wei.wrap(Bytes.wrap(StackArithmetic.getAt(s, top, 1).toBytesBE())); + final boolean hasValue = value.greaterThan(Wei.ZERO); + final Account recipient = frame.getWorldUpdater().get(to); + + final boolean accountIsWarm = frame.warmUpAddress(to); + + final long cost = cost(to, hasValue, recipient, accountIsWarm, gasCalculator); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + if (!hasValue || Objects.equals(frame.getSenderAddress(), to)) { + StackArithmetic.putAt(s, top, 1, 0, 0, 0, 1); + frame.setTopV2(top - 1); + return new OperationResult(cost, null); + } + + final MutableAccount senderAccount = + frame.getWorldUpdater().getOrCreate(frame.getSenderAddress()); + if (value.compareTo(senderAccount.getBalance()) > 0) { + StackArithmetic.putAt(s, top, 1, 0, 0, 0, 0); + frame.setTopV2(top - 1); + return new OperationResult(cost, null); + } + + final MutableAccount recipientAccount = frame.getWorldUpdater().getOrCreate(to); + senderAccount.decrementBalance(value); + recipientAccount.incrementBalance(value); + + StackArithmetic.putAt(s, top, 1, 0, 0, 0, 1); + frame.setTopV2(top - 1); + return new OperationResult(cost, null); + } + + private static long cost( + final Address to, + final boolean hasValue, + final Account recipient, + final boolean accountIsWarm, + final GasCalculator gasCalculator) { + long cost = 0; + if (hasValue) { + cost = gasCalculator.callValueTransferGasCost(); + } + if (accountIsWarm || gasCalculator.isPrecompile(to)) { + return clampedAdd(cost, gasCalculator.getWarmStorageReadCost()); + } + + cost = clampedAdd(cost, gasCalculator.getColdAccountAccessCost()); + + if (recipient == null && hasValue) { + cost = clampedAdd(cost, gasCalculator.newAccountGasCost()); + } + + return cost; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PcOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PcOperationV2.java new file mode 100644 index 00000000000..4d1ab23bb21 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PcOperationV2.java @@ -0,0 +1,55 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 PC operation — pushes the current program counter onto the stack. */ +public class PcOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult pcSuccess = new OperationResult(2, null); + + /** + * Instantiates a new PC operation. + * + * @param gasCalculator the gas calculator + */ + public PcOperationV2(final GasCalculator gasCalculator) { + super(0x58, "PC", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs the PC operation. + * + * @param frame the message frame + * @param stack the v2 long[] stack + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.pushLong(stack, frame.stackTopV2(), frame.getPC())); + return pcSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PopOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PopOperationV2.java new file mode 100644 index 00000000000..8d40d53576f --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PopOperationV2.java @@ -0,0 +1,57 @@ +/* + * 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; + +/** + * EVM v2 POP operation (0x50). + * + *

Discards the top stack item by decrementing the stack pointer. Gas cost is base tier (2). + */ +public class PopOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult POP_SUCCESS = new OperationResult(2, null); + + /** + * Instantiates a new Pop operation. + * + * @param gasCalculator the gas calculator + */ + public PopOperationV2(final GasCalculator gasCalculator) { + super(0x50, "POP", 1, 0, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame); + } + + /** + * Performs POP operation. + * + * @param frame the frame + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame) { + if (!frame.stackHasItems(1)) return UNDERFLOW_RESPONSE; + frame.setTopV2(frame.stackTopV2() - 1); + return POP_SUCCESS; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PrevRandaoOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PrevRandaoOperationV2.java new file mode 100644 index 00000000000..fce5b1aceed --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PrevRandaoOperationV2.java @@ -0,0 +1,59 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** + * EVM v2 PREVRANDAO (DIFFICULTY) operation — pushes the mix hash / prev randao value onto the + * stack. + */ +public class PrevRandaoOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult prevRandaoSuccess = new OperationResult(2, null); + + /** + * Instantiates a new PrevRandao operation. + * + * @param gasCalculator the gas calculator + */ + public PrevRandaoOperationV2(final GasCalculator gasCalculator) { + super(0x44, "PREVRANDAO", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs the PREVRANDAO operation. + * + * @param frame the message frame + * @param stack the v2 long[] stack + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + final byte[] randao = frame.getBlockValues().getMixHashOrPrevRandao().toArrayUnsafe(); + frame.setTopV2(StackArithmetic.pushFromBytes(stack, frame.stackTopV2(), randao, 0, 32)); + return prevRandaoSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Push0OperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Push0OperationV2.java new file mode 100644 index 00000000000..c0b10f4729f --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/Push0OperationV2.java @@ -0,0 +1,60 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** + * EVM v2 PUSH0 operation (0x5F). + * + *

Pushes the constant value 0 onto the stack. Requires Shanghai fork or later. Gas cost is base + * tier (2). + */ +public class Push0OperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult PUSH0_SUCCESS = new OperationResult(2, null); + + /** + * Instantiates a new Push0 operation. + * + * @param gasCalculator the gas calculator + */ + public Push0OperationV2(final GasCalculator gasCalculator) { + super(0x5F, "PUSH0", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs PUSH0 operation. + * + * @param frame the frame + * @param s the stack data array + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.pushZero(s, frame.stackTopV2())); + return PUSH0_SUCCESS; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PushOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PushOperationV2.java new file mode 100644 index 00000000000..4e154a88fe0 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/PushOperationV2.java @@ -0,0 +1,83 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** + * EVM v2 PUSH1-32 operation (opcodes 0x60–0x7F). + * + *

Reads {@code length} immediate bytes from bytecode at {@code pc+1} and pushes the resulting + * value onto the stack. Gas cost is veryLow tier (3). PC increment is {@code 1 + length}. + */ +public class PushOperationV2 extends AbstractFixedCostOperationV2 { + + /** The PUSH opcode base (PUSH0 = 0x5F, so PUSH1 = 0x60). */ + public static final int PUSH_BASE = 0x5F; + + private static final OperationResult PUSH_SUCCESS = new OperationResult(3, null); + + private final int length; + + /** + * Instantiates a new Push operation for PUSH{length}. + * + * @param length the number of bytes to push (1–32) + * @param gasCalculator the gas calculator + */ + public PushOperationV2(final int length, final GasCalculator gasCalculator) { + super( + PUSH_BASE + length, + "PUSH" + length, + 0, + 1, + gasCalculator, + gasCalculator.getVeryLowTierGasCost()); + this.length = length; + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + final byte[] code = frame.getCode().getBytes().toArrayUnsafe(); + return staticOperation(frame, frame.stackDataV2(), code, frame.getPC(), length); + } + + /** + * Performs PUSH operation. + * + * @param frame the frame + * @param s the stack data array + * @param code the bytecode array + * @param pc the current program counter + * @param pushSize the number of bytes to push + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, + final long[] s, + final byte[] code, + final int pc, + final int pushSize) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.pushFromBytes(s, frame.stackTopV2(), code, pc + 1, pushSize)); + frame.setPC(pc + pushSize); + return PUSH_SUCCESS; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ReturnDataCopyOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ReturnDataCopyOperationV2.java new file mode 100644 index 00000000000..dcedc0b8d28 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ReturnDataCopyOperationV2.java @@ -0,0 +1,94 @@ +/* + * 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.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +import org.apache.tuweni.bytes.Bytes; + +/** + * EVM v2 RETURNDATACOPY operation (0x3E). + * + *

Pops destOffset, sourceOffset, and size from the stack, then copies {@code size} bytes of the + * return data buffer into memory at {@code destOffset}. Halts with + * INVALID_RETURN_DATA_BUFFER_ACCESS if the source range exceeds the buffer. + */ +public class ReturnDataCopyOperationV2 extends AbstractOperationV2 { + + private static final OperationResult INVALID_RETURN_DATA_BUFFER_ACCESS = + new OperationResult(0L, ExceptionalHaltReason.INVALID_RETURN_DATA_BUFFER_ACCESS); + + private static final OperationResult OUT_OF_BOUNDS = + new OperationResult(0L, ExceptionalHaltReason.OUT_OF_BOUNDS); + + /** + * Instantiates a new ReturnDataCopy operation. + * + * @param gasCalculator the gas calculator + */ + public ReturnDataCopyOperationV2(final GasCalculator gasCalculator) { + super(0x3E, "RETURNDATACOPY", 3, 0, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), gasCalculator()); + } + + /** + * Execute RETURNDATACOPY on the v2 long[] stack. + * + * @param frame the message frame + * @param s the stack data + * @param gasCalculator the gas calculator + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) { + if (!frame.stackHasItems(3)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final int top = frame.stackTopV2(); + final long memOffset = StackArithmetic.clampedToLong(s, top, 0); + final long sourceOffset = StackArithmetic.clampedToLong(s, top, 1); + final long numBytes = StackArithmetic.clampedToLong(s, top, 2); + frame.setTopV2(top - 3); + + final Bytes returnData = frame.getReturnData(); + final int returnDataLength = returnData.size(); + + try { + final long end = Math.addExact(sourceOffset, numBytes); + if (end > returnDataLength) { + return INVALID_RETURN_DATA_BUFFER_ACCESS; + } + } catch (final ArithmeticException ae) { + return OUT_OF_BOUNDS; + } + + final long cost = gasCalculator.dataCopyOperationGasCost(frame, memOffset, numBytes); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + frame.writeMemory(memOffset, sourceOffset, numBytes, returnData, true); + + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ReturnDataSizeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ReturnDataSizeOperationV2.java new file mode 100644 index 00000000000..d7e6e90eb87 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ReturnDataSizeOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 RETURNDATASIZE operation — pushes the size of the return data buffer onto the stack. */ +public class ReturnDataSizeOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult returnDataSizeSuccess = new OperationResult(2, null); + + /** + * Instantiates a new Return data size operation. + * + * @param gasCalculator the gas calculator + */ + public ReturnDataSizeOperationV2(final GasCalculator gasCalculator) { + super(0x3D, "RETURNDATASIZE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs the RETURNDATASIZE operation. + * + * @param frame the message frame + * @param stack the v2 long[] stack + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTopV2( + StackArithmetic.pushLong(stack, frame.stackTopV2(), frame.getReturnData().size())); + return returnDataSizeSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ReturnOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ReturnOperationV2.java new file mode 100644 index 00000000000..4fd802b7a86 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ReturnOperationV2.java @@ -0,0 +1,70 @@ +/* + * 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.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 RETURN operation using long[] stack representation. */ +public class ReturnOperationV2 extends AbstractOperationV2 { + + /** RETURN opcode number */ + public static final int OPCODE = 0xF3; + + /** + * Instantiates a new Return operation. + * + * @param gasCalculator the gas calculator + */ + public ReturnOperationV2(final GasCalculator gasCalculator) { + super(OPCODE, "RETURN", 2, 0, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), gasCalculator()); + } + + /** + * Performs Return operation. + * + * @param frame the frame + * @param s the stack data array + * @param gasCalculator the gas calculator + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) { + if (!frame.stackHasItems(2)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final int top = frame.stackTopV2(); + final long from = StackArithmetic.clampedToLong(s, top, 0); + final long length = StackArithmetic.clampedToLong(s, top, 1); + frame.setTopV2(top - 2); + + final long cost = gasCalculator.memoryExpansionGasCost(frame, from, length); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + frame.setOutputData(frame.readMemory(from, length)); + frame.setState(MessageFrame.State.CODE_SUCCESS); + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/RevertOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/RevertOperationV2.java new file mode 100644 index 00000000000..41cb2206ed5 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/RevertOperationV2.java @@ -0,0 +1,74 @@ +/* + * 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.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +import org.apache.tuweni.bytes.Bytes; + +/** EVM v2 REVERT operation using long[] stack representation. */ +public class RevertOperationV2 extends AbstractOperationV2 { + + /** REVERT opcode number */ + public static final int OPCODE = 0xFD; + + /** + * Instantiates a new Revert operation. + * + * @param gasCalculator the gas calculator + */ + public RevertOperationV2(final GasCalculator gasCalculator) { + super(OPCODE, "REVERT", 2, 0, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), gasCalculator()); + } + + /** + * Performs Revert operation. + * + * @param frame the frame + * @param s the stack data array + * @param gasCalculator the gas calculator + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) { + if (!frame.stackHasItems(2)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final int top = frame.stackTopV2(); + final long from = StackArithmetic.clampedToLong(s, top, 0); + final long length = StackArithmetic.clampedToLong(s, top, 1); + frame.setTopV2(top - 2); + + final long cost = gasCalculator.memoryExpansionGasCost(frame, from, length); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + final Bytes reason = frame.readMemory(from, length); + frame.setOutputData(reason); + frame.setRevertReason(reason); + frame.setState(MessageFrame.State.REVERT); + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SDivOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SDivOperationV2.java new file mode 100644 index 00000000000..ea41754d391 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SDivOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The SDiv operation. */ +public class SDivOperationV2 extends AbstractFixedCostOperationV2 { + + /** The SDiv operation success result. */ + static final OperationResult sdivSuccess = new OperationResult(5, null); + + /** + * Instantiates a new SDiv operation. + * + * @param gasCalculator the gas calculator + */ + public SDivOperationV2(final GasCalculator gasCalculator) { + super(0x05, "SDIV", 2, 1, gasCalculator, gasCalculator.getLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs sdiv operation. + * + * @param frame the frame + * @param stack the v2 operand stack ({@code long[]} in big-endian limb order) + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.signedDiv(stack, frame.stackTopV2())); + return sdivSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SLoadOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SLoadOperationV2.java new file mode 100644 index 00000000000..482d4269f8e --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SLoadOperationV2.java @@ -0,0 +1,123 @@ +/* + * 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.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; + +/** + * EVM v2 SLOAD operation using long[] stack representation. + * + *

Pops a storage key from the stack, loads the value from the contract's storage, and pushes the + * result back. Applies warm/cold access costs per EIP-2929. + */ +public class SLoadOperationV2 extends AbstractOperationV2 { + + private final long warmCost; + private final long coldCost; + private final OperationResult warmSuccess; + private final OperationResult coldSuccess; + + /** + * Instantiates a new SLoad operation. + * + * @param gasCalculator the gas calculator + */ + public SLoadOperationV2(final GasCalculator gasCalculator) { + super(0x54, "SLOAD", 1, 1, gasCalculator); + final long baseCost = gasCalculator.getSloadOperationGasCost(); + warmCost = baseCost + gasCalculator.getWarmStorageReadCost(); + coldCost = baseCost + gasCalculator.getColdSloadCost(); + warmSuccess = new OperationResult(warmCost, null); + coldSuccess = new OperationResult(coldCost, null); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation( + frame, frame.stackDataV2(), warmCost, coldCost, warmSuccess, coldSuccess); + } + + /** + * Execute the SLOAD opcode on the v2 long[] stack. + * + *

Called from EVM.java dispatch. Computes costs from the gas calculator on first call; uses + * pre-cached results when called via the instance {@code execute()} method. + * + * @param frame the message frame + * @param s the stack as a long[] array + * @param gasCalculator the gas calculator + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) { + final long baseCost = gasCalculator.getSloadOperationGasCost(); + final long warmCost = baseCost + gasCalculator.getWarmStorageReadCost(); + final long coldCost = baseCost + gasCalculator.getColdSloadCost(); + return staticOperation( + frame, + s, + warmCost, + coldCost, + new OperationResult(warmCost, null), + new OperationResult(coldCost, null)); + } + + private static OperationResult staticOperation( + final MessageFrame frame, + final long[] s, + final long warmCost, + final long coldCost, + final OperationResult warmSuccess, + final OperationResult coldSuccess) { + if (!frame.stackHasItems(1)) { + return new OperationResult(warmCost, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + + final int top = frame.stackTopV2(); + + final byte[] keyBytes = new byte[32]; + StackArithmetic.toBytesAt(s, top, 0, keyBytes); + final Bytes32 keyBytes32 = Bytes32.wrap(keyBytes); + final UInt256 key = UInt256.fromBytes(keyBytes32); + + final boolean slotIsWarm = frame.warmUpStorage(frame.getRecipientAddress(), keyBytes32); + final long cost = slotIsWarm ? warmCost : coldCost; + + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + final Account account = frame.getWorldUpdater().get(frame.getRecipientAddress()); + frame.getEip7928AccessList().ifPresent(t -> t.addTouchedAccount(frame.getRecipientAddress())); + + final UInt256 value = account == null ? UInt256.ZERO : account.getStorageValue(key); + frame + .getEip7928AccessList() + .ifPresent(t -> t.addSlotAccessForAccount(frame.getRecipientAddress(), key)); + + final byte[] valueBytes = value.toArrayUnsafe(); + StackArithmetic.fromBytesAt(s, top, 0, valueBytes, 0, valueBytes.length); + + return slotIsWarm ? warmSuccess : coldSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SModOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SModOperationV2.java new file mode 100644 index 00000000000..dd4745fb57f --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SModOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The SMod operation. */ +public class SModOperationV2 extends AbstractFixedCostOperationV2 { + + /** The SMod operation success result. */ + static final OperationResult smodSuccess = new OperationResult(5, null); + + /** + * Instantiates a new SMod operation. + * + * @param gasCalculator the gas calculator + */ + public SModOperationV2(final GasCalculator gasCalculator) { + super(0x07, "SMOD", 2, 1, gasCalculator, gasCalculator.getLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs smod operation. + * + * @param frame the frame + * @param stack the v2 operand stack ({@code long[]} in big-endian limb order) + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.signedMod(stack, frame.stackTopV2())); + return smodSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SStoreOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SStoreOperationV2.java new file mode 100644 index 00000000000..76c4d0523f9 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SStoreOperationV2.java @@ -0,0 +1,166 @@ +/* + * 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.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.v2.StackArithmetic; + +import java.util.function.Supplier; + +import com.google.common.base.Suppliers; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; + +/** + * EVM v2 SSTORE operation using long[] stack representation. + * + *

Pops a storage key and new value from the stack, then writes the value to contract storage. + * Applies EIP-2200 gas costs and refund accounting. + */ +public class SStoreOperationV2 extends AbstractOperationV2 { + + /** Minimum gas remaining for Frontier (no minimum). */ + public static final long FRONTIER_MINIMUM = 0L; + + /** Minimum gas remaining for EIP-1706. */ + public static final long EIP_1706_MINIMUM = 2300L; + + /** Illegal state change result (static context or missing account). */ + protected static final OperationResult ILLEGAL_STATE_CHANGE = + new OperationResult(0L, ExceptionalHaltReason.ILLEGAL_STATE_CHANGE); + + private final long minimumGasRemaining; + + /** + * Instantiates a new SStore operation. + * + * @param gasCalculator the gas calculator + * @param minimumGasRemaining the minimum gas remaining + */ + public SStoreOperationV2(final GasCalculator gasCalculator, final long minimumGasRemaining) { + super(0x55, "SSTORE", 2, 0, gasCalculator); + this.minimumGasRemaining = minimumGasRemaining; + } + + /** + * Gets minimum gas remaining. + * + * @return the minimum gas remaining + */ + public long getMinimumGasRemaining() { + return minimumGasRemaining; + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), gasCalculator(), minimumGasRemaining); + } + + /** + * Execute the SSTORE opcode on the v2 long[] stack. + * + * @param frame the message frame + * @param s the stack as a long[] array + * @param gasCalculator the gas calculator + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final GasCalculator gasCalculator) { + return staticOperation(frame, s, gasCalculator, FRONTIER_MINIMUM); + } + + /** + * Execute the SSTORE opcode with an explicit minimum-gas-remaining check. + * + * @param frame the message frame + * @param s the stack as a long[] array + * @param gasCalculator the gas calculator + * @param minimumGasRemaining minimum gas required before executing (EIP-1706: 2300 for Istanbul+) + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, + final long[] s, + final GasCalculator gasCalculator, + final long minimumGasRemaining) { + if (!frame.stackHasItems(2)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + + final int top = frame.stackTopV2(); + + // Extract key (depth 0) and new value (depth 1) as raw 32-byte arrays + final byte[] keyBytes = new byte[32]; + final byte[] newValueBytes = new byte[32]; + StackArithmetic.toBytesAt(s, top, 0, keyBytes); + StackArithmetic.toBytesAt(s, top, 1, newValueBytes); + + // Pop 2 + frame.setTopV2(top - 2); + + final MutableAccount account = frame.getWorldUpdater().getAccount(frame.getRecipientAddress()); + frame.getEip7928AccessList().ifPresent(t -> t.addTouchedAccount(frame.getRecipientAddress())); + + if (account == null) { + return ILLEGAL_STATE_CHANGE; + } + + final long remainingGas = frame.getRemainingGas(); + + if (frame.isStatic()) { + return new OperationResult(remainingGas, ExceptionalHaltReason.ILLEGAL_STATE_CHANGE); + } + + if (remainingGas <= minimumGasRemaining) { + return new OperationResult(minimumGasRemaining, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + final Bytes32 keyBytes32 = Bytes32.wrap(keyBytes); + final UInt256 key = UInt256.fromBytes(keyBytes32); + final UInt256 newValue = UInt256.fromBytes(Bytes32.wrap(newValueBytes)); + + final boolean slotIsWarm = frame.warmUpStorage(frame.getRecipientAddress(), keyBytes32); + final Supplier currentValueSupplier = + Suppliers.memoize(() -> account.getStorageValue(key)); + final Supplier originalValueSupplier = + Suppliers.memoize(() -> account.getOriginalStorageValue(key)); + + final long cost = + gasCalculator.calculateStorageCost(newValue, currentValueSupplier, originalValueSupplier) + + (slotIsWarm ? 0L : gasCalculator.getColdSloadCost()); + + if (remainingGas < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + // Increment the refund counter + frame.incrementGasRefund( + gasCalculator.calculateStorageRefundAmount( + newValue, currentValueSupplier, originalValueSupplier)); + + account.setStorageValue(key, newValue); + frame.storageWasUpdated(key, Bytes.wrap(newValueBytes)); + frame + .getEip7928AccessList() + .ifPresent(t -> t.addSlotAccessForAccount(frame.getRecipientAddress(), key)); + + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2.java new file mode 100644 index 00000000000..d488886c5df --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The Sar operation. */ +public class SarOperationV2 extends AbstractFixedCostOperationV2 { + + /** The Sar operation success result. */ + static final OperationResult sarSuccess = new OperationResult(3, null); + + /** + * Instantiates a new Sar operation. + * + * @param gasCalculator the gas calculator + */ + public SarOperationV2(final GasCalculator gasCalculator) { + super(0x1d, "SAR", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs sar operation. + * + * @param frame the frame + * @param stack the v2 operand stack ({@code long[]} in big-endian limb order) + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.sar(stack, frame.stackTopV2())); + return sarSuccess; + } +} 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..dc7d51cf6f0 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java @@ -0,0 +1,65 @@ +/* + * 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; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** + * EVM v2 SELFBALANCE operation (0x47, Istanbul+). + * + *

Pushes the balance of the currently executing contract onto the stack. Fixed cost (low tier). + */ +public class SelfBalanceOperationV2 extends AbstractFixedCostOperationV2 { + + /** + * Instantiates a new SelfBalance 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) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Execute SELFBALANCE on the v2 long[] stack. + * + * @param frame the message frame + * @param s the stack data + * @return the operation result + */ + public static Operation.OperationResult staticOperation( + final MessageFrame frame, final long[] s) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + final int top = frame.stackTopV2(); + final Account account = getAccount(frame.getRecipientAddress(), frame); + if (account == null) { + frame.setTopV2(StackArithmetic.pushZero(s, top)); + } else { + frame.setTopV2(StackArithmetic.pushWei(s, top, account.getBalance())); + } + return new Operation.OperationResult(5, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfDestructOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfDestructOperationV2.java new file mode 100644 index 00000000000..2de0cec99b8 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfDestructOperationV2.java @@ -0,0 +1,168 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.EVM; +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.log.TransferLogEmitter; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 SELFDESTRUCT operation using long[] stack representation. */ +public class SelfDestructOperationV2 extends AbstractOperationV2 { + + private final boolean eip6780Semantics; + private final TransferLogEmitter transferLogEmitter; + + /** + * Instantiates a new Self destruct operation. + * + * @param gasCalculator the gas calculator + */ + public SelfDestructOperationV2(final GasCalculator gasCalculator) { + this(gasCalculator, false, TransferLogEmitter.NOOP); + } + + /** + * Instantiates a new Self destruct operation with optional EIP-6780 semantics. + * + * @param gasCalculator the gas calculator + * @param eip6780Semantics enforce EIP-6780 semantics (only destroy if created in same tx) + */ + public SelfDestructOperationV2( + final GasCalculator gasCalculator, final boolean eip6780Semantics) { + this(gasCalculator, eip6780Semantics, TransferLogEmitter.NOOP); + } + + /** + * Instantiates a new Self destruct operation with EIP-6780 and transfer log emission support. + * + * @param gasCalculator the gas calculator + * @param eip6780Semantics enforce EIP-6780 semantics (only destroy if created in same tx) + * @param transferLogEmitter strategy for emitting transfer logs + */ + public SelfDestructOperationV2( + final GasCalculator gasCalculator, + final boolean eip6780Semantics, + final TransferLogEmitter transferLogEmitter) { + super(0xFF, "SELFDESTRUCT", 1, 0, gasCalculator); + this.eip6780Semantics = eip6780Semantics; + this.transferLogEmitter = transferLogEmitter; + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + return staticOperation( + frame, frame.stackDataV2(), gasCalculator(), eip6780Semantics, transferLogEmitter); + } + + /** + * Performs SELFDESTRUCT operation. + * + * @param frame the frame + * @param s the stack data array + * @param gasCalculator the gas calculator + * @param eip6780Semantics true if EIP-6780 semantics should apply + * @param transferLogEmitter strategy for emitting transfer logs + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, + final long[] s, + final GasCalculator gasCalculator, + final boolean eip6780Semantics, + final TransferLogEmitter transferLogEmitter) { + // Check for static violations first — fewer account accesses on failure. + if (frame.isStatic()) { + return new OperationResult(0, ExceptionalHaltReason.ILLEGAL_STATE_CHANGE); + } + + if (!frame.stackHasItems(1)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + + final int top = frame.stackTopV2(); + final Address beneficiaryAddress = StackArithmetic.toAddressAt(s, top, 0); + frame.setTopV2(top - 1); + + final boolean beneficiaryIsWarm = + frame.warmUpAddress(beneficiaryAddress) || gasCalculator.isPrecompile(beneficiaryAddress); + final long beneficiaryAccessCost = + beneficiaryIsWarm ? 0L : gasCalculator.getColdAccountAccessCost(); + final long staticCost = + gasCalculator.selfDestructOperationStaticGasCost() + beneficiaryAccessCost; + + if (frame.getRemainingGas() < staticCost) { + return new OperationResult(staticCost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + final Account beneficiaryNullable = getAccount(beneficiaryAddress, frame); + final Address originatorAddress = frame.getRecipientAddress(); + final MutableAccount originatorAccount = getMutableAccount(originatorAddress, frame); + final Wei originatorBalance = originatorAccount.getBalance(); + + final long cost = + gasCalculator.selfDestructOperationGasCost(beneficiaryNullable, originatorBalance) + + beneficiaryAccessCost; + + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + // EIP-8037: Deduct regular gas before charging state gas (ordering requirement). + frame.decrementRemainingGas(cost); + + // EIP-8037: Charge state gas for new account creation in SELFDESTRUCT + if (!gasCalculator + .stateGasCostCalculator() + .chargeSelfDestructNewAccountStateGas(frame, beneficiaryNullable, originatorBalance)) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + // Add regular gas back — the EVM loop will deduct it via the OperationResult. + frame.incrementRemainingGas(cost); + + final MutableAccount beneficiaryAccount = getOrCreateAccount(beneficiaryAddress, frame); + + final boolean willBeDestroyed = + !eip6780Semantics || frame.wasCreatedInTransaction(originatorAccount.getAddress()); + + // Transfer all originator balance to beneficiary. + originatorAccount.decrementBalance(originatorBalance); + beneficiaryAccount.incrementBalance(originatorBalance); + + // Emit transfer log if applicable. + if (!originatorAddress.equals(beneficiaryAddress) || willBeDestroyed) { + transferLogEmitter.emitSelfDestructLog( + frame, originatorAddress, beneficiaryAddress, originatorBalance); + } + + // If actually destroying the originator, zero its balance and tag for cleanup. + if (willBeDestroyed) { + frame.addSelfDestruct(originatorAccount.getAddress()); + originatorAccount.setBalance(Wei.ZERO); + } + + frame.addRefund(beneficiaryAddress, originatorBalance); + frame.setState(MessageFrame.State.CODE_SUCCESS); + + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SgtOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SgtOperationV2.java new file mode 100644 index 00000000000..e0616d3601b --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SgtOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The Sgt operation. */ +public class SgtOperationV2 extends AbstractFixedCostOperationV2 { + + /** The Sgt operation success result. */ + static final OperationResult sgtSuccess = new OperationResult(3, null); + + /** + * Instantiates a new Sgt operation. + * + * @param gasCalculator the gas calculator + */ + public SgtOperationV2(final GasCalculator gasCalculator) { + super(0x13, "SGT", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs sgt operation. + * + * @param frame the frame + * @param stack the v2 operand stack ({@code long[]} in big-endian limb order) + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.sgt(stack, frame.stackTopV2())); + return sgtSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ShlOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ShlOperationV2.java new file mode 100644 index 00000000000..2f765e94269 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ShlOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The Shl (Shift Left) operation. */ +public class ShlOperationV2 extends AbstractFixedCostOperationV2 { + + /** The Shl operation success result. */ + static final OperationResult shlSuccess = new OperationResult(3, null); + + /** + * Instantiates a new Shl operation. + * + * @param gasCalculator the gas calculator + */ + public ShlOperationV2(final GasCalculator gasCalculator) { + super(0x1b, "SHL", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs Shift Left operation. + * + * @param frame the frame + * @param stack the v2 operand stack ({@code long[]} in big-endian limb order) + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.shl(stack, frame.stackTopV2())); + return shlSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ShrOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ShrOperationV2.java new file mode 100644 index 00000000000..71c412a04ab --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ShrOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The Shr (Shift Right) operation. */ +public class ShrOperationV2 extends AbstractFixedCostOperationV2 { + + /** The Shr operation success result. */ + static final OperationResult shrSuccess = new OperationResult(3, null); + + /** + * Instantiates a new Shr operation. + * + * @param gasCalculator the gas calculator + */ + public ShrOperationV2(final GasCalculator gasCalculator) { + super(0x1c, "SHR", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs SHR operation. + * + * @param frame the frame + * @param stack the v2 operand stack ({@code long[]} in big-endian limb order) + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.shr(stack, frame.stackTopV2())); + return shrSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SignExtendOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SignExtendOperationV2.java new file mode 100644 index 00000000000..fcd32b5b62d --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SignExtendOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 SIGNEXTEND operation (0x0B). */ +public class SignExtendOperationV2 extends AbstractFixedCostOperationV2 { + + /** The SignExtend operation success result. */ + static final OperationResult signExtendSuccess = new OperationResult(5, null); + + /** + * Instantiates a new SignExtend operation. + * + * @param gasCalculator the gas calculator + */ + public SignExtendOperationV2(final GasCalculator gasCalculator) { + super(0x0B, "SIGNEXTEND", 2, 1, gasCalculator, gasCalculator.getLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs SIGNEXTEND operation. + * + * @param frame the frame + * @param stack the v2 operand stack ({@code long[]} in big-endian limb order) + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.signExtend(stack, frame.stackTopV2())); + return signExtendSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SlotNumOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SlotNumOperationV2.java new file mode 100644 index 00000000000..71c659daf24 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SlotNumOperationV2.java @@ -0,0 +1,57 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 SLOTNUM operation — pushes the slot number onto the stack (Osaka+). */ +public class SlotNumOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult slotNumSuccess = new OperationResult(2, null); + + /** + * Instantiates a new SlotNum operation. + * + * @param gasCalculator the gas calculator + */ + public SlotNumOperationV2(final GasCalculator gasCalculator) { + super(0x4b, "SLOTNUM", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs the SLOTNUM operation. + * + * @param frame the message frame + * @param stack the v2 long[] stack + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTopV2( + StackArithmetic.pushLong( + stack, frame.stackTopV2(), frame.getBlockValues().getSlotNumber())); + return slotNumSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SltOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SltOperationV2.java new file mode 100644 index 00000000000..a8ab7be5307 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SltOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The Slt operation. */ +public class SltOperationV2 extends AbstractFixedCostOperationV2 { + + /** The Slt operation success result. */ + static final OperationResult sltSuccess = new OperationResult(3, null); + + /** + * Instantiates a new Slt operation. + * + * @param gasCalculator the gas calculator + */ + public SltOperationV2(final GasCalculator gasCalculator) { + super(0x12, "SLT", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs slt operation. + * + * @param frame the frame + * @param stack the v2 operand stack ({@code long[]} in big-endian limb order) + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.slt(stack, frame.stackTopV2())); + return sltSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/StaticCallOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/StaticCallOperationV2.java new file mode 100644 index 00000000000..077bf6d86e6 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/StaticCallOperationV2.java @@ -0,0 +1,112 @@ +/* + * 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.datatypes.Wei; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.EvmSpecVersion; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.InvalidOperation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** + * EVM v2 STATICCALL operation (opcode 0xFA, Byzantium+). Stack layout: gas, to, inOffset, inSize, + * outOffset, outSize → result. + */ +public class StaticCallOperationV2 extends AbstractCallOperationV2 { + + /** + * Instantiates a new STATICCALL operation for EVM v2. + * + * @param gasCalculator the gas calculator + */ + public StaticCallOperationV2(final GasCalculator gasCalculator) { + super(0xFA, "STATICCALL", 6, 1, gasCalculator); + } + + @Override + protected Address to(final long[] s, final int top) { + return StackArithmetic.toAddressAt(s, top, 1); + } + + @Override + protected Wei value(final long[] s, final int top) { + return Wei.ZERO; + } + + @Override + protected Wei apparentValue(final MessageFrame frame, final long[] s, final int top) { + return value(s, top); + } + + @Override + protected long inputDataOffset(final long[] s, final int top) { + return StackArithmetic.clampedToLong(s, top, 2); + } + + @Override + protected long inputDataLength(final long[] s, final int top) { + return StackArithmetic.clampedToLong(s, top, 3); + } + + @Override + protected long outputDataOffset(final long[] s, final int top) { + return StackArithmetic.clampedToLong(s, top, 4); + } + + @Override + protected long outputDataLength(final long[] s, final int top) { + return StackArithmetic.clampedToLong(s, top, 5); + } + + @Override + protected Address address(final MessageFrame frame, final long[] s, final int top) { + return to(s, top); + } + + @Override + protected Address sender(final MessageFrame frame) { + return frame.getRecipientAddress(); + } + + @Override + public long gasAvailableForChildCall(final MessageFrame frame, final long[] s, final int top) { + return gasCalculator().gasAvailableForChildCall(frame, gas(s, top), false); + } + + @Override + protected boolean isStatic(final MessageFrame frame) { + return true; + } + + /** + * Performs the STATICCALL operation on the v2 stack. Only available from Byzantium onwards. + * + * @param frame the current message frame + * @param s the v2 stack array + * @param gasCalculator the gas calculator + * @param evm the EVM + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final GasCalculator gasCalculator, final EVM evm) { + if (EvmSpecVersion.BYZANTIUM.ordinal() > evm.getEvmVersion().ordinal()) { + return InvalidOperation.invalidOperationResult(0xFA); + } + return new StaticCallOperationV2(gasCalculator).execute(frame, evm); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/StopOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/StopOperationV2.java new file mode 100644 index 00000000000..5be0ec74ca7 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/StopOperationV2.java @@ -0,0 +1,55 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; + +import org.apache.tuweni.bytes.Bytes; + +/** EVM v2 STOP operation. Sets frame state to CODE_SUCCESS with empty output. */ +public class StopOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult STOP_SUCCESS = new OperationResult(0, null); + + /** + * Instantiates a new Stop operation. + * + * @param gasCalculator the gas calculator + */ + public StopOperationV2(final GasCalculator gasCalculator) { + super(0x00, "STOP", 0, 0, gasCalculator, gasCalculator.getZeroTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame); + } + + /** + * Performs Stop operation. + * + * @param frame the frame + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame) { + frame.setState(MessageFrame.State.CODE_SUCCESS); + frame.setOutputData(Bytes.EMPTY); + return STOP_SUCCESS; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SubOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SubOperationV2.java new file mode 100644 index 00000000000..731a4f3b872 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SubOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** The Sub operation. */ +public class SubOperationV2 extends AbstractFixedCostOperationV2 { + + /** The Sub operation success result. */ + static final OperationResult subSuccess = new OperationResult(3, null); + + /** + * Instantiates a new Sub operation. + * + * @param gasCalculator the gas calculator + */ + public SubOperationV2(final GasCalculator gasCalculator) { + super(0x03, "SUB", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs sub operation. + * + * @param frame the frame + * @param stack the v2 operand stack ({@code long[]} in big-endian limb order) + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.sub(stack, frame.stackTopV2())); + return subSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SwapNOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SwapNOperationV2.java new file mode 100644 index 00000000000..b84b969da2b --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SwapNOperationV2.java @@ -0,0 +1,86 @@ +/* + * 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.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Eip8024Decoder; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** + * EVM v2 SWAPN operation (0xE7, EIP-8024). + * + *

Swaps the top of the stack with the (n+1)'th stack item, where n is decoded from a 1-byte + * immediate operand. PC increment is 2. Gas cost is veryLow tier (3). Requires Amsterdam fork or + * later. + */ +public class SwapNOperationV2 extends AbstractFixedCostOperationV2 { + + /** The SWAPN opcode value. */ + public static final int OPCODE = 0xe7; + + private static final OperationResult SWAPN_SUCCESS = new OperationResult(3, null, 2); + + private static final OperationResult INVALID_IMMEDIATE = + new OperationResult(3, ExceptionalHaltReason.INVALID_OPERATION, 2); + + private static final OperationResult SWAPN_UNDERFLOW = + new OperationResult(3, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS, 2); + + /** + * Instantiates a new SWAPN operation. + * + * @param gasCalculator the gas calculator + */ + public SwapNOperationV2(final GasCalculator gasCalculator) { + super(OPCODE, "SWAPN", 0, 0, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation( + frame, frame.stackDataV2(), frame.getCode().getBytes().toArrayUnsafe(), frame.getPC()); + } + + /** + * Performs SWAPN operation. + * + * @param frame the message frame + * @param s the stack data array + * @param code the bytecode array + * @param pc the current program counter + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final byte[] code, final int pc) { + final int imm = (pc + 1 >= code.length) ? 0 : code[pc + 1] & 0xFF; + + if (!Eip8024Decoder.VALID_SINGLE[imm]) { + return INVALID_IMMEDIATE; + } + + final int n = Eip8024Decoder.DECODE_SINGLE[imm]; + + if (!frame.stackHasItems(n + 1)) { + return SWAPN_UNDERFLOW; + } + StackArithmetic.swap(s, frame.stackTopV2(), n); + return SWAPN_SUCCESS; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SwapOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SwapOperationV2.java new file mode 100644 index 00000000000..e3f14487944 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SwapOperationV2.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 org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** + * EVM v2 SWAP1-16 operation (opcodes 0x90–0x9F). + * + *

Swaps the top stack item with the item at depth {@code index} (1-based). Gas cost is veryLow + * tier (3). + */ +public class SwapOperationV2 extends AbstractFixedCostOperationV2 { + + /** The SWAP opcode base (SWAP1 = 0x90, so base = 0x8F). */ + public static final int SWAP_BASE = 0x8F; + + static final OperationResult SWAP_SUCCESS = new OperationResult(3, null); + + private final int index; + + /** + * Instantiates a new Swap operation. + * + * @param index the 1-based swap depth (1–16) + * @param gasCalculator the gas calculator + */ + public SwapOperationV2(final int index, final GasCalculator gasCalculator) { + super( + SWAP_BASE + index, + "SWAP" + index, + index + 1, + index + 1, + gasCalculator, + gasCalculator.getVeryLowTierGasCost()); + this.index = index; + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2(), index); + } + + /** + * Performs SWAP operation. + * + * @param frame the frame + * @param s the stack data array + * @param index the 1-based swap depth + * @return the operation result + */ + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final int index) { + if (!frame.stackHasItems(index + 1)) return UNDERFLOW_RESPONSE; + StackArithmetic.swap(s, frame.stackTopV2(), index); + return SWAP_SUCCESS; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/TLoadOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/TLoadOperationV2.java new file mode 100644 index 00000000000..ff91e65a1c9 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/TLoadOperationV2.java @@ -0,0 +1,80 @@ +/* + * 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.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +import org.apache.tuweni.bytes.Bytes32; + +/** + * EVM v2 TLOAD operation (EIP-1153) using long[] stack representation. + * + *

Pops a slot key from the stack, loads the value from transient storage, and pushes the result + * back. Gas cost is WARM_STORAGE_READ_COST (100) per EIP-1153. + */ +public class TLoadOperationV2 extends AbstractFixedCostOperationV2 { + + private static final long GAS_COST = 100L; + private static final OperationResult TLOAD_SUCCESS = new OperationResult(GAS_COST, null); + private static final OperationResult TLOAD_OUT_OF_GAS = + new OperationResult(GAS_COST, ExceptionalHaltReason.INSUFFICIENT_GAS); + + /** + * Instantiates a new TLoad operation. + * + * @param gasCalculator the gas calculator + */ + public TLoadOperationV2(final GasCalculator gasCalculator) { + super(0x5C, "TLOAD", 1, 1, gasCalculator, gasCalculator.getTransientLoadOperationGasCost()); + } + + @Override + public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Execute the TLOAD opcode on the v2 long[] stack. + * + * @param frame the message frame + * @param s the stack as a long[] array + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(1)) { + return UNDERFLOW_RESPONSE; + } + + if (frame.getRemainingGas() < GAS_COST) { + return TLOAD_OUT_OF_GAS; + } + + final int top = frame.stackTopV2(); + + final byte[] keyBytes = new byte[32]; + StackArithmetic.toBytesAt(s, top, 0, keyBytes); + final Bytes32 slot = Bytes32.wrap(keyBytes); + + final byte[] result = + frame.getTransientStorageValue(frame.getRecipientAddress(), slot).toArrayUnsafe(); + StackArithmetic.fromBytesAt(s, top, 0, result, 0, result.length); + + return TLOAD_SUCCESS; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/TStoreOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/TStoreOperationV2.java new file mode 100644 index 00000000000..71e9cea9d13 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/TStoreOperationV2.java @@ -0,0 +1,89 @@ +/* + * 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.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +import org.apache.tuweni.bytes.Bytes32; + +/** + * EVM v2 TSTORE operation (EIP-1153) using long[] stack representation. + * + *

Pops a slot key and value from the stack, then writes the value to transient storage. Gas cost + * is WARM_STORAGE_READ_COST (100) per EIP-1153. + */ +public class TStoreOperationV2 extends AbstractFixedCostOperationV2 { + + private static final long GAS_COST = 100L; + private static final OperationResult TSTORE_SUCCESS = new OperationResult(GAS_COST, null); + private static final OperationResult TSTORE_OUT_OF_GAS = + new OperationResult(GAS_COST, ExceptionalHaltReason.INSUFFICIENT_GAS); + + /** + * Instantiates a new TStore operation. + * + * @param gasCalculator the gas calculator + */ + public TStoreOperationV2(final GasCalculator gasCalculator) { + super(0x5D, "TSTORE", 2, 0, gasCalculator, gasCalculator.getTransientStoreOperationGasCost()); + } + + @Override + public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Execute the TSTORE opcode on the v2 long[] stack. + * + * @param frame the message frame + * @param s the stack as a long[] array + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) { + return UNDERFLOW_RESPONSE; + } + + final int top = frame.stackTopV2(); + + final byte[] keyBytes = new byte[32]; + final byte[] valueBytes = new byte[32]; + StackArithmetic.toBytesAt(s, top, 0, keyBytes); + StackArithmetic.toBytesAt(s, top, 1, valueBytes); + + frame.setTopV2(top - 2); + + final long remainingGas = frame.getRemainingGas(); + if (frame.isStatic()) { + return new OperationResult(remainingGas, ExceptionalHaltReason.ILLEGAL_STATE_CHANGE); + } + + if (remainingGas < GAS_COST) { + return TSTORE_OUT_OF_GAS; + } + + final Bytes32 slot = Bytes32.wrap(keyBytes); + final Bytes32 value = Bytes32.wrap(valueBytes); + + frame.setTransientStorageValue(frame.getRecipientAddress(), slot, value); + + return TSTORE_SUCCESS; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/TimestampOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/TimestampOperationV2.java new file mode 100644 index 00000000000..7fdd8f98aaa --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/TimestampOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 TIMESTAMP operation — pushes the block timestamp onto the stack. */ +public class TimestampOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult timestampSuccess = new OperationResult(2, null); + + /** + * Instantiates a new Timestamp operation. + * + * @param gasCalculator the gas calculator + */ + public TimestampOperationV2(final GasCalculator gasCalculator) { + super(0x42, "TIMESTAMP", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs the TIMESTAMP operation. + * + * @param frame the message frame + * @param stack the v2 long[] stack + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTopV2( + StackArithmetic.pushLong(stack, frame.stackTopV2(), frame.getBlockValues().getTimestamp())); + return timestampSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/XorOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/XorOperationV2.java new file mode 100644 index 00000000000..9c80a1bdfcc --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/XorOperationV2.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +/** EVM v2 XOR operation (0x18). */ +public class XorOperationV2 extends AbstractFixedCostOperationV2 { + + /** The Xor operation success result. */ + static final OperationResult xorSuccess = new OperationResult(3, null); + + /** + * Instantiates a new Xor operation. + * + * @param gasCalculator the gas calculator + */ + public XorOperationV2(final GasCalculator gasCalculator) { + super(0x18, "XOR", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs XOR operation. + * + * @param frame the frame + * @param stack the v2 operand stack ({@code long[]} in big-endian limb order) + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTopV2(StackArithmetic.xor(stack, frame.stackTopV2())); + return xorSuccess; + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2Test.java new file mode 100644 index 00000000000..528b9515782 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2Test.java @@ -0,0 +1,196 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.gascalculator.SpuriousDragonGasCalculator; +import org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2; + +import java.util.List; + +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class SarOperationV2Test { + + private final GasCalculator gasCalculator = new SpuriousDragonGasCalculator(); + private final SarOperationV2 operation = new SarOperationV2(gasCalculator); + + static Iterable data() { + return List.of( + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x00", + "0x0000000000000000000000000000000000000000000000000000000000000001"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000000"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000001"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000004", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000002"), + Arguments.of( + "0x000000000000000000000000000000000000000000000000000000000000000f", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000007"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000004"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0x01", + "0xc000000000000000000000000000000000000000000000000000000000000000"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0xff", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0x100", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0x101", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x0", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x01", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0xff", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x100", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000000"), + Arguments.of( + "0x4000000000000000000000000000000000000000000000000000000000000000", + "0xfe", + "0x0000000000000000000000000000000000000000000000000000000000000001"), + Arguments.of( + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0xf8", + "0x000000000000000000000000000000000000000000000000000000000000007f"), + Arguments.of( + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0xfe", + "0x0000000000000000000000000000000000000000000000000000000000000001"), + Arguments.of( + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0xff", + "0x0000000000000000000000000000000000000000000000000000000000000000"), + Arguments.of( + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0x100", "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x80", + "0x0000000000000000000000000000000000000000000000000000000000000000"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", "0x8000", "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x80000000", + "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x8000000000000000", + "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x80000000000000000000000000000000", + "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0x"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000400", + "0x80", + "0xffffffffffffffffffffffffffffffff80000000000000000000000000000000"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000400", + "0x8000", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000400", + "0x80000000", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000400", + "0x8000000000000000", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000400", + "0x80000000000000000000000000000000", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000400", + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); + } + + @ParameterizedTest(name = "{index}: {0}, {1}, {2}") + @MethodSource("data") + void shiftOperation(final String number, final String shift, final String expectedResult) { + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .pushStackItem(Bytes32.fromHexStringLenient(number)) + .pushStackItem(Bytes32.fromHexStringLenient(shift)) + .build(); + operation.execute(frame, null); + UInt256 expected; + if (expectedResult.equals("0x") || expectedResult.equals("0x0")) { + expected = UInt256.ZERO; + } else { + expected = UInt256.fromBytesBE(Bytes32.fromHexStringLenient(expectedResult).toArrayUnsafe()); + } + assertThat(getV2StackItem(frame, 0)).isEqualTo(expected); + } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } + + private static UInt256 getV2StackItem(final MessageFrame frame, final int offset) { + final long[] s = frame.stackDataV2(); + final int idx = (frame.stackTopV2() - 1 - offset) << 2; + return new UInt256(s[idx], s[idx + 1], s[idx + 2], s[idx + 3]); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShiftOperationsV2PropertyBasedTest.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShiftOperationsV2PropertyBasedTest.java new file mode 100644 index 00000000000..e9d43c145ed --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShiftOperationsV2PropertyBasedTest.java @@ -0,0 +1,586 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.operation.SarOperation; +import org.hyperledger.besu.evm.operation.ShlOperation; +import org.hyperledger.besu.evm.operation.ShrOperation; +import org.hyperledger.besu.evm.v2.StackArithmetic; + +import java.util.ArrayDeque; +import java.util.Deque; + +import net.jqwik.api.Arbitraries; +import net.jqwik.api.Arbitrary; +import net.jqwik.api.ForAll; +import net.jqwik.api.Property; +import net.jqwik.api.Provide; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +/** + * Property-based tests comparing v2 StackArithmetic shift operations against the original + * non-optimized Bytes-based implementations. + */ +public class ShiftOperationsV2PropertyBasedTest { + + // region Arbitrary Providers + + @Provide + Arbitrary values1to32() { + return Arbitraries.bytes().array(byte[].class).ofMinSize(1).ofMaxSize(32); + } + + @Provide + Arbitrary shiftAmounts() { + return Arbitraries.bytes().array(byte[].class).ofMinSize(0).ofMaxSize(32); + } + + @Provide + Arbitrary smallShifts() { + return Arbitraries.integers().between(0, 255); + } + + @Provide + Arbitrary overflowShifts() { + return Arbitraries.integers().between(256, 1024); + } + + @Provide + Arbitrary negativeValues() { + return Arbitraries.bytes() + .array(byte[].class) + .ofSize(32) + .map( + bytes -> { + bytes[0] = (byte) (bytes[0] | 0x80); + return bytes; + }); + } + + @Provide + Arbitrary positiveValues() { + return Arbitraries.bytes() + .array(byte[].class) + .ofMinSize(1) + .ofMaxSize(32) + .map( + bytes -> { + bytes[0] = (byte) (bytes[0] & 0x7F); + return bytes; + }); + } + + // endregion + + // region SHL Property Tests + + @Property(tries = 10000) + void property_shlV2_matchesOriginal_randomInputs( + @ForAll("values1to32") final byte[] valueBytes, + @ForAll("shiftAmounts") final byte[] shiftBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shift = Bytes.wrap(shiftBytes); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalShl(shift, value)); + final Bytes32 v2Result = runV2Shl(shift, value); + + assertThat(v2Result) + .as( + "SHL v2 mismatch for shift=%s, value=%s", + shift.toHexString(), Bytes32.leftPad(value).toHexString()) + .isEqualTo(originalResult); + } + + @Property(tries = 5000) + void property_shlV2_matchesOriginal_smallShifts( + @ForAll("values1to32") final byte[] valueBytes, @ForAll("smallShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = Bytes.of(shift); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalShl(shiftBytes, value)); + final Bytes32 v2Result = runV2Shl(shiftBytes, value); + + assertThat(v2Result).as("SHL v2 mismatch for shift=%d", shift).isEqualTo(originalResult); + } + + @Property(tries = 1000) + void property_shlV2_matchesOriginal_overflowShifts( + @ForAll("values1to32") final byte[] valueBytes, @ForAll("overflowShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = intToMinimalBytes(shift); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalShl(shiftBytes, value)); + final Bytes32 v2Result = runV2Shl(shiftBytes, value); + + assertThat(v2Result) + .as("SHL v2 overflow mismatch for shift=%d", shift) + .isEqualTo(originalResult); + } + + // endregion + + // region SHR Property Tests + + @Property(tries = 10000) + void property_shrV2_matchesOriginal_randomInputs( + @ForAll("values1to32") final byte[] valueBytes, + @ForAll("shiftAmounts") final byte[] shiftBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shift = Bytes.wrap(shiftBytes); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalShr(shift, value)); + final Bytes32 v2Result = runV2Shr(shift, value); + + assertThat(v2Result) + .as( + "SHR v2 mismatch for shift=%s, value=%s", + shift.toHexString(), Bytes32.leftPad(value).toHexString()) + .isEqualTo(originalResult); + } + + @Property(tries = 5000) + void property_shrV2_matchesOriginal_smallShifts( + @ForAll("values1to32") final byte[] valueBytes, @ForAll("smallShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = Bytes.of(shift); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalShr(shiftBytes, value)); + final Bytes32 v2Result = runV2Shr(shiftBytes, value); + + assertThat(v2Result).as("SHR v2 mismatch for shift=%d", shift).isEqualTo(originalResult); + } + + @Property(tries = 1000) + void property_shrV2_matchesOriginal_overflowShifts( + @ForAll("values1to32") final byte[] valueBytes, @ForAll("overflowShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = intToMinimalBytes(shift); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalShr(shiftBytes, value)); + final Bytes32 v2Result = runV2Shr(shiftBytes, value); + + assertThat(v2Result) + .as("SHR v2 overflow mismatch for shift=%d", shift) + .isEqualTo(originalResult); + } + + // endregion + + // region SAR Property Tests - Random Inputs + + @Property(tries = 10000) + void property_sarV2_matchesOriginal_randomInputs( + @ForAll("values1to32") final byte[] valueBytes, + @ForAll("shiftAmounts") final byte[] shiftBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shift = Bytes.wrap(shiftBytes); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalSar(shift, value)); + final Bytes32 v2Result = runV2Sar(shift, value); + + assertThat(v2Result) + .as( + "SAR v2 mismatch for shift=%s, value=%s", + shift.toHexString(), Bytes32.leftPad(value).toHexString()) + .isEqualTo(originalResult); + } + + @Property(tries = 5000) + void property_sarV2_matchesOriginal_smallShifts( + @ForAll("values1to32") final byte[] valueBytes, @ForAll("smallShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = Bytes.of(shift); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalSar(shiftBytes, value)); + final Bytes32 v2Result = runV2Sar(shiftBytes, value); + + assertThat(v2Result).as("SAR v2 mismatch for shift=%d", shift).isEqualTo(originalResult); + } + + @Property(tries = 1000) + void property_sarV2_matchesOriginal_overflowShifts( + @ForAll("values1to32") final byte[] valueBytes, @ForAll("overflowShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = intToMinimalBytes(shift); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalSar(shiftBytes, value)); + final Bytes32 v2Result = runV2Sar(shiftBytes, value); + + assertThat(v2Result) + .as("SAR v2 overflow mismatch for shift=%d", shift) + .isEqualTo(originalResult); + } + + // endregion + + // region SAR Property Tests - Negative Values (Sign Extension) + + @Property(tries = 5000) + void property_sarV2_matchesOriginal_negativeValues_smallShifts( + @ForAll("negativeValues") final byte[] valueBytes, @ForAll("smallShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = Bytes.of(shift); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalSar(shiftBytes, value)); + final Bytes32 v2Result = runV2Sar(shiftBytes, value); + + assertThat(v2Result) + .as("SAR v2 negative mismatch for shift=%d, value=%s", shift, value.toHexString()) + .isEqualTo(originalResult); + } + + @Property(tries = 1000) + void property_sarV2_matchesOriginal_negativeValues_overflowShifts( + @ForAll("negativeValues") final byte[] valueBytes, + @ForAll("overflowShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = intToMinimalBytes(shift); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalSar(shiftBytes, value)); + final Bytes32 v2Result = runV2Sar(shiftBytes, value); + + assertThat(v2Result) + .as("SAR v2 negative overflow mismatch for shift=%d", shift) + .isEqualTo(originalResult); + } + + // endregion + + // region SAR Property Tests - Negative/Positive Values at Shift 255 + + @Property(tries = 3000) + void property_sarV2_matchesOriginal_negativeValues_shift255( + @ForAll("negativeValues") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shift = Bytes.of(255); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalSar(shift, value)); + final Bytes32 v2Result = runV2Sar(shift, value); + + assertThat(v2Result) + .as("SAR v2 negative shift=255 mismatch for value=%s", value.toHexString()) + .isEqualTo(originalResult); + } + + @Property(tries = 3000) + void property_sarV2_matchesOriginal_positiveValues_shift255( + @ForAll("positiveValues") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shift = Bytes.of(255); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalSar(shift, value)); + final Bytes32 v2Result = runV2Sar(shift, value); + + assertThat(v2Result) + .as("SAR v2 positive shift=255 mismatch for value=%s", value.toHexString()) + .isEqualTo(originalResult); + } + + // endregion + + // region SAR Property Tests - Positive Values + + @Property(tries = 5000) + void property_sarV2_matchesOriginal_positiveValues_smallShifts( + @ForAll("positiveValues") final byte[] valueBytes, @ForAll("smallShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = Bytes.of(shift); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalSar(shiftBytes, value)); + final Bytes32 v2Result = runV2Sar(shiftBytes, value); + + assertThat(v2Result) + .as("SAR v2 positive mismatch for shift=%d, value=%s", shift, value.toHexString()) + .isEqualTo(originalResult); + } + + @Property(tries = 1000) + void property_sarV2_matchesOriginal_positiveValues_overflowShifts( + @ForAll("positiveValues") final byte[] valueBytes, + @ForAll("overflowShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = intToMinimalBytes(shift); + + final Bytes32 originalResult = Bytes32.leftPad(runOriginalSar(shiftBytes, value)); + final Bytes32 v2Result = runV2Sar(shiftBytes, value); + + assertThat(v2Result) + .as("SAR v2 positive overflow mismatch for shift=%d", shift) + .isEqualTo(originalResult); + } + + // endregion + + // region Edge Case Tests - SHL / SHR + + @Property(tries = 1000) + void property_shlV2_shiftByZero_returnsOriginalValue( + @ForAll("values1to32") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shift = Bytes.of(0); + + final Bytes32 v2Result = runV2Shl(shift, value); + + assertThat(v2Result).isEqualTo(Bytes32.leftPad(value)); + } + + @Property(tries = 1000) + void property_shrV2_shiftByZero_returnsOriginalValue( + @ForAll("values1to32") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shift = Bytes.of(0); + + final Bytes32 v2Result = runV2Shr(shift, value); + + assertThat(v2Result).isEqualTo(Bytes32.leftPad(value)); + } + + @Property(tries = 500) + void property_shlV2_largeShift_returnsZero(@ForAll("values1to32") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes largeShift = Bytes.fromHexString("0x010000000000"); + + final Bytes32 v2Result = runV2Shl(largeShift, value); + + assertThat(v2Result).isEqualTo(Bytes32.ZERO); + } + + @Property(tries = 500) + void property_shrV2_largeShift_returnsZero(@ForAll("values1to32") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes largeShift = Bytes.fromHexString("0x010000000000"); + + final Bytes32 v2Result = runV2Shr(largeShift, value); + + assertThat(v2Result).isEqualTo(Bytes32.ZERO); + } + + // endregion + + // region Edge Case Tests - SAR + + @Property(tries = 1000) + void property_sarV2_shiftByZero_returnsOriginalValue( + @ForAll("values1to32") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shift = Bytes.of(0); + + final Bytes32 v2Result = runV2Sar(shift, value); + + assertThat(v2Result).isEqualTo(Bytes32.leftPad(value)); + } + + @Property(tries = 500) + void property_sarV2_negativeValue_largeShift_returnsAllOnes( + @ForAll("negativeValues") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes largeShift = Bytes.fromHexString("0x010000000000"); + + final Bytes32 v2Result = runV2Sar(largeShift, value); + + final Bytes32 allOnes = + Bytes32.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + assertThat(v2Result).isEqualTo(allOnes); + } + + @Property(tries = 500) + void property_sarV2_positiveValue_largeShift_returnsZero( + @ForAll("positiveValues") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes largeShift = Bytes.fromHexString("0x010000000000"); + + final Bytes32 v2Result = runV2Sar(largeShift, value); + + assertThat(v2Result).isEqualTo(Bytes32.ZERO); + } + + @Property(tries = 500) + void property_sarV2_allOnes_anyShift_returnsAllOnes(@ForAll("smallShifts") final int shift) { + + final Bytes value = + Bytes.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + final Bytes shiftBytes = Bytes.of(shift); + + final Bytes32 v2Result = runV2Sar(shiftBytes, value); + + assertThat(v2Result).isEqualTo(Bytes32.leftPad(value)); + } + + @Property(tries = 500) + void property_sarV2_minValue_shift255_returnsAllOnes() { + + final Bytes value = + Bytes.fromHexString("0x8000000000000000000000000000000000000000000000000000000000000000"); + final Bytes shift = Bytes.of(255); + + final Bytes32 v2Result = runV2Sar(shift, value); + + final Bytes32 allOnes = + Bytes32.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + assertThat(v2Result).isEqualTo(allOnes); + } + + @Property(tries = 500) + void property_sarV2_maxPositive_shift255_returnsZero() { + + final Bytes value = + Bytes.fromHexString("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + final Bytes shift = Bytes.of(255); + + final Bytes32 v2Result = runV2Sar(shift, value); + + assertThat(v2Result).isEqualTo(Bytes32.ZERO); + } + + // endregion + + // region V1 Oracle Helpers (mock-based, Bytes stack) + + private Bytes runOriginalShl(final Bytes shift, final Bytes value) { + return runOriginalOperation(shift, value, ShlOperation::staticOperation); + } + + private Bytes runOriginalShr(final Bytes shift, final Bytes value) { + return runOriginalOperation(shift, value, ShrOperation::staticOperation); + } + + private Bytes runOriginalSar(final Bytes shift, final Bytes value) { + return runOriginalOperation(shift, value, SarOperation::staticOperation); + } + + @FunctionalInterface + interface OriginalOperationExecutor { + Operation.OperationResult execute(MessageFrame frame); + } + + private Bytes runOriginalOperation( + final Bytes shift, final Bytes value, final OriginalOperationExecutor executor) { + final MessageFrame frame = mock(MessageFrame.class); + final Deque stack = new ArrayDeque<>(); + stack.push(value); + stack.push(shift); + + when(frame.popStackItem()).thenAnswer(invocation -> stack.pop()); + + final Bytes[] result = new Bytes[1]; + doAnswer( + invocation -> { + result[0] = invocation.getArgument(0); + return null; + }) + .when(frame) + .pushStackItem(any(Bytes.class)); + + executor.execute(frame); + return result[0]; + } + + // endregion + + // region V2 Helpers (long[] stack) + + private Bytes32 runV2Shl(final Bytes shift, final Bytes value) { + return runV2Operation(shift, value, StackArithmetic::shl); + } + + private Bytes32 runV2Shr(final Bytes shift, final Bytes value) { + return runV2Operation(shift, value, StackArithmetic::shr); + } + + private Bytes32 runV2Sar(final Bytes shift, final Bytes value) { + return runV2Operation(shift, value, StackArithmetic::sar); + } + + @FunctionalInterface + interface V2OperationExecutor { + int execute(long[] stack, int top); + } + + private Bytes32 runV2Operation( + final Bytes shift, final Bytes value, final V2OperationExecutor executor) { + final UInt256 shiftVal = UInt256.fromBytesBE(Bytes32.leftPad(shift).toArrayUnsafe()); + final UInt256 valueVal = UInt256.fromBytesBE(Bytes32.leftPad(value).toArrayUnsafe()); + + final long[] s = new long[8]; + writeLimbs(s, 0, valueVal); + writeLimbs(s, 4, shiftVal); + + executor.execute(s, 2); + + final UInt256 result = new UInt256(s[0], s[1], s[2], s[3]); + return Bytes32.wrap(result.toBytesBE()); + } + + private static void writeLimbs(final long[] s, final int offset, final UInt256 val) { + s[offset] = val.u3(); + s[offset + 1] = val.u2(); + s[offset + 2] = val.u1(); + s[offset + 3] = val.u0(); + } + + // endregion + + // region Utility + + private Bytes intToMinimalBytes(final int value) { + if (value == 0) { + return Bytes.EMPTY; + } + if (value <= 0xFF) { + return Bytes.of(value); + } + if (value <= 0xFFFF) { + return Bytes.of(value >> 8, value & 0xFF); + } + if (value <= 0xFFFFFF) { + return Bytes.of(value >> 16, (value >> 8) & 0xFF, value & 0xFF); + } + return Bytes.of(value >> 24, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF); + } + + // endregion +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShlOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShlOperationV2Test.java new file mode 100644 index 00000000000..246c4198201 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShlOperationV2Test.java @@ -0,0 +1,132 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.gascalculator.SpuriousDragonGasCalculator; +import org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2; + +import java.util.Arrays; + +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class ShlOperationV2Test { + + private final GasCalculator gasCalculator = new SpuriousDragonGasCalculator(); + private final ShlOperationV2 operation = new ShlOperationV2(gasCalculator); + + static Iterable data() { + return Arrays.asList( + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x00", + "0x0000000000000000000000000000000000000000000000000000000000000001"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000002"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000004"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000004", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000008"), + Arguments.of( + "0x000000000000000000000000000000000000000000000000000000000000000f", + "0x01", + "0x000000000000000000000000000000000000000000000000000000000000001e"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000010"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000001", "0x100", "0x"), + Arguments.of( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x01", + "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000000"), + Arguments.of( + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x01", + "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x80", + "0x0000000000000000000000000000040000000000000000000000000000000000"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", "0x8000", "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x80000000", + "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x8000000000000000", + "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x80000000000000000000000000000000", + "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0x")); + } + + @ParameterizedTest + @MethodSource("data") + void shiftOperation(final String number, final String shift, final String expectedResult) { + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .pushStackItem(Bytes32.fromHexStringLenient(number)) + .pushStackItem(Bytes32.fromHexStringLenient(shift)) + .build(); + operation.execute(frame, null); + UInt256 expected; + if (expectedResult.equals("0x") || expectedResult.equals("0x0")) { + expected = UInt256.ZERO; + } else { + expected = UInt256.fromBytesBE(Bytes32.fromHexStringLenient(expectedResult).toArrayUnsafe()); + } + assertThat(getV2StackItem(frame, 0)).isEqualTo(expected); + } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } + + private static UInt256 getV2StackItem(final MessageFrame frame, final int offset) { + final long[] s = frame.stackDataV2(); + final int idx = (frame.stackTopV2() - 1 - offset) << 2; + return new UInt256(s[idx], s[idx + 1], s[idx + 2], s[idx + 3]); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShrOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShrOperationV2Test.java new file mode 100644 index 00000000000..65949ff7a27 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShrOperationV2Test.java @@ -0,0 +1,144 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.gascalculator.SpuriousDragonGasCalculator; +import org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2; + +import java.util.Arrays; + +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class ShrOperationV2Test { + + private final GasCalculator gasCalculator = new SpuriousDragonGasCalculator(); + private final ShrOperationV2 operation = new ShrOperationV2(gasCalculator); + + static Iterable data() { + return Arrays.asList( + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x00", + "0x0000000000000000000000000000000000000000000000000000000000000001"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000000"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000001"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000004", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000002"), + Arguments.of( + "0x000000000000000000000000000000000000000000000000000000000000000f", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000007"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000004"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0xff", + "0x0000000000000000000000000000000000000000000000000000000000000001"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000000", "0x100", "0x"), + Arguments.of( + "0x8000000000000000000000000000000000000000000000000000000000000000", "0x101", "0x"), + Arguments.of( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x0", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x01", + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Arguments.of( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0xff", + "0x0000000000000000000000000000000000000000000000000000000000000001"), + Arguments.of( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0x100", "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000000"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x80", + "0x0000000000000000000000000000000000000000000000000000000000000000"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", "0x8000", "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x80000000", + "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x8000000000000000", + "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x80000000000000000000000000000000", + "0x"), + Arguments.of( + "0x0000000000000000000000000000000000000000000000000000000000000400", + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0x")); + } + + @ParameterizedTest + @MethodSource("data") + void shiftOperation(final String number, final String shift, final String expectedResult) { + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .pushStackItem(Bytes32.fromHexStringLenient(number)) + .pushStackItem(Bytes32.fromHexStringLenient(shift)) + .build(); + operation.execute(frame, null); + UInt256 expected; + if (expectedResult.equals("0x") || expectedResult.equals("0x0")) { + expected = UInt256.ZERO; + } else { + expected = UInt256.fromBytesBE(Bytes32.fromHexStringLenient(expectedResult).toArrayUnsafe()); + } + assertThat(getV2StackItem(frame, 0)).isEqualTo(expected); + } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } + + private static UInt256 getV2StackItem(final MessageFrame frame, final int offset) { + final long[] s = frame.stackDataV2(); + final int idx = (frame.stackTopV2() - 1 - offset) << 2; + return new UInt256(s[idx], s[idx + 1], s[idx + 2], s[idx + 3]); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/testutils/TestMessageFrameBuilderV2.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/testutils/TestMessageFrameBuilderV2.java new file mode 100644 index 00000000000..ebf852e768a --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/testutils/TestMessageFrameBuilderV2.java @@ -0,0 +1,190 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.testutils; + +import static org.hyperledger.besu.evm.frame.MessageFrame.DEFAULT_MAX_STACK_SIZE; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.blockhash.BlockHashLookup; +import org.hyperledger.besu.evm.frame.BlockValues; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.testutils.FakeBlockValues; +import org.hyperledger.besu.evm.toy.ToyWorld; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; + +public class TestMessageFrameBuilderV2 { + + public static final Address DEFAULT_ADDRESS = Address.fromHexString("0xe8f1b89"); + private static final int maxStackSize = DEFAULT_MAX_STACK_SIZE; + + private Optional blockValues = Optional.empty(); + private Optional worldUpdater = Optional.empty(); + private long initialGas = Long.MAX_VALUE; + private Address address = DEFAULT_ADDRESS; + private Address sender = DEFAULT_ADDRESS; + private Address originator = DEFAULT_ADDRESS; + private Address contract = DEFAULT_ADDRESS; + private Wei gasPrice = Wei.ZERO; + private Wei blobGasPrice = Wei.ZERO; + private Wei value = Wei.ZERO; + private Bytes inputData = Bytes.EMPTY; + private Code code = Code.EMPTY_CODE; + private int pc = 0; + private final List stackItems = new ArrayList<>(); + private Optional blockHashLookup = Optional.empty(); + private Bytes memory = Bytes.EMPTY; + private boolean isStatic = false; + + public TestMessageFrameBuilderV2 worldUpdater(final WorldUpdater worldUpdater) { + this.worldUpdater = Optional.of(worldUpdater); + return this; + } + + public TestMessageFrameBuilderV2 initialGas(final long initialGas) { + this.initialGas = initialGas; + return this; + } + + public TestMessageFrameBuilderV2 sender(final Address sender) { + this.sender = sender; + return this; + } + + public TestMessageFrameBuilderV2 address(final Address address) { + this.address = address; + return this; + } + + TestMessageFrameBuilderV2 originator(final Address originator) { + this.originator = originator; + return this; + } + + public TestMessageFrameBuilderV2 contract(final Address contract) { + this.contract = contract; + return this; + } + + public TestMessageFrameBuilderV2 gasPrice(final Wei gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public TestMessageFrameBuilderV2 blobGasPrice(final Wei blobGasPrice) { + this.blobGasPrice = blobGasPrice; + return this; + } + + public TestMessageFrameBuilderV2 value(final Wei value) { + this.value = value; + return this; + } + + public TestMessageFrameBuilderV2 inputData(final Bytes inputData) { + this.inputData = inputData; + return this; + } + + public TestMessageFrameBuilderV2 code(final Code code) { + this.code = code; + return this; + } + + public TestMessageFrameBuilderV2 pc(final int pc) { + this.pc = pc; + return this; + } + + public TestMessageFrameBuilderV2 blockValues(final BlockValues blockValues) { + this.blockValues = Optional.of(blockValues); + return this; + } + + public TestMessageFrameBuilderV2 pushStackItem(final Bytes item) { + stackItems.add(item); + return this; + } + + public TestMessageFrameBuilderV2 blockHashLookup(final BlockHashLookup blockHashLookup) { + this.blockHashLookup = Optional.of(blockHashLookup); + return this; + } + + public TestMessageFrameBuilderV2 memory(final Bytes memory) { + this.memory = memory; + return this; + } + + public TestMessageFrameBuilderV2 isStatic(final boolean isStatic) { + this.isStatic = isStatic; + return this; + } + + public MessageFrame build() { + final MessageFrame frame = + MessageFrame.builder() + .type(MessageFrame.Type.MESSAGE_CALL) + .worldUpdater(worldUpdater.orElseGet(this::createDefaultWorldUpdater)) + .initialGas(initialGas) + .address(address) + .originator(originator) + .gasPrice(gasPrice) + .blobGasPrice(blobGasPrice) + .inputData(inputData) + .sender(sender) + .value(value) + .apparentValue(value) + .contract(contract) + .code(code) + .blockValues(blockValues.orElseGet(() -> new FakeBlockValues(1337))) + .completer(c -> {}) + .miningBeneficiary(Address.ZERO) + .blockHashLookup( + blockHashLookup.orElse((__, number) -> Hash.hash(Words.longBytes(number)))) + .maxStackSize(maxStackSize) + .isStatic(isStatic) + .enableEvmV2(true) + .build(); + frame.setPC(pc); + stackItems.forEach( + item -> { + final UInt256 val = UInt256.fromBytesBE(item.toArrayUnsafe()); + final long[] s = frame.stackDataV2(); + final int dst = frame.stackTopV2() << 2; + s[dst] = val.u3(); + s[dst + 1] = val.u2(); + s[dst + 2] = val.u1(); + s[dst + 3] = val.u0(); + frame.setTopV2(frame.stackTopV2() + 1); + }); + frame.writeMemory(0, memory.size(), memory); + return frame; + } + + private WorldUpdater createDefaultWorldUpdater() { + return new ToyWorld(); + } +}