diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AbstractSarOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AbstractSarOperationBenchmark.java index 786fd254408..d2e3a2c1e0b 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AbstractSarOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AbstractSarOperationBenchmark.java @@ -14,13 +14,15 @@ */ package org.hyperledger.besu.ethereum.vm.operations; -import static org.hyperledger.besu.ethereum.vm.operations.BenchmarkHelper.randomNegativeValue; -import static org.hyperledger.besu.ethereum.vm.operations.BenchmarkHelper.randomPositiveValue; -import static org.hyperledger.besu.ethereum.vm.operations.BenchmarkHelper.randomValue; +import static org.hyperledger.besu.ethereum.vm.operations.BenchmarkHelper.bytesToUInt256; +import static org.hyperledger.besu.ethereum.vm.operations.BenchmarkHelper.randomNegativeUInt256Value; +import static org.hyperledger.besu.ethereum.vm.operations.BenchmarkHelper.randomPositiveUInt256Value; +import static org.hyperledger.besu.ethereum.vm.operations.BenchmarkHelper.randomUInt256Value; + +import org.hyperledger.besu.evm.UInt256; import java.util.concurrent.ThreadLocalRandom; -import org.apache.tuweni.bytes.Bytes; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Setup; @@ -58,10 +60,6 @@ public enum Case { FULL_RANDOM } - /** All bits set (32 bytes of 0xFF) - represents -1 in two's complement. */ - protected static final Bytes ALL_BITS = - Bytes.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - @Param({ "SHIFT_0", "NEGATIVE_SHIFT_1", @@ -83,76 +81,70 @@ public void setUp() { frame = BenchmarkHelper.createMessageCallFrame(); final Case scenario = Case.valueOf(caseName); - aPool = new Bytes[SAMPLE_SIZE]; // shift amount (pushed second, popped first) - bPool = new Bytes[SAMPLE_SIZE]; // value (pushed first, popped second) + 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] = Bytes.of(0); - bPool[i] = randomValue(random); + aPool[i] = UInt256.fromInt(0); + bPool[i] = randomUInt256Value(random); break; case NEGATIVE_SHIFT_1: - // shiftAmount = 0x1, value = 0xfff...fff (negative, tests OR path) - aPool[i] = Bytes.of(1); - bPool[i] = randomNegativeValue(random); + aPool[i] = UInt256.fromInt(1); + bPool[i] = randomNegativeUInt256Value(random); break; case ALL_BITS_SHIFT_1: - // shiftAmount = 0x1, value = 0xfff...fff (negative, tests OR path) - aPool[i] = Bytes.of(1); - bPool[i] = ALL_BITS; + aPool[i] = UInt256.fromInt(1); + bPool[i] = UInt256.MAX; break; case POSITIVE_SHIFT_1: - // shiftAmount = 0x1, random positive value (no sign extension) - aPool[i] = Bytes.of(1); - bPool[i] = randomPositiveValue(random); + aPool[i] = UInt256.fromInt(1); + bPool[i] = randomPositiveUInt256Value(random); break; case NEGATIVE_SHIFT_128: - aPool[i] = Bytes.of(128); - bPool[i] = randomNegativeValue(random); + aPool[i] = UInt256.fromInt(128); + bPool[i] = randomNegativeUInt256Value(random); break; case NEGATIVE_SHIFT_255: - aPool[i] = Bytes.of(255); - bPool[i] = randomNegativeValue(random); + aPool[i] = UInt256.fromInt(255); + bPool[i] = randomNegativeUInt256Value(random); break; case POSITIVE_SHIFT_128: - aPool[i] = Bytes.of(128); - bPool[i] = randomPositiveValue(random); + aPool[i] = UInt256.fromInt(128); + bPool[i] = randomPositiveUInt256Value(random); break; case POSITIVE_SHIFT_255: - aPool[i] = Bytes.of(255); - bPool[i] = randomPositiveValue(random); + aPool[i] = UInt256.fromInt(255); + bPool[i] = randomPositiveUInt256Value(random); break; case OVERFLOW_SHIFT_256: - // Shift of exactly 256 - overflow path - aPool[i] = Bytes.fromHexString("0x0100"); // 256 - bPool[i] = randomValue(random); + aPool[i] = UInt256.fromInt(256); + bPool[i] = randomUInt256Value(random); break; case OVERFLOW_LARGE_SHIFT: - // Shift amount > 4 bytes - overflow path - aPool[i] = Bytes.fromHexString("0x010000000000"); // > 4 bytes - bPool[i] = randomValue(random); + aPool[i] = bytesToUInt256(new byte[] {0x01, 0x00, 0x00, 0x00, 0x00, 0x00}); + bPool[i] = randomUInt256Value(random); break; case FULL_RANDOM: default: - // Original random behavior 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] = Bytes.wrap(shift); - bPool[i] = Bytes.wrap(value); + aPool[i] = bytesToUInt256(shift); + bPool[i] = bytesToUInt256(value); break; } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AbstractShiftOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AbstractShiftOperationBenchmark.java index 7ccd39cfec2..7c318d6b4c5 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AbstractShiftOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AbstractShiftOperationBenchmark.java @@ -14,11 +14,13 @@ */ package org.hyperledger.besu.ethereum.vm.operations; -import static org.hyperledger.besu.ethereum.vm.operations.BenchmarkHelper.randomValue; +import static org.hyperledger.besu.ethereum.vm.operations.BenchmarkHelper.bytesToUInt256; +import static org.hyperledger.besu.ethereum.vm.operations.BenchmarkHelper.randomUInt256Value; + +import org.hyperledger.besu.evm.UInt256; import java.util.concurrent.ThreadLocalRandom; -import org.apache.tuweni.bytes.Bytes; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Setup; @@ -65,41 +67,41 @@ public void setUp() { frame = BenchmarkHelper.createMessageCallFrame(); final Case scenario = Case.valueOf(caseName); - aPool = new Bytes[SAMPLE_SIZE]; // shift amount (pushed second, popped first) - bPool = new Bytes[SAMPLE_SIZE]; // value (pushed first, popped second) + 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] = Bytes.of(0); - bPool[i] = randomValue(random); + aPool[i] = UInt256.fromInt(0); + bPool[i] = randomUInt256Value(random); break; case SHIFT_1: - aPool[i] = Bytes.of(1); - bPool[i] = randomValue(random); + aPool[i] = UInt256.fromInt(1); + bPool[i] = randomUInt256Value(random); break; case SHIFT_128: - aPool[i] = Bytes.of(128); - bPool[i] = randomValue(random); + aPool[i] = UInt256.fromInt(128); + bPool[i] = randomUInt256Value(random); break; case SHIFT_255: - aPool[i] = Bytes.of(255); - bPool[i] = randomValue(random); + aPool[i] = UInt256.fromInt(255); + bPool[i] = randomUInt256Value(random); break; case OVERFLOW_SHIFT_256: - aPool[i] = Bytes.fromHexString("0x0100"); // 256 - bPool[i] = randomValue(random); + aPool[i] = UInt256.fromInt(256); + bPool[i] = randomUInt256Value(random); break; case OVERFLOW_LARGE_SHIFT: - aPool[i] = Bytes.fromHexString("0x010000000000"); // > 4 bytes - bPool[i] = randomValue(random); + aPool[i] = bytesToUInt256(new byte[] {0x01, 0x00, 0x00, 0x00, 0x00, 0x00}); + bPool[i] = randomUInt256Value(random); break; case FULL_RANDOM: @@ -108,8 +110,8 @@ public void setUp() { final byte[] value = new byte[1 + random.nextInt(32)]; random.nextBytes(shift); random.nextBytes(value); - aPool[i] = Bytes.wrap(shift); - bPool[i] = Bytes.wrap(value); + aPool[i] = bytesToUInt256(shift); + bPool[i] = bytesToUInt256(value); break; } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AddModOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AddModOperationBenchmark.java index ac042cdee15..a2c4a414cb5 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AddModOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AddModOperationBenchmark.java @@ -14,13 +14,13 @@ */ package org.hyperledger.besu.ethereum.vm.operations; +import org.hyperledger.besu.evm.UInt256; import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.operation.AddModOperationOptimized; +import org.hyperledger.besu.evm.operation.AddModOperation; import org.hyperledger.besu.evm.operation.Operation; import java.util.concurrent.ThreadLocalRandom; -import org.apache.tuweni.bytes.Bytes; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Setup; @@ -131,9 +131,9 @@ public void setUp() { frame = BenchmarkHelper.createMessageCallFrame(); Case scenario = Case.valueOf(caseName); - aPool = new Bytes[SAMPLE_SIZE]; - bPool = new Bytes[SAMPLE_SIZE]; - cPool = new Bytes[SAMPLE_SIZE]; + aPool = new UInt256[SAMPLE_SIZE]; + bPool = new UInt256[SAMPLE_SIZE]; + cPool = new UInt256[SAMPLE_SIZE]; final ThreadLocalRandom random = ThreadLocalRandom.current(); int aSize; @@ -154,15 +154,15 @@ public void setUp() { random.nextBytes(a); random.nextBytes(b); random.nextBytes(c); - aPool[i] = Bytes.wrap(a); - bPool[i] = Bytes.wrap(b); - cPool[i] = Bytes.wrap(c); + aPool[i] = BenchmarkHelper.bytesToUInt256(a); + bPool[i] = BenchmarkHelper.bytesToUInt256(b); + cPool[i] = BenchmarkHelper.bytesToUInt256(c); } index = 0; } @Override protected Operation.OperationResult invoke(final MessageFrame frame) { - return AddModOperationOptimized.staticOperation(frame); + return AddModOperation.staticOperation(frame, frame.stackData()); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AddOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AddOperationBenchmark.java index e55c68e4940..e7e208747ec 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AddOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AddOperationBenchmark.java @@ -15,13 +15,13 @@ package org.hyperledger.besu.ethereum.vm.operations; import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.operation.AddOperationOptimized; +import org.hyperledger.besu.evm.operation.AddOperation; import org.hyperledger.besu.evm.operation.Operation; public class AddOperationBenchmark extends BinaryOperationBenchmark { @Override protected Operation.OperationResult invoke(final MessageFrame frame) { - return AddOperationOptimized.staticOperation(frame); + return AddOperation.staticOperation(frame, frame.stackData()); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AndOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AndOperationBenchmark.java index 159523f618b..5c48e8892b4 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AndOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AndOperationBenchmark.java @@ -22,6 +22,6 @@ public class AndOperationBenchmark extends BinaryOperationBenchmark { @Override protected Operation.OperationResult invoke(final MessageFrame frame) { - return AndOperation.staticOperation(frame); + return AndOperation.staticOperation(frame, frame.stackData()); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AndOperationOptimizedBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AndOperationOptimizedBenchmark.java deleted file mode 100644 index 48a29ac22a8..00000000000 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AndOperationOptimizedBenchmark.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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; - -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.operation.AndOperationOptimized; -import org.hyperledger.besu.evm.operation.Operation; - -public class AndOperationOptimizedBenchmark extends BinaryOperationBenchmark { - - @Override - protected Operation.OperationResult invoke(final MessageFrame frame) { - return AndOperationOptimized.staticOperation(frame); - } -} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/BenchmarkHelper.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/BenchmarkHelper.java index cda01bfb05f..accef1ed7bd 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/BenchmarkHelper.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/BenchmarkHelper.java @@ -32,7 +32,6 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; public class BenchmarkHelper { /** @@ -205,24 +204,77 @@ static Bytes createCallData(final int size, final boolean nonZero) { * @param fixedSrcDst whether to use fixed zero source/destination offsets */ static void fillPoolsForCallData( - final Bytes[] sizePool, - final Bytes[] destOffsetPool, - final Bytes[] srcOffsetPool, + final org.hyperledger.besu.evm.UInt256[] sizePool, + final org.hyperledger.besu.evm.UInt256[] destOffsetPool, + final org.hyperledger.besu.evm.UInt256[] srcOffsetPool, final int dataSize, final boolean fixedSrcDst) { for (int i = 0; i < sizePool.length; i++) { - sizePool[i] = Bytes.wrap(UInt256.valueOf(dataSize)); + sizePool[i] = org.hyperledger.besu.evm.UInt256.fromInt(dataSize); if (fixedSrcDst) { - destOffsetPool[i] = Bytes.wrap(UInt256.valueOf(0)); - srcOffsetPool[i] = Bytes.wrap(UInt256.valueOf(0)); + destOffsetPool[i] = org.hyperledger.besu.evm.UInt256.ZERO; + srcOffsetPool[i] = org.hyperledger.besu.evm.UInt256.ZERO; } else { - destOffsetPool[i] = Bytes.wrap(UInt256.valueOf((i * 32) % 1024)); - srcOffsetPool[i] = Bytes.wrap(UInt256.valueOf(i % Math.max(1, dataSize))); + destOffsetPool[i] = org.hyperledger.besu.evm.UInt256.fromInt((i * 32) % 1024); + srcOffsetPool[i] = org.hyperledger.besu.evm.UInt256.fromInt(i % Math.max(1, dataSize)); } } } + /** + * Pushes a UInt256 value onto the frame's stack by writing limbs directly. + * + * @param frame the message frame + * @param value the UInt256 value to push + */ + static void pushUInt256(final MessageFrame frame, final org.hyperledger.besu.evm.UInt256 value) { + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final int dst = top << 2; + s[dst] = value.u3(); + s[dst + 1] = value.u2(); + s[dst + 2] = value.u1(); + s[dst + 3] = value.u0(); + frame.setTop(top + 1); + } + + /** + * Pops (discards) n items from the frame's stack using direct top manipulation. + * + * @param frame the message frame + * @param n the number of items to discard + */ + static void popItems(final MessageFrame frame, final int n) { + frame.setTop(frame.stackTop() - n); + } + + /** + * Fills an array with random UInt256 values. + * + * @param pool the destination array + */ + public static void fillUInt256Pool(final org.hyperledger.besu.evm.UInt256[] pool) { + final Random random = new Random(); + for (int i = 0; i < pool.length; i++) { + final byte[] a = new byte[1 + random.nextInt(32)]; + random.nextBytes(a); + pool[i] = bytesToUInt256(a); + } + } + + /** + * Converts a byte array to UInt256 (left-pads to 32 bytes). + * + * @param bytes the byte array + * @return the UInt256 value + */ + static org.hyperledger.besu.evm.UInt256 bytesToUInt256(final byte[] bytes) { + final byte[] padded = new byte[32]; + System.arraycopy(bytes, 0, padded, 32 - bytes.length, bytes.length); + return org.hyperledger.besu.evm.UInt256.fromBytesBE(padded); + } + /** * Generates a random 32-byte value. * @@ -235,6 +287,46 @@ static Bytes randomValue(final ThreadLocalRandom random) { return Bytes.wrap(value); } + /** + * 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); + } + /** * Generates a random positive signed 256-bit value (sign bit cleared). * diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/BinaryOperationBaselineBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/BinaryOperationBaselineBenchmark.java index 7c34bbc328a..a8f4275379b 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/BinaryOperationBaselineBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/BinaryOperationBaselineBenchmark.java @@ -23,7 +23,7 @@ public class BinaryOperationBaselineBenchmark extends BinaryOperationBenchmark { protected Operation.OperationResult invoke(final MessageFrame frame) { // Approximate baseline by popping the extra input and doing nothing // second pop is done by the caller - frame.popStackItem(); + frame.setTop(frame.stackTop() - 1); return null; } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/BinaryOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/BinaryOperationBenchmark.java index 77cac0ad110..89335c3f0b9 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/BinaryOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/BinaryOperationBenchmark.java @@ -14,12 +14,12 @@ */ package org.hyperledger.besu.ethereum.vm.operations; +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.apache.tuweni.bytes.Bytes; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Measurement; @@ -40,29 +40,29 @@ public abstract class BinaryOperationBenchmark { protected static final int SAMPLE_SIZE = 30_000; - protected Bytes[] aPool; - protected Bytes[] bPool; + protected UInt256[] aPool; + protected UInt256[] bPool; protected int index; protected MessageFrame frame; @Setup() public void setUp() { frame = BenchmarkHelper.createMessageCallFrame(); - aPool = new Bytes[SAMPLE_SIZE]; - bPool = new Bytes[SAMPLE_SIZE]; - BenchmarkHelper.fillPool(aPool); - BenchmarkHelper.fillPool(bPool); + aPool = new UInt256[SAMPLE_SIZE]; + bPool = new UInt256[SAMPLE_SIZE]; + BenchmarkHelper.fillUInt256Pool(aPool); + BenchmarkHelper.fillUInt256Pool(bPool); index = 0; } @Benchmark public void executeOperation(final Blackhole blackhole) { - frame.pushStackItem(bPool[index]); - frame.pushStackItem(aPool[index]); + BenchmarkHelper.pushUInt256(frame, bPool[index]); + BenchmarkHelper.pushUInt256(frame, aPool[index]); blackhole.consume(invoke(frame)); - frame.popStackItem(); + frame.setTop(frame.stackTop() - 1); index = (index + 1) % SAMPLE_SIZE; } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/BlockHashOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/BlockHashOperationBenchmark.java index c44bc660c91..60bfa83655a 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/BlockHashOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/BlockHashOperationBenchmark.java @@ -18,10 +18,9 @@ import org.hyperledger.besu.ethereum.vm.BlockchainBasedBlockHashLookup; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.PetersburgGasCalculator; +import org.hyperledger.besu.evm.internal.StackMath; import org.hyperledger.besu.evm.operation.BlockHashOperation; -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.units.bigints.UInt256; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Scope; @@ -56,14 +55,14 @@ public void cleanUp() throws Exception { } @Benchmark - public Bytes executeOperation() { - frame.pushStackItem(UInt256.valueOf(blockNumber)); + public void executeOperation() { + frame.setTop(StackMath.pushLong(frame.stackData(), frame.stackTop(), blockNumber)); operation.execute(frame, null); - return frame.popStackItem(); + frame.setTop(frame.stackTop() - 1); } @Benchmark - public Bytes executeOperationWithEmptyHashCache() { + public void executeOperationWithEmptyHashCache() { final MessageFrame cleanFrame = operationBenchmarkHelper .createMessageFrameBuilder() @@ -72,8 +71,9 @@ public Bytes executeOperationWithEmptyHashCache() { (ProcessableBlockHeader) frame.getBlockValues(), operationBenchmarkHelper.getBlockchain())) .build(); - cleanFrame.pushStackItem(UInt256.valueOf(blockNumber)); + cleanFrame.setTop( + StackMath.pushLong(cleanFrame.stackData(), cleanFrame.stackTop(), blockNumber)); operation.execute(cleanFrame, null); - return cleanFrame.popStackItem(); + cleanFrame.setTop(cleanFrame.stackTop() - 1); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/CallDataCopyOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/CallDataCopyOperationBenchmark.java index d5a6c7dce44..b3ff7abda36 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/CallDataCopyOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/CallDataCopyOperationBenchmark.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.ethereum.vm.operations; +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.operation.CallDataCopyOperation; @@ -58,9 +59,9 @@ public class CallDataCopyOperationBenchmark { @Param({"false", "true"}) public boolean nonZeroData; - protected Bytes[] destOffsetPool; - protected Bytes[] srcOffsetPool; - protected Bytes[] sizePool; + protected UInt256[] destOffsetPool; + protected UInt256[] srcOffsetPool; + protected UInt256[] sizePool; protected int index; protected MessageFrame frame; protected Bytes callData; @@ -72,9 +73,9 @@ public void setUp() { frame = BenchmarkHelper.createMessageCallFrameWithCallData(callData); // Initialize parameter pools - destOffsetPool = new Bytes[SAMPLE_SIZE]; - srcOffsetPool = new Bytes[SAMPLE_SIZE]; - sizePool = new Bytes[SAMPLE_SIZE]; + destOffsetPool = new UInt256[SAMPLE_SIZE]; + srcOffsetPool = new UInt256[SAMPLE_SIZE]; + sizePool = new UInt256[SAMPLE_SIZE]; // Fill pools with appropriate values based on test parameters BenchmarkHelper.fillPoolsForCallData( @@ -85,21 +86,19 @@ public void setUp() { @Benchmark public void baseline() { - frame.pushStackItem(sizePool[index]); - frame.pushStackItem(srcOffsetPool[index]); - frame.pushStackItem(destOffsetPool[index]); + BenchmarkHelper.pushUInt256(frame, sizePool[index]); + BenchmarkHelper.pushUInt256(frame, srcOffsetPool[index]); + BenchmarkHelper.pushUInt256(frame, destOffsetPool[index]); - frame.popStackItem(); - frame.popStackItem(); - frame.popStackItem(); + frame.setTop(frame.stackTop() - 3); index = (index + 1) % SAMPLE_SIZE; } @Benchmark public void executeOperation(final Blackhole blackhole) { - frame.pushStackItem(sizePool[index]); - frame.pushStackItem(srcOffsetPool[index]); - frame.pushStackItem(destOffsetPool[index]); + BenchmarkHelper.pushUInt256(frame, sizePool[index]); + BenchmarkHelper.pushUInt256(frame, srcOffsetPool[index]); + BenchmarkHelper.pushUInt256(frame, destOffsetPool[index]); blackhole.consume(callDataCopyOperation.execute(frame, null)); diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/CountLeadingZerosOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/CountLeadingZerosOperationBenchmark.java index 892406739e3..a473dbb1593 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/CountLeadingZerosOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/CountLeadingZerosOperationBenchmark.java @@ -22,12 +22,12 @@ 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.internal.StackMath; import org.hyperledger.besu.evm.operation.CountLeadingZerosOperation; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.concurrent.TimeUnit; -import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; @@ -63,7 +63,7 @@ public class CountLeadingZerosOperationBenchmark { }) private String bytesHex; - private Bytes bytes; + private byte[] valueBytes; private MessageFrame frame; @@ -89,25 +89,31 @@ public void setUp() { .code(Code.EMPTY_CODE) .completer(messageFrame -> {}) .build(); - bytes = Bytes.fromHexString(bytesHex); + final org.apache.tuweni.bytes.Bytes rawBytes = + org.apache.tuweni.bytes.Bytes.fromHexString(bytesHex); + valueBytes = new byte[32]; + final byte[] raw = rawBytes.toArrayUnsafe(); + System.arraycopy(raw, 0, valueBytes, 32 - raw.length, raw.length); } @Benchmark @OperationsPerInvocation(OPERATIONS_PER_INVOCATION) public void executeOperation() { + final long[] sd = frame.stackData(); for (int i = 0; i < OPERATIONS_PER_INVOCATION; i++) { - frame.pushStackItem(bytes); - CountLeadingZerosOperation.staticOperation(frame); - frame.popStackItem(); + frame.setTop(StackMath.pushFromBytes(sd, frame.stackTop(), valueBytes, 0, 32)); + CountLeadingZerosOperation.staticOperation(frame, frame.stackData()); + frame.setTop(frame.stackTop() - 1); } } @Benchmark @OperationsPerInvocation(OPERATIONS_PER_INVOCATION) public void baseline() { + final long[] sd = frame.stackData(); for (int i = 0; i < OPERATIONS_PER_INVOCATION; i++) { - frame.pushStackItem(bytes); - frame.popStackItem(); + frame.setTop(StackMath.pushFromBytes(sd, frame.stackTop(), valueBytes, 0, 32)); + frame.setTop(frame.stackTop() - 1); } } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/DivOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/DivOperationBenchmark.java index c7c3108c62c..b905877f1bc 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/DivOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/DivOperationBenchmark.java @@ -22,6 +22,6 @@ public class DivOperationBenchmark extends BinaryOperationBenchmark { @Override protected Operation.OperationResult invoke(final MessageFrame frame) { - return DivOperation.staticOperation(frame); + return DivOperation.staticOperation(frame, frame.stackData()); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/DupNOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/DupNOperationBenchmark.java index c4a8952ac18..45ab1edbef3 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/DupNOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/DupNOperationBenchmark.java @@ -35,7 +35,7 @@ protected byte getImmediate() { @Override protected Operation.OperationResult invoke( final MessageFrame frame, final byte[] code, final int pc) { - return DupNOperation.staticOperation(frame, code, pc); + return DupNOperation.staticOperation(frame, frame.stackData(), code, pc); } @Override diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/EqOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/EqOperationBenchmark.java index 6c17adbb75f..d5e1658f130 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/EqOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/EqOperationBenchmark.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.ethereum.vm.operations; +import org.hyperledger.besu.evm.UInt256; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.operation.EqOperation; import org.hyperledger.besu.evm.operation.Operation; @@ -54,72 +55,74 @@ public enum MODE { public void setUp() { frame = BenchmarkHelper.createMessageCallFrame(); - aPool = new Bytes[SAMPLE_SIZE]; - bPool = new Bytes[SAMPLE_SIZE]; + // Use temporary Bytes[] arrays for the complex scenario generation logic, + // then convert to UInt256[] at the end. + Bytes[] tmpA = new Bytes[SAMPLE_SIZE]; + Bytes[] tmpB = new Bytes[SAMPLE_SIZE]; switch (mode) { case EMPTY_INPUTS: - Arrays.fill(aPool, Bytes.EMPTY); - Arrays.fill(bPool, Bytes.EMPTY); + Arrays.fill(tmpA, Bytes.EMPTY); + Arrays.fill(tmpB, Bytes.EMPTY); break; case ONE_EMPTY_INPUT: - Arrays.fill(aPool, Bytes.EMPTY); - BenchmarkHelper.fillPool(bPool, () -> 16); - bPool = Arrays.stream(bPool).map(Bytes32::leftPad).toArray(Bytes[]::new); + Arrays.fill(tmpA, Bytes.EMPTY); + BenchmarkHelper.fillPool(tmpB, () -> 16); + tmpB = Arrays.stream(tmpB).map(Bytes32::leftPad).toArray(Bytes[]::new); break; case BYTES1_EQUAL: - BenchmarkHelper.fillPool(aPool, () -> 1); - bPool = - Arrays.stream(aPool) + BenchmarkHelper.fillPool(tmpA, () -> 1); + tmpB = + Arrays.stream(tmpA) .map(bytes -> Bytes.wrap(bytes.toArrayUnsafe())) .toArray(Bytes[]::new); break; case BYTES1_NOT_EQUAL: BenchmarkHelper.fillPools( - aPool, bPool, () -> 1, () -> 1, (value1, value2) -> !value1.equals(value2)); + tmpA, tmpB, () -> 1, () -> 1, (value1, value2) -> !value1.equals(value2)); break; case BYTES1_EQUAL_WITH_ZEROS: - BenchmarkHelper.fillPool(aPool, () -> 1); - aPool = Arrays.stream(aPool).map(Bytes32::leftPad).toArray(Bytes[]::new); - bPool = - Arrays.stream(aPool) + BenchmarkHelper.fillPool(tmpA, () -> 1); + tmpA = Arrays.stream(tmpA).map(Bytes32::leftPad).toArray(Bytes[]::new); + tmpB = + Arrays.stream(tmpA) .map(bytes -> Bytes.wrap(bytes.toArrayUnsafe())) .toArray(Bytes[]::new); break; case BYTES1_NOT_EQUAL_WITH_ZEROS: BenchmarkHelper.fillPools( - aPool, bPool, () -> 1, () -> 1, (value1, value2) -> !value1.equals(value2)); - aPool = Arrays.stream(aPool).map(Bytes32::leftPad).toArray(Bytes[]::new); - bPool = Arrays.stream(bPool).map(Bytes32::leftPad).toArray(Bytes[]::new); + tmpA, tmpB, () -> 1, () -> 1, (value1, value2) -> !value1.equals(value2)); + tmpA = Arrays.stream(tmpA).map(Bytes32::leftPad).toArray(Bytes[]::new); + tmpB = Arrays.stream(tmpB).map(Bytes32::leftPad).toArray(Bytes[]::new); break; case BYTES16_EQUAL: - BenchmarkHelper.fillPool(aPool, () -> 16); - bPool = - Arrays.stream(aPool) + BenchmarkHelper.fillPool(tmpA, () -> 16); + tmpB = + Arrays.stream(tmpA) .map(bytes -> Bytes.wrap(bytes.toArrayUnsafe())) .toArray(Bytes[]::new); break; case BYTES16_NOT_EQUAL: BenchmarkHelper.fillPools( - aPool, bPool, () -> 16, () -> 16, (value1, value2) -> !value1.equals(value2)); + tmpA, tmpB, () -> 16, () -> 16, (value1, value2) -> !value1.equals(value2)); break; case BYTES16_EQUAL_WITH_ZEROS: - BenchmarkHelper.fillPool(aPool, () -> 16); - aPool = Arrays.stream(aPool).map(Bytes32::leftPad).toArray(Bytes[]::new); - bPool = - Arrays.stream(aPool) + BenchmarkHelper.fillPool(tmpA, () -> 16); + tmpA = Arrays.stream(tmpA).map(Bytes32::leftPad).toArray(Bytes[]::new); + tmpB = + Arrays.stream(tmpA) .map(bytes -> Bytes.wrap(bytes.toArrayUnsafe())) .toArray(Bytes[]::new); break; case BYTES16_NOT_EQUAL_WITH_ZEROS: BenchmarkHelper.fillPools( - aPool, bPool, () -> 16, () -> 16, (value1, value2) -> !value1.equals(value2)); - aPool = Arrays.stream(aPool).map(Bytes32::leftPad).toArray(Bytes[]::new); - bPool = Arrays.stream(bPool).map(Bytes32::leftPad).toArray(Bytes[]::new); + tmpA, tmpB, () -> 16, () -> 16, (value1, value2) -> !value1.equals(value2)); + tmpA = Arrays.stream(tmpA).map(Bytes32::leftPad).toArray(Bytes[]::new); + tmpB = Arrays.stream(tmpB).map(Bytes32::leftPad).toArray(Bytes[]::new); break; case BYTES32_NOT_EQUAL_MOST_SIGNIFICANT: - BenchmarkHelper.fillPool(aPool, () -> 32, value -> value.get(0) != 0); - bPool = - Arrays.stream(aPool) + BenchmarkHelper.fillPool(tmpA, () -> 32, value -> value.get(0) != 0); + tmpB = + Arrays.stream(tmpA) .map( bytes -> { final MutableBytes mutableBytes = bytes.mutableCopy(); @@ -130,9 +133,9 @@ public void setUp() { .toArray(Bytes[]::new); break; case BYTES32_NOT_EQUAL_LEAST_SIGNIFICANT: - BenchmarkHelper.fillPool(aPool, () -> 32, value -> value.get(0) != 0); - bPool = - Arrays.stream(aPool) + BenchmarkHelper.fillPool(tmpA, () -> 32, value -> value.get(0) != 0); + tmpB = + Arrays.stream(tmpA) .map( bytes -> { final MutableBytes mutableBytes = bytes.mutableCopy(); @@ -144,17 +147,17 @@ public void setUp() { .toArray(Bytes[]::new); break; case BYTES32_RANDOM: - BenchmarkHelper.fillPool(aPool, () -> 32, value -> value.get(0) != 0); - BenchmarkHelper.fillPool(bPool, () -> 32, value -> value.get(0) != 0); + BenchmarkHelper.fillPool(tmpA, () -> 32, value -> value.get(0) != 0); + BenchmarkHelper.fillPool(tmpB, () -> 32, value -> value.get(0) != 0); break; case BYTES30_BYTES16_RANDOM: - BenchmarkHelper.fillPool(aPool, () -> 30, value -> value.get(0) != 0); - BenchmarkHelper.fillPool(bPool, () -> 16, value -> value.get(0) != 0); + BenchmarkHelper.fillPool(tmpA, () -> 30, value -> value.get(0) != 0); + BenchmarkHelper.fillPool(tmpB, () -> 16, value -> value.get(0) != 0); break; case BYTES32_EQUAL_BYTE_REPEAT: - BenchmarkHelper.fillPool(aPool, () -> 1); - aPool = - Arrays.stream(aPool) + BenchmarkHelper.fillPool(tmpA, () -> 1); + tmpA = + Arrays.stream(tmpA) .map( bytes -> { final byte[] newPool = new byte[32]; @@ -162,16 +165,34 @@ public void setUp() { return Bytes.wrap(newPool); }) .toArray(Bytes[]::new); - bPool = - Arrays.stream(aPool) + tmpB = + Arrays.stream(tmpA) .map(bytes -> Bytes.wrap(bytes.toArrayUnsafe())) .toArray(Bytes[]::new); } + + // Convert temporary Bytes[] to UInt256[] + aPool = new UInt256[SAMPLE_SIZE]; + bPool = new UInt256[SAMPLE_SIZE]; + for (int i = 0; i < SAMPLE_SIZE; i++) { + aPool[i] = bytesToUInt256(tmpA[i]); + bPool[i] = bytesToUInt256(tmpB[i]); + } index = 0; } + private static UInt256 bytesToUInt256(final Bytes bytes) { + if (bytes.isEmpty()) { + return UInt256.ZERO; + } + final byte[] padded = new byte[32]; + final byte[] raw = bytes.toArrayUnsafe(); + System.arraycopy(raw, 0, padded, 32 - raw.length, raw.length); + return UInt256.fromBytesBE(padded); + } + @Override protected Operation.OperationResult invoke(final MessageFrame frame) { - return EqOperation.staticOperation(frame); + return EqOperation.staticOperation(frame, frame.stackData()); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ExchangeOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ExchangeOperationBenchmark.java index a0117f36450..a4d27c46bbc 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ExchangeOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ExchangeOperationBenchmark.java @@ -36,7 +36,7 @@ protected byte getImmediate() { @Override protected Operation.OperationResult invoke( final MessageFrame frame, final byte[] code, final int pc) { - return ExchangeOperation.staticOperation(frame, code, pc); + return ExchangeOperation.staticOperation(frame, frame.stackData(), code, pc); } @Override diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ExpOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ExpOperationBenchmark.java index addb2014ea8..1c5fe7b7455 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ExpOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ExpOperationBenchmark.java @@ -26,6 +26,6 @@ public class ExpOperationBenchmark extends BinaryOperationBenchmark { @Override protected Operation.OperationResult invoke(final MessageFrame frame) { - return ExpOperation.staticOperation(frame, PRAGUE_GAS_CALCULATOR); + return ExpOperation.staticOperation(frame, frame.stackData(), PRAGUE_GAS_CALCULATOR); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/GtOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/GtOperationBenchmark.java index cc2a5895aba..d73400cdae0 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/GtOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/GtOperationBenchmark.java @@ -22,6 +22,6 @@ public class GtOperationBenchmark extends BinaryOperationBenchmark { @Override protected Operation.OperationResult invoke(final MessageFrame frame) { - return GtOperation.staticOperation(frame); + return GtOperation.staticOperation(frame, frame.stackData()); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ImmediateByteOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ImmediateByteOperationBenchmark.java index fe4c7b70359..eacbc4da8ab 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ImmediateByteOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ImmediateByteOperationBenchmark.java @@ -14,12 +14,12 @@ */ package org.hyperledger.besu.ethereum.vm.operations; +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.apache.tuweni.bytes.Bytes; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Measurement; @@ -47,7 +47,7 @@ public abstract class ImmediateByteOperationBenchmark { protected static final int SAMPLE_SIZE = 30_000; protected static final int STACK_DEPTH = 50; - protected Bytes[] valuePool; + protected UInt256[] valuePool; protected int index; protected MessageFrame frame; protected byte[] code; @@ -55,14 +55,14 @@ public abstract class ImmediateByteOperationBenchmark { @Setup public void setUp() { frame = BenchmarkHelper.createMessageCallFrame(); - valuePool = new Bytes[SAMPLE_SIZE]; - BenchmarkHelper.fillPool(valuePool); + valuePool = new UInt256[SAMPLE_SIZE]; + BenchmarkHelper.fillUInt256Pool(valuePool); index = 0; code = new byte[] {(byte) getOpcode(), getImmediate()}; for (int i = 0; i < STACK_DEPTH; i++) { - frame.pushStackItem(valuePool[i]); + BenchmarkHelper.pushUInt256(frame, valuePool[i]); } } @@ -104,12 +104,10 @@ public void executeOperation(final Blackhole blackhole) { int delta = getStackDelta(); if (delta > 0) { - for (int i = 0; i < delta; i++) { - frame.popStackItem(); - } + frame.setTop(frame.stackTop() - delta); } else if (delta < 0) { for (int i = 0; i < -delta; i++) { - frame.pushStackItem(valuePool[index]); + BenchmarkHelper.pushUInt256(frame, valuePool[index]); index = (index + 1) % SAMPLE_SIZE; } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/LtOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/LtOperationBenchmark.java index f3088fceec3..f2e90a22a71 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/LtOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/LtOperationBenchmark.java @@ -22,6 +22,6 @@ public class LtOperationBenchmark extends BinaryOperationBenchmark { @Override protected Operation.OperationResult invoke(final MessageFrame frame) { - return LtOperation.staticOperation(frame); + return LtOperation.staticOperation(frame, frame.stackData()); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ModOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ModOperationBenchmark.java index e20f84a69b6..bdd557aac05 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ModOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ModOperationBenchmark.java @@ -14,14 +14,14 @@ */ package org.hyperledger.besu.ethereum.vm.operations; +import org.hyperledger.besu.evm.UInt256; import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.operation.ModOperationOptimized; +import org.hyperledger.besu.evm.operation.ModOperation; import org.hyperledger.besu.evm.operation.Operation; import java.math.BigInteger; import java.util.concurrent.ThreadLocalRandom; -import org.apache.tuweni.bytes.Bytes; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Setup; @@ -89,8 +89,8 @@ public void setUp() { frame = BenchmarkHelper.createMessageCallFrame(); Case scenario = Case.valueOf(caseName); - aPool = new Bytes[SAMPLE_SIZE]; - bPool = new Bytes[SAMPLE_SIZE]; + aPool = new UInt256[SAMPLE_SIZE]; + bPool = new UInt256[SAMPLE_SIZE]; final ThreadLocalRandom random = ThreadLocalRandom.current(); int aSize; @@ -109,17 +109,17 @@ public void setUp() { // Swap a and b if necessary if ((scenario.divSize != scenario.modSize)) { - aPool[i] = Bytes.wrap(a); - bPool[i] = Bytes.wrap(b); + aPool[i] = BenchmarkHelper.bytesToUInt256(a); + bPool[i] = BenchmarkHelper.bytesToUInt256(b); } else { BigInteger aInt = new BigInteger(a); BigInteger bInt = new BigInteger(b); if ((aInt.compareTo(bInt) < 0)) { - aPool[i] = Bytes.wrap(b); - bPool[i] = Bytes.wrap(a); + aPool[i] = BenchmarkHelper.bytesToUInt256(b); + bPool[i] = BenchmarkHelper.bytesToUInt256(a); } else { - aPool[i] = Bytes.wrap(a); - bPool[i] = Bytes.wrap(b); + aPool[i] = BenchmarkHelper.bytesToUInt256(a); + bPool[i] = BenchmarkHelper.bytesToUInt256(b); } } } @@ -128,6 +128,6 @@ public void setUp() { @Override protected Operation.OperationResult invoke(final MessageFrame frame) { - return ModOperationOptimized.staticOperation(frame); + return ModOperation.staticOperation(frame, frame.stackData()); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/MulModOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/MulModOperationBenchmark.java index 3f390e19320..da821b57389 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/MulModOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/MulModOperationBenchmark.java @@ -14,13 +14,13 @@ */ package org.hyperledger.besu.ethereum.vm.operations; +import org.hyperledger.besu.evm.UInt256; import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.operation.MulModOperationOptimized; +import org.hyperledger.besu.evm.operation.MulModOperation; import org.hyperledger.besu.evm.operation.Operation; import java.util.concurrent.ThreadLocalRandom; -import org.apache.tuweni.bytes.Bytes; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Setup; @@ -131,9 +131,9 @@ public void setUp() { frame = BenchmarkHelper.createMessageCallFrame(); Case scenario = Case.valueOf(caseName); - aPool = new Bytes[SAMPLE_SIZE]; - bPool = new Bytes[SAMPLE_SIZE]; - cPool = new Bytes[SAMPLE_SIZE]; + aPool = new UInt256[SAMPLE_SIZE]; + bPool = new UInt256[SAMPLE_SIZE]; + cPool = new UInt256[SAMPLE_SIZE]; final ThreadLocalRandom random = ThreadLocalRandom.current(); int aSize; @@ -154,15 +154,15 @@ public void setUp() { random.nextBytes(a); random.nextBytes(b); random.nextBytes(c); - aPool[i] = Bytes.wrap(a); - bPool[i] = Bytes.wrap(b); - cPool[i] = Bytes.wrap(c); + aPool[i] = BenchmarkHelper.bytesToUInt256(a); + bPool[i] = BenchmarkHelper.bytesToUInt256(b); + cPool[i] = BenchmarkHelper.bytesToUInt256(c); } index = 0; } @Override protected Operation.OperationResult invoke(final MessageFrame frame) { - return MulModOperationOptimized.staticOperation(frame); + return MulModOperation.staticOperation(frame, frame.stackData()); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/MulOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/MulOperationBenchmark.java index 25879719a91..f3417be717f 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/MulOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/MulOperationBenchmark.java @@ -22,6 +22,6 @@ public class MulOperationBenchmark extends BinaryOperationBenchmark { @Override protected Operation.OperationResult invoke(final MessageFrame frame) { - return MulOperation.staticOperation(frame); + return MulOperation.staticOperation(frame, frame.stackData()); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/NotOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/NotOperationBenchmark.java index bec8f91ef3c..dabc83a5bda 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/NotOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/NotOperationBenchmark.java @@ -22,6 +22,6 @@ public class NotOperationBenchmark extends UnaryOperationBenchmark { @Override protected Operation.OperationResult invoke(final MessageFrame frame) { - return NotOperation.staticOperation(frame); + return NotOperation.staticOperation(frame, frame.stackData()); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/NotOperationOptimizedBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/NotOperationOptimizedBenchmark.java deleted file mode 100644 index f9992c5ddb8..00000000000 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/NotOperationOptimizedBenchmark.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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; - -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.operation.NotOperationOptimized; -import org.hyperledger.besu.evm.operation.Operation; - -public class NotOperationOptimizedBenchmark extends UnaryOperationBenchmark { - - @Override - protected Operation.OperationResult invoke(final MessageFrame frame) { - return NotOperationOptimized.staticOperation(frame); - } -} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OperandStackBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OperandStackBenchmark.java index c627250e6e6..d59c6aaded7 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OperandStackBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OperandStackBenchmark.java @@ -15,11 +15,9 @@ package org.hyperledger.besu.ethereum.vm.operations; import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.internal.OperandStack; 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; @@ -42,16 +40,24 @@ public class OperandStackBenchmark { @Param({"6", "15", "34", "100", "234", "500", "800", "1024"}) private int stackDepth; - private static final Bytes BYTES = - Bytes.fromHexString("0x3232323232323232323232323232323232323232323232323232323232323232"); + private static final long U3 = 0x3232323232323232L; + private static final long U2 = 0x3232323232323232L; + private static final long U1 = 0x3232323232323232L; + private static final long U0 = 0x3232323232323232L; @Benchmark @OperationsPerInvocation(OPERATIONS_PER_INVOCATION) public void fillUp() { for (int i = 0; i < OPERATIONS_PER_INVOCATION; i++) { - OperandStack stack = new OperandStack(MessageFrame.DEFAULT_MAX_STACK_SIZE); + long[] data = new long[MessageFrame.DEFAULT_MAX_STACK_SIZE << 2]; + int top = 0; for (int j = 0; j < stackDepth; j++) { - stack.push(BYTES); + final int off = top << 2; + data[off] = U3; + data[off + 1] = U2; + data[off + 2] = U1; + data[off + 3] = U0; + top++; } } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OrOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OrOperationBenchmark.java index ca6affd06ec..53ffcfdde5d 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OrOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OrOperationBenchmark.java @@ -22,6 +22,6 @@ public class OrOperationBenchmark extends BinaryOperationBenchmark { @Override protected Operation.OperationResult invoke(final MessageFrame frame) { - return OrOperation.staticOperation(frame); + return OrOperation.staticOperation(frame, frame.stackData()); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OrOperationOptimizedBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OrOperationOptimizedBenchmark.java deleted file mode 100644 index ec96c04c653..00000000000 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OrOperationOptimizedBenchmark.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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; - -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.operation.Operation; -import org.hyperledger.besu.evm.operation.OrOperationOptimized; - -public class OrOperationOptimizedBenchmark extends BinaryOperationBenchmark { - - @Override - protected Operation.OperationResult invoke(final MessageFrame frame) { - return OrOperationOptimized.staticOperation(frame); - } -} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SDivOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SDivOperationBenchmark.java index 42d016559fe..5cc08c4bc01 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SDivOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SDivOperationBenchmark.java @@ -21,6 +21,6 @@ public class SDivOperationBenchmark extends BinaryOperationBenchmark { @Override protected Operation.OperationResult invoke(final MessageFrame frame) { - return SDivOperation.staticOperation(frame); + return SDivOperation.staticOperation(frame, frame.stackData()); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SLtOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SLtOperationBenchmark.java index 33a13c9ea2b..2c2b17d2814 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SLtOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SLtOperationBenchmark.java @@ -22,6 +22,6 @@ public class SLtOperationBenchmark extends BinaryOperationBenchmark { @Override protected Operation.OperationResult invoke(final MessageFrame frame) { - return SLtOperation.staticOperation(frame); + return SLtOperation.staticOperation(frame, frame.stackData()); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SModOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SModOperationBenchmark.java index 528ea16bf21..4237bc9c08f 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SModOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SModOperationBenchmark.java @@ -14,14 +14,14 @@ */ package org.hyperledger.besu.ethereum.vm.operations; +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.SModOperationOptimized; +import org.hyperledger.besu.evm.operation.SModOperation; import java.math.BigInteger; import java.util.concurrent.ThreadLocalRandom; -import org.apache.tuweni.bytes.Bytes; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Setup; @@ -89,8 +89,8 @@ public void setUp() { frame = BenchmarkHelper.createMessageCallFrame(); Case scenario = Case.valueOf(caseName); - aPool = new Bytes[SAMPLE_SIZE]; - bPool = new Bytes[SAMPLE_SIZE]; + aPool = new UInt256[SAMPLE_SIZE]; + bPool = new UInt256[SAMPLE_SIZE]; final ThreadLocalRandom random = ThreadLocalRandom.current(); int aSize; @@ -109,17 +109,17 @@ public void setUp() { // Swap a and b if necessary if ((scenario.divSize != scenario.modSize)) { - aPool[i] = Bytes.wrap(a); - bPool[i] = Bytes.wrap(b); + aPool[i] = BenchmarkHelper.bytesToUInt256(a); + bPool[i] = BenchmarkHelper.bytesToUInt256(b); } else { BigInteger aInt = new BigInteger(a); BigInteger bInt = new BigInteger(b); if ((aInt.abs().compareTo(bInt.abs()) < 0)) { - aPool[i] = Bytes.wrap(b); - bPool[i] = Bytes.wrap(a); + aPool[i] = BenchmarkHelper.bytesToUInt256(b); + bPool[i] = BenchmarkHelper.bytesToUInt256(a); } else { - aPool[i] = Bytes.wrap(a); - bPool[i] = Bytes.wrap(b); + aPool[i] = BenchmarkHelper.bytesToUInt256(a); + bPool[i] = BenchmarkHelper.bytesToUInt256(b); } } } @@ -128,6 +128,6 @@ public void setUp() { @Override protected Operation.OperationResult invoke(final MessageFrame frame) { - return SModOperationOptimized.staticOperation(frame); + return SModOperation.staticOperation(frame, frame.stackData()); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SarOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SarOperationBenchmark.java index 83e6db2ed45..902a93af288 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SarOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SarOperationBenchmark.java @@ -23,6 +23,6 @@ public class SarOperationBenchmark extends AbstractSarOperationBenchmark { @Override protected Operation.OperationResult invoke(final MessageFrame frame) { - return SarOperation.staticOperation(frame); + return SarOperation.staticOperation(frame, frame.stackData()); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SarOperationOptimizedBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SarOperationOptimizedBenchmark.java deleted file mode 100644 index ceea8d402f4..00000000000 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SarOperationOptimizedBenchmark.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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; - -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.operation.Operation; -import org.hyperledger.besu.evm.operation.SarOperationOptimized; - -/** JMH benchmark for the optimized SAR (Shift Arithmetic Right) operation. */ -public class SarOperationOptimizedBenchmark extends AbstractSarOperationBenchmark { - - @Override - protected Operation.OperationResult invoke(final MessageFrame frame) { - return SarOperationOptimized.staticOperation(frame); - } -} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SelfBalanceOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SelfBalanceOperationBenchmark.java index cf1d71ee357..841cf902bcf 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SelfBalanceOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SelfBalanceOperationBenchmark.java @@ -110,7 +110,7 @@ public void prepare() throws Exception { public void executeOperation() { final MessageFrame executingFrame = frames[executingFrameIndex++]; operation.execute(executingFrame, null); - executingFrame.popStackItem(); + executingFrame.setTop(executingFrame.stackTop() - 1); executingFrameIndex = executingFrameIndex % NUMBER_ADDRESSES; } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShlOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShlOperationBenchmark.java index 791cc6a95dd..33d5a668bbb 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShlOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShlOperationBenchmark.java @@ -23,6 +23,6 @@ public class ShlOperationBenchmark extends AbstractShiftOperationBenchmark { @Override protected Operation.OperationResult invoke(final MessageFrame frame) { - return ShlOperation.staticOperation(frame); + return ShlOperation.staticOperation(frame, frame.stackData()); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShlOperationOptimizedBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShlOperationOptimizedBenchmark.java deleted file mode 100644 index aa88abe98de..00000000000 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShlOperationOptimizedBenchmark.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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; - -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.operation.Operation; -import org.hyperledger.besu.evm.operation.ShlOperationOptimized; - -/** JMH benchmark for the optimized SHL (Shift Left) operation. */ -public class ShlOperationOptimizedBenchmark extends AbstractShiftOperationBenchmark { - - @Override - protected Operation.OperationResult invoke(final MessageFrame frame) { - return ShlOperationOptimized.staticOperation(frame); - } -} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShrOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShrOperationBenchmark.java index f4bcb2c28d7..526776a334a 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShrOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShrOperationBenchmark.java @@ -23,6 +23,6 @@ public class ShrOperationBenchmark extends AbstractShiftOperationBenchmark { @Override protected Operation.OperationResult invoke(final MessageFrame frame) { - return ShrOperation.staticOperation(frame); + return ShrOperation.staticOperation(frame, frame.stackData()); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShrOperationOptimizedBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShrOperationOptimizedBenchmark.java deleted file mode 100644 index 2cf2a31bba3..00000000000 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShrOperationOptimizedBenchmark.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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; - -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.operation.Operation; -import org.hyperledger.besu.evm.operation.ShrOperationOptimized; - -/** JMH benchmark for the optimized SHR (Shift Right Logical) operation. */ -public class ShrOperationOptimizedBenchmark extends AbstractShiftOperationBenchmark { - - @Override - protected Operation.OperationResult invoke(final MessageFrame frame) { - return ShrOperationOptimized.staticOperation(frame); - } -} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SignExtendOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SignExtendOperationBenchmark.java index c2f019441fc..6c874bc58d0 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SignExtendOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SignExtendOperationBenchmark.java @@ -22,6 +22,6 @@ public class SignExtendOperationBenchmark extends BinaryOperationBenchmark { @Override protected Operation.OperationResult invoke(final MessageFrame frame) { - return SignExtendOperation.staticOperation(frame); + return SignExtendOperation.staticOperation(frame, frame.stackData()); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SubOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SubOperationBenchmark.java index 9de9fa5c768..249a1993859 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SubOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SubOperationBenchmark.java @@ -22,6 +22,6 @@ public class SubOperationBenchmark extends BinaryOperationBenchmark { @Override protected Operation.OperationResult invoke(final MessageFrame frame) { - return SubOperation.staticOperation(frame); + return SubOperation.staticOperation(frame, frame.stackData()); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SwapNOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SwapNOperationBenchmark.java index e139e648f7e..a725fd51765 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SwapNOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SwapNOperationBenchmark.java @@ -35,7 +35,7 @@ protected byte getImmediate() { @Override protected Operation.OperationResult invoke( final MessageFrame frame, final byte[] code, final int pc) { - return SwapNOperation.staticOperation(frame, code, pc); + return SwapNOperation.staticOperation(frame, frame.stackData(), code, pc); } @Override diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/TernaryOperationBaselineBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/TernaryOperationBaselineBenchmark.java index e167a823fb2..d4f729ba181 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/TernaryOperationBaselineBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/TernaryOperationBaselineBenchmark.java @@ -23,8 +23,7 @@ public class TernaryOperationBaselineBenchmark extends TernaryOperationBenchmark protected Operation.OperationResult invoke(final MessageFrame frame) { // Approximate baseline by popping the two extra inputs and doing nothing // third pop is done by the caller - frame.popStackItem(); - frame.popStackItem(); + frame.setTop(frame.stackTop() - 2); return null; } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/TernaryOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/TernaryOperationBenchmark.java index 21e1cdeee64..63667e792f5 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/TernaryOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/TernaryOperationBenchmark.java @@ -14,12 +14,12 @@ */ package org.hyperledger.besu.ethereum.vm.operations; +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.apache.tuweni.bytes.Bytes; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Measurement; @@ -40,33 +40,33 @@ public abstract class TernaryOperationBenchmark { protected static final int SAMPLE_SIZE = 30_000; - protected Bytes[] aPool; - protected Bytes[] bPool; - protected Bytes[] cPool; + protected UInt256[] aPool; + protected UInt256[] bPool; + protected UInt256[] cPool; protected int index; protected MessageFrame frame; @Setup() public void setUp() { frame = BenchmarkHelper.createMessageCallFrame(); - aPool = new Bytes[SAMPLE_SIZE]; - bPool = new Bytes[SAMPLE_SIZE]; - cPool = new Bytes[SAMPLE_SIZE]; - BenchmarkHelper.fillPool(aPool); - BenchmarkHelper.fillPool(bPool); - BenchmarkHelper.fillPool(cPool); + aPool = new UInt256[SAMPLE_SIZE]; + bPool = new UInt256[SAMPLE_SIZE]; + cPool = new UInt256[SAMPLE_SIZE]; + BenchmarkHelper.fillUInt256Pool(aPool); + BenchmarkHelper.fillUInt256Pool(bPool); + BenchmarkHelper.fillUInt256Pool(cPool); index = 0; } @Benchmark public void executeOperation(final Blackhole blackhole) { - frame.pushStackItem(cPool[index]); - frame.pushStackItem(bPool[index]); - frame.pushStackItem(aPool[index]); + BenchmarkHelper.pushUInt256(frame, cPool[index]); + BenchmarkHelper.pushUInt256(frame, bPool[index]); + BenchmarkHelper.pushUInt256(frame, aPool[index]); blackhole.consume(invoke(frame)); - frame.popStackItem(); + frame.setTop(frame.stackTop() - 1); index = (index + 1) % SAMPLE_SIZE; } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/TransientStorageOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/TransientStorageOperationBenchmark.java index dda593ac3a5..2478628f1b5 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/TransientStorageOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/TransientStorageOperationBenchmark.java @@ -26,12 +26,11 @@ import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator; +import org.hyperledger.besu.evm.internal.StackMath; import org.hyperledger.besu.evm.operation.TLoadOperation; import org.hyperledger.besu.evm.operation.TStoreOperation; import org.hyperledger.besu.evm.worldstate.WorldUpdater; -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.units.bigints.UInt256; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; @@ -80,12 +79,12 @@ public void cleanUp() throws Exception { } @Benchmark - public Bytes executeOperation() { - frame.pushStackItem(UInt256.ONE); - frame.pushStackItem(UInt256.fromHexString("0x01")); + public void executeOperation() { + frame.setTop(StackMath.pushLong(frame.stackData(), frame.stackTop(), 1)); + frame.setTop(StackMath.pushLong(frame.stackData(), frame.stackTop(), 1)); tstore.execute(frame, null); - frame.pushStackItem(UInt256.fromHexString("0x01")); + frame.setTop(StackMath.pushLong(frame.stackData(), frame.stackTop(), 1)); tload.execute(frame, null); - return frame.popStackItem(); + frame.setTop(frame.stackTop() - 1); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/UnaryOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/UnaryOperationBenchmark.java index 4311aa4f346..6e6a6481338 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/UnaryOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/UnaryOperationBenchmark.java @@ -14,12 +14,12 @@ */ package org.hyperledger.besu.ethereum.vm.operations; +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.apache.tuweni.bytes.Bytes; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Measurement; @@ -39,23 +39,23 @@ public abstract class UnaryOperationBenchmark { protected static final int SAMPLE_SIZE = 30_000; - protected Bytes[] valuePool; + protected UInt256[] valuePool; protected int index; protected MessageFrame frame; @Setup() public void setUp() { frame = BenchmarkHelper.createMessageCallFrame(); - valuePool = new Bytes[SAMPLE_SIZE]; - BenchmarkHelper.fillPool(valuePool); + valuePool = new UInt256[SAMPLE_SIZE]; + BenchmarkHelper.fillUInt256Pool(valuePool); index = 0; } @Benchmark public void executeOperation(final Blackhole blackhole) { - frame.pushStackItem(valuePool[index]); + BenchmarkHelper.pushUInt256(frame, valuePool[index]); blackhole.consume(invoke(frame)); - frame.popStackItem(); + frame.setTop(frame.stackTop() - 1); index = (index + 1) % SAMPLE_SIZE; } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/XorOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/XorOperationBenchmark.java index 056084e0b0b..41a541c081c 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/XorOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/XorOperationBenchmark.java @@ -22,6 +22,6 @@ public class XorOperationBenchmark extends BinaryOperationBenchmark { @Override protected Operation.OperationResult invoke(final MessageFrame frame) { - return XorOperation.staticOperation(frame); + return XorOperation.staticOperation(frame, frame.stackData()); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/XorOperationOptimizedBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/XorOperationOptimizedBenchmark.java deleted file mode 100644 index a4f7ea9cb96..00000000000 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/XorOperationOptimizedBenchmark.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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; - -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.operation.Operation; -import org.hyperledger.besu.evm.operation.XorOperationOptimized; - -public class XorOperationOptimizedBenchmark extends BinaryOperationBenchmark { - - @Override - protected Operation.OperationResult invoke(final MessageFrame frame) { - return XorOperationOptimized.staticOperation(frame); - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java index b6a05d4a4bf..9803b90ce7d 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java @@ -302,7 +302,7 @@ private Optional captureStack(final MessageFrame frame) { final Bytes[] stackContents = new Bytes[frame.stackSize()]; for (int i = 0; i < stackContents.length; i++) { // Record stack contents in reverse - stackContents[i] = frame.getStackItem(stackContents.length - i - 1); + stackContents[i] = frame.getStackBytes(stackContents.length - i - 1); } return Optional.of(stackContents); } diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/MessageFrameTestFixture.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/MessageFrameTestFixture.java index 1efb56ec0c3..2cae5bb89b9 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/MessageFrameTestFixture.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/MessageFrameTestFixture.java @@ -185,7 +185,7 @@ public MessageFrame build() { .createBlockHashLookup(localBlockchain, localBlockHeader))) .maxStackSize(maxStackSize) .build(); - stackItems.forEach(frame::pushStackItem); + stackItems.forEach(frame::pushStackBytes); return frame; } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/BlockchainBasedBlockHashLookupTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/BlockchainBasedBlockHashLookupTest.java index 822c7dbbfce..b0dbb55a5c9 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/BlockchainBasedBlockHashLookupTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/BlockchainBasedBlockHashLookupTest.java @@ -31,11 +31,11 @@ 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.internal.StackMath; import org.hyperledger.besu.evm.operation.BlockHashOperation; import java.util.Optional; -import org.apache.tuweni.bytes.Bytes; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -147,15 +147,21 @@ private void assertHashForBlockNumber(final int blockNumber, final Hash hash) { clearInvocations(messageFrameMock, blockValuesMock); BlockHashOperation op = new BlockHashOperation(new CancunGasCalculator()); + long[] s = new long[4 * 4]; + StackMath.putAt(s, 1, 0, org.hyperledger.besu.evm.UInt256.fromInt(blockNumber)); when(messageFrameMock.getRemainingGas()).thenReturn(10_000_000L); - when(messageFrameMock.popStackItem()).thenReturn(Bytes.ofUnsignedInt(blockNumber)); + when(messageFrameMock.stackHasItems(1)).thenReturn(true); + when(messageFrameMock.stackData()).thenReturn(s); + when(messageFrameMock.stackTop()).thenReturn(1); when(messageFrameMock.getBlockValues()).thenReturn(blockValuesMock); when(messageFrameMock.getBlockHashLookup()).thenReturn(lookup); when(blockValuesMock.getNumber()).thenReturn((long) CURRENT_BLOCK_NUMBER); op.execute(messageFrameMock, null); - verify(messageFrameMock).pushStackItem(hash.getBytes()); + org.hyperledger.besu.evm.UInt256 expected = + org.hyperledger.besu.evm.UInt256.fromBytesBE(hash.getBytes().toArrayUnsafe()); + assertThat(StackMath.getAt(s, 1, 0)).isEqualTo(expected); } private BlockHeader createHeader(final int blockNumber, final BlockHeader parentHeader) { diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java index 0c824a9a0eb..fa626bed19b 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java @@ -112,9 +112,9 @@ void shouldRecordStackWhenEnabled() { final UInt256 stackItem1 = UInt256.fromHexString("0x01"); final UInt256 stackItem2 = UInt256.fromHexString("0x02"); final UInt256 stackItem3 = UInt256.fromHexString("0x03"); - frame.pushStackItem(stackItem1); - frame.pushStackItem(stackItem2); - frame.pushStackItem(stackItem3); + frame.pushStackBytes(stackItem1); + frame.pushStackBytes(stackItem2); + frame.pushStackBytes(stackItem3); final TraceFrame traceFrame = traceFrame( frame, diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/Eip7709BlockHashLookupTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/Eip7709BlockHashLookupTest.java index 8c24e690832..53d3b7dffc4 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/Eip7709BlockHashLookupTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/Eip7709BlockHashLookupTest.java @@ -35,13 +35,13 @@ 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.internal.StackMath; import org.hyperledger.besu.evm.operation.BlockHashOperation; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.ArrayList; import java.util.List; -import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -181,11 +181,17 @@ private void assertHashForBlockNumber(final int blockNumber, final Hash hash) { clearInvocations(frame); BlockHashOperation op = new BlockHashOperation(new CancunGasCalculator()); - when(frame.popStackItem()).thenReturn(Bytes.ofUnsignedInt(blockNumber)); + long[] s = new long[4 * 4]; + StackMath.putAt(s, 1, 0, org.hyperledger.besu.evm.UInt256.fromInt(blockNumber)); + when(frame.stackHasItems(1)).thenReturn(true); + when(frame.stackData()).thenReturn(s); + when(frame.stackTop()).thenReturn(1); op.execute(frame, null); - verify(frame).pushStackItem(hash.getBytes()); + org.hyperledger.besu.evm.UInt256 expected = + org.hyperledger.besu.evm.UInt256.fromBytesBE(hash.getBytes().toArrayUnsafe()); + assertThat(StackMath.getAt(s, 1, 0)).isEqualTo(expected); } private BlockHeader createHeader(final long blockNumber, final BlockHeader parentHeader) { diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/MemoryTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/MemoryTest.java index d37a1fccf54..1a3a3986d20 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/MemoryTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/MemoryTest.java @@ -18,30 +18,31 @@ import org.hyperledger.besu.evm.frame.Memory; -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; +import java.util.HexFormat; + import org.junit.jupiter.api.Test; public class MemoryTest { private final Memory memory = new Memory(); - private static final Bytes32 WORD1 = fillBytes32(1); - private static final Bytes32 WORD2 = fillBytes32(2); - private static final Bytes32 WORD3 = fillBytes32(3); - private static final Bytes32 WORD4 = fillBytes32(4); + private static final byte[] WORD1 = fillBytes32(1); + private static final byte[] WORD2 = fillBytes32(2); + private static final byte[] WORD3 = fillBytes32(3); + private static final byte[] WORD4 = fillBytes32(4); + private static final byte[] ZERO_WORD = new byte[32]; @Test public void shouldSetAndGetMemoryByWord() { final int index = 20; - final Bytes32 value = Bytes32.fromHexString("0xABCDEF"); + final byte[] value = hexToBytes32("0xABCDEF"); memory.setWord(index, value); assertThat(memory.getWord(index)).isEqualTo(value); } @Test public void shouldSetMemoryWhenLengthEqualToSourceLength() { - final Bytes value = Bytes.concatenate(WORD1, WORD2, WORD3); - memory.setBytes(0, value.size(), value); + final byte[] value = concat(WORD1, WORD2, WORD3); + memory.setBytes(0, value.length, value); assertThat(memory.getWord(0)).isEqualTo(WORD1); assertThat(memory.getWord(32)).isEqualTo(WORD2); assertThat(memory.getWord(64)).isEqualTo(WORD3); @@ -49,35 +50,35 @@ public void shouldSetMemoryWhenLengthEqualToSourceLength() { @Test public void shouldSetMemoryWhenLengthLessThanSourceLength() { - final Bytes value = Bytes.concatenate(WORD1, WORD2, WORD3); + final byte[] value = concat(WORD1, WORD2, WORD3); memory.setBytes(0, 64, value); assertThat(memory.getWord(0)).isEqualTo(WORD1); assertThat(memory.getWord(32)).isEqualTo(WORD2); - assertThat(memory.getWord(64)).isEqualTo(Bytes32.ZERO); + assertThat(memory.getWord(64)).isEqualTo(ZERO_WORD); } @Test public void shouldSetMemoryWhenLengthGreaterThanSourceLength() { - final Bytes value = Bytes.concatenate(WORD1, WORD2); + final byte[] value = concat(WORD1, WORD2); memory.setBytes(0, 96, value); assertThat(memory.getWord(0)).isEqualTo(WORD1); assertThat(memory.getWord(32)).isEqualTo(WORD2); - assertThat(memory.getWord(64)).isEqualTo(Bytes32.ZERO); + assertThat(memory.getWord(64)).isEqualTo(ZERO_WORD); } @Test public void shouldNotIncreaseActiveWordsIfGetBytesWithoutGrowth() { - final Bytes value = Bytes.concatenate(WORD1, WORD2); - memory.setBytes(0, value.size(), value); + final byte[] value = concat(WORD1, WORD2); + memory.setBytes(0, value.length, value); final int initialActiveWords = memory.getActiveWords(); - assertThat(memory.getBytesWithoutGrowth(64, Bytes32.SIZE)).isEqualTo((Bytes32.ZERO)); + assertThat(memory.getBytesWithoutGrowth(64, 32)).isEqualTo(ZERO_WORD); assertThat(memory.getActiveWords()).isEqualTo(initialActiveWords); - assertThat(memory.getBytes(32, Bytes32.SIZE)).isEqualTo((WORD2)); + assertThat(memory.getBytes(32, 32)).isEqualTo(WORD2); assertThat(memory.getActiveWords()).isEqualTo(initialActiveWords); - assertThat(memory.getBytes(64, Bytes32.SIZE)).isEqualTo((Bytes32.ZERO)); + assertThat(memory.getBytes(64, 32)).isEqualTo(ZERO_WORD); assertThat(memory.getActiveWords()).isEqualTo(initialActiveWords + 1); } @@ -88,11 +89,11 @@ public void shouldClearMemoryAfterSourceDataWhenLengthGreaterThanSourceLength() assertThat(memory.getWord(64)).isEqualTo(WORD3); assertThat(memory.getWord(96)).isEqualTo(WORD4); - final Bytes value = Bytes.concatenate(WORD1, WORD2); + final byte[] value = concat(WORD1, WORD2); memory.setBytes(0, 96, value); assertThat(memory.getWord(0)).isEqualTo(WORD1); assertThat(memory.getWord(32)).isEqualTo(WORD2); - assertThat(memory.getWord(64)).isEqualTo(Bytes32.ZERO); + assertThat(memory.getWord(64)).isEqualTo(ZERO_WORD); assertThat(memory.getWord(96)).isEqualTo(WORD4); } @@ -103,15 +104,15 @@ public void shouldClearMemoryAfterSourceDataWhenLengthGreaterThanSourceLengthWit assertThat(memory.getWord(64)).isEqualTo(WORD3); assertThat(memory.getWord(96)).isEqualTo(WORD4); - final Bytes value = Bytes.concatenate(WORD1, WORD2); + final byte[] value = concat(WORD1, WORD2); memory.setBytes(10, 96, value); assertThat(memory.getWord(10)).isEqualTo(WORD1); assertThat(memory.getWord(42)).isEqualTo(WORD2); - assertThat(memory.getWord(74)).isEqualTo(Bytes32.ZERO); + assertThat(memory.getWord(74)).isEqualTo(ZERO_WORD); // Word 4 got partially cleared because of the starting offset. assertThat(memory.getWord(106)) .isEqualTo( - Bytes32.fromHexString( + hexToBytes32( "0x4444444444444444444444444444444444444444444400000000000000000000")); } @@ -120,10 +121,10 @@ public void shouldClearMemoryAfterSourceDataWhenSourceOffsetPlusLengthGreaterTha memory.setWord(64, WORD3); assertThat(memory.getWord(64)).isEqualTo(WORD3); - final Bytes value = Bytes.concatenate(WORD1, WORD2); + final byte[] value = concat(WORD1, WORD2); memory.setBytes(0, 32, 64, value); assertThat(memory.getWord(0)).isEqualTo(WORD2); - assertThat(memory.getWord(32)).isEqualTo(Bytes32.ZERO); + assertThat(memory.getWord(32)).isEqualTo(ZERO_WORD); assertThat(memory.getWord(64)).isEqualTo(WORD3); } @@ -132,10 +133,10 @@ public void shouldClearMemoryWhenSourceOffsetIsGreaterThanSourceLength() { memory.setWord(64, WORD3); assertThat(memory.getWord(64)).isEqualTo(WORD3); - final Bytes value = Bytes.concatenate(WORD1, WORD2); + final byte[] value = concat(WORD1, WORD2); memory.setBytes(0, 94, 64, value); - assertThat(memory.getWord(0)).isEqualTo(Bytes32.ZERO); - assertThat(memory.getWord(32)).isEqualTo(Bytes32.ZERO); + assertThat(memory.getWord(0)).isEqualTo(ZERO_WORD); + assertThat(memory.getWord(32)).isEqualTo(ZERO_WORD); assertThat(memory.getWord(64)).isEqualTo(WORD3); } @@ -144,11 +145,11 @@ public void shouldClearMemoryWhenSourceDataIsEmpty() { memory.setWord(64, WORD3); assertThat(memory.getWord(64)).isEqualTo(WORD3); - memory.setBytes(0, 96, Bytes.EMPTY); + memory.setBytes(0, 96, new byte[0]); - assertThat(memory.getWord(0)).isEqualTo(Bytes32.ZERO); - assertThat(memory.getWord(32)).isEqualTo(Bytes32.ZERO); - assertThat(memory.getWord(64)).isEqualTo(Bytes32.ZERO); + assertThat(memory.getWord(0)).isEqualTo(ZERO_WORD); + assertThat(memory.getWord(32)).isEqualTo(ZERO_WORD); + assertThat(memory.getWord(64)).isEqualTo(ZERO_WORD); } @Test @@ -156,21 +157,21 @@ public void shouldClearMemoryWhenSourceDataIsEmptyWithSourceOffset() { memory.setWord(64, WORD3); assertThat(memory.getWord(64)).isEqualTo(WORD3); - memory.setBytes(0, 0, 96, Bytes.EMPTY); + memory.setBytes(0, 0, 96, new byte[0]); - assertThat(memory.getWord(0)).isEqualTo(Bytes32.ZERO); - assertThat(memory.getWord(32)).isEqualTo(Bytes32.ZERO); - assertThat(memory.getWord(64)).isEqualTo(Bytes32.ZERO); + assertThat(memory.getWord(0)).isEqualTo(ZERO_WORD); + assertThat(memory.getWord(32)).isEqualTo(ZERO_WORD); + assertThat(memory.getWord(64)).isEqualTo(ZERO_WORD); } - private static Bytes32 fillBytes32(final long value) { - return Bytes32.fromHexString(Long.toString(value).repeat(64)); + private static byte[] fillBytes32(final long value) { + return hexToBytes32(Long.toString(value).repeat(64)); } @Test public void shouldSetMemoryRightAlignedWhenLengthEqualToSourceLength() { - final Bytes value = Bytes.concatenate(WORD1, WORD2, WORD3); - memory.setBytesRightAligned(0, value.size(), value); + final byte[] value = concat(WORD1, WORD2, WORD3); + memory.setBytesRightAligned(0, value.length, value); assertThat(memory.getWord(0)).isEqualTo(WORD1); assertThat(memory.getWord(32)).isEqualTo(WORD2); assertThat(memory.getWord(64)).isEqualTo(WORD3); @@ -178,18 +179,18 @@ public void shouldSetMemoryRightAlignedWhenLengthEqualToSourceLength() { @Test public void shouldSetMemoryRightAlignedWhenLengthLessThanSourceLength() { - final Bytes value = Bytes.concatenate(WORD1, WORD2, WORD3); + final byte[] value = concat(WORD1, WORD2, WORD3); memory.setBytesRightAligned(0, 64, value); assertThat(memory.getWord(0)).isEqualTo(WORD1); assertThat(memory.getWord(32)).isEqualTo(WORD2); - assertThat(memory.getWord(64)).isEqualTo(Bytes32.ZERO); + assertThat(memory.getWord(64)).isEqualTo(ZERO_WORD); } @Test public void shouldSetMemoryRightAlignedWhenLengthGreaterThanSourceLength() { - final Bytes value = Bytes.concatenate(WORD1, WORD2); + final byte[] value = concat(WORD1, WORD2); memory.setBytesRightAligned(0, 96, value); - assertThat(memory.getWord(0)).isEqualTo(Bytes32.ZERO); + assertThat(memory.getWord(0)).isEqualTo(ZERO_WORD); assertThat(memory.getWord(32)).isEqualTo(WORD1); assertThat(memory.getWord(64)).isEqualTo(WORD2); } @@ -201,9 +202,9 @@ public void shouldClearMemoryRightAlignedAfterSourceDataWhenLengthGreaterThanSou assertThat(memory.getWord(64)).isEqualTo(WORD3); assertThat(memory.getWord(96)).isEqualTo(WORD4); - final Bytes value = Bytes.concatenate(WORD1, WORD2); + final byte[] value = concat(WORD1, WORD2); memory.setBytesRightAligned(0, 96, value); - assertThat(memory.getWord(0)).isEqualTo(Bytes32.ZERO); + assertThat(memory.getWord(0)).isEqualTo(ZERO_WORD); assertThat(memory.getWord(32)).isEqualTo(WORD1); assertThat(memory.getWord(64)).isEqualTo(WORD2); assertThat(memory.getWord(96)).isEqualTo(WORD4); @@ -217,15 +218,15 @@ public void shouldClearMemoryRightAlignedAfterSourceDataWhenLengthGreaterThanSou assertThat(memory.getWord(64)).isEqualTo(WORD3); assertThat(memory.getWord(96)).isEqualTo(WORD4); - final Bytes value = Bytes.concatenate(WORD1, WORD2); + final byte[] value = concat(WORD1, WORD2); memory.setBytesRightAligned(10, 96, value); - assertThat(memory.getWord(10)).isEqualTo(Bytes32.ZERO); + assertThat(memory.getWord(10)).isEqualTo(ZERO_WORD); assertThat(memory.getWord(42)).isEqualTo(WORD1); assertThat(memory.getWord(74)).isEqualTo(WORD2); // Word 4 got partially set because of the starting offset. assertThat(memory.getWord(96)) .isEqualTo( - Bytes32.fromHexString( + hexToBytes32( "0x2222222222222222222244444444444444444444444444444444444444444444")); } @@ -234,10 +235,30 @@ public void shouldClearMemoryRightAlignedWhenSourceDataIsEmpty() { memory.setWord(64, WORD3); assertThat(memory.getWord(64)).isEqualTo(WORD3); - memory.setBytesRightAligned(0, 96, Bytes.EMPTY); + memory.setBytesRightAligned(0, 96, new byte[0]); + + assertThat(memory.getWord(0)).isEqualTo(ZERO_WORD); + assertThat(memory.getWord(32)).isEqualTo(ZERO_WORD); + assertThat(memory.getWord(64)).isEqualTo(ZERO_WORD); + } + + private static byte[] hexToBytes32(final String hex) { + final String clean = hex.startsWith("0x") ? hex.substring(2) : hex; + final String padded = "0".repeat(64 - clean.length()) + clean; + return HexFormat.of().parseHex(padded); + } - assertThat(memory.getWord(0)).isEqualTo(Bytes32.ZERO); - assertThat(memory.getWord(32)).isEqualTo(Bytes32.ZERO); - assertThat(memory.getWord(64)).isEqualTo(Bytes32.ZERO); + private static byte[] concat(final byte[]... arrays) { + int totalLen = 0; + for (byte[] a : arrays) { + totalLen += a.length; + } + byte[] result = new byte[totalLen]; + int pos = 0; + for (byte[] a : arrays) { + System.arraycopy(a, 0, result, pos, a.length); + pos += a.length; + } + return result; } } 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 95d3dcc7285..8c26fad144a 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java @@ -25,14 +25,9 @@ import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.internal.EvmConfiguration; 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.operation.AddModOperation; -import org.hyperledger.besu.evm.operation.AddModOperationOptimized; import org.hyperledger.besu.evm.operation.AddOperation; -import org.hyperledger.besu.evm.operation.AddOperationOptimized; import org.hyperledger.besu.evm.operation.AndOperation; -import org.hyperledger.besu.evm.operation.AndOperationOptimized; import org.hyperledger.besu.evm.operation.ByteOperation; import org.hyperledger.besu.evm.operation.ChainIdOperation; import org.hyperledger.besu.evm.operation.CountLeadingZerosOperation; @@ -49,17 +44,13 @@ import org.hyperledger.besu.evm.operation.JumpiOperation; import org.hyperledger.besu.evm.operation.LtOperation; import org.hyperledger.besu.evm.operation.ModOperation; -import org.hyperledger.besu.evm.operation.ModOperationOptimized; import org.hyperledger.besu.evm.operation.MulModOperation; -import org.hyperledger.besu.evm.operation.MulModOperationOptimized; import org.hyperledger.besu.evm.operation.MulOperation; import org.hyperledger.besu.evm.operation.NotOperation; -import org.hyperledger.besu.evm.operation.NotOperationOptimized; import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.operation.Operation.OperationResult; import org.hyperledger.besu.evm.operation.OperationRegistry; import org.hyperledger.besu.evm.operation.OrOperation; -import org.hyperledger.besu.evm.operation.OrOperationOptimized; import org.hyperledger.besu.evm.operation.PopOperation; import org.hyperledger.besu.evm.operation.Push0Operation; import org.hyperledger.besu.evm.operation.PushOperation; @@ -67,13 +58,9 @@ import org.hyperledger.besu.evm.operation.SGtOperation; import org.hyperledger.besu.evm.operation.SLtOperation; import org.hyperledger.besu.evm.operation.SModOperation; -import org.hyperledger.besu.evm.operation.SModOperationOptimized; import org.hyperledger.besu.evm.operation.SarOperation; -import org.hyperledger.besu.evm.operation.SarOperationOptimized; import org.hyperledger.besu.evm.operation.ShlOperation; -import org.hyperledger.besu.evm.operation.ShlOperationOptimized; import org.hyperledger.besu.evm.operation.ShrOperation; -import org.hyperledger.besu.evm.operation.ShrOperationOptimized; import org.hyperledger.besu.evm.operation.SignExtendOperation; import org.hyperledger.besu.evm.operation.StopOperation; import org.hyperledger.besu.evm.operation.SubOperation; @@ -81,7 +68,6 @@ import org.hyperledger.besu.evm.operation.SwapOperation; 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.tracing.OperationTracer; import java.util.Optional; @@ -94,13 +80,9 @@ public class EVM { private static final Logger LOG = LoggerFactory.getLogger(EVM.class); - /** The constant OVERFLOW_RESPONSE. */ - protected static final OperationResult OVERFLOW_RESPONSE = - new OperationResult(0L, ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); - - /** The constant UNDERFLOW_RESPONSE. */ - protected static final OperationResult UNDERFLOW_RESPONSE = - new OperationResult(0L, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + /** Safety-net response for missing pre-validation in new opcodes. */ + private static final OperationResult STACK_OUT_OF_BOUNDS_RESPONSE = + new OperationResult(0L, ExceptionalHaltReason.STACK_OUT_OF_BOUNDS); private final OperationRegistry operations; private final GasCalculator gasCalculator; @@ -109,6 +91,7 @@ public class EVM { private final EvmSpecVersion evmSpecVersion; // Optimized operation flags + private final boolean enableConstantinople; private final boolean enableShanghai; private final boolean enableAmsterdam; private final boolean enableOsaka; @@ -135,6 +118,7 @@ public EVM( this.evmSpecVersion = evmSpecVersion; this.jumpDestOnlyCodeCache = new JumpDestOnlyCodeCache(evmConfiguration); + enableConstantinople = EvmSpecVersion.CONSTANTINOPLE.ordinal() <= evmSpecVersion.ordinal(); enableShanghai = EvmSpecVersion.SHANGHAI.ordinal() <= evmSpecVersion.ordinal(); enableAmsterdam = EvmSpecVersion.AMSTERDAM.ordinal() <= evmSpecVersion.ordinal(); enableOsaka = EvmSpecVersion.OSAKA.ordinal() <= evmSpecVersion.ordinal(); @@ -215,6 +199,7 @@ public void runToHalt(final MessageFrame frame, final OperationTracer tracing) { var operationTracer = tracing == OperationTracer.NO_TRACING ? null : tracing; byte[] code = frame.getCode().getBytes().toArrayUnsafe(); + final long[] s = frame.stackData(); Operation[] operationArray = operations.getOperations(); while (frame.getState() == MessageFrame.State.CODE_EXECUTING) { Operation currentOperation; @@ -237,78 +222,51 @@ public void runToHalt(final MessageFrame frame, final OperationTracer tracing) { result = switch (opcode) { case 0x00 -> StopOperation.staticOperation(frame); - case 0x01 -> - evmConfiguration.enableOptimizedOpcodes() - ? AddOperationOptimized.staticOperation(frame) - : AddOperation.staticOperation(frame); - case 0x02 -> MulOperation.staticOperation(frame); - case 0x03 -> SubOperation.staticOperation(frame); - case 0x04 -> DivOperation.staticOperation(frame); - case 0x05 -> SDivOperation.staticOperation(frame); - case 0x06 -> - evmConfiguration.enableOptimizedOpcodes() - ? ModOperationOptimized.staticOperation(frame) - : ModOperation.staticOperation(frame); - case 0x07 -> - evmConfiguration.enableOptimizedOpcodes() - ? SModOperationOptimized.staticOperation(frame) - : SModOperation.staticOperation(frame); - case 0x08 -> - evmConfiguration.enableOptimizedOpcodes() - ? AddModOperationOptimized.staticOperation(frame) - : AddModOperation.staticOperation(frame); - case 0x09 -> - evmConfiguration.enableOptimizedOpcodes() - ? MulModOperationOptimized.staticOperation(frame) - : MulModOperation.staticOperation(frame); - case 0x0a -> ExpOperation.staticOperation(frame, gasCalculator); - case 0x0b -> SignExtendOperation.staticOperation(frame); + case 0x01 -> AddOperation.staticOperation(frame, s); + case 0x02 -> MulOperation.staticOperation(frame, s); + case 0x03 -> SubOperation.staticOperation(frame, s); + case 0x04 -> DivOperation.staticOperation(frame, s); + case 0x05 -> SDivOperation.staticOperation(frame, s); + case 0x06 -> ModOperation.staticOperation(frame, s); + case 0x07 -> SModOperation.staticOperation(frame, s); + case 0x08 -> AddModOperation.staticOperation(frame, s); + case 0x09 -> MulModOperation.staticOperation(frame, s); + case 0x0a -> ExpOperation.staticOperation(frame, s, gasCalculator); + case 0x0b -> SignExtendOperation.staticOperation(frame, s); case 0x0c, 0x0d, 0x0e, 0x0f -> InvalidOperation.invalidOperationResult(opcode); - case 0x10 -> LtOperation.staticOperation(frame); - case 0x11 -> GtOperation.staticOperation(frame); - case 0x12 -> SLtOperation.staticOperation(frame); - case 0x13 -> SGtOperation.staticOperation(frame); - case 0x15 -> IsZeroOperation.staticOperation(frame); - case 0x16 -> - evmConfiguration.enableOptimizedOpcodes() - ? AndOperationOptimized.staticOperation(frame) - : AndOperation.staticOperation(frame); - case 0x17 -> - evmConfiguration.enableOptimizedOpcodes() - ? OrOperationOptimized.staticOperation(frame) - : OrOperation.staticOperation(frame); - case 0x18 -> - evmConfiguration.enableOptimizedOpcodes() - ? XorOperationOptimized.staticOperation(frame) - : XorOperation.staticOperation(frame); - case 0x19 -> - evmConfiguration.enableOptimizedOpcodes() - ? NotOperationOptimized.staticOperation(frame) - : NotOperation.staticOperation(frame); - case 0x1a -> ByteOperation.staticOperation(frame); + case 0x10 -> LtOperation.staticOperation(frame, s); + case 0x11 -> GtOperation.staticOperation(frame, s); + case 0x12 -> SLtOperation.staticOperation(frame, s); + case 0x13 -> SGtOperation.staticOperation(frame, s); + case 0x15 -> IsZeroOperation.staticOperation(frame, s); + case 0x16 -> AndOperation.staticOperation(frame, s); + case 0x17 -> OrOperation.staticOperation(frame, s); + case 0x18 -> XorOperation.staticOperation(frame, s); + case 0x19 -> NotOperation.staticOperation(frame, s); + case 0x1a -> ByteOperation.staticOperation(frame, s); case 0x1b -> - evmConfiguration.enableOptimizedOpcodes() - ? ShlOperationOptimized.staticOperation(frame) - : ShlOperation.staticOperation(frame); + enableConstantinople + ? ShlOperation.staticOperation(frame, s) + : InvalidOperation.invalidOperationResult(opcode); case 0x1c -> - evmConfiguration.enableOptimizedOpcodes() - ? ShrOperationOptimized.staticOperation(frame) - : ShrOperation.staticOperation(frame); + enableConstantinople + ? ShrOperation.staticOperation(frame, s) + : InvalidOperation.invalidOperationResult(opcode); case 0x1d -> - evmConfiguration.enableOptimizedOpcodes() - ? SarOperationOptimized.staticOperation(frame) - : SarOperation.staticOperation(frame); + enableConstantinople + ? SarOperation.staticOperation(frame, s) + : InvalidOperation.invalidOperationResult(opcode); case 0x1e -> enableOsaka - ? CountLeadingZerosOperation.staticOperation(frame) + ? CountLeadingZerosOperation.staticOperation(frame, s) : InvalidOperation.invalidOperationResult(opcode); case 0x50 -> PopOperation.staticOperation(frame); - case 0x56 -> JumpOperation.staticOperation(frame); - case 0x57 -> JumpiOperation.staticOperation(frame); + case 0x56 -> JumpOperation.staticOperation(frame, s); + case 0x57 -> JumpiOperation.staticOperation(frame, s); case 0x5b -> JumpDestOperation.JUMPDEST_SUCCESS; case 0x5f -> enableShanghai - ? Push0Operation.staticOperation(frame) + ? Push0Operation.staticOperation(frame, s) : InvalidOperation.invalidOperationResult(opcode); case 0x60, // PUSH1-32 0x61, @@ -342,7 +300,7 @@ public void runToHalt(final MessageFrame frame, final OperationTracer tracing) { 0x7d, 0x7e, 0x7f -> - PushOperation.staticOperation(frame, code, pc, opcode - PUSH_BASE); + PushOperation.staticOperation(frame, s, code, pc, opcode - PUSH_BASE); case 0x80, // DUP1-16 0x81, 0x82, @@ -359,7 +317,7 @@ public void runToHalt(final MessageFrame frame, final OperationTracer tracing) { 0x8d, 0x8e, 0x8f -> - DupOperation.staticOperation(frame, opcode - DupOperation.DUP_BASE); + DupOperation.staticOperation(frame, s, opcode - DupOperation.DUP_BASE); case 0x90, // SWAP1-16 0x91, 0x92, @@ -376,28 +334,26 @@ public void runToHalt(final MessageFrame frame, final OperationTracer tracing) { 0x9d, 0x9e, 0x9f -> - SwapOperation.staticOperation(frame, opcode - SWAP_BASE); + SwapOperation.staticOperation(frame, s, opcode - SWAP_BASE); case 0xe6 -> // DUPN (EIP-8024) enableAmsterdam - ? DupNOperation.staticOperation(frame, code, pc) + ? DupNOperation.staticOperation(frame, s, code, pc) : InvalidOperation.invalidOperationResult(opcode); case 0xe7 -> // SWAPN (EIP-8024) enableAmsterdam - ? SwapNOperation.staticOperation(frame, code, pc) + ? SwapNOperation.staticOperation(frame, s, code, pc) : InvalidOperation.invalidOperationResult(opcode); case 0xe8 -> // EXCHANGE (EIP-8024) enableAmsterdam - ? ExchangeOperation.staticOperation(frame, code, pc) + ? ExchangeOperation.staticOperation(frame, s, code, pc) : InvalidOperation.invalidOperationResult(opcode); default -> { // unoptimized operations frame.setCurrentOperation(currentOperation); yield currentOperation.execute(frame, this); } }; - } catch (final OverflowException oe) { - result = OVERFLOW_RESPONSE; - } catch (final UnderflowException ue) { - result = UNDERFLOW_RESPONSE; + } catch (final ArrayIndexOutOfBoundsException aioobe) { + result = STACK_OUT_OF_BOUNDS_RESPONSE; } final ExceptionalHaltReason haltReason = result.getHaltReason(); if (haltReason != null) { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java b/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java index 120893515f7..f22a66f7492 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java @@ -32,12 +32,9 @@ import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.log.EIP7708TransferLogEmitter; import org.hyperledger.besu.evm.operation.AddModOperation; -import org.hyperledger.besu.evm.operation.AddModOperationOptimized; import org.hyperledger.besu.evm.operation.AddOperation; -import org.hyperledger.besu.evm.operation.AddOperationOptimized; import org.hyperledger.besu.evm.operation.AddressOperation; import org.hyperledger.besu.evm.operation.AndOperation; -import org.hyperledger.besu.evm.operation.AndOperationOptimized; import org.hyperledger.besu.evm.operation.BalanceOperation; import org.hyperledger.besu.evm.operation.BaseFeeOperation; import org.hyperledger.besu.evm.operation.BlobBaseFeeOperation; @@ -87,16 +84,12 @@ import org.hyperledger.besu.evm.operation.MStore8Operation; import org.hyperledger.besu.evm.operation.MStoreOperation; import org.hyperledger.besu.evm.operation.ModOperation; -import org.hyperledger.besu.evm.operation.ModOperationOptimized; import org.hyperledger.besu.evm.operation.MulModOperation; -import org.hyperledger.besu.evm.operation.MulModOperationOptimized; import org.hyperledger.besu.evm.operation.MulOperation; import org.hyperledger.besu.evm.operation.NotOperation; -import org.hyperledger.besu.evm.operation.NotOperationOptimized; import org.hyperledger.besu.evm.operation.NumberOperation; import org.hyperledger.besu.evm.operation.OperationRegistry; import org.hyperledger.besu.evm.operation.OrOperation; -import org.hyperledger.besu.evm.operation.OrOperationOptimized; import org.hyperledger.besu.evm.operation.OriginOperation; import org.hyperledger.besu.evm.operation.PCOperation; import org.hyperledger.besu.evm.operation.PayOperation; @@ -113,16 +106,12 @@ import org.hyperledger.besu.evm.operation.SLoadOperation; import org.hyperledger.besu.evm.operation.SLtOperation; import org.hyperledger.besu.evm.operation.SModOperation; -import org.hyperledger.besu.evm.operation.SModOperationOptimized; import org.hyperledger.besu.evm.operation.SStoreOperation; import org.hyperledger.besu.evm.operation.SarOperation; -import org.hyperledger.besu.evm.operation.SarOperationOptimized; import org.hyperledger.besu.evm.operation.SelfBalanceOperation; import org.hyperledger.besu.evm.operation.SelfDestructOperation; import org.hyperledger.besu.evm.operation.ShlOperation; -import org.hyperledger.besu.evm.operation.ShlOperationOptimized; import org.hyperledger.besu.evm.operation.ShrOperation; -import org.hyperledger.besu.evm.operation.ShrOperationOptimized; import org.hyperledger.besu.evm.operation.SignExtendOperation; import org.hyperledger.besu.evm.operation.SlotNumOperation; import org.hyperledger.besu.evm.operation.StaticCallOperation; @@ -134,7 +123,6 @@ import org.hyperledger.besu.evm.operation.TStoreOperation; import org.hyperledger.besu.evm.operation.TimestampOperation; import org.hyperledger.besu.evm.operation.XorOperation; -import org.hyperledger.besu.evm.operation.XorOperationOptimized; import java.math.BigInteger; @@ -171,7 +159,7 @@ public static EVM frontier(final EvmConfiguration evmConfiguration) { public static EVM frontier( final GasCalculator gasCalculator, final EvmConfiguration evmConfiguration) { return new EVM( - frontierOperations(gasCalculator, evmConfiguration), + frontierOperations(gasCalculator), gasCalculator, evmConfiguration, EvmSpecVersion.FRONTIER); @@ -183,10 +171,9 @@ public static EVM frontier( * @param gasCalculator the gas calculator * @return the operation registry */ - private static OperationRegistry frontierOperations( - final GasCalculator gasCalculator, final EvmConfiguration evmConfiguration) { + private static OperationRegistry frontierOperations(final GasCalculator gasCalculator) { OperationRegistry operationRegistry = new OperationRegistry(); - registerFrontierOperations(operationRegistry, gasCalculator, evmConfiguration); + registerFrontierOperations(operationRegistry, gasCalculator); return operationRegistry; } @@ -197,9 +184,7 @@ private static OperationRegistry frontierOperations( * @param gasCalculator the gas calculator */ private static void registerFrontierOperations( - final OperationRegistry registry, - final GasCalculator gasCalculator, - final EvmConfiguration evmConfiguration) { + final OperationRegistry registry, final GasCalculator gasCalculator) { for (int i = 0; i < 255; i++) { registry.put(new InvalidOperation(i, gasCalculator)); } @@ -207,27 +192,15 @@ private static void registerFrontierOperations( registry.put(new SubOperation(gasCalculator)); registry.put(new DivOperation(gasCalculator)); registry.put(new SDivOperation(gasCalculator)); - if (evmConfiguration.enableOptimizedOpcodes()) { - registry.put(new AddOperationOptimized(gasCalculator)); - registry.put(new ModOperationOptimized(gasCalculator)); - registry.put(new SModOperationOptimized(gasCalculator)); - registry.put(new AddModOperationOptimized(gasCalculator)); - registry.put(new MulModOperationOptimized(gasCalculator)); - registry.put(new AndOperationOptimized(gasCalculator)); - registry.put(new XorOperationOptimized(gasCalculator)); - registry.put(new OrOperationOptimized(gasCalculator)); - registry.put(new NotOperationOptimized(gasCalculator)); - } else { - registry.put(new AddOperation(gasCalculator)); - registry.put(new ModOperation(gasCalculator)); - registry.put(new SModOperation(gasCalculator)); - registry.put(new AddModOperation(gasCalculator)); - registry.put(new MulModOperation(gasCalculator)); - registry.put(new AndOperation(gasCalculator)); - registry.put(new XorOperation(gasCalculator)); - registry.put(new OrOperation(gasCalculator)); - registry.put(new NotOperation(gasCalculator)); - } + registry.put(new AddOperation(gasCalculator)); + registry.put(new ModOperation(gasCalculator)); + registry.put(new SModOperation(gasCalculator)); + registry.put(new AddModOperation(gasCalculator)); + registry.put(new MulModOperation(gasCalculator)); + registry.put(new AndOperation(gasCalculator)); + registry.put(new XorOperation(gasCalculator)); + registry.put(new OrOperation(gasCalculator)); + registry.put(new NotOperation(gasCalculator)); registry.put(new ExpOperation(gasCalculator)); registry.put(new SignExtendOperation(gasCalculator)); registry.put(new LtOperation(gasCalculator)); @@ -318,7 +291,7 @@ public static EVM homestead(final EvmConfiguration evmConfiguration) { public static EVM homestead( final GasCalculator gasCalculator, final EvmConfiguration evmConfiguration) { return new EVM( - homesteadOperations(gasCalculator, evmConfiguration), + homesteadOperations(gasCalculator), gasCalculator, evmConfiguration, EvmSpecVersion.HOMESTEAD); @@ -330,10 +303,9 @@ public static EVM homestead( * @param gasCalculator the gas calculator * @return the operation registry */ - private static OperationRegistry homesteadOperations( - final GasCalculator gasCalculator, final EvmConfiguration evmConfiguration) { + private static OperationRegistry homesteadOperations(final GasCalculator gasCalculator) { OperationRegistry operationRegistry = new OperationRegistry(); - registerHomesteadOperations(operationRegistry, gasCalculator, evmConfiguration); + registerHomesteadOperations(operationRegistry, gasCalculator); return operationRegistry; } @@ -344,10 +316,8 @@ private static OperationRegistry homesteadOperations( * @param gasCalculator the gas calculator */ private static void registerHomesteadOperations( - final OperationRegistry registry, - final GasCalculator gasCalculator, - final EvmConfiguration evmConfiguration) { - registerFrontierOperations(registry, gasCalculator, evmConfiguration); + final OperationRegistry registry, final GasCalculator gasCalculator) { + registerFrontierOperations(registry, gasCalculator); registry.put(new DelegateCallOperation(gasCalculator)); } @@ -360,7 +330,7 @@ private static void registerHomesteadOperations( public static EVM spuriousDragon(final EvmConfiguration evmConfiguration) { GasCalculator gasCalculator = new SpuriousDragonGasCalculator(); return new EVM( - homesteadOperations(gasCalculator, evmConfiguration), + homesteadOperations(gasCalculator), gasCalculator, evmConfiguration, EvmSpecVersion.SPURIOUS_DRAGON); @@ -375,7 +345,7 @@ public static EVM spuriousDragon(final EvmConfiguration evmConfiguration) { public static EVM tangerineWhistle(final EvmConfiguration evmConfiguration) { GasCalculator gasCalculator = new TangerineWhistleGasCalculator(); return new EVM( - homesteadOperations(gasCalculator, evmConfiguration), + homesteadOperations(gasCalculator), gasCalculator, evmConfiguration, EvmSpecVersion.TANGERINE_WHISTLE); @@ -401,7 +371,7 @@ public static EVM byzantium(final EvmConfiguration evmConfiguration) { public static EVM byzantium( final GasCalculator gasCalculator, final EvmConfiguration evmConfiguration) { return new EVM( - byzantiumOperations(gasCalculator, evmConfiguration), + byzantiumOperations(gasCalculator), gasCalculator, evmConfiguration, EvmSpecVersion.BYZANTIUM); @@ -413,10 +383,9 @@ public static EVM byzantium( * @param gasCalculator the gas calculator * @return the operation registry */ - private static OperationRegistry byzantiumOperations( - final GasCalculator gasCalculator, final EvmConfiguration evmConfiguration) { + private static OperationRegistry byzantiumOperations(final GasCalculator gasCalculator) { OperationRegistry operationRegistry = new OperationRegistry(); - registerByzantiumOperations(operationRegistry, gasCalculator, evmConfiguration); + registerByzantiumOperations(operationRegistry, gasCalculator); return operationRegistry; } @@ -427,10 +396,8 @@ private static OperationRegistry byzantiumOperations( * @param gasCalculator the gas calculator */ private static void registerByzantiumOperations( - final OperationRegistry registry, - final GasCalculator gasCalculator, - final EvmConfiguration evmConfiguration) { - registerHomesteadOperations(registry, gasCalculator, evmConfiguration); + final OperationRegistry registry, final GasCalculator gasCalculator) { + registerHomesteadOperations(registry, gasCalculator); registry.put(new ReturnDataCopyOperation(gasCalculator)); registry.put(new ReturnDataSizeOperation(gasCalculator)); registry.put(new RevertOperation(gasCalculator)); @@ -465,10 +432,7 @@ private static EVM constantiNOPEl( final EvmConfiguration evmConfiguration, final EvmSpecVersion version) { return new EVM( - constantinopleOperations(gasCalculator, evmConfiguration), - gasCalculator, - evmConfiguration, - version); + constantinopleOperations(gasCalculator), gasCalculator, evmConfiguration, version); } /** @@ -477,10 +441,9 @@ private static EVM constantiNOPEl( * @param gasCalculator the gas calculator * @return the operation registry */ - private static OperationRegistry constantinopleOperations( - final GasCalculator gasCalculator, final EvmConfiguration evmConfiguration) { + private static OperationRegistry constantinopleOperations(final GasCalculator gasCalculator) { OperationRegistry operationRegistry = new OperationRegistry(); - registerConstantinopleOperations(operationRegistry, gasCalculator, evmConfiguration); + registerConstantinopleOperations(operationRegistry, gasCalculator); return operationRegistry; } @@ -491,20 +454,12 @@ private static OperationRegistry constantinopleOperations( * @param gasCalculator the gas calculator */ private static void registerConstantinopleOperations( - final OperationRegistry registry, - final GasCalculator gasCalculator, - final EvmConfiguration evmConfiguration) { - registerByzantiumOperations(registry, gasCalculator, evmConfiguration); + final OperationRegistry registry, final GasCalculator gasCalculator) { + registerByzantiumOperations(registry, gasCalculator); registry.put(new Create2Operation(gasCalculator)); - if (evmConfiguration.enableOptimizedOpcodes()) { - registry.put(new ShlOperationOptimized(gasCalculator)); - registry.put(new ShrOperationOptimized(gasCalculator)); - registry.put(new SarOperationOptimized(gasCalculator)); - } else { - registry.put(new ShlOperation(gasCalculator)); - registry.put(new ShrOperation(gasCalculator)); - registry.put(new SarOperation(gasCalculator)); - } + registry.put(new ShlOperation(gasCalculator)); + registry.put(new ShrOperation(gasCalculator)); + registry.put(new SarOperation(gasCalculator)); registry.put(new ExtCodeHashOperation(gasCalculator)); } @@ -553,7 +508,7 @@ public static EVM istanbul( final BigInteger chainId, final EvmConfiguration evmConfiguration) { return new EVM( - istanbulOperations(gasCalculator, chainId, evmConfiguration), + istanbulOperations(gasCalculator, chainId), gasCalculator, evmConfiguration, EvmSpecVersion.ISTANBUL); @@ -567,11 +522,9 @@ public static EVM istanbul( * @return the operation registry */ private static OperationRegistry istanbulOperations( - final GasCalculator gasCalculator, - final BigInteger chainId, - final EvmConfiguration evmConfiguration) { + final GasCalculator gasCalculator, final BigInteger chainId) { OperationRegistry operationRegistry = new OperationRegistry(); - registerIstanbulOperations(operationRegistry, gasCalculator, chainId, evmConfiguration); + registerIstanbulOperations(operationRegistry, gasCalculator, chainId); return operationRegistry; } @@ -585,9 +538,8 @@ private static OperationRegistry istanbulOperations( static void registerIstanbulOperations( final OperationRegistry registry, final GasCalculator gasCalculator, - final BigInteger chainId, - final EvmConfiguration evmConfiguration) { - registerConstantinopleOperations(registry, gasCalculator, evmConfiguration); + final BigInteger chainId) { + registerConstantinopleOperations(registry, gasCalculator); registry.put( new ChainIdOperation(gasCalculator, Bytes32.leftPad(Bytes.of(chainId.toByteArray())))); registry.put(new SelfBalanceOperation(gasCalculator)); @@ -628,7 +580,7 @@ public static EVM berlin( final BigInteger chainId, final EvmConfiguration evmConfiguration) { return new EVM( - istanbulOperations(gasCalculator, chainId, evmConfiguration), + istanbulOperations(gasCalculator, chainId), gasCalculator, evmConfiguration, EvmSpecVersion.BERLIN); @@ -668,7 +620,7 @@ public static EVM london( final BigInteger chainId, final EvmConfiguration evmConfiguration) { return new EVM( - londonOperations(gasCalculator, chainId, evmConfiguration), + londonOperations(gasCalculator, chainId), gasCalculator, evmConfiguration, EvmSpecVersion.LONDON); @@ -682,11 +634,9 @@ public static EVM london( * @return the operation registry */ private static OperationRegistry londonOperations( - final GasCalculator gasCalculator, - final BigInteger chainId, - final EvmConfiguration evmConfiguration) { + final GasCalculator gasCalculator, final BigInteger chainId) { OperationRegistry operationRegistry = new OperationRegistry(); - registerLondonOperations(operationRegistry, gasCalculator, chainId, evmConfiguration); + registerLondonOperations(operationRegistry, gasCalculator, chainId); return operationRegistry; } @@ -700,9 +650,8 @@ private static OperationRegistry londonOperations( private static void registerLondonOperations( final OperationRegistry registry, final GasCalculator gasCalculator, - final BigInteger chainId, - final EvmConfiguration evmConfiguration) { - registerIstanbulOperations(registry, gasCalculator, chainId, evmConfiguration); + final BigInteger chainId) { + registerIstanbulOperations(registry, gasCalculator, chainId); registry.put(new BaseFeeOperation(gasCalculator)); } @@ -740,7 +689,7 @@ public static EVM paris( final BigInteger chainId, final EvmConfiguration evmConfiguration) { return new EVM( - parisOperations(gasCalculator, chainId, evmConfiguration), + parisOperations(gasCalculator, chainId), gasCalculator, evmConfiguration, EvmSpecVersion.PARIS); @@ -754,11 +703,9 @@ public static EVM paris( * @return the operation registry */ private static OperationRegistry parisOperations( - final GasCalculator gasCalculator, - final BigInteger chainId, - final EvmConfiguration evmConfiguration) { + final GasCalculator gasCalculator, final BigInteger chainId) { OperationRegistry operationRegistry = new OperationRegistry(); - registerParisOperations(operationRegistry, gasCalculator, chainId, evmConfiguration); + registerParisOperations(operationRegistry, gasCalculator, chainId); return operationRegistry; } @@ -772,9 +719,8 @@ private static OperationRegistry parisOperations( private static void registerParisOperations( final OperationRegistry registry, final GasCalculator gasCalculator, - final BigInteger chainID, - final EvmConfiguration evmConfiguration) { - registerLondonOperations(registry, gasCalculator, chainID, evmConfiguration); + final BigInteger chainID) { + registerLondonOperations(registry, gasCalculator, chainID); registry.put(new PrevRanDaoOperation(gasCalculator)); } @@ -812,7 +758,7 @@ public static EVM shanghai( final BigInteger chainId, final EvmConfiguration evmConfiguration) { return new EVM( - shanghaiOperations(gasCalculator, chainId, evmConfiguration), + shanghaiOperations(gasCalculator, chainId), gasCalculator, evmConfiguration, EvmSpecVersion.SHANGHAI); @@ -826,11 +772,9 @@ public static EVM shanghai( * @return the operation registry */ private static OperationRegistry shanghaiOperations( - final GasCalculator gasCalculator, - final BigInteger chainId, - final EvmConfiguration evmConfiguration) { + final GasCalculator gasCalculator, final BigInteger chainId) { OperationRegistry operationRegistry = new OperationRegistry(); - registerShanghaiOperations(operationRegistry, gasCalculator, chainId, evmConfiguration); + registerShanghaiOperations(operationRegistry, gasCalculator, chainId); return operationRegistry; } @@ -844,9 +788,8 @@ private static OperationRegistry shanghaiOperations( private static void registerShanghaiOperations( final OperationRegistry registry, final GasCalculator gasCalculator, - final BigInteger chainID, - final EvmConfiguration evmConfiguration) { - registerParisOperations(registry, gasCalculator, chainID, evmConfiguration); + final BigInteger chainID) { + registerParisOperations(registry, gasCalculator, chainID); registry.put(new Push0Operation(gasCalculator)); } @@ -884,7 +827,7 @@ public static EVM cancun( final BigInteger chainId, final EvmConfiguration evmConfiguration) { return new EVM( - cancunOperations(gasCalculator, chainId, evmConfiguration), + cancunOperations(gasCalculator, chainId), gasCalculator, evmConfiguration, EvmSpecVersion.CANCUN); @@ -898,11 +841,9 @@ public static EVM cancun( * @return the operation registry */ private static OperationRegistry cancunOperations( - final GasCalculator gasCalculator, - final BigInteger chainId, - final EvmConfiguration evmConfiguration) { + final GasCalculator gasCalculator, final BigInteger chainId) { OperationRegistry operationRegistry = new OperationRegistry(); - registerCancunOperations(operationRegistry, gasCalculator, chainId, evmConfiguration); + registerCancunOperations(operationRegistry, gasCalculator, chainId); return operationRegistry; } @@ -916,9 +857,8 @@ private static OperationRegistry cancunOperations( private static void registerCancunOperations( final OperationRegistry registry, final GasCalculator gasCalculator, - final BigInteger chainID, - final EvmConfiguration evmConfiguration) { - registerShanghaiOperations(registry, gasCalculator, chainID, evmConfiguration); + final BigInteger chainID) { + registerShanghaiOperations(registry, gasCalculator, chainID); // EIP-1153 TSTORE/TLOAD registry.put(new TStoreOperation(gasCalculator)); @@ -971,7 +911,7 @@ public static EVM prague( final BigInteger chainId, final EvmConfiguration evmConfiguration) { return new EVM( - pragueOperations(gasCalculator, chainId, evmConfiguration), + pragueOperations(gasCalculator, chainId), gasCalculator, evmConfiguration, EvmSpecVersion.PRAGUE); @@ -985,11 +925,9 @@ public static EVM prague( * @return the operation registry */ private static OperationRegistry pragueOperations( - final GasCalculator gasCalculator, - final BigInteger chainId, - final EvmConfiguration evmConfiguration) { + final GasCalculator gasCalculator, final BigInteger chainId) { OperationRegistry operationRegistry = new OperationRegistry(); - registerPragueOperations(operationRegistry, gasCalculator, chainId, evmConfiguration); + registerPragueOperations(operationRegistry, gasCalculator, chainId); return operationRegistry; } @@ -1003,9 +941,8 @@ private static OperationRegistry pragueOperations( private static void registerPragueOperations( final OperationRegistry registry, final GasCalculator gasCalculator, - final BigInteger chainID, - final EvmConfiguration evmConfiguration) { - registerCancunOperations(registry, gasCalculator, chainID, evmConfiguration); + final BigInteger chainID) { + registerCancunOperations(registry, gasCalculator, chainID); } /** @@ -1032,7 +969,7 @@ public static EVM osaka( final BigInteger chainId, final EvmConfiguration evmConfiguration) { return new EVM( - osakaOperations(gasCalculator, chainId, evmConfiguration), + osakaOperations(gasCalculator, chainId), gasCalculator, evmConfiguration, EvmSpecVersion.OSAKA); @@ -1046,11 +983,9 @@ public static EVM osaka( * @return the operation registry */ private static OperationRegistry osakaOperations( - final GasCalculator gasCalculator, - final BigInteger chainId, - final EvmConfiguration evmConfiguration) { + final GasCalculator gasCalculator, final BigInteger chainId) { OperationRegistry operationRegistry = new OperationRegistry(); - registerOsakaOperations(operationRegistry, gasCalculator, chainId, evmConfiguration); + registerOsakaOperations(operationRegistry, gasCalculator, chainId); return operationRegistry; } @@ -1064,9 +999,8 @@ private static OperationRegistry osakaOperations( private static void registerOsakaOperations( final OperationRegistry registry, final GasCalculator gasCalculator, - final BigInteger chainID, - final EvmConfiguration evmConfiguration) { - registerPragueOperations(registry, gasCalculator, chainID, evmConfiguration); + final BigInteger chainID) { + registerPragueOperations(registry, gasCalculator, chainID); // EIP-7939: CLZ opcode registry.put(new CountLeadingZerosOperation(gasCalculator)); @@ -1106,7 +1040,7 @@ public static EVM amsterdam( final BigInteger chainId, final EvmConfiguration evmConfiguration) { return new EVM( - amsterdamOperations(gasCalculator, chainId, evmConfiguration), + amsterdamOperations(gasCalculator, chainId), gasCalculator, evmConfiguration, EvmSpecVersion.AMSTERDAM); @@ -1120,11 +1054,9 @@ public static EVM amsterdam( * @return the operation registry */ private static OperationRegistry amsterdamOperations( - final GasCalculator gasCalculator, - final BigInteger chainId, - final EvmConfiguration evmConfiguration) { + final GasCalculator gasCalculator, final BigInteger chainId) { OperationRegistry operationRegistry = new OperationRegistry(); - registerAmsterdamOperations(operationRegistry, gasCalculator, chainId, evmConfiguration); + registerAmsterdamOperations(operationRegistry, gasCalculator, chainId); return operationRegistry; } @@ -1138,9 +1070,8 @@ private static OperationRegistry amsterdamOperations( private static void registerAmsterdamOperations( final OperationRegistry registry, final GasCalculator gasCalculator, - final BigInteger chainID, - final EvmConfiguration evmConfiguration) { - registerOsakaOperations(registry, gasCalculator, chainID, evmConfiguration); + final BigInteger chainID) { + registerOsakaOperations(registry, gasCalculator, chainID); // EIP-7708: SelfDestruct with transfer log emission registry.put( @@ -1189,7 +1120,7 @@ public static EVM bogota( final BigInteger chainId, final EvmConfiguration evmConfiguration) { return new EVM( - bogotaOperations(gasCalculator, chainId, evmConfiguration), + bogotaOperations(gasCalculator, chainId), gasCalculator, evmConfiguration, EvmSpecVersion.BOGOTA); @@ -1203,11 +1134,9 @@ public static EVM bogota( * @return the operation registry */ private static OperationRegistry bogotaOperations( - final GasCalculator gasCalculator, - final BigInteger chainId, - final EvmConfiguration evmConfiguration) { + final GasCalculator gasCalculator, final BigInteger chainId) { OperationRegistry operationRegistry = new OperationRegistry(); - registerBogotaOperations(operationRegistry, gasCalculator, chainId, evmConfiguration); + registerBogotaOperations(operationRegistry, gasCalculator, chainId); return operationRegistry; } @@ -1221,9 +1150,8 @@ private static OperationRegistry bogotaOperations( private static void registerBogotaOperations( final OperationRegistry registry, final GasCalculator gasCalculator, - final BigInteger chainID, - final EvmConfiguration evmConfiguration) { - registerAmsterdamOperations(registry, gasCalculator, chainID, evmConfiguration); + final BigInteger chainID) { + registerAmsterdamOperations(registry, gasCalculator, chainID); } /** @@ -1260,7 +1188,7 @@ public static EVM polis( final BigInteger chainId, final EvmConfiguration evmConfiguration) { return new EVM( - polisOperations(gasCalculator, chainId, evmConfiguration), + polisOperations(gasCalculator, chainId), gasCalculator, evmConfiguration, EvmSpecVersion.POLIS); @@ -1274,11 +1202,9 @@ public static EVM polis( * @return the operation registry */ private static OperationRegistry polisOperations( - final GasCalculator gasCalculator, - final BigInteger chainId, - final EvmConfiguration evmConfiguration) { + final GasCalculator gasCalculator, final BigInteger chainId) { OperationRegistry operationRegistry = new OperationRegistry(); - registerPolisOperations(operationRegistry, gasCalculator, chainId, evmConfiguration); + registerPolisOperations(operationRegistry, gasCalculator, chainId); return operationRegistry; } @@ -1292,9 +1218,8 @@ private static OperationRegistry polisOperations( private static void registerPolisOperations( final OperationRegistry registry, final GasCalculator gasCalculator, - final BigInteger chainID, - final EvmConfiguration evmConfiguration) { - registerBogotaOperations(registry, gasCalculator, chainID, evmConfiguration); + final BigInteger chainID) { + registerBogotaOperations(registry, gasCalculator, chainID); } /** @@ -1331,7 +1256,7 @@ public static EVM bangkok( final BigInteger chainId, final EvmConfiguration evmConfiguration) { return new EVM( - bangkokOperations(gasCalculator, chainId, evmConfiguration), + bangkokOperations(gasCalculator, chainId), gasCalculator, evmConfiguration, EvmSpecVersion.BANGKOK); @@ -1345,11 +1270,9 @@ public static EVM bangkok( * @return the operation registry */ private static OperationRegistry bangkokOperations( - final GasCalculator gasCalculator, - final BigInteger chainId, - final EvmConfiguration evmConfiguration) { + final GasCalculator gasCalculator, final BigInteger chainId) { OperationRegistry operationRegistry = new OperationRegistry(); - registerBangkokOperations(operationRegistry, gasCalculator, chainId, evmConfiguration); + registerBangkokOperations(operationRegistry, gasCalculator, chainId); return operationRegistry; } @@ -1363,9 +1286,8 @@ private static OperationRegistry bangkokOperations( private static void registerBangkokOperations( final OperationRegistry registry, final GasCalculator gasCalculator, - final BigInteger chainID, - final EvmConfiguration evmConfiguration) { - registerPolisOperations(registry, gasCalculator, chainID, evmConfiguration); + final BigInteger chainID) { + registerPolisOperations(registry, gasCalculator, chainID); } /** @@ -1402,7 +1324,7 @@ public static EVM futureEips( final BigInteger chainId, final EvmConfiguration evmConfiguration) { return new EVM( - futureEipsOperations(gasCalculator, chainId, evmConfiguration), + futureEipsOperations(gasCalculator, chainId), gasCalculator, evmConfiguration, EvmSpecVersion.FUTURE_EIPS); @@ -1416,11 +1338,9 @@ public static EVM futureEips( * @return the operation registry */ private static OperationRegistry futureEipsOperations( - final GasCalculator gasCalculator, - final BigInteger chainId, - final EvmConfiguration evmConfiguration) { + final GasCalculator gasCalculator, final BigInteger chainId) { OperationRegistry operationRegistry = new OperationRegistry(); - registerFutureEipsOperations(operationRegistry, gasCalculator, chainId, evmConfiguration); + registerFutureEipsOperations(operationRegistry, gasCalculator, chainId); return operationRegistry; } @@ -1434,9 +1354,8 @@ private static OperationRegistry futureEipsOperations( private static void registerFutureEipsOperations( final OperationRegistry registry, final GasCalculator gasCalculator, - final BigInteger chainID, - final EvmConfiguration evmConfiguration) { - registerBogotaOperations(registry, gasCalculator, chainID, evmConfiguration); + final BigInteger chainID) { + registerBogotaOperations(registry, gasCalculator, chainID); // EIP-5920 PAY opcode registry.put(new PayOperation(gasCalculator)); @@ -1477,7 +1396,7 @@ public static EVM experimentalEips( final BigInteger chainId, final EvmConfiguration evmConfiguration) { return new EVM( - experimentalEipsOperations(gasCalculator, chainId, evmConfiguration), + experimentalEipsOperations(gasCalculator, chainId), gasCalculator, evmConfiguration, EvmSpecVersion.EXPERIMENTAL_EIPS); @@ -1491,11 +1410,9 @@ public static EVM experimentalEips( * @return the operation registry */ private static OperationRegistry experimentalEipsOperations( - final GasCalculator gasCalculator, - final BigInteger chainId, - final EvmConfiguration evmConfiguration) { + final GasCalculator gasCalculator, final BigInteger chainId) { OperationRegistry operationRegistry = new OperationRegistry(); - registerExperimentalEipsOperations(operationRegistry, gasCalculator, chainId, evmConfiguration); + registerExperimentalEipsOperations(operationRegistry, gasCalculator, chainId); return operationRegistry; } @@ -1509,8 +1426,7 @@ private static OperationRegistry experimentalEipsOperations( private static void registerExperimentalEipsOperations( final OperationRegistry registry, final GasCalculator gasCalculator, - final BigInteger chainID, - final EvmConfiguration evmConfiguration) { - registerFutureEipsOperations(registry, gasCalculator, chainID, evmConfiguration); + final BigInteger chainID) { + registerFutureEipsOperations(registry, gasCalculator, chainID); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/UInt256.java b/evm/src/main/java/org/hyperledger/besu/evm/UInt256.java index 290ef16cffb..d8b7b98a94e 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/UInt256.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/UInt256.java @@ -15,23 +15,25 @@ package org.hyperledger.besu.evm; import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -import com.google.common.annotations.VisibleForTesting; /** * 256-bits wide unsigned integer class. * *

This class is an optimised version of BigInteger for fixed width 256-bits integers. + * + * @param u3 4th digit + * @param u2 3rd digit + * @param u1 2nd digit + * @param u0 1st digit */ -public final class UInt256 { - // region Internals +public record UInt256(long u3, long u2, long u1, long u0) { + + // region Values + // -------------------------------------------------------------------------- + // UInt256 represents a big-endian 256-bits integer. + // As opposed to Java int, operations are by default unsigned, + // and signed version are interpreted in two-complements as usual. // -------------------------------------------------------------------------- - // UInt256 is a big-endian up to 256-bits integer. - // Internally, it is represented with fixed-size int/long limbs in little-endian order. - // Length is used to optimise algorithms, skipping leading zeroes. - // Nonetheless, 256bits are always allocated and initialised to zeroes. /** Fixed size in bytes. */ public static final int BYTESIZE = 32; @@ -39,54 +41,34 @@ public final class UInt256 { /** Fixed size in bits. */ public static final int BITSIZE = 256; - // Fixed number of limbs or digits - private static final int N_LIMBS = 8; - // Fixed number of bits per limb. - private static final int N_BITS_PER_LIMB = 32; - // Mask for long values - private static final long MASK_L = 0xFFFFFFFFL; - - private final int[] limbs; - private final int length; + /** The constant 0. */ + public static final UInt256 ZERO = new UInt256(0, 0, 0, 0); - @VisibleForTesting - int[] limbs() { - return limbs; - } + /** The constant All ones */ + public static final UInt256 MAX = + new UInt256( + 0xFFFFFFFFFFFFFFFFL, 0xFFFFFFFFFFFFFFFFL, 0xFFFFFFFFFFFFFFFFL, 0xFFFFFFFFFFFFFFFFL); // -------------------------------------------------------------------------- // endregion - /** The constant 0. */ - public static final UInt256 ZERO = new UInt256(new int[] {0, 0, 0, 0, 0, 0, 0, 0}, 0); - - /** The constant All ones */ - public static final UInt256 ALL_ONES = - new UInt256( - new int[] { - 0xFFFFFFFF, - 0xFFFFFFFF, - 0xFFFFFFFF, - 0xFFFFFFFF, - 0xFFFFFFFF, - 0xFFFFFFFF, - 0xFFFFFFFF, - 0xFFFFFFFF - }, - N_LIMBS); - - // region Constructors + // region (private) Internal Values // -------------------------------------------------------------------------- - UInt256(final int[] limbs, final int length) { - // Unchecked length: assumes limbs have length == N_LIMBS - this.limbs = limbs; - this.length = length; - } + // Fixed number of limbs or digits + // private static final int N_LIMBS = 4; + // Fixed number of bits per limb. + private static final int N_BITS_PER_LIMB = 64; - UInt256(final int[] limbs) { - this(limbs, N_LIMBS); - } + // Arrays of zeros. + // We accomodate up to a result of a multiplication + // private static final long[] ZERO_LONGS = new long[9]; + + // -------------------------------------------------------------------------- + // endregion + + // region Alternative Constructors + // -------------------------------------------------------------------------- /** * Instantiates a new UInt256 from byte array. @@ -95,48 +77,54 @@ int[] limbs() { * @return Big-endian UInt256 represented by the bytes. */ public static UInt256 fromBytesBE(final byte[] bytes) { - int byteLen = bytes.length; - if (byteLen == 0) return ZERO; - - int[] limbs = new int[N_LIMBS]; - - // Fast path for exactly 32 bytes - if (byteLen == 32) { - limbs[7] = getIntBE(bytes, 0); - limbs[6] = getIntBE(bytes, 4); - limbs[5] = getIntBE(bytes, 8); - limbs[4] = getIntBE(bytes, 12); - limbs[3] = getIntBE(bytes, 16); - limbs[2] = getIntBE(bytes, 20); - limbs[1] = getIntBE(bytes, 24); - limbs[0] = getIntBE(bytes, 28); - return new UInt256(limbs, N_LIMBS); - } - - // General path for variable length - int limbIndex = 0; - int byteIndex = byteLen - 1; - - while (byteIndex >= 0 && limbIndex < N_LIMBS) { - int limb = 0; - int shift = 0; - - for (int j = 0; j < 4 && byteIndex >= 0; j++, byteIndex--, shift += 8) { - limb |= (bytes[byteIndex] & 0xFF) << shift; - } - - limbs[limbIndex++] = limb; + if (bytes.length == 0) return ZERO; + long u3 = 0; + long u2 = 0; + long u1 = 0; + long u0 = 0; + int b = bytes.length - 1; // Index in bytes array + for (int shift = 0; shift < 64 && b >= 0; b--, shift += 8) { + u0 |= ((bytes[b] & 0xFFL) << shift); + } + for (int shift = 0; shift < 64 && b >= 0; b--, shift += 8) { + u1 |= ((bytes[b] & 0xFFL) << shift); } + for (int shift = 0; shift < 64 && b >= 0; b--, shift += 8) { + u2 |= ((bytes[b] & 0xFFL) << shift); + } + for (int shift = 0; shift < 64 && b >= 0; b--, shift += 8) { + u3 |= ((bytes[b] & 0xFFL) << shift); + } + return new UInt256(u3, u2, u1, u0); + } - return new UInt256(limbs, limbIndex); + /** + * Converts a Tuweni UInt256 to a native UInt256. + * + * @param tuweni the Tuweni UInt256 value + * @return the native UInt256 equivalent + */ + public static UInt256 fromTuweni(final org.apache.tuweni.units.bigints.UInt256 tuweni) { + return fromBytesBE(tuweni.toArrayUnsafe()); } - // Helper method to read 4 bytes as big-endian int - private static int getIntBE(final byte[] bytes, final int offset) { - return ((bytes[offset] & 0xFF) << 24) - | ((bytes[offset + 1] & 0xFF) << 16) - | ((bytes[offset + 2] & 0xFF) << 8) - | (bytes[offset + 3] & 0xFF); + /** + * Converts this native UInt256 to a Tuweni UInt256. + * + * @return the Tuweni UInt256 equivalent + */ + public org.apache.tuweni.units.bigints.UInt256 toTuweni() { + return org.apache.tuweni.units.bigints.UInt256.fromBytes( + org.apache.tuweni.bytes.Bytes32.wrap(toBytesBE())); + } + + /** + * Converts this native UInt256 to a Tuweni Bytes32. + * + * @return the Tuweni Bytes32 equivalent + */ + public org.apache.tuweni.bytes.Bytes32 toBytes32() { + return org.apache.tuweni.bytes.Bytes32.wrap(toBytesBE()); } /** @@ -146,10 +134,7 @@ private static int getIntBE(final byte[] bytes, final int offset) { * @return The UInt256 equivalent of value. */ public static UInt256 fromInt(final int value) { - if (value == 0) return ZERO; - int[] limbs = new int[N_LIMBS]; - limbs[0] = value; - return new UInt256(limbs, 1); + return new UInt256(0, 0, 0, value & 0xFFFFFFFFL); } /** @@ -159,27 +144,25 @@ public static UInt256 fromInt(final int value) { * @return The UInt256 equivalent of value. */ public static UInt256 fromLong(final long value) { - if (value == 0) return ZERO; - int[] limbs = new int[N_LIMBS]; - limbs[0] = (int) value; - limbs[1] = (int) (value >>> 32); - return new UInt256(limbs, 2); + return new UInt256(0, 0, 0, value); } /** - * Instantiates a new UInt256 from an int array. + * Instantiates a new UInt256 from an array. * - *

The array is interpreted in little-endian order. It is either padded with 0s or truncated if - * necessary. + *

Read digits from an array starting from the end. The array must have at least N_LIMBS + * elements. * - * @param arr int array of limbs. - * @return The UInt256 equivalent of value. + * @param limbs The array holding the digits. + * @return The UInt256 from the array */ - public static UInt256 fromArray(final int[] arr) { - int[] limbs = new int[N_LIMBS]; - int len = Math.min(N_LIMBS, arr.length); - System.arraycopy(arr, 0, limbs, 0, len); - return new UInt256(limbs, len); + public static UInt256 fromArray(final long[] limbs) { + int i = limbs.length; + long z0 = limbs[--i]; + long z1 = limbs[--i]; + long z2 = limbs[--i]; + long z3 = limbs[--i]; + return new UInt256(z3, z2, z1, z0); } // -------------------------------------------------------------------------- @@ -193,7 +176,7 @@ public static UInt256 fromArray(final int[] arr) { * @return Value truncated to an int, possibly lossy. */ public int intValue() { - return limbs[0]; + return (int) u0; } /** @@ -202,7 +185,7 @@ public int intValue() { * @return Value truncated to a long, possibly lossy. */ public long longValue() { - return (limbs[0] & MASK_L) | ((limbs[1] & MASK_L) << 32); + return u0; } /** @@ -211,11 +194,24 @@ public long longValue() { * @return Big-endian ordered bytes for this UInt256 value. */ public byte[] toBytesBE() { - ByteBuffer buf = ByteBuffer.allocate(BYTESIZE).order(ByteOrder.BIG_ENDIAN); - for (int i = N_LIMBS - 1; i >= 0; i--) { - buf.putInt(limbs[i]); - } - return buf.array(); + byte[] result = new byte[BYTESIZE]; + longIntoBytes(result, 0, u3); + longIntoBytes(result, 8, u2); + longIntoBytes(result, 16, u1); + longIntoBytes(result, 24, u0); + return result; + } + + // Helper method to write 8 bytes from big-endian int + private static void longIntoBytes(final byte[] bytes, final int offset, final long value) { + bytes[offset] = (byte) (value >>> 56); + bytes[offset + 1] = (byte) (value >>> 48); + bytes[offset + 2] = (byte) (value >>> 40); + bytes[offset + 3] = (byte) (value >>> 32); + bytes[offset + 4] = (byte) (value >>> 24); + bytes[offset + 5] = (byte) (value >>> 16); + bytes[offset + 6] = (byte) (value >>> 8); + bytes[offset + 7] = (byte) value; } /** @@ -227,8 +223,14 @@ public BigInteger toBigInteger() { return new BigInteger(1, toBytesBE()); } - @Override - public String toString() { + /** + * Convert to hexstring. + * + *

Convert this integer into big-endian hexstring representation. + * + * @return The hexstring representing the integer. + */ + public String toHexString() { StringBuilder sb = new StringBuilder("0x"); for (byte b : toBytesBE()) { sb.append(String.format("%02x", b)); @@ -236,6 +238,42 @@ public String toString() { return sb.toString(); } + /** + * Fills an array with digits. + * + *

Fills an array with the integer's digits starting from the end. The array must have at least + * N_LIMBS elements. + * + * @param limbs The array to fill + */ + public void intoArray(final long[] limbs) { + int len = limbs.length; + limbs[len--] = u0; + limbs[len--] = u1; + limbs[len--] = u2; + limbs[len--] = u3; + } + + private UInt320 UInt320Value() { + return new UInt320(0, u3, u2, u1, u0); + } + + private Modulus64 asModulus64() { + return new Modulus64(u0); + } + + private Modulus128 asModulus128() { + return new Modulus128(u1, u0); + } + + private Modulus192 asModulus192() { + return new Modulus192(u2, u1, u0); + } + + private Modulus256 asModulus256() { + return new Modulus256(u3, u2, u1, u0); + } + // -------------------------------------------------------------------------- // endregion @@ -248,8 +286,61 @@ public String toString() { * @return true if this UInt256 value is 0. */ public boolean isZero() { - return (limbs[0] | limbs[1] | limbs[2] | limbs[3] | limbs[4] | limbs[5] | limbs[6] | limbs[7]) - == 0; + return (u0 | u1 | u2 | u3) == 0; + } + + /** + * Is the value 1 ? + * + * @return true if this UInt256 value is 1. + */ + public boolean isOne() { + return ((u0 ^ 1L) | u1 | u2 | u3) == 0; + } + + /** + * Is the value 0 or 1 ? + * + * @return true if this UInt256 value is 1. + */ + public boolean isZeroOrOne() { + return ((u0 & -2L) | u1 | u2 | u3) == 0; + } + + /** + * Is the two complements signed representation of this integer negative. + * + * @return True if the two complements representation of this integer is negative. + */ + public boolean isNegative() { + return u3 < 0; + } + + /** + * Does the value fit a long. + * + * @return true if it has at most 1 effective digit. + */ + public boolean isUInt64() { + return (u1 | u2 | u3) == 0; + } + + /** + * Does the value fit 2 longs. + * + * @return true if it has at most 2 effective digits. + */ + public boolean isUInt128() { + return (u2 | u3) == 0; + } + + /** + * Does the value fit 3 longs. + * + * @return true if it has at most 3 effective digits. + */ + public boolean isUInt192() { + return u3 == 0; } /** @@ -260,39 +351,171 @@ public boolean isZero() { * @return 0 if a == b, negative if a < b and positive if a > b. */ public static int compare(final UInt256 a, final UInt256 b) { - int comp; - for (int i = N_LIMBS - 1; i >= 0; i--) { - comp = Integer.compareUnsigned(a.limbs[i], b.limbs[i]); - if (comp != 0) return comp; - } - return 0; + if (a.u3 != b.u3) return Long.compareUnsigned(a.u3, b.u3); + if (a.u2 != b.u2) return Long.compareUnsigned(a.u2, b.u2); + if (a.u1 != b.u1) return Long.compareUnsigned(a.u1, b.u1); + return Long.compareUnsigned(a.u0, b.u0); } - @Override - public boolean equals(final Object obj) { - if (this == obj) return true; - if (!(obj instanceof UInt256)) return false; - UInt256 other = (UInt256) obj; + // -------------------------------------------------------------------------- + // endregion - int xor = - (this.limbs[0] ^ other.limbs[0]) - | (this.limbs[1] ^ other.limbs[1]) - | (this.limbs[2] ^ other.limbs[2]) - | (this.limbs[3] ^ other.limbs[3]) - | (this.limbs[4] ^ other.limbs[4]) - | (this.limbs[5] ^ other.limbs[5]) - | (this.limbs[6] ^ other.limbs[6]) - | (this.limbs[7] ^ other.limbs[7]); - return xor == 0; + // region Bitwise Operations + // -------------------------------------------------------------------------- + + /** + * Bitwise AND operation + * + * @param other The UInt256 to AND with this. + * @return The UInt256 result from the bitwise AND operation + */ + public UInt256 and(final UInt256 other) { + return new UInt256(u3 & other.u3, u2 & other.u2, u1 & other.u1, u0 & other.u0); } - @Override - public int hashCode() { - int h = 1; - for (int i = 0; i < N_LIMBS; i++) { - h = 31 * h + limbs[i]; + /** + * Bitwise XOR operation + * + * @param other The UInt256 to XOR with this. + * @return The UInt256 result from the bitwise XOR operation + */ + public UInt256 xor(final UInt256 other) { + return new UInt256(u3 ^ other.u3, u2 ^ other.u2, u1 ^ other.u1, u0 ^ other.u0); + } + + /** + * Bitwise OR operation + * + * @param other The UInt256 to OR with this. + * @return The UInt256 result from the bitwise OR operation + */ + public UInt256 or(final UInt256 other) { + return new UInt256(u3 | other.u3, u2 | other.u2, u1 | other.u1, u0 | other.u0); + } + + /** + * Bitwise NOT operation + * + * @return The UInt256 result from the bitwise NOT operation + */ + public UInt256 not() { + return new UInt256(~u3, ~u2, ~u1, ~u0); + } + + /** + * Bitwise shift left. + * + * @param shift The number of bits to shift left (0-255). + * @return The shifted UInt256. + */ + public UInt256 shiftLeft(final int shift) { + if (shift == 0) return this; + if (shift >= 256) return ZERO; + + // First handle whole-limb shifts + final int limbShift = shift >>> 6; // shift / 64 + final int bitShift = shift & 63; // shift % 64 + + // Shift limbs + long a0, a1, a2, a3; + switch (limbShift) { + case 0: + a0 = u0; + a1 = u1; + a2 = u2; + a3 = u3; + break; + case 1: + a0 = 0; + a1 = u0; + a2 = u1; + a3 = u2; + break; + case 2: + a0 = 0; + a1 = 0; + a2 = u0; + a3 = u1; + break; + case 3: + a0 = 0; + a1 = 0; + a2 = 0; + a3 = u0; + break; + default: + return ZERO; + } + + if (bitShift == 0) { + return new UInt256(a3, a2, a1, a0); + } + + // Then handle sub-limb bit shifts + int invShift = 64 - bitShift; + long z0 = (a0 << bitShift); + long z1 = (a1 << bitShift) | (a0 >>> invShift); + long z2 = (a2 << bitShift) | (a1 >>> invShift); + long z3 = (a3 << bitShift) | (a2 >>> invShift); + return new UInt256(z3, z2, z1, z0); + } + + /** + * Bitwise shift right. + * + * @param shift The number of bits to shift right (0-255). + * @return The shifted UInt256. + */ + public UInt256 shiftRight(final int shift) { + if (shift == 0) return this; + if (shift >= 256) return ZERO; + + // First handle whole-limb shifts + final int limbShift = shift >>> 6; // shift / 64 + final int bitShift = shift & 63; // shift % 64 + + // Shift limbs + long a0, a1, a2, a3; + switch (limbShift) { + case 0: + a0 = u0; + a1 = u1; + a2 = u2; + a3 = u3; + break; + case 1: + a0 = u1; + a1 = u2; + a2 = u3; + a3 = 0; + break; + case 2: + a0 = u2; + a1 = u3; + a2 = 0; + a3 = 0; + break; + case 3: + a0 = u3; + a1 = 0; + a2 = 0; + a3 = 0; + break; + default: + return ZERO; + } + + if (bitShift == 0) { + return new UInt256(a3, a2, a1, a0); } - return h; + + // Then handle sub-limb bit shifts + int invShift = 64 - bitShift; + long z3 = (a3 >>> bitShift); + long z2 = (a2 >>> bitShift) | (a3 << invShift); + long z1 = (a1 >>> bitShift) | (a2 << invShift); + long z0 = (a0 >>> bitShift) | (a1 << invShift); + return new UInt256(z3, z2, z1, z0); } // -------------------------------------------------------------------------- @@ -301,15 +524,76 @@ public int hashCode() { // region Arithmetic Operations // -------------------------------------------------------------------------- + /** + * Compute the two-complement negative representation of this integer. + * + * @return The negative of this integer. + */ + public UInt256 neg() { + long carry = 1; + long z0 = ~u0 + carry; + carry = (z0 == 0 && carry == 1) ? 1 : 0; + long z1 = ~u1 + carry; + carry = (z1 == 0 && carry == 1) ? 1 : 0; + long z2 = ~u2 + carry; + carry = (z2 == 0 && carry == 1) ? 1 : 0; + long z3 = ~u3 + carry; + return new UInt256(z3, z2, z1, z0); + } + + /** + * Compute the absolute value for a two-complement negative representation of this integer. + * + * @return The absolute value of this integer. + */ + public UInt256 abs() { + return isNegative() ? neg() : this; + } + + /** + * Addition + * + *

Compute the wrapping sum of 2 256-bits integers. + * + * @param other Integer to add to this integer. + * @return The sum. + */ + public UInt256 add(final UInt256 other) { + if (isZero()) return other; + if (other.isZero()) return this; + return adc(other).UInt256Value(); + } + + /** + * Multiplication + * + *

Compute the wrapping product of 2 256-bits integers. + * + * @param other Integer to multiply with this integer. + * @return The product. + */ + public UInt256 mul(final UInt256 other) { + if (isZero() || other.isZero()) return ZERO; + if (other.isOne()) return this; + if (this.isOne()) return other; + return mul256(other).UInt256Value(); + } + /** * Unsigned modulo reduction. * - * @param modulus The modulus of the reduction - * @return The remainder modulo {@code modulus}. + *

Compute dividend (mod modulus) as unsigned big-endian integer. + * + * @param modulus The modulus of the reduction. + * @return The remainder. */ public UInt256 mod(final UInt256 modulus) { - if (this.isZero() || modulus.isZero()) return ZERO; - return new UInt256(knuthRemainder(this.limbs, modulus.limbs), modulus.length); + if (isZero()) return ZERO; + if (modulus.u3 != 0) return modulus.asModulus256().reduce(this); + if (modulus.u2 != 0) return modulus.asModulus192().reduce(this); + if (modulus.u1 != 0) return modulus.asModulus128().reduce(this); + if ((modulus.u0 == 0) || (modulus.u0 == 1)) return ZERO; + return modulus.asModulus64().reduce(this); } /** @@ -322,17 +606,12 @@ public UInt256 mod(final UInt256 modulus) { * @return The remainder modulo {@code modulus}. */ public UInt256 signedMod(final UInt256 modulus) { - if (this.isZero() || modulus.isZero()) return ZERO; - int[] x = new int[N_LIMBS]; - int[] y = new int[N_LIMBS]; - absInto(x, this.limbs, N_LIMBS); - absInto(y, modulus.limbs, N_LIMBS); - int[] r = knuthRemainder(x, y); - if (isNeg(this.limbs, N_LIMBS)) { - negate(r, N_LIMBS); - return new UInt256(r); - } - return new UInt256(r, modulus.length); + if (isZero() || modulus.isZeroOrOne() || modulus.equals(MAX)) return ZERO; + UInt256 a = abs(); + UInt256 m = modulus.abs(); + UInt256 r = a.mod(m); + if (isNegative()) r = r.neg(); + return r; } /** @@ -343,10 +622,13 @@ public UInt256 signedMod(final UInt256 modulus) { * @return This integer this + other (mod modulus). */ public UInt256 addMod(final UInt256 other, final UInt256 modulus) { - if (modulus.isZero()) return ZERO; - int[] sum = addWithCarry(this.limbs, this.length, other.limbs, other.length); - int[] rem = knuthRemainder(sum, modulus.limbs); - return new UInt256(rem, modulus.length); + if (isZero()) return other.mod(modulus); + if (other.isZero()) return this.mod(modulus); + if (modulus.isZeroOrOne()) return ZERO; + if (modulus.u3 != 0) return modulus.asModulus256().sum(this, other); + if (modulus.u2 != 0) return modulus.asModulus192().sum(this, other); + if (modulus.u1 != 0) return modulus.asModulus128().sum(this, other); + return modulus.asModulus64().sum(this, other); } /** @@ -357,365 +639,1708 @@ public UInt256 addMod(final UInt256 other, final UInt256 modulus) { * @return This integer this + other (mod modulus). */ public UInt256 mulMod(final UInt256 other, final UInt256 modulus) { - if (this.isZero() || other.isZero() || modulus.isZero()) return ZERO; - int[] result = addMul(this.limbs, this.length, other.limbs, other.length); - result = knuthRemainder(result, modulus.limbs); - return new UInt256(result, modulus.length); + if (this.isZero() || other.isZero() || modulus.isZeroOrOne()) return ZERO; + if (this.isOne()) return other.mod(modulus); + if (other.isOne()) return this.mod(modulus); + if (modulus.u3 != 0) return modulus.asModulus256().mul(this, other); + if (modulus.u2 != 0) return modulus.asModulus192().mul(this, other); + if (modulus.u1 != 0) return modulus.asModulus128().mul(this, other); + return modulus.asModulus64().mul(this, other); } + /** The constant 1. */ + public static final UInt256 ONE = new UInt256(0, 0, 0, 1); + /** - * Bitwise AND operation + * Subtraction * - * @param other The UInt256 to AND with this. - * @return The UInt256 result from the bitwise AND operation + *

Compute the wrapping difference of 2 256-bits integers. + * + * @param other Integer to subtract from this integer. + * @return The difference (this - other), wrapping on underflow. */ - public UInt256 and(final UInt256 other) { - int[] result = new int[N_LIMBS]; - result[0] = this.limbs[0] & other.limbs[0]; - result[1] = this.limbs[1] & other.limbs[1]; - result[2] = this.limbs[2] & other.limbs[2]; - result[3] = this.limbs[3] & other.limbs[3]; - result[4] = this.limbs[4] & other.limbs[4]; - result[5] = this.limbs[5] & other.limbs[5]; - result[6] = this.limbs[6] & other.limbs[6]; - result[7] = this.limbs[7] & other.limbs[7]; - return new UInt256(result, N_LIMBS); + public UInt256 sub(final UInt256 other) { + if (other.isZero()) return this; + return this.add(other.neg()); } /** - * Bitwise XOR operation + * Unsigned division. * - * @param other The UInt256 to XOR with this. - * @return The UInt256 result from the bitwise XOR operation + *

Compute the quotient of this / divisor as unsigned integers. + * + * @param divisor The divisor. + * @return The quotient. */ - public UInt256 xor(final UInt256 other) { - int[] result = new int[N_LIMBS]; - result[0] = this.limbs[0] ^ other.limbs[0]; - result[1] = this.limbs[1] ^ other.limbs[1]; - result[2] = this.limbs[2] ^ other.limbs[2]; - result[3] = this.limbs[3] ^ other.limbs[3]; - result[4] = this.limbs[4] ^ other.limbs[4]; - result[5] = this.limbs[5] ^ other.limbs[5]; - result[6] = this.limbs[6] ^ other.limbs[6]; - result[7] = this.limbs[7] ^ other.limbs[7]; - return new UInt256(result, N_LIMBS); + public UInt256 div(final UInt256 divisor) { + if (divisor.isZero()) return ZERO; + if (divisor.isOne()) return this; + if (this.isZero()) return ZERO; + int cmp = compare(this, divisor); + if (cmp < 0) return ZERO; + if (cmp == 0) return ONE; + // Single-limb divisor fast path + if (divisor.u3 == 0 && divisor.u2 == 0 && divisor.u1 == 0) { + return divBy1(this, divisor.u0); + } + // General multi-limb: Knuth Algorithm D + return divGeneral(this, divisor); + } + + private static UInt256 divBy1(final UInt256 dividend, final long divisor) { + int shift = Long.numberOfLeadingZeros(divisor); + long nd = divisor << shift; + long yInv = reciprocal(nd); + + // Normalize dividend (shift left by 'shift' bits) + long u0, u1, u2, u3, u4; + if (shift == 0) { + u0 = dividend.u0; + u1 = dividend.u1; + u2 = dividend.u2; + u3 = dividend.u3; + u4 = 0; + } else { + int invShift = 64 - shift; + u0 = dividend.u0 << shift; + u1 = (dividend.u1 << shift) | (dividend.u0 >>> invShift); + u2 = (dividend.u2 << shift) | (dividend.u1 >>> invShift); + u3 = (dividend.u3 << shift) | (dividend.u2 >>> invShift); + u4 = dividend.u3 >>> invShift; + } + + // Divide from MSB to LSB, threading remainder + DivEstimate qr; + qr = div2by1(u4, u3, nd, yInv); + long q3 = qr.q; + qr = div2by1(qr.r, u2, nd, yInv); + long q2 = qr.q; + qr = div2by1(qr.r, u1, nd, yInv); + long q1 = qr.q; + qr = div2by1(qr.r, u0, nd, yInv); + long q0 = qr.q; + + return new UInt256(q3, q2, q1, q0); + } + + private static UInt256 divGeneral(final UInt256 dividend, final UInt256 divisor) { + // Determine n (number of significant divisor limbs, >= 2) + int n; + if (divisor.u3 != 0) n = 4; + else if (divisor.u2 != 0) n = 3; + else n = 2; + + int m = 4 - n; // number of extra quotient limbs (loop produces m+1 digits) + + // D1: Normalize — shift so MSB of top divisor limb is set + long topDivisor = (n == 4) ? divisor.u3 : (n == 3) ? divisor.u2 : divisor.u1; + int shift = Long.numberOfLeadingZeros(topDivisor); + + // Normalized divisor (little-endian: dn[0] = LSB) + long[] dn = new long[n]; + dn[0] = divisor.u0; + if (n >= 2) dn[1] = divisor.u1; + if (n >= 3) dn[2] = divisor.u2; + if (n >= 4) dn[3] = divisor.u3; + + // Normalized dividend (5 limbs: un[0] = LSB, un[4] = overflow from shift) + long[] un = new long[5]; + un[0] = dividend.u0; + un[1] = dividend.u1; + un[2] = dividend.u2; + un[3] = dividend.u3; + + if (shift > 0) { + int invShift = 64 - shift; + // Shift divisor + for (int i = n - 1; i > 0; i--) { + dn[i] = (dn[i] << shift) | (dn[i - 1] >>> invShift); + } + dn[0] <<= shift; + // Shift dividend + un[4] = un[3] >>> invShift; + for (int i = 3; i > 0; i--) { + un[i] = (un[i] << shift) | (un[i - 1] >>> invShift); + } + un[0] <<= shift; + } + + long yInv = reciprocal(dn[n - 1]); + long q0 = 0, q1 = 0, q2 = 0; + + // D2-D7: Main loop — one quotient digit per iteration + for (int j = m; j >= 0; j--) { + // D3: Trial division with Knuth refinement + long qhat; + long rhat; + if (un[j + n] == dn[n - 1]) { + qhat = -1L; // 0xFFFFFFFFFFFFFFFF + rhat = un[j + n - 1] + dn[n - 1]; + // If rhat overflowed, skip refinement (qhat is already at most 1 too large) + if (Long.compareUnsigned(rhat, dn[n - 1]) < 0) { + rhat = -1L; // signal overflow: skip refinement loop + } + } else { + DivEstimate est = div2by1(un[j + n], un[j + n - 1], dn[n - 1], yInv); + qhat = est.q; + rhat = est.r; + } + // Knuth D3 refinement: reduce qhat while qhat * dn[n-2] > (rhat, un[j+n-2]) + // This guarantees qhat is at most 1 too large, so a single add-back suffices. + if (rhat != -1L) { + long dn2 = (n >= 2) ? dn[n - 2] : 0; + long unJN2 = un[j + n - 2]; + // Compare 128-bit: (qhat * dn2) vs (rhat, unJN2) + long prodHi = Math.unsignedMultiplyHigh(qhat, dn2); + long prodLo = qhat * dn2; + while (Long.compareUnsigned(prodHi, rhat) > 0 + || (prodHi == rhat && Long.compareUnsigned(prodLo, unJN2) > 0)) { + qhat--; + rhat += dn[n - 1]; + // If rhat overflowed past 64 bits, stop + if (Long.compareUnsigned(rhat, dn[n - 1]) < 0) break; + prodHi = Math.unsignedMultiplyHigh(qhat, dn2); + prodLo = qhat * dn2; + } + } + + // D4: Multiply and subtract — un[j..j+n] -= qhat * dn[0..n-1] + // Use separate carry (mul chain, up to 2^64-1) and borrow (sub chain, 0 or 1) + // to avoid overflow when both reach their maximums simultaneously. + long carry = 0; + long borrow = 0; + for (int i = 0; i < n; i++) { + long prodLo = qhat * dn[i]; + long prodHi = Math.unsignedMultiplyHigh(qhat, dn[i]); + // Add multiplication carry from previous iteration + long prevLo = prodLo; + prodLo += carry; + if (Long.compareUnsigned(prodLo, prevLo) < 0) prodHi++; + carry = prodHi; + // Subtract prodLo and borrow from dividend + long prev = un[j + i]; + long diff = prev - prodLo; + long b1 = (Long.compareUnsigned(diff, prev) > 0) ? 1L : 0L; + un[j + i] = diff - borrow; + long b2 = (Long.compareUnsigned(un[j + i], diff) > 0) ? 1L : 0L; + borrow = b1 + b2; + } + // Apply carry and borrow to top word; detect over-subtraction without overflow + // Over-subtracted when carry + borrow > prevTop (as true integers, up to 2^64) + long prevTop = un[j + n]; + un[j + n] = prevTop - carry - borrow; + boolean overSubtracted = + (borrow == 0) + ? Long.compareUnsigned(carry, prevTop) > 0 + : Long.compareUnsigned(carry, prevTop) >= 0; + + // D5-D6: If over-subtracted, add back (rare: probability < 2/B) + if (overSubtracted) { + qhat--; + long addCarry = 0; + for (int i = 0; i < n; i++) { + long prev = un[j + i]; + un[j + i] = prev + dn[i] + addCarry; + if (addCarry == 0) { + addCarry = (Long.compareUnsigned(un[j + i], prev) < 0) ? 1L : 0L; + } else { + addCarry = (Long.compareUnsigned(un[j + i], prev) <= 0) ? 1L : 0L; + } + } + un[j + n] += addCarry; + } + + // D7: Store quotient digit + switch (j) { + case 0: + q0 = qhat; + break; + case 1: + q1 = qhat; + break; + case 2: + q2 = qhat; + break; + default: + break; + } + } + + return new UInt256(0, q2, q1, q0); } /** - * Bitwise OR operation + * Signed division. * - * @param other The UInt256 to OR with this. - * @return The UInt256 result from the bitwise OR operation + *

Compute the quotient of this / divisor as signed two's complement integers. + * + * @param divisor The divisor. + * @return The signed quotient. */ - public UInt256 or(final UInt256 other) { - int[] result = new int[N_LIMBS]; - result[0] = this.limbs[0] | other.limbs[0]; - result[1] = this.limbs[1] | other.limbs[1]; - result[2] = this.limbs[2] | other.limbs[2]; - result[3] = this.limbs[3] | other.limbs[3]; - result[4] = this.limbs[4] | other.limbs[4]; - result[5] = this.limbs[5] | other.limbs[5]; - result[6] = this.limbs[6] | other.limbs[6]; - result[7] = this.limbs[7] | other.limbs[7]; - return new UInt256(result, N_LIMBS); + public UInt256 signedDiv(final UInt256 divisor) { + if (divisor.isZero()) return ZERO; + boolean thisNeg = this.isNegative(); + boolean otherNeg = divisor.isNegative(); + UInt256 absThis = thisNeg ? this.neg() : this; + UInt256 absOther = otherNeg ? divisor.neg() : divisor; + UInt256 quotient = absThis.div(absOther); + return (thisNeg != otherNeg) ? quotient.neg() : quotient; } /** - * Bitwise NOT operation + * Signed comparison. * - * @return The UInt256 result from the bitwise NOT operation + *

Compare two UInt256 values as signed two's complement integers. + * + * @param a left UInt256 + * @param b right UInt256 + * @return negative if a < b, 0 if a == b, positive if a > b (signed). */ - public UInt256 not() { - int[] result = new int[N_LIMBS]; - result[0] = ~this.limbs[0]; - result[1] = ~this.limbs[1]; - result[2] = ~this.limbs[2]; - result[3] = ~this.limbs[3]; - result[4] = ~this.limbs[4]; - result[5] = ~this.limbs[5]; - result[6] = ~this.limbs[6]; - result[7] = ~this.limbs[7]; - return new UInt256(result, N_LIMBS); + public static int signedCompare(final UInt256 a, final UInt256 b) { + boolean aNeg = a.isNegative(); + boolean bNeg = b.isNegative(); + if (aNeg && !bNeg) return -1; + if (!aNeg && bNeg) return 1; + // Same sign: unsigned compare gives correct result + return compare(a, b); } - // -------------------------------------------------------------------------- - // endregion + /** + * Full-range shift left (0-255). + * + *

For EVM SHL opcode. Handles shifts across limb boundaries. + * + * @param shift The number of bits to shift left (0-255). + * @return The shifted UInt256. Returns ZERO for shift >= 256. + */ + public UInt256 shl(final int shift) { + if (shift == 0) return this; + if (shift >= 256) return ZERO; + + int limbShift = shift / 64; + int bitShift = shift % 64; + + long z0 = 0, z1 = 0, z2 = 0, z3 = 0; + // Source limbs after limb shift + long s0 = limbShift <= 0 ? u0 : 0; + long s1 = limbShift <= 1 ? (limbShift == 0 ? u1 : limbShift == 1 ? u0 : 0) : 0; + long s2, s3; + switch (limbShift) { + case 0: + s2 = u2; + s3 = u3; + break; + case 1: + s2 = u1; + s3 = u2; + break; + case 2: + s2 = u0; + s3 = u1; + break; + case 3: + s2 = 0; + s3 = u0; + break; + default: + s2 = 0; + s3 = 0; + } + + if (bitShift == 0) { + z0 = s0; + z1 = s1; + z2 = s2; + z3 = s3; + } else { + // Shift each limb and carry from below + long[] src = new long[4]; + switch (limbShift) { + case 0: + src[0] = u0; + src[1] = u1; + src[2] = u2; + src[3] = u3; + break; + case 1: + src[0] = 0; + src[1] = u0; + src[2] = u1; + src[3] = u2; + break; + case 2: + src[0] = 0; + src[1] = 0; + src[2] = u0; + src[3] = u1; + break; + case 3: + src[0] = 0; + src[1] = 0; + src[2] = 0; + src[3] = u0; + break; + default: + break; + } + int invShift = 64 - bitShift; + z0 = src[0] << bitShift; + z1 = (src[1] << bitShift) | (src[0] >>> invShift); + z2 = (src[2] << bitShift) | (src[1] >>> invShift); + z3 = (src[3] << bitShift) | (src[2] >>> invShift); + } + return new UInt256(z3, z2, z1, z0); + } /** - * Simple addition + * Full-range logical shift right (0-255). + * + *

For EVM SHR opcode. Handles shifts across limb boundaries. * - * @param other The UInt256 to add to this. - * @return The UInt256 result from the addition + * @param shift The number of bits to shift right (0-255). + * @return The shifted UInt256. Returns ZERO for shift >= 256. */ - public UInt256 add(final UInt256 other) { - return new UInt256( - addWithCarry(this.limbs, this.limbs.length, other.limbs, other.limbs.length)); + public UInt256 shr(final int shift) { + if (shift == 0) return this; + if (shift >= 256) return ZERO; + + int limbShift = shift / 64; + int bitShift = shift % 64; + + long[] src = {u0, u1, u2, u3}; + long z0 = (limbShift < 4) ? src[limbShift] : 0; + long z1 = (limbShift + 1 < 4) ? src[limbShift + 1] : 0; + long z2 = (limbShift + 2 < 4) ? src[limbShift + 2] : 0; + long z3 = (limbShift + 3 < 4) ? src[limbShift + 3] : 0; + + if (bitShift == 0) { + return new UInt256(z3, z2, z1, z0); + } + + int invShift = 64 - bitShift; + long r0 = (z0 >>> bitShift) | (z1 << invShift); + long r1 = (z1 >>> bitShift) | (z2 << invShift); + long r2 = (z2 >>> bitShift) | (z3 << invShift); + long r3 = (z3 >>> bitShift); + return new UInt256(r3, r2, r1, r0); } - // region Support (private) Algorithms - // -------------------------------------------------------------------------- - private static int nSetLimbs(final int[] x) { - int offset = x.length - 1; - while ((offset >= 0) && (x[offset] == 0)) offset--; - return offset + 1; - } - - private static int compareLimbs(final int[] a, final int aLen, final int[] b, final int bLen) { - int cmp; - if (aLen > bLen) { - for (int i = aLen - 1; i >= bLen; i--) { - cmp = Integer.compareUnsigned(a[i], 0); - if (cmp != 0) return cmp; - } - } else if (aLen < bLen) { - for (int i = bLen - 1; i >= aLen; i--) { - cmp = Integer.compareUnsigned(0, b[i]); - if (cmp != 0) return cmp; - } + /** + * Full-range arithmetic shift right (0-255). + * + *

For EVM SAR opcode. Sign-extends from the left. + * + * @param shift The number of bits to shift right (0-255). + * @return The arithmetically shifted UInt256. + */ + public UInt256 sar(final int shift) { + if (shift == 0) return this; + boolean negative = isNegative(); + if (shift >= 256) return negative ? MAX : ZERO; + + int limbShift = shift / 64; + int bitShift = shift % 64; + + long fill = negative ? -1L : 0L; + long[] src = {u0, u1, u2, u3}; + long z0 = (limbShift < 4) ? src[limbShift] : fill; + long z1 = (limbShift + 1 < 4) ? src[limbShift + 1] : fill; + long z2 = (limbShift + 2 < 4) ? src[limbShift + 2] : fill; + long z3 = (limbShift + 3 < 4) ? src[limbShift + 3] : fill; + + if (bitShift == 0) { + return new UInt256(z3, z2, z1, z0); } - for (int i = Math.min(aLen, bLen) - 1; i >= 0; i--) { - cmp = Integer.compareUnsigned(a[i], b[i]); - if (cmp != 0) return cmp; + + int invShift = 64 - bitShift; + long r0 = (z0 >>> bitShift) | (z1 << invShift); + long r1 = (z1 >>> bitShift) | (z2 << invShift); + long r2 = (z2 >>> bitShift) | (z3 << invShift); + long r3 = (z3 >> bitShift); // Arithmetic shift for top limb + return new UInt256(r3, r2, r1, r0); + } + + /** + * Convert from BigInteger. + * + * @param bi BigInteger to convert. + * @return The UInt256 equivalent. + */ + public static UInt256 fromBigInteger(final BigInteger bi) { + return fromBytesBE(toUnsigned32Bytes(bi)); + } + + /** + * Convert BigInteger to unsigned 32-byte big-endian array. + * + * @param bi the BigInteger value + * @return 32-byte array, zero-padded or truncated + */ + private static byte[] toUnsigned32Bytes(final BigInteger bi) { + byte[] raw = bi.toByteArray(); + if (raw.length == 32) return raw; + byte[] result = new byte[32]; + if (raw.length > 32) { + System.arraycopy(raw, raw.length - 32, result, 0, 32); + } else { + if (bi.signum() < 0) { + java.util.Arrays.fill(result, 0, 32 - raw.length, (byte) 0xFF); + } + System.arraycopy(raw, 0, result, 32 - raw.length, raw.length); } + return result; + } + + /** + * Number of significant bytes needed to represent this value. + * + * @return the number of bytes (0 for ZERO, up to 32 for MAX). + */ + public int byteLength() { + if (u3 != 0) return 24 + byteLen(u3); + if (u2 != 0) return 16 + byteLen(u2); + if (u1 != 0) return 8 + byteLen(u1); + if (u0 != 0) return byteLen(u0); return 0; } - private static boolean isNeg(final int[] x, final int xLen) { - return x[xLen - 1] < 0; + private static int byteLen(final long v) { + return (64 - Long.numberOfLeadingZeros(v) + 7) / 8; + } + + /** + * Number of significant bits. + * + * @return the number of bits (0 for ZERO). + */ + public int bitLength() { + if (u3 != 0) return 192 + (64 - Long.numberOfLeadingZeros(u3)); + if (u2 != 0) return 128 + (64 - Long.numberOfLeadingZeros(u2)); + if (u1 != 0) return 64 + (64 - Long.numberOfLeadingZeros(u1)); + if (u0 != 0) return 64 - Long.numberOfLeadingZeros(u0); + return 0; } - private static void negate(final int[] x, final int xLen) { - int carry = 1; - for (int i = 0; i < xLen; i++) { - x[i] = ~x[i] + carry; - carry = (x[i] == 0 && carry == 1) ? 1 : 0; + // -------------------------------------------------------------------------- + // endregion + + // region private basic operations + // + // adc (add and carry): carry, a <- a + b + // mac (multiply accumulate): a <- a + b * c + carryIn + // -------------------------------------------------------------------------- + + private UInt320 shiftLeftWide(final int shift) { + if (shift == 0) return UInt320Value(); + int invShift = (N_BITS_PER_LIMB - shift); + long z0 = (u0 << shift); + long z1 = (u1 << shift) | u0 >>> invShift; + long z2 = (u2 << shift) | u1 >>> invShift; + long z3 = (u3 << shift) | u2 >>> invShift; + long z4 = u3 >>> invShift; + return new UInt320(z4, z3, z2, z1, z0); + } + + private UInt256 shiftDigitsRight() { + return new UInt256(0, u3, u2, u1); + } + + private UInt257 adc(final UInt256 other) { + boolean carry; + if (isZero()) return new UInt257(false, other); + if (other.isZero()) return new UInt257(false, this); + long z0 = u0 + other.u0; + carry = Long.compareUnsigned(z0, u0) < 0; + long z1 = u1 + other.u1 + (carry ? 1L : 0L); + carry = Long.compareUnsigned(z1, u1) < 0 || (z1 == u1 && carry); + long z2 = u2 + other.u2 + (carry ? 1L : 0L); + carry = Long.compareUnsigned(z2, u2) < 0 || (z2 == u2 && carry); + long z3 = u3 + other.u3 + (carry ? 1L : 0L); + carry = Long.compareUnsigned(z3, u3) < 0 || (z3 == u3 && carry); + return new UInt257(carry, new UInt256(z3, z2, z1, z0)); + } + + private UInt256 mac128(final long multiplier, final UInt256 carryIn) { + // Multiply accumulate for 128bits integer (this): + // Returns this * multiplier + (carryIn >>> 64) + long hi, lo, carry; + if (multiplier == 0) return carryIn.shiftDigitsRight(); + + lo = u0 * multiplier; + hi = Math.unsignedMultiplyHigh(u0, multiplier); + long z0 = lo + carryIn.u1; + hi += (Long.compareUnsigned(z0, lo) < 0) ? 1 : 0; + + long z1 = hi + carryIn.u2; + carry = (Long.compareUnsigned(z1, hi) < 0) ? 1 : 0; + lo = u1 * multiplier; + hi = Math.unsignedMultiplyHigh(u1, multiplier); + z1 += lo; + hi += (Long.compareUnsigned(z1, lo) < 0) ? carry + 1 : carry; + + return new UInt256(0, hi, z1, z0); + } + + private UInt256 mac192(final long multiplier, final UInt256 carryIn) { + // Multiply accumulate for 192bits integer (this): + // Returns this * multiplier + (carryIn >>> 64) + long hi, lo, carry; + if (multiplier == 0) return carryIn.shiftDigitsRight(); + + lo = u0 * multiplier; + hi = Math.unsignedMultiplyHigh(u0, multiplier); + long z0 = lo + carryIn.u1; + hi += (Long.compareUnsigned(z0, lo) < 0) ? 1 : 0; + + long z1 = hi + carryIn.u2; + carry = (Long.compareUnsigned(z1, hi) < 0) ? 1 : 0; + lo = u1 * multiplier; + hi = Math.unsignedMultiplyHigh(u1, multiplier); + z1 += lo; + hi += (Long.compareUnsigned(z1, lo) < 0) ? carry + 1 : carry; + + long z2 = hi + carryIn.u3; + carry = (Long.compareUnsigned(z2, hi) < 0) ? 1 : 0; + lo = u2 * multiplier; + hi = Math.unsignedMultiplyHigh(u2, multiplier); + z2 += lo; + hi += (Long.compareUnsigned(z2, lo) < 0) ? carry + 1 : carry; + + return new UInt256(hi, z2, z1, z0); + } + + private UInt320 mac256(final long multiplier, final UInt320 carryIn) { + // Multiply accumulate for 192bits integer (this): + // Returns this * multiplier + carryIn + long hi, lo, carry; + if (multiplier == 0) return carryIn.shiftDigitsRight(); + + lo = u0 * multiplier; + hi = Math.unsignedMultiplyHigh(u0, multiplier); + long z0 = lo + carryIn.u1; + hi += (Long.compareUnsigned(z0, lo) < 0) ? 1 : 0; + carry = 0; + + long z1 = hi + carryIn.u2; + carry = (Long.compareUnsigned(z1, hi) < 0) ? 1 : 0; + lo = u1 * multiplier; + hi = Math.unsignedMultiplyHigh(u1, multiplier); + z1 += lo; + hi += (Long.compareUnsigned(z1, lo) < 0) ? carry + 1 : carry; + + long z2 = hi + carryIn.u3; + carry = (Long.compareUnsigned(z2, hi) < 0) ? 1 : 0; + lo = u2 * multiplier; + hi = Math.unsignedMultiplyHigh(u2, multiplier); + z2 += lo; + hi += (Long.compareUnsigned(z2, lo) < 0) ? carry + 1 : carry; + + long z3 = hi + carryIn.u4; + carry = (Long.compareUnsigned(z3, hi) < 0) ? 1 : 0; + lo = u3 * multiplier; + hi = Math.unsignedMultiplyHigh(u3, multiplier); + z3 += lo; + hi += (Long.compareUnsigned(z3, lo) < 0) ? carry + 1 : carry; + + return new UInt320(hi, z3, z2, z1, z0); + } + + // -------------------------------------------------------------------------- + // endregion + + // region private multiplication + // -------------------------------------------------------------------------- + + private UInt256 mul64(final UInt256 v) { + long lo = u0 * v.u0; + long hi = Math.unsignedMultiplyHigh(u0, v.u0); + return new UInt256(0, 0, hi, lo); + } + + private UInt256 mul128(final UInt256 v) { + long z0; + UInt256 res; + + res = mac128(v.u0, ZERO); + z0 = res.u0; + res = mac128(v.u1, res); + + return new UInt256(res.u2, res.u1, res.u0, z0); + } + + private UInt512 mul192(final UInt256 v) { + UInt256 res; + res = mac192(v.u0, ZERO); + long z0 = res.u0; + res = mac192(v.u1, res); + long z1 = res.u0; + res = mac192(v.u2, res); + + return new UInt512(0, 0, res.u3, res.u2, res.u1, res.u0, z1, z0); + } + + private UInt512 mul256(final UInt256 v) { + UInt320 res; + res = mac256(v.u0, UInt320.ZERO); + long z0 = res.u0; + res = mac256(v.u1, res); + long z1 = res.u0; + res = mac256(v.u2, res); + long z2 = res.u0; + res = mac256(v.u3, res); + return new UInt512(res.u4, res.u3, res.u2, res.u1, res.u0, z2, z1, z0); + } + + // -------------------------------------------------------------------------- + // endregion + + // region private quotient estimation + // -------------------------------------------------------------------------- + + // Lookup table for $\floor{\frac{2^{19} -3 ⋅ 2^8}{d_9 - 256}}$ + private static final short[] LUT = + new short[] { + 2045, 2037, 2029, 2021, 2013, 2005, 1998, 1990, 1983, 1975, 1968, 1960, 1953, 1946, 1938, + 1931, 1924, 1917, 1910, 1903, 1896, 1889, 1883, 1876, 1869, 1863, 1856, 1849, 1843, 1836, + 1830, 1824, 1817, 1811, 1805, 1799, 1792, 1786, 1780, 1774, 1768, 1762, 1756, 1750, 1745, + 1739, 1733, 1727, 1722, 1716, 1710, 1705, 1699, 1694, 1688, 1683, 1677, 1672, 1667, 1661, + 1656, 1651, 1646, 1641, 1636, 1630, 1625, 1620, 1615, 1610, 1605, 1600, 1596, 1591, 1586, + 1581, 1576, 1572, 1567, 1562, 1558, 1553, 1548, 1544, 1539, 1535, 1530, 1526, 1521, 1517, + 1513, 1508, 1504, 1500, 1495, 1491, 1487, 1483, 1478, 1474, 1470, 1466, 1462, 1458, 1454, + 1450, 1446, 1442, 1438, 1434, 1430, 1426, 1422, 1418, 1414, 1411, 1407, 1403, 1399, 1396, + 1392, 1388, 1384, 1381, 1377, 1374, 1370, 1366, 1363, 1359, 1356, 1352, 1349, 1345, 1342, + 1338, 1335, 1332, 1328, 1325, 1322, 1318, 1315, 1312, 1308, 1305, 1302, 1299, 1295, 1292, + 1289, 1286, 1283, 1280, 1276, 1273, 1270, 1267, 1264, 1261, 1258, 1255, 1252, 1249, 1246, + 1243, 1240, 1237, 1234, 1231, 1228, 1226, 1223, 1220, 1217, 1214, 1211, 1209, 1206, 1203, + 1200, 1197, 1195, 1192, 1189, 1187, 1184, 1181, 1179, 1176, 1173, 1171, 1168, 1165, 1163, + 1160, 1158, 1155, 1153, 1150, 1148, 1145, 1143, 1140, 1138, 1135, 1133, 1130, 1128, 1125, + 1123, 1121, 1118, 1116, 1113, 1111, 1109, 1106, 1104, 1102, 1099, 1097, 1095, 1092, 1090, + 1088, 1086, 1083, 1081, 1079, 1077, 1074, 1072, 1070, 1068, 1066, 1064, 1061, 1059, 1057, + 1055, 1053, 1051, 1049, 1047, 1044, 1042, 1040, 1038, 1036, 1034, 1032, 1030, 1028, 1026, + 1024, + }; + + private static long reciprocal(final long x) { + // Unchecked: x >= (1 << 63) + long x0 = x & 1L; + int x9 = (int) (x >>> 55); + long x40 = 1 + (x >>> 24); + long x63 = (x + 1) >>> 1; + long v0 = LUT[x9 - 256] & 0xFFFFL; + long v1 = (v0 << 11) - ((v0 * v0 * x40) >>> 40) - 1; + long v2 = (v1 << 13) + ((v1 * ((1L << 60) - v1 * x40)) >>> 47); + long e = ((v2 >>> 1) & (-x0)) - v2 * x63; + long s = Math.unsignedMultiplyHigh(v2, e); + long v3 = (s >>> 1) + (v2 << 31); + long t0 = v3 * x; + long t1 = Math.unsignedMultiplyHigh(v3, x); + t0 += x; + if (Long.compareUnsigned(t0, x) < 0) t1++; + t1 += x; + long v4 = v3 - t1; + return v4; + } + + // private static long reciprocal2(final long x1, final long x0) { + // // Unchecked: >= (1 << 127) + // long v = reciprocal(x1); + // long p = x1 * v + x0; + // if (Long.compareUnsigned(p, x0) < 0) { + // v--; + // if (Long.compareUnsigned(p, x1) >= 0) { + // v--; + // p -= x1; + // } + // p -= x1; + // } + // long t0 = v * x0; + // long t1 = Math.unsignedMultiplyHigh(v, x0); + // p += t1; + // if (Long.compareUnsigned(p, t1) < 0) { + // v--; + // int cmp = Long.compareUnsigned(p, x1); + // if ((cmp > 0) || ((cmp == 0) && (Long.compareUnsigned(t0, x0) >= 0))) v--; + // } + // return v; + // } + + private static DivEstimate div2by1(final long x1, final long x0, final long y, final long yInv) { + long z1 = x1; + long z0 = x0; + + // wrapping umul z1 * yInv + long q0 = z1 * yInv; + long q1 = Math.unsignedMultiplyHigh(z1, yInv); + + // wrapping uadd + + <1, 0> + long sum = q0 + z0; + long carry = ((q0 & z0) | ((q0 | z0) & ~sum)) >>> 63; + q0 = sum; + q1 += z1 + carry + 1; + + z0 -= q1 * y; + if (Long.compareUnsigned(z0, q0) > 0) { + q1 -= 1; + z0 += y; + } + if (Long.compareUnsigned(z0, y) >= 0) { + q1 += 1; + z0 -= y; } + return new DivEstimate(q1, z0); } - private static void absInplace(final int[] x, final int xLen) { - if (isNeg(x, xLen)) negate(x, xLen); + private static long mod2by1(final long x1, final long x0, final long y, final long yInv) { + long z1 = x1; + long z0 = x0; + // wrapping umul z1 * yInv + long q0 = z1 * yInv; + long q1 = Math.unsignedMultiplyHigh(z1, yInv); + + // wrapping uadd + + <1, 0> + long sum = q0 + z0; + long carry = ((q0 & z0) | ((q0 | z0) & ~sum)) >>> 63; + q0 = sum; + q1 += z1 + carry + 1; + + z0 -= q1 * y; + if (Long.compareUnsigned(z0, q0) > 0) { + q1 -= 1; + z0 += y; + } + if (Long.compareUnsigned(z0, y) >= 0) { + q1 += 1; + z0 -= y; + } + return z0; } - private static void absInto(final int[] dst, final int[] src, final int srcLen) { - System.arraycopy(src, 0, dst, 0, srcLen); - absInplace(dst, dst.length); + // private static Div2Estimate div3by2( + // final long x2, final long x1, final long x0, final long y1, final long y0, final long yInv) + // { + // // divided by . + // // Requires < otherwise quotient overflows. + // long overflow; // carry or borrow + // long res; // sum or diff + // long z2 = x2; + // long z1 = x1; + // long z0 = x0; + + // // = z2 * yInv + + // long q0 = z2 * yInv; + // long q1 = Math.unsignedMultiplyHigh(z2, yInv); + // res = q0 + z1; + // overflow = ((q0 & z1) | ((q0 | z1) & ~res)) >>> 63; + // q0 = res; + // q1 = q1 + z2 + overflow; + + // // r1 <- z1 - q1 * y1 mod B + // z1 -= q1 * y1; + + // // wrapping sub − q1*y0 − + // long t0 = q1 * y0; + // long t1 = Math.unsignedMultiplyHigh(q1, y0); + + // res = z0 - t0; + // overflow = ((~z0 & t0) | ((~z0 | t0) & res)) >>> 63; + // z0 = res; + // z1 -= (t1 + overflow); + + // res = z0 - y0; + // overflow = ((~z0 & y0) | ((~z0 | y0) & res)) >>> 63; + // z0 = res; + // z1 -= (y1 + overflow); + + // // Adjustments + // q1 += 1; + // if (Long.compareUnsigned(z1, q0) >= 0) { + // q1 -= 1; + // res = z0 + y0; + // overflow = ((z0 & y0) | ((z0 | y0) & ~res)) >>> 63; + // z0 = res; + // z1 += y1 + overflow; + // } + + // int cmp = Long.compareUnsigned(z1, y1); + // if ((cmp > 0) || ((cmp == 0) && (Long.compareUnsigned(z0, y0) >= 0))) { + // q1 += 1; + // res = z0 - y0; + // overflow = ((~z0 & y0) | ((~z0 | y0) & res)) >>> 63; + // z0 = res; + // z1 -= (y1 + overflow); + // } + // return new Div2Estimate(q1, z1, z0); + // } + + // -------------------------------------------------------------------------- + // endregion + + // region Records + // -------------------------------------------------------------------------- + record UInt257(boolean carry, UInt256 u) { + boolean isUInt64() { + return !carry && u.isUInt64(); + } + + boolean isUInt256() { + return !carry; + } + + UInt256 UInt256Value() { + return u; + } + + UInt320 shiftLeftWide(final int shift) { + long u4 = (carry ? 1L : 0L); + if (shift == 0) return new UInt320(u4, u.u3, u.u2, u.u1, u.u0); + int invShift = (N_BITS_PER_LIMB - shift); + long z0 = (u.u0 << shift); + long z1 = (u.u1 << shift) | u.u0 >>> invShift; + long z2 = (u.u2 << shift) | u.u1 >>> invShift; + long z3 = (u.u3 << shift) | u.u2 >>> invShift; + long z4 = (u4 << shift) | u.u3 >>> invShift; + return new UInt320(z4, z3, z2, z1, z0); + } } - private static int numberOfLeadingZeros(final int[] x, final int xLen) { - int leadingIndex = xLen - 1; - while ((leadingIndex >= 0) && (x[leadingIndex] == 0)) leadingIndex--; - return 32 * (xLen - leadingIndex - 1) + Integer.numberOfLeadingZeros(x[leadingIndex]); + record UInt128(long u1, long u0) {} + + record UInt192(long u2, long u1, long u0) {} + + record UInt320(long u4, long u3, long u2, long u1, long u0) { + static final UInt320 ZERO = new UInt320(0, 0, 0, 0, 0); + + // UInt256 UInt256ValueHigh() { + // return new UInt256(u4, u3, u2, u1); + // } + + UInt320 shiftDigitsRight() { + return new UInt320(0, u4, u3, u2, u1); + } } - private static void shiftLeftInto( - final int[] result, final int[] x, final int xLen, final int shift) { - // Unchecked: result should be initialised with zeroes - // Unchecked: result length should be at least x.length + limbShift - int limbShift = shift / N_BITS_PER_LIMB; - int bitShift = shift % N_BITS_PER_LIMB; - if (bitShift == 0) { - System.arraycopy(x, 0, result, limbShift, xLen); - return; + record UInt512(long u7, long u6, long u5, long u4, long u3, long u2, long u1, long u0) { + boolean isUInt64() { + return (u7 & u6 & u5 & u4 & u3 & u2 & u1) == 0; + } + + UInt256 UInt256Value() { + return new UInt256(u3, u2, u1, u0); } - int j = limbShift; - int carry = 0; - for (int i = 0; i < xLen; ++i, ++j) { - result[j] = (x[i] << bitShift) | carry; - carry = x[i] >>> (32 - bitShift); + UInt576 shiftLeftWide(final int shift) { + if (shift == 0) return new UInt576(0, u7, u6, u5, u4, u3, u2, u1, u0); + int invShift = (N_BITS_PER_LIMB - shift); + long z0 = (u0 << shift); + long z1 = (u1 << shift) | u0 >>> invShift; + long z2 = (u2 << shift) | u1 >>> invShift; + long z3 = (u3 << shift) | u2 >>> invShift; + long z4 = (u4 << shift) | u3 >>> invShift; + long z5 = (u5 << shift) | u4 >>> invShift; + long z6 = (u6 << shift) | u5 >>> invShift; + long z7 = (u7 << shift) | u6 >>> invShift; + long z8 = u7 >>> invShift; + return new UInt576(z8, z7, z6, z5, z4, z3, z2, z1, z0); } - if (carry != 0) result[j] = carry; // last carry } - private static void shiftRightInto( - final int[] result, final int[] x, final int xLen, final int shift) { - // Unchecked: result length should be at least x.length - limbShift - int limbShift = shift / 32; - int bitShift = shift % 32; - int nLimbs = xLen - limbShift; - if (nLimbs <= 0) return; + record UInt576(long u8, long u7, long u6, long u5, long u4, long u3, long u2, long u1, long u0) {} - if (bitShift == 0) { - System.arraycopy(x, limbShift, result, 0, nLimbs); - return; + private record DivEstimate(long q, long r) {} + + record Div2Estimate(long q, long r1, long r0) {} + + // -------------------------------------------------------------------------- + // endregion + + // region 64bits Modulus + // -------------------------------------------------------------------------- + record Modulus64(long u0) { + Modulus64 shiftLeft(final int shift) { + return (shift == 0) ? this : new Modulus64(u0 << shift); + } + + UInt256 reduce(final UInt256 that) { + if (that.isUInt64()) { + return UInt256.fromLong(Long.remainderUnsigned(that.u0, u0)); + } + int shift = Long.numberOfLeadingZeros(u0); + Modulus64 m = shiftLeft(shift); + long inv = reciprocal(m.u0); + return m.reduceNormalised(that, shift, inv); + } + + UInt256 reduce(final UInt512 that) { + if (that.isUInt64()) return UInt256.fromLong(Long.remainderUnsigned(that.u0(), u0)); + int shift = Long.numberOfLeadingZeros(u0); + Modulus64 m = shiftLeft(shift); + long inv = reciprocal(m.u0); + return m.reduceNormalised(that, shift, inv); } - int carry = 0; - for (int i = nLimbs - 1 + limbShift, j = nLimbs - 1; j >= 0; i--, j--) { - int r = (x[i] >>> bitShift) | carry; - result[j] = r; - carry = x[i] << (32 - bitShift); + UInt256 sum(final UInt256 a, final UInt256 b) { + UInt257 sum = a.adc(b); + if (sum.isUInt64()) return UInt256.fromLong(Long.remainderUnsigned(sum.u().u0, u0)); + int shift = Long.numberOfLeadingZeros(u0); + Modulus64 m = shiftLeft(shift); + long inv = reciprocal(m.u0); + return m.reduceNormalised(sum, shift, inv); + } + + UInt256 mul(final UInt256 a, final UInt256 b) { + // multiply-reduce + if (a.isUInt64() && b.isUInt64()) { + UInt256 prod = a.mul64(b); + if (prod.isUInt64()) return UInt256.fromLong(Long.remainderUnsigned(prod.u0, u0)); + return reduce(prod); + } + // reduce-multiply-reduce + int shift = Long.numberOfLeadingZeros(u0); + Modulus64 m = shiftLeft(shift); + long inv = reciprocal(m.u0); + UInt256 x = (a.isUInt64()) ? a : m.reduceNormalised(a, shift, inv); + UInt256 y = (b.isUInt64()) ? b : m.reduceNormalised(b, shift, inv); + UInt256 prod = x.mul64(y); + return prod.isUInt64() + ? UInt256.fromLong(Long.remainderUnsigned(prod.u0, u0)) + : m.reduceNormalised(prod, shift, inv); + } + + private long reduceStep(final long v1, final long v0, final long inv) { + return (v1 == u0) ? v0 : mod2by1(v1, v0, u0, inv); + } + + private UInt256 reduceNormalised(final UInt256 that, final int shift, final long inv) { + long r; + UInt320 v = that.shiftLeftWide(shift); + if (v.u4 != 0 || Long.compareUnsigned(v.u3, u0) > 0) { + r = reduceStep(v.u4, v.u3, inv); + r = reduceStep(r, v.u2, inv); + r = reduceStep(r, v.u1, inv); + r = reduceStep(r, v.u0, inv); + } else if (v.u3 != 0 || Long.compareUnsigned(v.u2, u0) > 0) { + r = reduceStep(v.u3, v.u2, inv); + r = reduceStep(r, v.u1, inv); + r = reduceStep(r, v.u0, inv); + } else if (v.u2 != 0 || Long.compareUnsigned(v.u1, u0) > 0) { + r = reduceStep(v.u2, v.u1, inv); + r = reduceStep(r, v.u0, inv); + } else { + r = reduceStep(v.u1, v.u0, inv); + } + return UInt256.fromLong(r >>> shift); + } + + private UInt256 reduceNormalised(final UInt257 that, final int shift, final long inv) { + long r; + UInt320 v = that.shiftLeftWide(shift); + if (v.u4 != 0 || Long.compareUnsigned(v.u3, u0) > 0) { + r = reduceStep(v.u4, v.u3, inv); + r = reduceStep(r, v.u2, inv); + r = reduceStep(r, v.u1, inv); + r = reduceStep(r, v.u0, inv); + } else if (v.u3 != 0 || Long.compareUnsigned(v.u2, u0) > 0) { + r = reduceStep(v.u3, v.u2, inv); + r = reduceStep(r, v.u1, inv); + r = reduceStep(r, v.u0, inv); + } else if (v.u2 != 0 || Long.compareUnsigned(v.u1, u0) > 0) { + r = reduceStep(v.u2, v.u1, inv); + r = reduceStep(r, v.u0, inv); + } else { + r = reduceStep(v.u1, v.u0, inv); + } + return UInt256.fromLong(r >>> shift); + } + + private UInt256 reduceNormalised(final UInt512 that, final int shift, final long inv) { + long r; + UInt576 v = that.shiftLeftWide(shift); + if (v.u8 != 0 || Long.compareUnsigned(v.u7, u0) > 0) { + r = reduceStep(v.u8, v.u7, inv); + r = reduceStep(r, v.u6, inv); + r = reduceStep(r, v.u5, inv); + r = reduceStep(r, v.u4, inv); + r = reduceStep(r, v.u3, inv); + r = reduceStep(r, v.u2, inv); + r = reduceStep(r, v.u1, inv); + r = reduceStep(r, v.u0, inv); + } else if (v.u7 != 0 || Long.compareUnsigned(v.u6, u0) > 0) { + r = reduceStep(v.u7, v.u6, inv); + r = reduceStep(r, v.u5, inv); + r = reduceStep(r, v.u4, inv); + r = reduceStep(r, v.u3, inv); + r = reduceStep(r, v.u2, inv); + r = reduceStep(r, v.u1, inv); + r = reduceStep(r, v.u0, inv); + } else if (v.u6 != 0 || Long.compareUnsigned(v.u5, u0) > 0) { + r = reduceStep(v.u6, v.u5, inv); + r = reduceStep(r, v.u4, inv); + r = reduceStep(r, v.u3, inv); + r = reduceStep(r, v.u2, inv); + r = reduceStep(r, v.u1, inv); + r = reduceStep(r, v.u0, inv); + } else if (v.u5 != 0 || Long.compareUnsigned(v.u4, u0) > 0) { + r = reduceStep(v.u5, v.u4, inv); + r = reduceStep(r, v.u3, inv); + r = reduceStep(r, v.u2, inv); + r = reduceStep(r, v.u1, inv); + r = reduceStep(r, v.u0, inv); + } else if (v.u4 != 0 || Long.compareUnsigned(v.u3, u0) > 0) { + r = reduceStep(v.u4, v.u3, inv); + r = reduceStep(r, v.u2, inv); + r = reduceStep(r, v.u1, inv); + r = reduceStep(r, v.u0, inv); + } else if (v.u3 != 0 || Long.compareUnsigned(v.u2, u0) > 0) { + r = reduceStep(v.u3, v.u2, inv); + r = reduceStep(r, v.u1, inv); + r = reduceStep(r, v.u0, inv); + } else if (v.u2 != 0 || Long.compareUnsigned(v.u1, u0) > 0) { + r = reduceStep(v.u2, v.u1, inv); + r = reduceStep(r, v.u0, inv); + } else { + r = reduceStep(v.u1, v.u0, inv); + } + return UInt256.fromLong(r >>> shift); } } - private static int[] addWithCarry(final int[] x, final int xLen, final int[] y, final int yLen) { - // Step 1: Add with carry - int[] a; - int[] b; - int aLen; - int bLen; - if (xLen < yLen) { - a = y; - aLen = yLen; - b = x; - bLen = xLen; - } else { - a = x; - aLen = xLen; - b = y; - bLen = yLen; - } - int[] sum = new int[aLen + 1]; - long carry = 0; - for (int i = 0; i < bLen; i++) { - long ai = a[i] & MASK_L; - long bi = b[i] & MASK_L; - long s = ai + bi + carry; - sum[i] = (int) s; - carry = s >>> 32; - } - int icarry = (int) carry; - for (int i = bLen; i < aLen; i++) { - sum[i] = a[i] + icarry; - icarry = (a[i] != 0 && sum[i] == 0) ? 1 : 0; - } - sum[aLen] = icarry; - return sum; - } - - private static int[] addMul(final int[] a, final int aLen, final int[] b, final int bLen) { - // Shortest in outer loop, swap if needed - int[] x; - int xLen; - int[] y; - int yLen; - if (a.length < b.length) { - x = b; - xLen = bLen; - y = a; - yLen = aLen; - } else { - x = a; - xLen = aLen; - y = b; - yLen = bLen; + // -------------------------------------------------------------------------- + // endregion 64bits Modulus + + // region 128bits Modulus + // -------------------------------------------------------------------------- + record Modulus128(long u1, long u0) { + Modulus128 shiftLeft(final int shift) { + if (shift == 0) return this; + int invShift = N_BITS_PER_LIMB - shift; + return new Modulus128((u1 << shift) | (u0 >>> invShift), u0 << shift); } - int[] lhs = new int[xLen + yLen + 1]; - // Main algo - for (int i = 0; i < yLen; i++) { - long carry = 0; - long yi = y[i] & MASK_L; - - int k = i; - for (int j = 0; j < xLen; j++, k++) { - long prod = yi * (x[j] & MASK_L); - long sum = (lhs[k] & MASK_L) + prod + carry; - lhs[k] = (int) sum; - carry = sum >>> 32; + int compareTo(final UInt256 v) { + if ((v.u3 | v.u2) != 0) return -1; + if (v.u1 != u1) return Long.compareUnsigned(u1, v.u1); + return Long.compareUnsigned(u0, v.u0); + } + + int compareTo(final UInt512 v) { + if ((v.u7 | v.u6 | v.u5 | v.u4 | v.u3 | v.u2) != 0) return -1; + if (v.u1 != u1) return Long.compareUnsigned(u1, v.u1); + return Long.compareUnsigned(u0, v.u0); + } + + UInt256 reduce(final UInt256 that) { + int cmp = compareTo(that); + if (cmp == 0) return ZERO; + if (cmp > 0) return that; + int shift = Long.numberOfLeadingZeros(u1); + Modulus128 m = shiftLeft(shift); + long inv = reciprocal(m.u1); + return m.reduceNormalised(that, shift, inv); + } + + UInt256 reduce(final UInt512 that) { + int cmp = compareTo(that); + if (cmp == 0) return ZERO; + if (cmp > 0) return that.UInt256Value(); + int shift = Long.numberOfLeadingZeros(u1); + Modulus128 m = shiftLeft(shift); + long inv = reciprocal(m.u1); + return m.reduceNormalised(that, shift, inv); + } + + UInt256 sum(final UInt256 a, final UInt256 b) { + UInt257 sum = a.adc(b); + int cmp = sum.isUInt256() ? compareTo(sum.UInt256Value()) : -1; + if (cmp == 0) return ZERO; + if (cmp > 0) return sum.UInt256Value(); + int shift = Long.numberOfLeadingZeros(u1); + Modulus128 m = shiftLeft(shift); + long inv = reciprocal(m.u1); + return m.reduceNormalised(sum, shift, inv); + } + + UInt256 mul(final UInt256 a, final UInt256 b) { + // multiply-reduce + if (a.isUInt128() && b.isUInt128()) { + UInt256 prod = a.mul128(b); + int cmp = compareTo(prod); + if (cmp == 0) return ZERO; + if (cmp > 0) return prod; + return reduce(prod); } + // reduce-multiply-reduce + int shift = Long.numberOfLeadingZeros(u1); + Modulus128 m = shiftLeft(shift); + long inv = reciprocal(m.u1); + UInt256 x = (a.isUInt128()) ? a : m.reduceNormalised(a, shift, inv); + UInt256 y = (b.isUInt128()) ? b : m.reduceNormalised(b, shift, inv); + UInt256 prod = x.mul128(y); + int cmp = compareTo(prod); + if (cmp == 0) return ZERO; + if (cmp > 0) return prod; + return m.reduceNormalised(prod, shift, inv); + } - // propagate leftover carry - while (carry != 0 && k < lhs.length) { - long sum = (lhs[k] & MASK_L) + carry; - lhs[k] = (int) sum; - carry = sum >>> 32; - k++; + private UInt128 reduceStep(final long v2, final long v1, final long v0, final long inv) { + long borrow, p0, p1, res; + long z2 = v2; + long z1 = v1; + long z0 = v0; + + if (z2 == u1) { + // Overflow case: div2by1 quotient would be <1, 0>, but adjusts to <0, -1> + // = -1 * u0 = + res = z0 + u0; + borrow = ((~z0 & ~u0) | ((~z0 | ~u0) & res)) >>> 63; + p1 = u0 - 1 + borrow; + z0 = res; + z1 = z1 - p1 + u1; + } else { + DivEstimate qr = div2by1(z2, z1, u1, inv); + z2 = 0; + z1 = qr.r; + + if (qr.q != 0) { + // Multiply-subtract: highest limb is already substracted + // = * q + p0 = u0 * qr.q; + p1 = Math.unsignedMultiplyHigh(u0, qr.q); + res = z0 - p0; + p1 += ((~z0 & p0) | ((~z0 | p0) & res)) >>> 63; + z0 = res; + + // Propagate overflows (borrows) + res = z1 - p1; + borrow = ((~z1 & p1) | ((~z1 | p1) & res)) >>> 63; + z1 = res; + + if (borrow != 0) { // unlikely + // Add back + res = z0 + u0; + long carry = (Long.compareUnsigned(res, z0) < 0) ? 1 : 0; + z0 = res; + res = z1 + u1 + carry; + carry = (Long.compareUnsigned(res, z1) < 0 || (u1 == -1 && carry == 1)) ? 1 : 0; + z1 = res; + if (carry == 0) { // unlikely: add back again + // Add back + res = z0 + u0; + carry = (Long.compareUnsigned(res, z0) < 0) ? 1 : 0; + z0 = res; + z1 = z1 + u1 + carry; + } + } + } } + return new UInt128(z1, z0); } - return lhs; - } - - private static int[] knuthRemainder(final int[] dividend, final int[] modulus) { - int[] result = new int[N_LIMBS]; - int divLen = nSetLimbs(dividend); - int modLen = nSetLimbs(modulus); - int cmp = compareLimbs(dividend, divLen, modulus, modLen); - if (cmp < 0) { - System.arraycopy(dividend, 0, result, 0, divLen); - return result; - } else if (cmp == 0) { - return result; - } - - int shift = numberOfLeadingZeros(modulus, modLen); - int limbShift = shift / 32; - int n = modLen - limbShift; - if (n == 0) return result; - if (n == 1) { - if (divLen == 1) { - result[0] = Integer.remainderUnsigned(dividend[0], modulus[0]); - return result; + + private UInt256 reduceNormalised(final UInt256 that, final int shift, final long inv) { + UInt128 r; + UInt320 v = that.shiftLeftWide(shift); + if (v.u4 != 0 || Long.compareUnsigned(v.u3, u1) >= 0) { + r = reduceStep(v.u4, v.u3, v.u2, inv); + r = reduceStep(r.u1, r.u0, v.u1, inv); + r = reduceStep(r.u1, r.u0, v.u0, inv); + } else if (v.u3 != 0 || Long.compareUnsigned(v.u2, u1) >= 0) { + r = reduceStep(v.u3, v.u2, v.u1, inv); + r = reduceStep(r.u1, r.u0, v.u0, inv); + } else { + r = reduceStep(v.u2, v.u1, v.u0, inv); } - long d = modulus[0] & MASK_L; - long rem = 0; - // Process from most significant limb downwards - for (int i = divLen - 1; i >= 0; i--) { - long cur = (rem << 32) | (dividend[i] & MASK_L); - rem = Long.remainderUnsigned(cur, d); + return new UInt256(0, 0, r.u1, r.u0).shiftRight(shift); + } + + private UInt256 reduceNormalised(final UInt257 that, final int shift, final long inv) { + UInt128 r; + UInt320 v = that.shiftLeftWide(shift); + if (v.u4 != 0 || Long.compareUnsigned(v.u3, u1) >= 0) { + r = reduceStep(v.u4, v.u3, v.u2, inv); + r = reduceStep(r.u1, r.u0, v.u1, inv); + r = reduceStep(r.u1, r.u0, v.u0, inv); + } else if (v.u3 != 0 || Long.compareUnsigned(v.u2, u1) >= 0) { + r = reduceStep(v.u3, v.u2, v.u1, inv); + r = reduceStep(r.u1, r.u0, v.u0, inv); + } else { + r = reduceStep(v.u2, v.u1, v.u0, inv); } - result[0] = (int) rem; - result[1] = (int) (rem >>> 32); - return result; - } - // Normalize - int m = divLen - n; - int bitShift = shift % 32; - int[] vLimbs = new int[n]; - shiftLeftInto(vLimbs, modulus, modLen, shift); - int[] uLimbs = new int[divLen + 1]; - shiftLeftInto(uLimbs, dividend, divLen, bitShift); - - long[] vLimbsAsLong = new long[n]; - for (int i = 0; i < n; i++) { - vLimbsAsLong[i] = vLimbs[i] & MASK_L; - } - - // Main division loop - long vn1 = vLimbsAsLong[n - 1]; - long vn2 = vLimbsAsLong[n - 2]; - for (int j = m; j >= 0; j--) { - long ujn = (uLimbs[j + n] & MASK_L); - long ujn1 = (uLimbs[j + n - 1] & MASK_L); - long ujn2 = (uLimbs[j + n - 2] & MASK_L); + return new UInt256(0, 0, r.u1, r.u0).shiftRight(shift); + } - long dividendPart = (ujn << 32) | ujn1; - // Check that no need for Unsigned version of divrem. - long qhat = Long.divideUnsigned(dividendPart, vn1); - long rhat = Long.remainderUnsigned(dividendPart, vn1); + private UInt256 reduceNormalised(final UInt512 that, final int shift, final long inv) { + UInt128 r; + UInt576 v = that.shiftLeftWide(shift); + if (v.u8 != 0 || Long.compareUnsigned(v.u7, u1) >= 0) { + r = reduceStep(v.u8, v.u7, v.u6, inv); + r = reduceStep(r.u1, r.u0, v.u5, inv); + r = reduceStep(r.u1, r.u0, v.u4, inv); + r = reduceStep(r.u1, r.u0, v.u3, inv); + r = reduceStep(r.u1, r.u0, v.u2, inv); + r = reduceStep(r.u1, r.u0, v.u1, inv); + r = reduceStep(r.u1, r.u0, v.u0, inv); + } else if (v.u7 != 0 || Long.compareUnsigned(v.u6, u1) >= 0) { + r = reduceStep(v.u7, v.u6, v.u5, inv); + r = reduceStep(r.u1, r.u0, v.u4, inv); + r = reduceStep(r.u1, r.u0, v.u3, inv); + r = reduceStep(r.u1, r.u0, v.u2, inv); + r = reduceStep(r.u1, r.u0, v.u1, inv); + r = reduceStep(r.u1, r.u0, v.u0, inv); + } else if (v.u6 != 0 || Long.compareUnsigned(v.u5, u1) >= 0) { + r = reduceStep(v.u6, v.u5, v.u4, inv); + r = reduceStep(r.u1, r.u0, v.u3, inv); + r = reduceStep(r.u1, r.u0, v.u2, inv); + r = reduceStep(r.u1, r.u0, v.u1, inv); + r = reduceStep(r.u1, r.u0, v.u0, inv); + } else if (v.u5 != 0 || Long.compareUnsigned(v.u4, u1) >= 0) { + r = reduceStep(v.u5, v.u4, v.u3, inv); + r = reduceStep(r.u1, r.u0, v.u2, inv); + r = reduceStep(r.u1, r.u0, v.u1, inv); + r = reduceStep(r.u1, r.u0, v.u0, inv); + } else if (v.u4 != 0 || Long.compareUnsigned(v.u3, u1) >= 0) { + r = reduceStep(v.u4, v.u3, v.u2, inv); + r = reduceStep(r.u1, r.u0, v.u1, inv); + r = reduceStep(r.u1, r.u0, v.u0, inv); + } else if (v.u3 != 0 || Long.compareUnsigned(v.u2, u1) >= 0) { + r = reduceStep(v.u3, v.u2, v.u1, inv); + r = reduceStep(r.u1, r.u0, v.u0, inv); + } else { + r = reduceStep(v.u2, v.u1, v.u0, inv); + } + return new UInt256(0, 0, r.u1, r.u0).shiftRight(shift); + } + } - while (qhat == 0x1_0000_0000L || Long.compareUnsigned(qhat * vn2, (rhat << 32) | ujn2) > 0) { - qhat--; - rhat += vn1; - if (rhat >= 0x1_0000_0000L) break; + // -------------------------------------------------------------------------- + // endregion 128bits Modulus + + // region 192bits Modulus + // -------------------------------------------------------------------------- + record Modulus192(long u2, long u1, long u0) { + Modulus192 shiftLeft(final int shift) { + if (shift == 0) return this; + int invShift = N_BITS_PER_LIMB - shift; + long z0 = u0 << shift; + long z1 = (u1 << shift) | (u0 >>> invShift); + long z2 = (u2 << shift) | (u1 >>> invShift); + return new Modulus192(z2, z1, z0); + } + + int compareTo(final UInt256 v) { + if (v.u3 != 0) return -1; + if (v.u2 != u2) return Long.compareUnsigned(u2, v.u2); + if (v.u1 != u1) return Long.compareUnsigned(u1, v.u1); + return Long.compareUnsigned(u0, v.u0); + } + + int compareTo(final UInt512 v) { + if ((v.u7 | v.u6 | v.u5 | v.u4 | v.u3) != 0) return -1; + if (v.u2 != u2) return Long.compareUnsigned(u2, v.u2); + if (v.u1 != u1) return Long.compareUnsigned(u1, v.u1); + return Long.compareUnsigned(u0, v.u0); + } + + UInt256 reduce(final UInt256 that) { + int cmp = compareTo(that); + if (cmp == 0) return ZERO; + if (cmp > 0) return that; + int shift = Long.numberOfLeadingZeros(u2); + Modulus192 m = shiftLeft(shift); + long inv = reciprocal(m.u2); + return m.reduceNormalised(that, shift, inv); + } + + UInt256 reduce(final UInt512 that) { + int cmp = compareTo(that); + if (cmp == 0) return ZERO; + if (cmp > 0) return that.UInt256Value(); + int shift = Long.numberOfLeadingZeros(u2); + Modulus192 m = shiftLeft(shift); + long inv = reciprocal(m.u2); + return m.reduceNormalised(that, shift, inv); + } + + UInt256 sum(final UInt256 a, final UInt256 b) { + UInt257 sum = a.adc(b); + if (!sum.carry()) { + int cmp = compareTo(sum.UInt256Value()); + if (cmp == 0) return ZERO; + if (cmp > 0) return sum.UInt256Value(); } + int shift = Long.numberOfLeadingZeros(u2); + Modulus192 m = shiftLeft(shift); + long inv = reciprocal(m.u2); + return m.reduceNormalised(sum, shift, inv); + } - // Multiply-subtract qhat*v from u slice - long borrow = 0; - for (int i = 0; i < n; i++) { - long prod = vLimbsAsLong[i] * qhat; - long sub = (uLimbs[i + j] & MASK_L) - (prod & MASK_L) - borrow; - uLimbs[i + j] = (int) sub; - borrow = (prod >>> 32) - (sub >> 32); + UInt256 mul(final UInt256 a, final UInt256 b) { + // multiply-reduce + if (a.isUInt192() && b.isUInt192()) { + UInt512 prod = a.mul192(b); + int cmp = compareTo(prod); + if (cmp == 0) return ZERO; + if (cmp > 0) return prod.UInt256Value(); + return reduce(prod); } - long sub = (uLimbs[j + n] & MASK_L) - borrow; - uLimbs[j + n] = (int) sub; + // reduce-multiply-reduce + int shift = Long.numberOfLeadingZeros(u2); + Modulus192 m = shiftLeft(shift); + long inv = reciprocal(m.u2); + UInt256 x = (a.isUInt192()) ? a : m.reduceNormalised(a, shift, inv); + UInt256 y = (b.isUInt192()) ? b : m.reduceNormalised(b, shift, inv); + UInt512 prod = x.mul192(y); + int cmp = compareTo(prod); + if (cmp == 0) return ZERO; + if (cmp > 0) return prod.UInt256Value(); + return m.reduceNormalised(prod, shift, inv); + } - if (sub < 0) { - // Add back - long carry = 0; - for (int i = 0; i < n; i++) { - long sum = (uLimbs[i + j] & MASK_L) + vLimbsAsLong[i] + carry; - uLimbs[i + j] = (int) sum; - carry = sum >>> 32; + private UInt192 reduceStep( + final long v3, final long v2, final long v1, final long v0, final long inv) { + long borrow, p0, p1, p2, res; + // Divide step -> get highest 2 limbs. + long z3 = v3; + long z2 = v2; + long z1 = v1; + long z0 = v0; + + if (z3 == u2) { + // Overflow case: div2by1 quotient would be <1, 0>, but adjusts to <0, -1> + // = -1 * u0 = + res = z0 + u0; + borrow = ((~z0 & ~u0) | ((~z0 | ~u0) & res)) >>> 63; + p1 = u0 - 1 + borrow; + z0 = res; + + res = z1 - p1; + borrow = ((~z1 & p1) | ((~z1 | p1) & res)) >>> 63; + p1 = u1 - 1 + borrow; + z1 = res + u1; + borrow = ((~res & ~u1) | ((~res | ~u1) & z1)) >>> 63; + + z2 = z2 - p1 + u2 - borrow; + z3 = 0; + // borrow = ((~z2 & p1) | ((~z2 | p1) & res)) >>> 63; + // p1 = u2 - 1 + borrow; + // borrow = ((~res & ~u1) | ((~res | ~u1) & z1)) >>> 63; + // assert p1 + borrow == z3 : "Division did not cancel top digit" + } else { + DivEstimate qr = div2by1(z3, z2, u2, inv); + z3 = 0; + z2 = qr.r; + + if (qr.q != 0) { + // Multiply-subtract: already have highest 2 limbs + // = * q + p0 = u0 * qr.q; + p1 = Math.unsignedMultiplyHigh(u0, qr.q); + res = z0 - p0; + p1 += ((~z0 & p0) | ((~z0 | p0) & res)) >>> 63; + z0 = res; + + p0 = u1 * qr.q; + p2 = Math.unsignedMultiplyHigh(u1, qr.q); + res = z1 - p0; + p2 += ((~z1 & p0) | ((~z1 | p0) & res)) >>> 63; + z1 = res - p1; + borrow = ((~res & p1) | ((~res | p1) & z1)) >>> 63; + + // Propagate overflows (borrows) + res = z2 - p2 - borrow; + borrow = ((~z2 & p2) | ((~z2 | p2) & res)) >>> 63; + z2 = res; + + if (borrow != 0) { // unlikely + // Add back + res = z0 + u0; + long carry = (Long.compareUnsigned(res, z0) < 0) ? 1 : 0; + z0 = res; + res = z1 + u1 + carry; + carry = (Long.compareUnsigned(res, z1) < 0 || (u1 == -1 && carry == 1)) ? 1 : 0; + z1 = res; + res = z2 + u2 + carry; + carry = (Long.compareUnsigned(res, z2) < 0 || (u2 == -1 && carry == 1)) ? 1 : 0; + z2 = res; + + if (carry == 0) { // unlikely: add back again + // Add back + res = z0 + u0; + carry = (Long.compareUnsigned(res, z0) < 0) ? 1 : 0; + z0 = res; + res = z1 + u1 + carry; + carry = (Long.compareUnsigned(res, z1) < 0 || (u1 == -1 && carry == 1)) ? 1 : 0; + z1 = res; + z2 = z2 + u2 + carry; + } + } } - uLimbs[j + n] = (int) (uLimbs[j + n] + carry); } + return new UInt192(z2, z1, z0); + } + + private UInt256 reduceNormalised(final UInt256 that, final int shift, final long inv) { + UInt192 r; + UInt320 v = that.shiftLeftWide(shift); + if (v.u4 != 0 || Long.compareUnsigned(v.u3, u2) >= 0) { + r = reduceStep(v.u4, v.u3, v.u2, v.u1, inv); + r = reduceStep(r.u2, r.u1, r.u0, v.u0, inv); + } else { + r = reduceStep(v.u3, v.u2, v.u1, v.u0, inv); + } + return new UInt256(0, r.u2, r.u1, r.u0).shiftRight(shift); + } + + private UInt256 reduceNormalised(final UInt257 that, final int shift, final long inv) { + UInt192 r; + UInt320 v = that.shiftLeftWide(shift); + if (v.u4 != 0 || Long.compareUnsigned(v.u3, u2) >= 0) { + r = reduceStep(v.u4, v.u3, v.u2, v.u1, inv); + r = reduceStep(r.u2, r.u1, r.u0, v.u0, inv); + } else { + r = reduceStep(v.u3, v.u2, v.u1, v.u0, inv); + } + return new UInt256(0, r.u2, r.u1, r.u0).shiftRight(shift); + } + + private UInt256 reduceNormalised(final UInt512 that, final int shift, final long inv) { + UInt192 r; + UInt576 v = that.shiftLeftWide(shift); + if (v.u8 != 0 || Long.compareUnsigned(v.u7, u2) >= 0) { + r = reduceStep(v.u8, v.u7, v.u6, v.u5, inv); + r = reduceStep(r.u2, r.u1, r.u0, v.u4, inv); + r = reduceStep(r.u2, r.u1, r.u0, v.u3, inv); + r = reduceStep(r.u2, r.u1, r.u0, v.u2, inv); + r = reduceStep(r.u2, r.u1, r.u0, v.u1, inv); + r = reduceStep(r.u2, r.u1, r.u0, v.u0, inv); + } else if (v.u7 != 0 || Long.compareUnsigned(v.u6, u2) >= 0) { + r = reduceStep(v.u7, v.u6, v.u5, v.u4, inv); + r = reduceStep(r.u2, r.u1, r.u0, v.u3, inv); + r = reduceStep(r.u2, r.u1, r.u0, v.u2, inv); + r = reduceStep(r.u2, r.u1, r.u0, v.u1, inv); + r = reduceStep(r.u2, r.u1, r.u0, v.u0, inv); + } else if (v.u6 != 0 || Long.compareUnsigned(v.u5, u2) >= 0) { + r = reduceStep(v.u6, v.u5, v.u4, v.u3, inv); + r = reduceStep(r.u2, r.u1, r.u0, v.u2, inv); + r = reduceStep(r.u2, r.u1, r.u0, v.u1, inv); + r = reduceStep(r.u2, r.u1, r.u0, v.u0, inv); + } else if (v.u5 != 0 || Long.compareUnsigned(v.u4, u2) >= 0) { + r = reduceStep(v.u5, v.u4, v.u3, v.u2, inv); + r = reduceStep(r.u2, r.u1, r.u0, v.u1, inv); + r = reduceStep(r.u2, r.u1, r.u0, v.u0, inv); + } else if (v.u4 != 0 || Long.compareUnsigned(v.u3, u2) >= 0) { + r = reduceStep(v.u4, v.u3, v.u2, v.u1, inv); + r = reduceStep(r.u2, r.u1, r.u0, v.u0, inv); + } else { + r = reduceStep(v.u3, v.u2, v.u1, v.u0, inv); + } + return new UInt256(0, r.u2, r.u1, r.u0).shiftRight(shift); } - // Unnormalize remainder - shiftRightInto(result, uLimbs, n, bitShift); - return result; } + // -------------------------------------------------------------------------- - // endregion + // endregion 192bits Modulus + + // region 256bits Modulus + // -------------------------------------------------------------------------- + record Modulus256(long u3, long u2, long u1, long u0) { + Modulus256 shiftLeft(final int shift) { + if (shift == 0) return this; + int invShift = N_BITS_PER_LIMB - shift; + long z0 = u0 << shift; + long z1 = (u1 << shift) | (u0 >>> invShift); + long z2 = (u2 << shift) | (u1 >>> invShift); + long z3 = (u3 << shift) | (u2 >>> invShift); + return new Modulus256(z3, z2, z1, z0); + } + + int compareTo(final UInt256 v) { + if (v.u3 != u3) return Long.compareUnsigned(u3, v.u3); + if (v.u2 != u2) return Long.compareUnsigned(u2, v.u2); + if (v.u1 != u1) return Long.compareUnsigned(u1, v.u1); + return Long.compareUnsigned(u0, v.u0); + } + + int compareTo(final UInt512 v) { + if ((v.u7 | v.u6 | v.u5 | v.u4) != 0) return -1; + if (v.u3 != u3) return Long.compareUnsigned(u3, v.u3); + if (v.u2 != u2) return Long.compareUnsigned(u2, v.u2); + if (v.u1 != u1) return Long.compareUnsigned(u1, v.u1); + return Long.compareUnsigned(u0, v.u0); + } + + UInt256 reduce(final UInt256 that) { + int cmp = compareTo(that); + if (cmp == 0) return ZERO; + if (cmp > 0) return that; + int shift = Long.numberOfLeadingZeros(u3); + Modulus256 m = shiftLeft(shift); + long inv = reciprocal(m.u3); + return m.reduceNormalised(that, shift, inv); + } + + UInt256 reduce(final UInt512 that) { + int cmp = compareTo(that); + if (cmp == 0) return ZERO; + if (cmp > 0) return that.UInt256Value(); + int shift = Long.numberOfLeadingZeros(u3); + Modulus256 m = shiftLeft(shift); + long inv = reciprocal(m.u3); + return m.reduceNormalised(that, shift, inv); + } + + UInt256 sum(final UInt256 a, final UInt256 b) { + UInt257 sum = a.adc(b); + if (!sum.carry()) { + int cmp = compareTo(sum.UInt256Value()); + if (cmp == 0) return ZERO; + if (cmp > 0) return sum.UInt256Value(); + } + int shift = Long.numberOfLeadingZeros(u3); + Modulus256 m = shiftLeft(shift); + long inv = reciprocal(m.u3); + return m.reduceNormalised(sum, shift, inv); + } + + UInt256 mul(final UInt256 a, final UInt256 b) { + // multiply-reduce + UInt512 prod = a.mul256(b); + int cmp = compareTo(prod); + if (cmp == 0) return ZERO; + if (cmp > 0) return prod.UInt256Value(); + return reduce(prod); + } + + private UInt256 reduceStep( + final long v4, final long v3, final long v2, final long v1, final long v0, final long inv) { + long borrow, p0, p1, p2, res; + long z4 = v4; + long z3 = v3; + long z2 = v2; + long z1 = v1; + long z0 = v0; + + if (z4 == u3) { + // Overflow case: div2by1 quotient would be <1, 0>, but adjusts to <0, -1> + // = -1 * u0 = + res = z0 + u0; + borrow = ((~z0 & ~u0) | ((~z0 | ~u0) & res)) >>> 63; + p1 = u0 - 1 + borrow; + z0 = res; + + res = z1 - p1; + borrow = ((~z1 & p1) | ((~z1 | p1) & res)) >>> 63; + p1 = u1 - 1 + borrow; + z1 = res + u1; + borrow = ((~res & ~u1) | ((~res | ~u1) & z1)) >>> 63; + + res = z2 - p1 - borrow; + borrow = ((~z2 & p1) | ((~z2 | p1) & res)) >>> 63; + p1 = u2 - 1 + borrow; + z2 = res + u2; + borrow = ((~res & ~u2) | ((~res | ~u2) & z2)) >>> 63; + + z3 = z3 - p1 + u3 - borrow; + } else { + DivEstimate qr = div2by1(z4, z3, u3, inv); + z3 = qr.r; + + // Multiply-subtract: already have highest 1 limbs + // = * q + p0 = u0 * qr.q; + p1 = Math.unsignedMultiplyHigh(u0, qr.q); + res = z0 - p0; + p1 += ((~z0 & p0) | ((~z0 | p0) & res)) >>> 63; + z0 = res; + + p0 = u1 * qr.q; + p2 = Math.unsignedMultiplyHigh(u1, qr.q); + res = z1 - p0; + p2 += ((~z1 & p0) | ((~z1 | p0) & res)) >>> 63; + z1 = res - p1; + borrow = ((~res & p1) | ((~res | p1) & z1)) >>> 63; + + p0 = u2 * qr.q; + p1 = Math.unsignedMultiplyHigh(u2, qr.q); + res = z2 - p0 - borrow; + p1 += ((~z2 & p0) | ((~z2 | p0) & res)) >>> 63; + z2 = res - p2; + borrow = ((~res & p2) | ((~res | p2) & z2)) >>> 63; + + // Propagate overflows (borrows) + res = z3 - p1 - borrow; + borrow = ((~z3 & p1) | ((~z3 | p1) & res)) >>> 63; + z3 = res; + + if (borrow != 0) { // unlikely + // Add back + res = z0 + u0; + long carry = (Long.compareUnsigned(res, z0) < 0) ? 1 : 0; + z0 = res; + res = z1 + u1 + carry; + carry = (Long.compareUnsigned(res, z1) < 0 || (u1 == -1 && carry == 1)) ? 1 : 0; + z1 = res; + res = z2 + u2 + carry; + carry = (Long.compareUnsigned(res, z2) < 0 || (u2 == -1 && carry == 1)) ? 1 : 0; + z2 = res; + res = z3 + u3 + carry; + carry = (Long.compareUnsigned(res, z3) < 0 || (u3 == -1 && carry == 1)) ? 1 : 0; + z3 = res; + + if (carry == 0) { // unlikely: add back again + // Add back + res = z0 + u0; + carry = (Long.compareUnsigned(res, z0) < 0) ? 1 : 0; + z0 = res; + res = z1 + u1 + carry; + carry = (Long.compareUnsigned(res, z1) < 0 || (u1 == -1 && carry == 1)) ? 1 : 0; + z1 = res; + res = z2 + u2 + carry; + carry = (Long.compareUnsigned(res, z2) < 0 || (u2 == -1 && carry == 1)) ? 1 : 0; + z2 = res; + z3 = z3 + u3 + carry; + } + } + } + return new UInt256(z3, z2, z1, z0); + } + + private UInt256 reduceNormalised(final UInt256 that, final int shift, final long inv) { + UInt320 v = that.shiftLeftWide(shift); + return reduceStep(v.u4, v.u3, v.u2, v.u1, v.u0, inv).shiftRight(shift); + } + + private UInt256 reduceNormalised(final UInt257 that, final int shift, final long inv) { + UInt320 v = that.shiftLeftWide(shift); + return reduceStep(v.u4, v.u3, v.u2, v.u1, v.u0, inv).shiftRight(shift); + } + + private UInt256 reduceNormalised(final UInt512 that, final int shift, final long inv) { + UInt256 r; + UInt576 v = that.shiftLeftWide(shift); + if (v.u8 != 0 || Long.compareUnsigned(v.u7, u3) >= 0) { + r = reduceStep(v.u8, v.u7, v.u6, v.u5, v.u4, inv); + r = reduceStep(r.u3, r.u2, r.u1, r.u0, v.u3, inv); + r = reduceStep(r.u3, r.u2, r.u1, r.u0, v.u2, inv); + r = reduceStep(r.u3, r.u2, r.u1, r.u0, v.u1, inv); + r = reduceStep(r.u3, r.u2, r.u1, r.u0, v.u0, inv); + } else if (v.u7 != 0 || Long.compareUnsigned(v.u6, u3) >= 0) { + r = reduceStep(v.u7, v.u6, v.u5, v.u4, v.u3, inv); + r = reduceStep(r.u3, r.u2, r.u1, r.u0, v.u2, inv); + r = reduceStep(r.u3, r.u2, r.u1, r.u0, v.u1, inv); + r = reduceStep(r.u3, r.u2, r.u1, r.u0, v.u0, inv); + } else if (v.u6 != 0 || Long.compareUnsigned(v.u5, u3) >= 0) { + r = reduceStep(v.u6, v.u5, v.u4, v.u3, v.u2, inv); + r = reduceStep(r.u3, r.u2, r.u1, r.u0, v.u1, inv); + r = reduceStep(r.u3, r.u2, r.u1, r.u0, v.u0, inv); + } else if (v.u5 != 0 || Long.compareUnsigned(v.u4, u3) >= 0) { + r = reduceStep(v.u5, v.u4, v.u3, v.u2, v.u1, inv); + r = reduceStep(r.u3, r.u2, r.u1, r.u0, v.u0, inv); + } else { + r = reduceStep(v.u4, v.u3, v.u2, v.u1, v.u0, inv); + } + return r.shiftRight(shift); + } + } + // -------------------------------------------------------------------------- + // endregion 256bits Modulus + } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/ExceptionalHaltReason.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/ExceptionalHaltReason.java index b68a052af50..45224570bc3 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/ExceptionalHaltReason.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/ExceptionalHaltReason.java @@ -59,6 +59,9 @@ public interface ExceptionalHaltReason { /** The constant ADDRESS_OUT_OF_RANGE. */ ExceptionalHaltReason ADDRESS_OUT_OF_RANGE = DefaultExceptionalHaltReason.ADDRESS_OUT_OF_RANGE; + /** The constant STACK_OUT_OF_BOUNDS. */ + ExceptionalHaltReason STACK_OUT_OF_BOUNDS = DefaultExceptionalHaltReason.STACK_OUT_OF_BOUNDS; + /** * Name string. * @@ -127,7 +130,9 @@ enum DefaultExceptionalHaltReason implements ExceptionalHaltReason { /** The Precompile error. */ PRECOMPILE_ERROR("Precompile error"), /** The Address out of range. */ - ADDRESS_OUT_OF_RANGE("Address out of range"); + ADDRESS_OUT_OF_RANGE("Address out of range"), + /** Stack index out of bounds (safety net for missing pre-validation). */ + STACK_OUT_OF_BOUNDS("Stack index out of bounds"); /** The Description. */ final String description; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/Memory.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/Memory.java index fbc96b034db..e13f6826dc6 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/Memory.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/Memory.java @@ -16,11 +16,11 @@ import org.hyperledger.besu.evm.internal.Words; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.nio.ByteOrder; import java.util.Arrays; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.bytes.MutableBytes; +import java.util.HexFormat; /** * An EVM memory implementation. @@ -30,6 +30,12 @@ */ public class Memory { + private static final VarHandle LONG_BE = + MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN); + + private static final byte[] EMPTY_BYTES = new byte[0]; + private static final HexFormat HEX = HexFormat.of(); + // See below. private static final long MAX_BYTES = Integer.MAX_VALUE; @@ -120,7 +126,7 @@ void ensureCapacityForBytes(final long offset, final long numBytes) { return; } final long lastByteIndex = Math.addExact(offset, numBytes); - final long lastWordRequired = ((lastByteIndex - 1) / Bytes32.SIZE); + final long lastWordRequired = ((lastByteIndex - 1) / 32); maybeExpandCapacity((int) lastWordRequired + 1); } @@ -132,7 +138,7 @@ void ensureCapacityForBytes(final long offset, final long numBytes) { private void maybeExpandCapacity(final int newActiveWords) { if (activeWords >= newActiveWords) return; - int neededSize = newActiveWords * Bytes32.SIZE; + int neededSize = newActiveWords * 32; if (neededSize > memBytes.length) { int newSize = Math.max(neededSize, memBytes.length * 2); byte[] newMem = new byte[newSize]; @@ -168,7 +174,7 @@ public int hashCode() { * @return The current number of active bytes stored in memory. */ int getActiveBytes() { - return activeWords * Bytes32.SIZE; + return activeWords * 32; } /** @@ -188,19 +194,19 @@ public int getActiveWords() { * @return A fresh copy of the bytes from memory starting at {@code location} and extending {@code * numBytes}. */ - public Bytes getBytes(final long location, final long numBytes) { + public byte[] getBytes(final long location, final long numBytes) { // Note: if length == 0, we don't require any memory expansion, whatever location is. So // we must call asByteIndex(location) after this check so as it doesn't throw if the location // is too big but the length is 0 (which is somewhat nonsensical, but is exercise by some // tests). final int length = asByteLength(numBytes); if (length == 0) { - return Bytes.EMPTY; + return EMPTY_BYTES; } final int start = asByteIndex(location); ensureCapacityForBytes(start, length); - return Bytes.wrap(Arrays.copyOfRange(memBytes, start, start + length)); + return Arrays.copyOfRange(memBytes, start, start + length); } /** @@ -211,14 +217,14 @@ public Bytes getBytes(final long location, final long numBytes) { * @return A fresh copy of the bytes from memory starting at {@code location} and extending {@code * numBytes}. */ - public Bytes getBytesWithoutGrowth(final long location, final long numBytes) { + public byte[] getBytesWithoutGrowth(final long location, final long numBytes) { // Note: if length == 0, we don't require any memory expansion, whatever location is. So // we must call asByteIndex(location) after this check so as it doesn't throw if the location // is too big but the length is 0 (which is somewhat nonsensical, but is exercise by some // tests). final int length = asByteLength(numBytes); if (length == 0) { - return Bytes.EMPTY; + return EMPTY_BYTES; } final int start = asByteIndex(location); @@ -227,34 +233,10 @@ public Bytes getBytesWithoutGrowth(final long location, final long numBytes) { // number of zeros without expanding the memory. // Otherwise, just follow the happy path. if (start > memBytes.length) { - return Bytes.wrap(new byte[(int) numBytes]); + return new byte[(int) numBytes]; } else { - return Bytes.wrap(Arrays.copyOfRange(memBytes, start, start + length)); - } - } - - /** - * Returns a copy of bytes from memory. - * - * @param location The location in memory to start with. - * @param numBytes The number of bytes to get. - * @return A fresh copy of the bytes from memory starting at {@code location} and extending {@code - * numBytes}. - */ - public MutableBytes getMutableBytes(final long location, final long numBytes) { - // Note: if length == 0, we don't require any memory expansion, whatever location is. So - // we must call asByteIndex(location) after this check so as it doesn't throw if the location - // is too big but the length is 0 (which is somewhat nonsensical, but is exercise by some - // tests). - final int length = asByteLength(numBytes); - if (length == 0) { - return MutableBytes.EMPTY; + return Arrays.copyOfRange(memBytes, start, start + length); } - - final int start = asByteIndex(location); - - ensureCapacityForBytes(start, length); - return MutableBytes.wrap(memBytes, start, length); } /** @@ -276,15 +258,16 @@ public MutableBytes getMutableBytes(final long location, final long numBytes) { * @param bytes the bytes to copy to memory from {@code location}. */ public void setBytes( - final long memOffset, final long offset, final long length, final Bytes bytes) { + final long memOffset, final long offset, final long length, final byte[] bytes) { - if (offset >= bytes.size()) { + if (offset >= bytes.length) { clearBytes(memOffset, length); return; } - final Bytes toCopy = - bytes.slice((int) offset, Math.min((int) length, bytes.size() - (int) offset)); + final int srcOff = (int) offset; + final int copyLen = Math.min((int) length, bytes.length - srcOff); + final byte[] toCopy = Arrays.copyOfRange(bytes, srcOff, srcOff + copyLen); setBytes(memOffset, length, toCopy); } @@ -305,23 +288,23 @@ public void setBytes( * {@code value} being smaller). * @param taintedValue the bytes to copy to memory from {@code location}. */ - public void setBytes(final long location, final long numBytes, final Bytes taintedValue) { + public void setBytes(final long location, final long numBytes, final byte[] taintedValue) { if (numBytes == 0) { return; } final int start = asByteIndex(location); final int length = asByteLength(numBytes); - final int srcLength = taintedValue.size(); + final int srcLength = taintedValue.length; final int end = Math.addExact(start, length); ensureCapacityForBytes(start, length); if (srcLength >= length) { - System.arraycopy(taintedValue.toArrayUnsafe(), 0, memBytes, start, length); + System.arraycopy(taintedValue, 0, memBytes, start, length); } else { Arrays.fill(memBytes, start + srcLength, end, (byte) 0); if (srcLength > 0) { - System.arraycopy(taintedValue.toArrayUnsafe(), 0, memBytes, start, srcLength); + System.arraycopy(taintedValue, 0, memBytes, start, srcLength); } } } @@ -345,24 +328,24 @@ public void setBytes(final long location, final long numBytes, final Bytes taint * {@code value} being smaller). These create bytes will be added to the left as needed. * @param value the bytes to copy into memory starting at {@code location}. */ - public void setBytesRightAligned(final long location, final long numBytes, final Bytes value) { + public void setBytesRightAligned(final long location, final long numBytes, final byte[] value) { if (numBytes == 0) { return; } final int start = asByteIndex(location); final int length = asByteLength(numBytes); - final int srcLength = value.size(); + final int srcLength = value.length; final int end = Math.addExact(start, length); ensureCapacityForBytes(start, length); if (srcLength >= length) { - System.arraycopy(value.toArrayUnsafe(), 0, memBytes, start, length); + System.arraycopy(value, 0, memBytes, start, length); } else { int divider = end - srcLength; Arrays.fill(memBytes, start, divider, (byte) 0); if (srcLength > 0) { - System.arraycopy(value.toArrayUnsafe(), 0, memBytes, divider, srcLength); + System.arraycopy(value, 0, memBytes, divider, srcLength); } } } @@ -409,16 +392,48 @@ void setByte(final long location, final byte value) { memBytes[start] = value; } + /** + * Read 4 big-endian longs (32 bytes) from memory directly into a long array. + * + * @param location The offset in memory to read from. + * @param s The destination long array. + * @param off The offset in the destination array. + */ + void readLimbs(final long location, final long[] s, final int off) { + final int start = asByteIndex(location); + ensureCapacityForBytes(start, 32); + s[off] = (long) LONG_BE.get(memBytes, start); + s[off + 1] = (long) LONG_BE.get(memBytes, start + 8); + s[off + 2] = (long) LONG_BE.get(memBytes, start + 16); + s[off + 3] = (long) LONG_BE.get(memBytes, start + 24); + } + + /** + * Write 4 big-endian longs (32 bytes) from a long array directly into memory. + * + * @param location The offset in memory to write to. + * @param s The source long array. + * @param off The offset in the source array. + */ + void writeLimbs(final long location, final long[] s, final int off) { + final int start = asByteIndex(location); + ensureCapacityForBytes(start, 32); + LONG_BE.set(memBytes, start, s[off]); + LONG_BE.set(memBytes, start + 8, s[off + 1]); + LONG_BE.set(memBytes, start + 16, s[off + 2]); + LONG_BE.set(memBytes, start + 24, s[off + 3]); + } + /** * Returns a copy of the 32-bytes word that begins at the specified memory location. * * @param location The memory location the 256-bit word begins at. * @return a copy of the 32-bytes word that begins at the specified memory location. */ - public Bytes32 getWord(final long location) { + public byte[] getWord(final long location) { final int start = asByteIndex(location); - ensureCapacityForBytes(start, Bytes32.SIZE); - return Bytes32.wrap(Arrays.copyOfRange(memBytes, start, start + Bytes32.SIZE)); + ensureCapacityForBytes(start, 32); + return Arrays.copyOfRange(memBytes, start, start + 32); } /** @@ -430,10 +445,10 @@ public Bytes32 getWord(final long location) { * @param location the location at which to start setting the bytes. * @param bytes the 32 bytes to copy at {@code location}. */ - public void setWord(final long location, final Bytes32 bytes) { + public void setWord(final long location, final byte[] bytes) { final int start = asByteIndex(location); - ensureCapacityForBytes(start, Bytes32.SIZE); - System.arraycopy(bytes.toArrayUnsafe(), 0, memBytes, start, Bytes32.SIZE); + ensureCapacityForBytes(start, 32); + System.arraycopy(bytes, 0, memBytes, start, 32); } /** @@ -452,6 +467,6 @@ public void copy(final long dst, final long src, final long length) { @Override public String toString() { - return Bytes.wrap(memBytes).toHexString(); + return "0x" + HEX.formatHex(memBytes); } } 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 8af519097dd..4b480877f94 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 @@ -27,14 +27,14 @@ import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.blockhash.BlockHashLookup; import org.hyperledger.besu.evm.internal.MemoryEntry; -import org.hyperledger.besu.evm.internal.OperandStack; +import org.hyperledger.besu.evm.internal.StackPool; 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.worldstate.WorldUpdater; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Arrays; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; @@ -50,8 +50,6 @@ import com.google.common.collect.Table; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.bytes.MutableBytes; -import org.apache.tuweni.units.bigints.UInt256; /** * A container object for all the states associated with a message. @@ -207,7 +205,9 @@ public enum Type { private long gasRemaining; private int pc; private final Memory memory = new Memory(); - private final OperandStack stack; + private long[] stackData; + private int stackTop; + private final int stackMaxSize; private Bytes output = Bytes.EMPTY; private Bytes returnData = Bytes.EMPTY; private Code createdCode = null; @@ -274,7 +274,9 @@ private MessageFrame( this.type = type; this.worldUpdater = worldUpdater; this.gasRemaining = initialGas; - this.stack = new OperandStack(txValues.maxStackSize()); + this.stackMaxSize = txValues.maxStackSize(); + this.stackData = StackPool.borrow(stackMaxSize); + this.stackTop = 0; this.pc = 0; this.recipient = recipient; this.contract = contract; @@ -416,63 +418,173 @@ public void clearReturnData() { setReturnData(Bytes.EMPTY); } + // region Raw long[] stack access (used by StackMath and operations) + // --------------------------------------------------------------------------- + + /** + * Returns the backing long[] array of the operand stack. + * + * @return the raw data array + */ + public long[] stackData() { + return stackData; + } + + /** + * Returns the current stack top (item count, 0 = empty). + * + * @return the item count + */ + public int stackTop() { + return stackTop; + } + + /** + * Sets the stack top (item count). Used after StackMath operations. + * + * @param newTop the new item count + */ + public void setTop(final int newTop) { + this.stackTop = newTop; + } + + // --------------------------------------------------------------------------- + // endregion + /** - * Returns the item at the specified offset in the stack. + * Returns the item at the specified offset in the stack without bounds check. Caller must + * pre-validate via {@link #stackHasItems}. * * @param offset The item's position relative to the top of the stack * @return The item at the specified offset in the stack - * @throws UnderflowException if the offset is out of range */ - public Bytes getStackItem(final int offset) { - return stack.get(offset); + public org.hyperledger.besu.evm.UInt256 getStackItem(final int offset) { + final int off = (stackTop - 1 - offset) << 2; + return new org.hyperledger.besu.evm.UInt256( + stackData[off], stackData[off + 1], stackData[off + 2], stackData[off + 3]); } + // region Stack validation methods + // --------------------------------------------------------------------------- + /** - * Removes the item at the top of the stack. + * Returns true if the stack has at least {@code n} items. * - * @return the item at the top of the stack - * @throws UnderflowException if the stack is empty + * @param n the number of items required + * @return true if the stack contains at least n items */ - public Bytes popStackItem() { - return stack.pop(); + public boolean stackHasItems(final int n) { + return stackTop >= n; } /** - * Removes the corresponding number of items from the top of the stack. + * Returns true if the stack has space for {@code n} more items. * - * @param n The number of items to pop off the stack + * @param n the number of additional items + * @return true if the stack can accommodate n more items */ - public void popStackItems(final int n) { - stack.bulkPop(n); + public boolean stackHasSpace(final int n) { + return stackTop + n <= stackMaxSize; } + // --------------------------------------------------------------------------- + // endregion + + // region Bytes wrapper methods for non-converted operations + // -------------------------------------------------------------------------- + /** - * Pushes the corresponding item onto the top of the stack + * Pops a stack item and converts it to Bytes. For non-phase-1 operations that still work with + * Bytes. * - * @param value The value to push onto the stack. + * @return the top stack item as Bytes */ - public void pushStackItem(final Bytes value) { - stack.push(value); + public Bytes popStackBytes() { + stackTop--; + final int off = stackTop << 2; + return Bytes.wrap( + new org.hyperledger.besu.evm.UInt256( + stackData[off], stackData[off + 1], stackData[off + 2], stackData[off + 3]) + .toBytesBE()); } /** - * Sets the stack item at the specified offset from the top of the stack to the value + * Pushes a Bytes value onto the stack by converting it to UInt256 first. For non-phase-1 + * operations that still work with Bytes. + * + * @param value The Bytes value to push + */ + public void pushStackBytes(final Bytes value) { + org.hyperledger.besu.evm.UInt256 operand; + if (value.size() >= 32) { + operand = + org.hyperledger.besu.evm.UInt256.fromBytesBE( + value.slice(value.size() - 32, 32).toArrayUnsafe()); + } else if (value.size() == 0) { + operand = org.hyperledger.besu.evm.UInt256.ZERO; + } else { + operand = + org.hyperledger.besu.evm.UInt256.fromBytesBE(Bytes32.leftPad(value).toArrayUnsafe()); + } + final int off = stackTop << 2; + stackData[off] = operand.u3(); + stackData[off + 1] = operand.u2(); + stackData[off + 2] = operand.u1(); + stackData[off + 3] = operand.u0(); + stackTop++; + } + + /** + * Returns the stack item at the specified offset as Bytes. For non-phase-1 operations that still + * work with Bytes. * * @param offset The item's position relative to the top of the stack - * @param value The value to set the stack item to - * @throws IllegalStateException if the stack is too small + * @return The item as Bytes */ - public void setStackItem(final int offset, final Bytes value) { - stack.set(offset, value); + public Bytes getStackBytes(final int offset) { + final int off = (stackTop - 1 - offset) << 2; + return Bytes.wrap( + new org.hyperledger.besu.evm.UInt256( + stackData[off], stackData[off + 1], stackData[off + 2], stackData[off + 3]) + .toBytesBE()); + } + + /** + * Sets the stack item at the specified offset from a Bytes value. For non-phase-1 operations that + * still work with Bytes. + * + * @param offset The item's position relative to the top of the stack + * @param value The Bytes value to set + */ + public void setStackBytes(final int offset, final Bytes value) { + org.hyperledger.besu.evm.UInt256 operand; + if (value.size() >= 32) { + operand = + org.hyperledger.besu.evm.UInt256.fromBytesBE( + value.slice(value.size() - 32, 32).toArrayUnsafe()); + } else if (value.size() == 0) { + operand = org.hyperledger.besu.evm.UInt256.ZERO; + } else { + operand = + org.hyperledger.besu.evm.UInt256.fromBytesBE(Bytes32.leftPad(value).toArrayUnsafe()); + } + final int off = (stackTop - 1 - offset) << 2; + stackData[off] = operand.u3(); + stackData[off + 1] = operand.u2(); + stackData[off + 2] = operand.u1(); + stackData[off + 3] = operand.u0(); } + // -------------------------------------------------------------------------- + // endregion + /** * Return the current stack size. * * @return The current stack size */ public int stackSize() { - return stack.size(); + return stackTop; } /** @@ -549,7 +661,7 @@ public void setRevertReason(final Bytes revertReason) { * @param length The length of the bytes to read * @return The bytes in the specified range */ - public MutableBytes readMutableMemory(final long offset, final long length) { + public Bytes readMutableMemory(final long offset, final long length) { return readMutableMemory(offset, length, false); } @@ -561,7 +673,7 @@ public MutableBytes readMutableMemory(final long offset, final long length) { * @return The bytes in the specified range */ public Bytes shadowReadMemory(final long offset, final long length) { - return memory.getBytesWithoutGrowth(offset, length); + return Bytes.wrap(memory.getBytesWithoutGrowth(offset, length)); } /** @@ -572,27 +684,50 @@ public Bytes shadowReadMemory(final long offset, final long length) { * @return The bytes in the specified range */ public Bytes readMemory(final long offset, final long length) { - return readMutableMemory(offset, length, false).copy(); + return readMutableMemory(offset, length, false); } /** - * Read bytes in memory. Contents should not be considered stable outside the scope of the current - * operation. + * Read bytes in memory. * * @param offset The offset in memory * @param length The length of the bytes to read * @param explicitMemoryRead true if triggered by a memory opcode, false otherwise * @return The bytes in the specified range */ - public MutableBytes readMutableMemory( + public Bytes readMutableMemory( final long offset, final long length, final boolean explicitMemoryRead) { - final MutableBytes memBytes = memory.getMutableBytes(offset, length); + final Bytes memBytes = Bytes.wrap(memory.getBytes(offset, length)); if (explicitMemoryRead) { setUpdatedMemory(offset, memBytes); } return memBytes; } + /** + * Read 32 bytes from memory directly into stack limbs, bypassing intermediate buffers. + * + * @param offset The offset in memory to read from. + * @param s The stack long array. + * @param slotOff The offset in the stack array (limb index). + */ + public void mloadDirect(final long offset, final long[] s, final int slotOff) { + memory.readLimbs(offset, s, slotOff); + setUpdatedMemory(offset, Bytes.wrap(memory.getBytes(offset, 32))); + } + + /** + * Write 32 bytes from stack limbs directly into memory, bypassing intermediate buffers. + * + * @param offset The offset in memory to write to. + * @param s The stack long array. + * @param slotOff The offset in the stack array (limb index). + */ + public void mstoreDirect(final long offset, final long[] s, final int slotOff) { + memory.writeLimbs(offset, s, slotOff); + setUpdatedMemory(offset, Bytes.wrap(memory.getBytes(offset, 32))); + } + /** * Write byte to memory * @@ -628,7 +763,7 @@ public void writeMemory(final long offset, final long length, final Bytes value) */ public void writeMemory( final long offset, final long length, final Bytes value, final boolean explicitMemoryUpdate) { - memory.setBytes(offset, length, value); + memory.setBytes(offset, length, value.toArrayUnsafe()); if (explicitMemoryUpdate) { setUpdatedMemory(offset, 0, length, value); } @@ -646,7 +781,7 @@ public void writeMemory( */ public void writeMemoryRightAligned( final long offset, final long length, final Bytes value, final boolean explicitMemoryUpdate) { - memory.setBytesRightAligned(offset, length, value); + memory.setBytesRightAligned(offset, length, value.toArrayUnsafe()); if (explicitMemoryUpdate) { setUpdatedMemoryRightAligned(offset, length, value); } @@ -680,7 +815,7 @@ public void writeMemory( final long length, final Bytes value, final boolean explicitMemoryUpdate) { - memory.setBytes(offset, sourceOffset, length, value); + memory.setBytes(offset, sourceOffset, length, value.toArrayUnsafe()); if (explicitMemoryUpdate && length > 0) { setUpdatedMemory(offset, sourceOffset, length, value); } @@ -701,7 +836,7 @@ public void copyMemory( if (length > 0) { memory.copy(dst, src, length); if (explicitMemoryUpdate) { - setUpdatedMemory(dst, memory.getBytes(dst, length)); + setUpdatedMemory(dst, Bytes.wrap(memory.getBytes(dst, length))); } } } @@ -712,11 +847,12 @@ private void setUpdatedMemory( if (sourceOffset >= 0 && endIndex > 0) { final int srcSize = value.size(); if (endIndex > srcSize) { - final MutableBytes paddedAnswer = MutableBytes.create((int) length); + final byte[] paddedAnswer = new byte[(int) length]; if (sourceOffset < srcSize) { - value.slice((int) sourceOffset, (int) (srcSize - sourceOffset)).copyTo(paddedAnswer, 0); + final int copyLen = (int) (srcSize - sourceOffset); + System.arraycopy(value.toArrayUnsafe(), (int) sourceOffset, paddedAnswer, 0, copyLen); } - setUpdatedMemory(offset, paddedAnswer.copy()); + setUpdatedMemory(offset, Bytes.wrap(paddedAnswer)); } else { setUpdatedMemory(offset, value.slice((int) sourceOffset, (int) length).copy()); } @@ -728,11 +864,12 @@ private void setUpdatedMemoryRightAligned( if (length > 0) { final int srcSize = value.size(); if (length > srcSize) { - final MutableBytes paddedAnswer = MutableBytes.create((int) length); - if ((long) 0 < srcSize) { - value.slice(0, srcSize).copyTo(paddedAnswer, (int) (length - srcSize)); + final byte[] paddedAnswer = new byte[(int) length]; + if (srcSize > 0) { + System.arraycopy( + value.toArrayUnsafe(), 0, paddedAnswer, (int) (length - srcSize), srcSize); } - setUpdatedMemory(offset, paddedAnswer.copy()); + setUpdatedMemory(offset, Bytes.wrap(paddedAnswer)); } else { setUpdatedMemory(offset, value.slice(0, (int) length).copy()); } @@ -749,7 +886,8 @@ private void setUpdatedMemory(final long offset, final Bytes value) { * @param storageAddress the storage address * @param value the value */ - public void storageWasUpdated(final UInt256 storageAddress, final Bytes value) { + public void storageWasUpdated( + final org.apache.tuweni.units.bigints.UInt256 storageAddress, final Bytes value) { maybeUpdatedStorage = Optional.of(new StorageEntry(storageAddress, value)); } @@ -1079,6 +1217,16 @@ public BlockValues getBlockValues() { return txValues.blockValues(); } + /** Returns this frame's operand stack to the thread-local pool for reuse. */ + public void returnStackToPool() { + if (stackTop > 0) { + Arrays.fill(stackData, 0, stackTop << 2, 0L); + } + stackTop = 0; + StackPool.release(stackData, stackMaxSize); + stackData = null; + } + /** Performs updates based on the message frame's execution. */ public void notifyCompletion() { completer.accept(this); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/FixedStack.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/FixedStack.java deleted file mode 100644 index 9c9b47e32ca..00000000000 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/FixedStack.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * 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.internal; - -import static com.google.common.base.Preconditions.checkArgument; - -import java.lang.reflect.Array; -import java.util.Arrays; - -/** - * An operand stack for the Ethereum Virtual machine (EVM). This stack allocates the entire stack at - * initialization time. - * - *

The operand stack is responsible for storing the current operands that the EVM can execute. It - * is assumed to have a fixed size. - * - * @param the type parameter - */ -public class FixedStack { - - private final T[] entries; - - private final int maxSize; - - private int top; - - /** - * Instantiates a new Fixed stack. - * - * @param maxSize the max size - * @param klass the klass - */ - @SuppressWarnings("unchecked") - public FixedStack(final int maxSize, final Class klass) { - checkArgument(maxSize >= 0, "max size must be non-negative"); - - this.entries = (T[]) Array.newInstance(klass, maxSize); - this.maxSize = maxSize; - this.top = -1; - } - - /** - * Get operand. - * - * @param offset the offset - * @return the operand - */ - public T get(final int offset) { - if (offset < 0 || offset >= size()) { - throw new UnderflowException(); - } - - return entries[top - offset]; - } - - /** - * Pop operand. - * - * @return the operand - */ - public T pop() { - if (top < 0) { - throw new UnderflowException(); - } - - final T removed = entries[top]; - entries[top--] = null; - return removed; - } - - /** - * Peek and return type T. - * - * @return the T entry - */ - public T peek() { - if (top < 0) { - return null; - } else { - return entries[top]; - } - } - - /** - * Pops the specified number of operands from the stack. - * - * @param items the number of operands to pop off the stack - * @throws IllegalArgumentException if the items to pop is negative. - * @throws UnderflowException when the items to pop is greater than {@link #size()} - */ - public void bulkPop(final int items) { - checkArgument(items > 0, "number of items to pop must be greater than 0"); - if (items > size()) { - throw new UnderflowException(); - } - - Arrays.fill(entries, top - items + 1, top + 1, null); - top -= items; - } - - /** - * Trims the "middle" section of items out of the stack. Items below the cutpoint remains, and of - * the items above only the itemsToKeep items remain. All items in the middle are removed. - * - * @param cutPoint Point at which to start removing items - * @param itemsToKeep itemsToKeep Number of items on top to place at the cutPoint - * @throws IllegalArgumentException if the cutPoint or items to keep is negative. - * @throws UnderflowException If there are less than itemsToKeep above the cutPoint - */ - public void preserveTop(final int cutPoint, final int itemsToKeep) { - checkArgument(cutPoint >= 0, "cutPoint must be positive"); - checkArgument(itemsToKeep >= 0, "itemsToKeep must be positive"); - if (itemsToKeep == 0) { - if (cutPoint < size()) { - bulkPop(top - cutPoint); - } - } else { - int targetSize = cutPoint + itemsToKeep; - int currentSize = size(); - if (targetSize > currentSize) { - throw new UnderflowException(); - } else if (targetSize < currentSize) { - System.arraycopy(entries, currentSize - itemsToKeep, entries, cutPoint, itemsToKeep); - Arrays.fill(entries, targetSize, currentSize, null); - top = targetSize - 1; - } - } - } - - /** - * Push operand. - * - * @param operand the operand - */ - public void push(final T operand) { - final int nextTop = top + 1; - if (nextTop == maxSize) { - throw new OverflowException(); - } - entries[nextTop] = operand; - top = nextTop; - } - - /** - * Set operand. - * - * @param offset the offset - * @param operand the operand - */ - public void set(final int offset, final T operand) { - if (offset < 0) { - throw new UnderflowException(); - } else if (offset >= size()) { - throw new OverflowException(); - } - - entries[top - offset] = operand; - } - - /** - * Size of entries. - * - * @return the size - */ - public int size() { - return top + 1; - } - - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - for (int i = 0; i < entries.length; ++i) { - builder.append(String.format("%n0x%04X ", i)).append(entries[i]); - } - return builder.toString(); - } - - @Override - public int hashCode() { - return Arrays.hashCode(entries); - } - - @SuppressWarnings("unchecked") - @Override - public boolean equals(final Object other) { - if (!(other instanceof FixedStack)) { - return false; - } - - final FixedStack that = (FixedStack) other; - return Arrays.deepEquals(this.entries, that.entries); - } - - /** - * Is stack full. - * - * @return the boolean - */ - public boolean isFull() { - return top + 1 >= maxSize; - } - - /** - * Is stack empty. - * - * @return the boolean - */ - public boolean isEmpty() { - return top < 0; - } -} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/FlexStack.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/FlexStack.java deleted file mode 100644 index c0267489c5d..00000000000 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/FlexStack.java +++ /dev/null @@ -1,282 +0,0 @@ -/* - * 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.internal; - -import static com.google.common.base.Preconditions.checkArgument; - -import org.hyperledger.besu.evm.frame.MessageFrame; - -import java.lang.reflect.Array; -import java.util.Arrays; -import java.util.Objects; - -/** - * An operand stack for the Ethereum Virtual machine (EVM). The stack grows 32 entries at a time if - * it expands past the top of the allocated stack, up to maxSize. - * - *

The operand stack is responsible for storing the current operands that the EVM can execute. It - * is assumed to have a fixed maximum size but may have a smaller memory footprint. - * - * @param the type parameter - */ -public class FlexStack { - /** - * Formula `x = round( y / ( (1 + 0,5)^n ) ) + 1`, computes the initial stack size, `x` that one - * has to start with to reach a maximum stack size, `y`, in `n` number of array resizes at a - * growth rate of 50%. Currently, for mainnet y=1024 and, if considering n=6 in the worst case, - * the start size is 91 which is reasonable for mainnet. - */ - private static final int INITIAL_SIZE = - (int) Math.round(MessageFrame.DEFAULT_MAX_STACK_SIZE / Math.pow(1.5D, 6D)) + 1; - - /** - * Soft limit imposed for growing arrays. JVMs do not allow to allocate arrays above certain - * length, and you will get the following exception if trying to do so: - * - *

java.lang.OutOfMemoryError: Requested array size exceeds VM limit - * - *

Therefore the maxSize of any stack is capped to this value. This max value is not arbitrary - * and was taken from OpenJDK. - */ - private static final int MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8; - - private T[] entries; - - private final int maxSize; - private int currentCapacity; - - private int top; - - /** - * Instantiates a new Flex stack. - * - * @param maxSize the max size - * @param klass the klass - */ - @SuppressWarnings("unchecked") - public FlexStack(final int maxSize, final Class klass) { - checkArgument(maxSize > 0, "max size must be positive"); - checkArgument(maxSize <= MAX_ARRAY_LENGTH, "max size is too large"); - - this.currentCapacity = Math.min(INITIAL_SIZE, maxSize); - this.entries = (T[]) Array.newInstance(klass, currentCapacity); - this.maxSize = maxSize; - this.top = -1; - } - - /** - * Get operand. - * - * @param offset the offset - * @return the operand - */ - public T get(final int offset) { - if (offset < 0 || offset >= size()) { - throw new UnderflowException(); - } - - return entries[top - offset]; - } - - /** - * Pop operand. - * - * @return the operand - */ - public T pop() { - if (top < 0) { - throw new UnderflowException(); - } - - final T removed = entries[top]; - entries[top--] = null; - return removed; - } - - /** - * Peek and return type T. - * - * @return the T entry - */ - public T peek() { - if (top < 0) { - return null; - } else { - return entries[top]; - } - } - - /** - * Pops the specified number of operands from the stack. - * - * @param items the number of operands to pop off the stack - * @throws IllegalArgumentException if the items to pop is negative. - * @throws UnderflowException when the items to pop is greater than {@link #size()} - */ - public void bulkPop(final int items) { - checkArgument(items > 0, "number of items to pop must be greater than 0"); - if (items > size()) { - throw new UnderflowException(); - } - - Arrays.fill(entries, top - items + 1, top + 1, null); - top -= items; - } - - /** - * Trims the "middle" section of items out of the stack. Items below the cutpoint remains, and of - * the items above only the itemsToKeep items remain. All items in the middle are removed. - * - * @param cutPoint Point at which to start removing items - * @param itemsToKeep itemsToKeep Number of items on top to place at the cutPoint - * @throws IllegalArgumentException if the cutPoint or items to keep is negative. - * @throws UnderflowException If there are less than itemsToKeep above the cutPoint - */ - public void preserveTop(final int cutPoint, final int itemsToKeep) { - checkArgument(cutPoint >= 0, "cutPoint must be positive"); - checkArgument(itemsToKeep >= 0, "itemsToKeep must be positive"); - if (itemsToKeep == 0) { - if (cutPoint < size()) { - bulkPop(top - cutPoint); - } - } else { - int targetSize = cutPoint + itemsToKeep; - int currentSize = size(); - if (targetSize > currentSize) { - throw new UnderflowException(); - } else if (targetSize < currentSize) { - System.arraycopy(entries, currentSize - itemsToKeep, entries, cutPoint, itemsToKeep); - Arrays.fill(entries, targetSize, currentSize, null); - top = targetSize - 1; - } - } - } - - @SuppressWarnings("unchecked") - private void expandEntries(final int nextSize) { - var nextEntries = (T[]) Array.newInstance(entries.getClass().getComponentType(), nextSize); - System.arraycopy(entries, 0, nextEntries, 0, currentCapacity); - entries = nextEntries; - currentCapacity = nextSize; - } - - /** - * Push operand. - * - * @param operand the operand - */ - public void push(final T operand) { - final int nextTop = top + 1; - if (nextTop >= maxSize) { - throw new OverflowException(); - } - if (nextTop >= currentCapacity) { - final int newCapacity = newLength(currentCapacity, currentCapacity >> 1); - expandEntries(newCapacity); - } - entries[nextTop] = operand; - top = nextTop; - } - - private int newLength(final int oldCapacity, final int prefGrowth) { - final int growth = Math.max(1, prefGrowth); - if (MAX_ARRAY_LENGTH - growth < oldCapacity) { - return maxSize; - } - return Math.min(oldCapacity + growth, maxSize); - } - - /** - * Set operand. - * - * @param offset the offset - * @param operand the operand - */ - public void set(final int offset, final T operand) { - if (offset < 0) { - throw new UnderflowException(); - } else if (offset > top) { - throw new OverflowException(); - } - - entries[top - offset] = operand; - } - - /** - * Size of entries. - * - * @return the size - */ - public int size() { - return top + 1; - } - - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - for (int i = 0; i < top; ++i) { - builder.append(String.format("%n0x%04X ", i)).append(entries[i]); - } - return builder.toString(); - } - - @Override - public int hashCode() { - int result = 1; - - for (int i = 0; i < currentCapacity; i++) { - result = 31 * result + (entries[i] == null ? 0 : entries[i].hashCode()); - } - - return result; - } - - @SuppressWarnings("unchecked") - @Override - public boolean equals(final Object other) { - if (!(other instanceof FlexStack)) { - return false; - } - - final FlexStack that = (FlexStack) other; - if (this.currentCapacity != that.currentCapacity) { - return false; - } - for (int i = 0; i < currentCapacity; i++) { - if (!Objects.deepEquals(this.entries[i], that.entries[i])) { - return false; - } - } - return true; - } - - /** - * Is stack full. - * - * @return the boolean - */ - public boolean isFull() { - return top + 1 >= maxSize; - } - - /** - * Is stack empty. - * - * @return the boolean - */ - public boolean isEmpty() { - return top < 0; - } -} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/OperandStack.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/OperandStack.java deleted file mode 100644 index a88c0e65ca5..00000000000 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/OperandStack.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.internal; - -import org.apache.tuweni.bytes.Bytes; - -/** The Operand stack. */ -public class OperandStack extends FlexStack { - - /** - * Instantiates a new Operand stack. - * - * @param maxSize the max size - */ - public OperandStack(final int maxSize) { - super(maxSize, Bytes.class); - } -} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/OverflowException.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/OverflowException.java deleted file mode 100644 index 0621bbc3244..00000000000 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/OverflowException.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.internal; - -/** - * Overflow exception for {@link FixedStack} and {@link FlexStack}. The main need for a separate - * class is to remove the stack trace generation as the exception is not used to signal a debuggable - * failure but instead an expected edge case the EVM should handle. - */ -public class OverflowException extends RuntimeException { - - /** Default constructor. */ - public OverflowException() {} - - /** - * Overload the stack trace fill in so no stack is filled in. This is done for performance reasons - * as this exception signals an expected corner case not a debuggable failure. - * - * @return the exception, with no stack trace filled in. - */ - @Override - public synchronized Throwable fillInStackTrace() { - return this; - } -} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/StackMath.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/StackMath.java new file mode 100644 index 00000000000..91dccbacd82 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/StackMath.java @@ -0,0 +1,1006 @@ +/* + * 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.internal; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.UInt256; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.math.BigInteger; +import java.nio.ByteOrder; + +/** + * 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 final class StackMath { + + 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); + + 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); + } + + private StackMath() {} + + // ── Binary ops (pop 2, push 1, return top-1) ────────────────────────── + + /** ADD: s[top-2] = s[top-1] + s[top-2], return top-1. */ + 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. */ + 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. */ + 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. */ + 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. */ + 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. */ + 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. */ + 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; + } + + /** AND: s[top-2] = s[top-1] & s[top-2], return top-1. */ + 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. */ + 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. */ + 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; + } + + /** LT: s[top-2] = (s[top-1] < s[top-2]) ? 1 : 0, return top-1. Unsigned. */ + 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. */ + 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. */ + 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. */ + 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; + } + + /** SHL: s[top-2] = s[top-2] << s[top-1], return top-1. */ + public static int shl(final long[] s, final int top) { + final int a = (top - 1) << 2; // shift amount + final int b = (top - 2) << 2; // value + // If shift amount > 255 or value is zero, result is zero + if (s[a] != 0 + || s[a + 1] != 0 + || s[a + 2] != 0 + || Long.compareUnsigned(s[a + 3], 256) >= 0 + || (s[b] == 0 && s[b + 1] == 0 && s[b + 2] == 0 && s[b + 3] == 0)) { + s[b] = 0; + s[b + 1] = 0; + s[b + 2] = 0; + s[b + 3] = 0; + return top - 1; + } + int shift = (int) s[a + 3]; + shiftLeftInPlace(s, b, shift); + return top - 1; + } + + /** SHR: s[top-2] = s[top-2] >>> s[top-1], return top-1. */ + public static int shr(final long[] s, final int top) { + final int a = (top - 1) << 2; // shift amount + final int b = (top - 2) << 2; // value + if (s[a] != 0 + || s[a + 1] != 0 + || s[a + 2] != 0 + || Long.compareUnsigned(s[a + 3], 256) >= 0 + || (s[b] == 0 && s[b + 1] == 0 && s[b + 2] == 0 && s[b + 3] == 0)) { + s[b] = 0; + s[b + 1] = 0; + s[b + 2] = 0; + s[b + 3] = 0; + return top - 1; + } + int shift = (int) s[a + 3]; + shiftRightInPlace(s, b, shift); + return top - 1; + } + + /** SAR: s[top-2] = s[top-2] >> s[top-1] (arithmetic), return top-1. */ + public static int sar(final long[] s, final int top) { + final int a = (top - 1) << 2; // shift amount + final int b = (top - 2) << 2; // value + boolean negative = s[b] < 0; // MSB of u3 + + if (s[a] != 0 || s[a + 1] != 0 || s[a + 2] != 0 || Long.compareUnsigned(s[a + 3], 256) >= 0) { + long fill = negative ? -1L : 0L; + s[b] = fill; + s[b + 1] = fill; + s[b + 2] = fill; + s[b + 3] = fill; + return top - 1; + } + int shift = (int) s[a + 3]; + sarInPlace(s, b, shift, negative); + return top - 1; + } + + /** BYTE: s[top-2] = byte at offset s[top-1] of s[top-2], return top-1. */ + 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. */ + 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; + } + + // ── Unary ops (pop 1, push 1, return top) ───────────────────────────── + + /** NOT: s[top-1] = ~s[top-1], return top. */ + 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. */ + 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. */ + 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; + } + + // ── Ternary ops (pop 3, push 1, return top-2) ───────────────────────── + + /** ADDMOD: s[top-3] = (s[top-1] + s[top-2]) mod s[top-3], return top-2. */ + 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. */ + 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; + } + + /** EXP: s[top-2] = s[top-1] ** s[top-2] mod 2^256, return top-1. */ + 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)); + UInt256 r = UInt256.fromBigInteger(result); + s[b] = r.u3(); + s[b + 1] = r.u2(); + s[b + 2] = r.u1(); + s[b + 3] = r.u0(); + return top - 1; + } + + // ── Stack manipulation ───────────────────────────────────────────────── + + /** DUP: copy slot at depth → new top, return top+1. depth is 1-based (DUP1 = depth 1). */ + 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 ↔ slot at depth, return top. depth is 1-based (SWAP1 = depth 1). */ + 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 ↔ slot at m (both 0-indexed from top), return top. */ + 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. */ + 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. */ + 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; + } + + /** 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; + } + + /** Push a long value (GAS, NUMBER, etc.), return top+1. */ + 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 an Address (20 bytes), return top+1. */ + 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; + } + + // ── Boundary helpers (read/write slots without changing top) ─────────── + + /** Extract u0 (LSB limb) of slot at depth from top. */ + 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. */ + 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. */ + 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. */ + 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. + */ + 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. + */ + 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. */ + 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. */ + 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; + } + } + + /** 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); + } + + /** Extract 20-byte Address from slot at depth. */ + 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). */ + 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 UInt256 record into slot at depth. */ + 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. */ + 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. */ + 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; + } + + // ── Internal helpers ─────────────────────────────────────────────────── + + private static int byteLen(final long v) { + return (64 - Long.numberOfLeadingZeros(v) + 7) / 8; + } + + private static void longIntoBytes(final byte[] bytes, final int offset, final long value) { + putLong(bytes, offset, value); + } + + /** 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]); + } + + /** Shift left in place. shift must be 0..255. */ + private static void shiftLeftInPlace(final long[] s, final int off, final int shift) { + if (shift == 0) return; + int limbShift = shift >>> 6; + int bitShift = shift & 63; + + // Move limbs (stored as [u3, u2, u1, u0] at [off, off+1, off+2, off+3]) + // u3=off, u2=off+1, u1=off+2, u0=off+3 + long u0 = s[off + 3], u1 = s[off + 2], u2 = s[off + 1], u3 = s[off]; + long a0, a1, a2, a3; + switch (limbShift) { + case 0: + a0 = u0; + a1 = u1; + a2 = u2; + a3 = u3; + break; + case 1: + a0 = 0; + a1 = u0; + a2 = u1; + a3 = u2; + break; + case 2: + a0 = 0; + a1 = 0; + a2 = u0; + a3 = u1; + break; + case 3: + a0 = 0; + a1 = 0; + a2 = 0; + a3 = u0; + break; + default: + s[off] = 0; + s[off + 1] = 0; + s[off + 2] = 0; + s[off + 3] = 0; + return; + } + + if (bitShift == 0) { + s[off] = a3; + s[off + 1] = a2; + s[off + 2] = a1; + s[off + 3] = a0; + } else { + int inv = 64 - bitShift; + s[off + 3] = a0 << bitShift; + s[off + 2] = (a1 << bitShift) | (a0 >>> inv); + s[off + 1] = (a2 << bitShift) | (a1 >>> inv); + s[off] = (a3 << bitShift) | (a2 >>> inv); + } + } + + /** Logical shift right in place. shift must be 0..255. */ + private static void shiftRightInPlace(final long[] s, final int off, final int shift) { + if (shift == 0) return; + int limbShift = shift >>> 6; + int bitShift = shift & 63; + + long u0 = s[off + 3], u1 = s[off + 2], u2 = s[off + 1], u3 = s[off]; + long a0, a1, a2, a3; + switch (limbShift) { + case 0: + a0 = u0; + a1 = u1; + a2 = u2; + a3 = u3; + break; + case 1: + a0 = u1; + a1 = u2; + a2 = u3; + a3 = 0; + break; + case 2: + a0 = u2; + a1 = u3; + a2 = 0; + a3 = 0; + break; + case 3: + a0 = u3; + a1 = 0; + a2 = 0; + a3 = 0; + break; + default: + s[off] = 0; + s[off + 1] = 0; + s[off + 2] = 0; + s[off + 3] = 0; + return; + } + + if (bitShift == 0) { + s[off] = a3; + s[off + 1] = a2; + s[off + 2] = a1; + s[off + 3] = a0; + } else { + int inv = 64 - bitShift; + s[off] = a3 >>> bitShift; + s[off + 1] = (a2 >>> bitShift) | (a3 << inv); + s[off + 2] = (a1 >>> bitShift) | (a2 << inv); + s[off + 3] = (a0 >>> bitShift) | (a1 << inv); + } + } + + /** Arithmetic shift right in place. shift must be 0..255. */ + private static void sarInPlace( + final long[] s, final int off, final int shift, final boolean negative) { + if (shift == 0) return; + int limbShift = shift >>> 6; + int bitShift = shift & 63; + long fill = negative ? -1L : 0L; + + long u0 = s[off + 3], u1 = s[off + 2], u2 = s[off + 1], u3 = s[off]; + long a0, a1, a2, a3; + switch (limbShift) { + case 0: + a0 = u0; + a1 = u1; + a2 = u2; + a3 = u3; + break; + case 1: + a0 = u1; + a1 = u2; + a2 = u3; + a3 = fill; + break; + case 2: + a0 = u2; + a1 = u3; + a2 = fill; + a3 = fill; + break; + case 3: + a0 = u3; + a1 = fill; + a2 = fill; + a3 = fill; + break; + default: + s[off] = fill; + s[off + 1] = fill; + s[off + 2] = fill; + s[off + 3] = fill; + return; + } + + if (bitShift == 0) { + s[off] = a3; + s[off + 1] = a2; + s[off + 2] = a1; + s[off + 3] = a0; + } else { + int inv = 64 - bitShift; + s[off] = a3 >> bitShift; // arithmetic shift for MSB + s[off + 1] = (a2 >>> bitShift) | (a3 << inv); + s[off + 2] = (a1 >>> bitShift) | (a2 << inv); + s[off + 3] = (a0 >>> bitShift) | (a1 << inv); + } + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/StackPool.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/StackPool.java new file mode 100644 index 00000000000..01058dc9214 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/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.internal; + +/** + * 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/internal/UnderflowException.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/UnderflowException.java deleted file mode 100644 index 9f102690e8d..00000000000 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/UnderflowException.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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.internal; - -/** - * Underflow exception for {@link FixedStack} and {@link FlexStack}. The main need for a separate - * class is to remove the stack trace generation as the exception is not used to signal a debuggable - * failure but instead an expected edge case the EVM should handle. - */ -public class UnderflowException extends RuntimeException { - /** Default constructor. */ - public UnderflowException() {} - - /** - * Overload the stack trace fill in so no stack is filled in. This is done for performance reasons - * as this exception signals an expected corner case not a debuggable failure. - * - * @return the exception, with no stack trace filled in. - */ - @Override - public synchronized Throwable fillInStackTrace() { - return this; - } -} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/Words.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/Words.java index 252164f436e..8ac34273417 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/Words.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/Words.java @@ -58,6 +58,17 @@ static Address toAddress(final Bytes bytes) { } } + /** + * Extract an address from a native UInt256 value. + * + * @param value The UInt256 to extract the address from. + * @return An address built from the right-most 160-bits of the value. + */ + static Address toAddress(final org.hyperledger.besu.evm.UInt256 value) { + final byte[] bytes = value.toBytesBE(); + return Address.wrap(Bytes.wrap(bytes, 12, 20)); + } + /** * The number of words corresponding to the provided input. * @@ -91,6 +102,36 @@ static int numWords(final int length) { * @param uint the unsigned integer * @return the least of the integer value or Long.MAX_VALUE */ + /** + * Clamp a native UInt256 value to a long, returning Long.MAX_VALUE if it doesn't fit. + * + * @param uint the unsigned integer + * @return the least of the value or Long.MAX_VALUE + */ + static long clampedToLong(final org.hyperledger.besu.evm.UInt256 uint) { + if (uint.u3() != 0 || uint.u2() != 0 || uint.u1() != 0 || uint.u0() < 0) { + return Long.MAX_VALUE; + } + return uint.u0(); + } + + /** + * Clamp a native UInt256 value to an int, returning Integer.MAX_VALUE if it doesn't fit. + * + * @param uint the unsigned integer + * @return the least of the value or Integer.MAX_VALUE + */ + static int clampedToInt(final org.hyperledger.besu.evm.UInt256 uint) { + if (uint.u3() != 0 + || uint.u2() != 0 + || uint.u1() != 0 + || uint.u0() < 0 + || uint.u0() > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + return (int) uint.u0(); + } + static long clampedToLong(final Bytes uint) { if (uint.size() <= 8) { final long result = uint.toLong(); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java index 6130f48c117..c1f2cdf00be 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java @@ -17,7 +17,6 @@ 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.internal.Words.clampedToLong; import static org.hyperledger.besu.evm.worldstate.CodeDelegationHelper.getTarget; import static org.hyperledger.besu.evm.worldstate.CodeDelegationHelper.hasCodeDelegation; @@ -32,6 +31,7 @@ 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.internal.StackMath; import org.hyperledger.besu.evm.worldstate.CodeDelegationHelper; import org.apache.tuweni.bytes.Bytes; @@ -76,7 +76,7 @@ public abstract class AbstractCallOperation extends AbstractOperation { * @return the additional gas to provide the call operation */ protected long gas(final MessageFrame frame) { - return clampedToLong(frame.getStackItem(0)); + return StackMath.clampedToLong(frame.stackData(), frame.stackTop(), 0); } /** @@ -254,8 +254,10 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { // charged. If we return zero then the traces don't have the right per-opcode cost. final long gasAvailableForChildCall = gasAvailableForChildCall(frame); frame.incrementRemainingGas(gasAvailableForChildCall + cost); - frame.popStackItems(getStackItemsConsumed()); - frame.pushStackItem(LEGACY_FAILURE_STACK_ITEM); + final long[] s = frame.stackData(); + final int newTop = frame.stackTop() - getStackItemsConsumed() + 1; + StackMath.putAt(s, newTop, 0, 0, 0, 0, 0); + frame.setTop(newTop); final SoftFailureReason softFailureReason = insufficientBalance ? LEGACY_INSUFFICIENT_BALANCE : LEGACY_MAX_CALL_DEPTH; return new OperationResult(cost, 1, softFailureReason, gasAvailableForChildCall); @@ -326,24 +328,16 @@ public void complete(final MessageFrame frame, final MessageFrame childFrame) { final long gasRemaining = childFrame.getRemainingGas(); frame.incrementRemainingGas(gasRemaining); - frame.popStackItems(getStackItemsConsumed()); - Bytes resultItem; - - resultItem = getCallResultStackItem(childFrame); - frame.pushStackItem(resultItem); + final long[] s = frame.stackData(); + final int newTop = frame.stackTop() - getStackItemsConsumed() + 1; + final long resultU0 = childFrame.getState() == State.COMPLETED_SUCCESS ? 1L : 0L; + StackMath.putAt(s, newTop, 0, 0, 0, 0, resultU0); + frame.setTop(newTop); final int currentPC = frame.getPC(); frame.setPC(currentPC + 1); } - Bytes getCallResultStackItem(final MessageFrame childFrame) { - if (childFrame.getState() == State.COMPLETED_SUCCESS) { - return LEGACY_SUCCESS_STACK_ITEM; - } else { - return LEGACY_FAILURE_STACK_ITEM; - } - } - /** * Gets the code from the contract or EOA with delegated code. * diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java index 7ed27d2fe5c..5549c3bc41d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java @@ -17,7 +17,6 @@ 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 static org.hyperledger.besu.evm.internal.Words.clampedToLong; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; @@ -28,7 +27,7 @@ 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.internal.Words; +import org.hyperledger.besu.evm.internal.StackMath; import java.util.Optional; import java.util.function.Supplier; @@ -82,7 +81,8 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { if (frame.getRemainingGas() < cost) { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); } - final Wei value = Wei.wrap(frame.getStackItem(0)); + final Wei value = + Wei.wrap(Bytes.wrap(StackMath.getAt(frame.stackData(), frame.stackTop(), 0).toBytesBE())); final Address address = frame.getRecipientAddress(); final MutableAccount account = getMutableAccount(address, frame); @@ -92,7 +92,7 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { Code code = codeSupplier.get(); if (code != null && code.getSize() > evm.getMaxInitcodeSize()) { - frame.popStackItems(getStackItemsConsumed()); + frame.setTop(frame.stackTop() - getStackItemsConsumed()); return new OperationResult(cost, ExceptionalHaltReason.CODE_TOO_LARGE); } @@ -161,15 +161,19 @@ protected int getPcIncrement() { * @param frame the current execution frame */ protected void fail(final MessageFrame frame) { - final long inputOffset = clampedToLong(frame.getStackItem(1)); - final long inputSize = clampedToLong(frame.getStackItem(2)); + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final long inputOffset = StackMath.clampedToLong(s, top, 1); + final long inputSize = StackMath.clampedToLong(s, top, 2); frame.readMutableMemory(inputOffset, inputSize); - frame.popStackItems(getStackItemsConsumed()); - frame.pushStackItem(Bytes.EMPTY); + final int newTop = top - getStackItemsConsumed() + 1; + StackMath.putAt(s, newTop, 0, 0, 0, 0, 0); + frame.setTop(newTop); } private void spawnChildMessage(final MessageFrame parent, final Code code) { - final Wei value = Wei.wrap(parent.getStackItem(0)); + final Wei value = + Wei.wrap(Bytes.wrap(StackMath.getAt(parent.stackData(), parent.stackTop(), 0).toBytesBE())); final Address contractAddress = generateTargetContractAddress(parent, code); final Bytes inputData = getInputData(parent); @@ -220,16 +224,18 @@ private void complete(final MessageFrame frame, final MessageFrame childFrame) { frame.addLogs(childFrame.getLogs()); frame.addSelfDestructs(childFrame.getSelfDestructs()); frame.addCreates(childFrame.getCreates()); - frame.popStackItems(getStackItemsConsumed()); + final long[] s = frame.stackData(); + final int newTop = frame.stackTop() - getStackItemsConsumed() + 1; if (childFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { - Address createdAddress = childFrame.getContractAddress(); - frame.pushStackItem(Words.fromAddress(createdAddress)); + final Address createdAddress = childFrame.getContractAddress(); + frame.setTop(StackMath.pushAddress(s, newTop - 1, createdAddress)); frame.setReturnData(Bytes.EMPTY); onSuccess(frame, createdAddress); } else { frame.setReturnData(childFrame.getOutputData()); - frame.pushStackItem(Bytes.EMPTY); + StackMath.putAt(s, newTop, 0, 0, 0, 0, 0); + frame.setTop(newTop); onFailure(frame, childFrame.getExceptionalHaltReason()); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractFixedCostOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractFixedCostOperation.java index bc9058c7f73..193c1dd6a82 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractFixedCostOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractFixedCostOperation.java @@ -18,21 +18,24 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.OverflowException; -import org.hyperledger.besu.evm.internal.UnderflowException; /** The Abstract fixed cost operation. */ abstract class AbstractFixedCostOperation extends AbstractOperation { + /** 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; /** The Out of gas response. */ protected final OperationResult outOfGasResponse; - private final OperationResult underflowResponse; - private final OperationResult overflowResponse; - /** The Gas cost. */ protected final long gasCost; @@ -57,24 +60,14 @@ protected AbstractFixedCostOperation( gasCost = fixedCost; successResponse = new OperationResult(gasCost, null); outOfGasResponse = new OperationResult(gasCost, ExceptionalHaltReason.INSUFFICIENT_GAS); - underflowResponse = - new OperationResult(gasCost, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); - overflowResponse = new OperationResult(gasCost, ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); } @Override public final OperationResult execute(final MessageFrame frame, final EVM evm) { - try { - if (frame.getRemainingGas() < gasCost) { - return outOfGasResponse; - } else { - return executeFixedCostOperation(frame, evm); - } - } catch (final UnderflowException ufe) { - return underflowResponse; - } catch (final OverflowException ofe) { - return overflowResponse; + if (frame.getRemainingGas() < gasCost) { + return outOfGasResponse; } + return executeFixedCostOperation(frame, evm); } /** diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AddModOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AddModOperation.java index 35ef56d6621..3c8c5092aae 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AddModOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AddModOperation.java @@ -17,11 +17,7 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import java.math.BigInteger; -import java.util.Arrays; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The Add mod operation. */ public class AddModOperation extends AbstractFixedCostOperation { @@ -40,7 +36,7 @@ public AddModOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -49,30 +45,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - - final Bytes value0 = frame.popStackItem(); - final Bytes value1 = frame.popStackItem(); - final Bytes value2 = frame.popStackItem(); - - if (value2.isZero()) { - frame.pushStackItem(Bytes.EMPTY); - } else { - BigInteger b0 = new BigInteger(1, value0.toArrayUnsafe()); - BigInteger b1 = new BigInteger(1, value1.toArrayUnsafe()); - BigInteger b2 = new BigInteger(1, value2.toArrayUnsafe()); - - BigInteger result = b0.add(b1).mod(b2); - Bytes resultBytes = Bytes.wrap(result.toByteArray()); - if (resultBytes.size() > 32) { - resultBytes = resultBytes.slice(resultBytes.size() - 32, 32); - } - - final byte[] padding = new byte[32 - resultBytes.size()]; - Arrays.fill(padding, result.signum() < 0 ? (byte) 0xFF : 0x00); - - frame.pushStackItem(Bytes.concatenate(Bytes.wrap(padding), resultBytes)); - } + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(3)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.addMod(s, frame.stackTop())); return addModSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AddModOperationOptimized.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AddModOperationOptimized.java deleted file mode 100644 index 9cbe403359b..00000000000 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AddModOperationOptimized.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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.operation; - -import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.UInt256; -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; - -/** The Add mod operation. */ -public class AddModOperationOptimized extends AbstractFixedCostOperation { - - private static final OperationResult addModSuccess = new OperationResult(8, null); - - /** - * Instantiates a new Add mod operation. - * - * @param gasCalculator the gas calculator - */ - public AddModOperationOptimized(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); - } - - /** - * Static operation. - * - * @param frame the frame - * @return the operation result - */ - public static OperationResult staticOperation(final MessageFrame frame) { - Bytes resultBytes; - - final Bytes value0 = frame.popStackItem(); - final Bytes value1 = frame.popStackItem(); - final Bytes value2 = frame.popStackItem(); - - if (value2.isZero()) { - resultBytes = Bytes.EMPTY; - } else { - UInt256 b0 = UInt256.fromBytesBE(value0.toArrayUnsafe()); - UInt256 b1 = UInt256.fromBytesBE(value1.toArrayUnsafe()); - UInt256 b2 = UInt256.fromBytesBE(value2.toArrayUnsafe()); - resultBytes = Bytes.wrap(b0.addMod(b1, b2).toBytesBE()); - } - - frame.pushStackItem(resultBytes); - return addModSuccess; - } -} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AddOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AddOperation.java index 976e30c1d5d..23c7a757555 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AddOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AddOperation.java @@ -17,10 +17,7 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import java.math.BigInteger; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The Add operation. */ public class AddOperation extends AbstractFixedCostOperation { @@ -40,7 +37,7 @@ public AddOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -49,20 +46,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - final BigInteger value0 = new BigInteger(1, frame.popStackItem().toArrayUnsafe()); - final BigInteger value1 = new BigInteger(1, frame.popStackItem().toArrayUnsafe()); - - final BigInteger result = value0.add(value1); - - byte[] resultArray = result.toByteArray(); - int length = resultArray.length; - if (length > 32) { - frame.pushStackItem(Bytes.wrap(resultArray, length - 32, 32)); - } else { - frame.pushStackItem(Bytes.wrap(resultArray)); - } - + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.add(s, frame.stackTop())); return addSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AddOperationOptimized.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AddOperationOptimized.java deleted file mode 100644 index dcccad8ca7a..00000000000 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AddOperationOptimized.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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.operation; - -import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.UInt256; -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; - -/** The Add operation. */ -public class AddOperationOptimized extends AbstractFixedCostOperation { - - /** The Add operation success result. */ - static final OperationResult addSuccess = new OperationResult(3, null); - - /** - * Instantiates a new Add operation. - * - * @param gasCalculator the gas calculator - */ - public AddOperationOptimized(final GasCalculator gasCalculator) { - super(0x01, "ADD", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); - } - - @Override - public Operation.OperationResult executeFixedCostOperation( - final MessageFrame frame, final EVM evm) { - return staticOperation(frame); - } - - /** - * Static operation. - * - * @param frame the frame - * @return the operation result - */ - public static OperationResult staticOperation(final MessageFrame frame) { - - final Bytes value0 = frame.popStackItem(); - final Bytes value1 = frame.popStackItem(); - - UInt256 b0 = UInt256.fromBytesBE(value0.toArrayUnsafe()); - UInt256 b1 = UInt256.fromBytesBE(value1.toArrayUnsafe()); - - UInt256 result = b0.add(b1); - byte[] resultArray = result.toBytesBE(); - frame.pushStackItem(Bytes.wrap(resultArray)); - - return addSuccess; - } -} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AddressOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AddressOperation.java index 0fd9e814785..c9bfb11a328 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AddressOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AddressOperation.java @@ -17,6 +17,7 @@ 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.internal.StackMath; /** The Address operation. */ public class AddressOperation extends AbstractFixedCostOperation { @@ -33,7 +34,9 @@ public AddressOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - frame.pushStackItem(frame.getRecipientAddress().getBytes()); + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTop( + StackMath.pushAddress(frame.stackData(), frame.stackTop(), frame.getRecipientAddress())); return successResponse; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AndOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AndOperation.java index 21047b78c2f..edee7056062 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AndOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AndOperation.java @@ -17,8 +17,7 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The And operation. */ public class AndOperation extends AbstractFixedCostOperation { @@ -38,7 +37,7 @@ public AndOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -47,13 +46,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - final Bytes value0 = frame.popStackItem(); - final Bytes value1 = frame.popStackItem(); - - final Bytes result = value0.and(value1); - frame.pushStackItem(result); - + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.and(s, frame.stackTop())); return andSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AndOperationOptimized.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AndOperationOptimized.java deleted file mode 100644 index 70ce0f52a1f..00000000000 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AndOperationOptimized.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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.operation; - -import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.UInt256; -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; - -/** The And operation. */ -public class AndOperationOptimized extends AbstractFixedCostOperation { - - /** The And operation success result. */ - static final OperationResult addSuccess = new OperationResult(3, null); - - /** - * Instantiates a new And operation. - * - * @param gasCalculator the gas calculator - */ - public AndOperationOptimized(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); - } - - /** - * Static operation. - * - * @param frame the frame - * @return the operation result - */ - public static OperationResult staticOperation(final MessageFrame frame) { - - final Bytes value0 = frame.popStackItem(); - final Bytes value1 = frame.popStackItem(); - - UInt256 b0 = UInt256.fromBytesBE(value0.toArrayUnsafe()); - UInt256 b1 = UInt256.fromBytesBE(value1.toArrayUnsafe()); - - UInt256 result = b0.and(b1); - byte[] resultArray = result.toBytesBE(); - frame.pushStackItem(Bytes.wrap(resultArray)); - - return addSuccess; - } -} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/BalanceOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/BalanceOperation.java index d3d3bcac6ca..e46dca9dbab 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/BalanceOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/BalanceOperation.java @@ -16,15 +16,12 @@ 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.internal.OverflowException; -import org.hyperledger.besu.evm.internal.UnderflowException; -import org.hyperledger.besu.evm.internal.Words; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The Balance operation. */ public class BalanceOperation extends AbstractOperation { @@ -53,22 +50,25 @@ protected long cost(final boolean accountIsWarm) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - try { - final Address address = Words.toAddress(frame.popStackItem()); - final boolean accountIsWarm = - frame.warmUpAddress(address) || gasCalculator().isPrecompile(address); - final long cost = cost(accountIsWarm); - if (frame.getRemainingGas() < cost) { - return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); - } else { - final Account account = getAccount(address, frame); - frame.pushStackItem(account == null ? Bytes.EMPTY : account.getBalance()); - return new OperationResult(cost, null); - } - } catch (final UnderflowException ufe) { + if (!frame.stackHasItems(1)) { return new OperationResult(cost(true), ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); - } catch (final OverflowException ofe) { - return new OperationResult(cost(true), ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); } + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final Address address = StackMath.toAddressAt(s, top, 0); + final boolean accountIsWarm = + frame.warmUpAddress(address) || gasCalculator().isPrecompile(address); + final long cost = cost(accountIsWarm); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + final Account account = getAccount(address, frame); + // Overwrite in place (pop 1, push 1) + if (account == null) { + StackMath.putAt(s, top, 0, UInt256.ZERO); + } else { + StackMath.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/operation/BaseFeeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/BaseFeeOperation.java index e403fafdb72..50a8446dea5 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/BaseFeeOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/BaseFeeOperation.java @@ -19,6 +19,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.internal.StackMath; import java.util.Optional; @@ -37,11 +38,14 @@ public BaseFeeOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; final Optional maybeBaseFee = frame.getBlockValues().getBaseFee(); if (maybeBaseFee.isEmpty()) { return new Operation.OperationResult(gasCost, ExceptionalHaltReason.INVALID_OPERATION); } - frame.pushStackItem(maybeBaseFee.orElseThrow()); + final byte[] bytes = maybeBaseFee.orElseThrow().toBytes().toArrayUnsafe(); + frame.setTop( + StackMath.pushFromBytes(frame.stackData(), frame.stackTop(), bytes, 0, bytes.length)); return successResponse; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/BlobBaseFeeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/BlobBaseFeeOperation.java index f4a87c8da76..0f791040bc0 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/BlobBaseFeeOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/BlobBaseFeeOperation.java @@ -14,10 +14,10 @@ */ package org.hyperledger.besu.evm.operation; -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.internal.StackMath; /** The Blob Base fee operation. */ public class BlobBaseFeeOperation extends AbstractFixedCostOperation { @@ -33,9 +33,10 @@ public BlobBaseFeeOperation(final GasCalculator gasCalculator) { @Override public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { - - final Wei blobGasPrice = frame.getBlobGasPrice(); - frame.pushStackItem(blobGasPrice.toBytes()); + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + final byte[] bytes = frame.getBlobGasPrice().toBytes().toArrayUnsafe(); + frame.setTop( + StackMath.pushFromBytes(frame.stackData(), frame.stackTop(), bytes, 0, bytes.length)); return successResponse; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/BlobHashOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/BlobHashOperation.java index e37f8702272..808f585892e 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/BlobHashOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/BlobHashOperation.java @@ -16,13 +16,13 @@ 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.internal.StackMath; import java.util.List; -import org.apache.tuweni.bytes.Bytes; - /** * The BlobHash operation. As specified in EIP-4844 @@ -47,24 +47,28 @@ public BlobHashOperation(final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - Bytes versionedHashIndexParam = frame.popStackItem(); + if (!frame.stackHasItems(1)) { + return new OperationResult(3, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final long[] s = frame.stackData(); + final int top = frame.stackTop(); if (frame.getVersionedHashes().isPresent()) { - List versionedHashes = frame.getVersionedHashes().get(); - Bytes trimmedIndex = versionedHashIndexParam.trimLeadingZeros(); - if (trimmedIndex.size() > 4) { - // won't fit in an int - frame.pushStackItem(Bytes.EMPTY); + final List versionedHashes = frame.getVersionedHashes().get(); + // If index doesn't fit in a positive int, it's out of range + if (!StackMath.fitsInInt(s, top, 0)) { + StackMath.putAt(s, top, 0, 0L, 0L, 0L, 0L); return new OperationResult(3, null); } - int versionedHashIndex = trimmedIndex.toInt(); + final int versionedHashIndex = (int) StackMath.longAt(s, top, 0); if (versionedHashIndex < versionedHashes.size() && versionedHashIndex >= 0) { - VersionedHash requested = versionedHashes.get(versionedHashIndex); - frame.pushStackItem(requested.getBytes()); + final VersionedHash requested = versionedHashes.get(versionedHashIndex); + final byte[] hashBytes = requested.getBytes().toArrayUnsafe(); + StackMath.fromBytesAt(s, top, 0, hashBytes, 0, hashBytes.length); } else { - frame.pushStackItem(Bytes.EMPTY); + StackMath.putAt(s, top, 0, 0L, 0L, 0L, 0L); } } else { - frame.pushStackItem(Bytes.EMPTY); + StackMath.putAt(s, top, 0, 0L, 0L, 0L, 0L); } return new OperationResult(3, null); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/BlockHashOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/BlockHashOperation.java index 97928ac6ae5..776471dacbb 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/BlockHashOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/BlockHashOperation.java @@ -21,13 +21,10 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; +import org.hyperledger.besu.evm.internal.StackMath; /** The Block hash operation. */ public class BlockHashOperation extends AbstractOperation { - private static final int MAX_BLOCK_ARG_SIZE = 8; /** * Instantiates a new Block hash operation. @@ -41,18 +38,22 @@ public BlockHashOperation(final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { final long cost = gasCalculator().getBlockHashOperationGasCost(); + if (!frame.stackHasItems(1)) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } if (frame.getRemainingGas() < cost) { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); } - // Make sure we can convert to long - final Bytes blockArg = frame.popStackItem().trimLeadingZeros(); - if (blockArg.size() > MAX_BLOCK_ARG_SIZE) { - frame.pushStackItem(Hash.ZERO.getBytes()); + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + // If blockArg doesn't fit in a non-negative long, it's out of range + if (!StackMath.fitsInLong(s, top, 0)) { + StackMath.putAt(s, top, 0, 0L, 0L, 0L, 0L); return new OperationResult(cost, null); } - final long soughtBlock = blockArg.toLong(); + final long soughtBlock = StackMath.longAt(s, top, 0); final BlockValues blockValues = frame.getBlockValues(); final long currentBlockNumber = blockValues.getNumber(); final BlockHashLookup blockHashLookup = frame.getBlockHashLookup(); @@ -62,10 +63,11 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { if (soughtBlock < 0 || soughtBlock >= currentBlockNumber || soughtBlock < (currentBlockNumber - blockHashLookup.getLookback())) { - frame.pushStackItem(Bytes32.ZERO); + StackMath.putAt(s, top, 0, 0L, 0L, 0L, 0L); } else { final Hash blockHash = blockHashLookup.apply(frame, soughtBlock); - frame.pushStackItem(blockHash.getBytes()); + final byte[] hashBytes = blockHash.getBytes().toArrayUnsafe(); + StackMath.fromBytesAt(s, top, 0, hashBytes, 0, hashBytes.length); } return new OperationResult(cost, null); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ByteOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ByteOperation.java index e615c20ec2a..ab2e894286b 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ByteOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ByteOperation.java @@ -17,8 +17,7 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The Byte operation. */ public class ByteOperation extends AbstractFixedCostOperation { @@ -35,27 +34,10 @@ public ByteOperation(final GasCalculator gasCalculator) { super(0x1A, "BYTE", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); } - private static Bytes getByte(final Bytes seq, final Bytes offset) { - Bytes trimmedOffset = offset.trimLeadingZeros(); - if (trimmedOffset.size() > 1) { - return Bytes.EMPTY; - } - final int index = trimmedOffset.toInt(); - - int size = seq.size(); - int pos = index - 32 + size; - if (pos >= size || pos < 0) { - return Bytes.EMPTY; - } else { - final byte b = seq.get(pos); - return Bytes.of(b); - } - } - @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -64,15 +46,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - final Bytes value0 = frame.popStackItem(); - final Bytes value1 = frame.popStackItem(); - - // Stack items are reversed for the BYTE operation. - final Bytes result = getByte(value1, value0); - - frame.pushStackItem(result); - + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.byte_(s, frame.stackTop())); return byteSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallCodeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallCodeOperation.java index 471da16906d..1feeb03a555 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallCodeOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallCodeOperation.java @@ -14,13 +14,11 @@ */ package org.hyperledger.besu.evm.operation; -import static org.hyperledger.besu.evm.internal.Words.clampedToLong; - import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.internal.StackMath; /** The Call code operation. */ public class CallCodeOperation extends AbstractCallOperation { @@ -36,12 +34,14 @@ public CallCodeOperation(final GasCalculator gasCalculator) { @Override protected Address to(final MessageFrame frame) { - return Words.toAddress(frame.getStackItem(1)); + return StackMath.toAddressAt(frame.stackData(), frame.stackTop(), 1); } @Override protected Wei value(final MessageFrame frame) { - return Wei.wrap(frame.getStackItem(2)); + return Wei.wrap( + org.apache.tuweni.bytes.Bytes.wrap( + StackMath.getAt(frame.stackData(), frame.stackTop(), 2).toBytesBE())); } @Override @@ -51,22 +51,22 @@ protected Wei apparentValue(final MessageFrame frame) { @Override protected long inputDataOffset(final MessageFrame frame) { - return clampedToLong(frame.getStackItem(3)); + return StackMath.clampedToLong(frame.stackData(), frame.stackTop(), 3); } @Override protected long inputDataLength(final MessageFrame frame) { - return clampedToLong(frame.getStackItem(4)); + return StackMath.clampedToLong(frame.stackData(), frame.stackTop(), 4); } @Override protected long outputDataOffset(final MessageFrame frame) { - return clampedToLong(frame.getStackItem(5)); + return StackMath.clampedToLong(frame.stackData(), frame.stackTop(), 5); } @Override protected long outputDataLength(final MessageFrame frame) { - return clampedToLong(frame.getStackItem(6)); + return StackMath.clampedToLong(frame.stackData(), frame.stackTop(), 6); } @Override diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallDataCopyOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallDataCopyOperation.java index c19f3f12d01..98748f5a859 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallDataCopyOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallDataCopyOperation.java @@ -14,12 +14,11 @@ */ package org.hyperledger.besu.evm.operation; -import static org.hyperledger.besu.evm.internal.Words.clampedToLong; - 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.internal.StackMath; import org.apache.tuweni.bytes.Bytes; @@ -37,9 +36,15 @@ public CallDataCopyOperation(final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - final long memOffset = clampedToLong(frame.popStackItem()); - final Bytes sourceOffsetBytes = frame.popStackItem(); - final long numBytes = clampedToLong(frame.popStackItem()); + if (!frame.stackHasItems(3)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final long memOffset = StackMath.clampedToLong(s, top, 0); + final long sourceOffset = StackMath.clampedToLong(s, top, 1); + final long numBytes = StackMath.clampedToLong(s, top, 2); + frame.setTop(top - 3); final long cost = gasCalculator().dataCopyOperationGasCost(frame, memOffset, numBytes); if (frame.getRemainingGas() < cost) { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); @@ -50,7 +55,6 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { } final Bytes callData = frame.getInputData(); - final long sourceOffset = clampedToLong(sourceOffsetBytes); frame.writeMemory(memOffset, sourceOffset, numBytes, callData, true); return new OperationResult(cost, null); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallDataLoadOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallDataLoadOperation.java index b1ea8f781a9..e473bda745c 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallDataLoadOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallDataLoadOperation.java @@ -17,10 +17,9 @@ 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.internal.StackMath; import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.bytes.MutableBytes32; /** The Call data load operation. */ public class CallDataLoadOperation extends AbstractFixedCostOperation { @@ -37,29 +36,36 @@ public CallDataLoadOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - final Bytes startWord = frame.popStackItem().trimLeadingZeros(); + if (!frame.stackHasItems(1)) return UNDERFLOW_RESPONSE; + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final int off = (top - 1) << 2; - // If the start index doesn't fit in an int, it comes after anything in data, and so the - // returned - // word should be zero. - if (startWord.size() > 4) { - frame.pushStackItem(Bytes.EMPTY); + // If the start index doesn't fit in a positive int, result is zero + if (s[off] != 0 + || s[off + 1] != 0 + || s[off + 2] != 0 + || s[off + 3] < 0 + || s[off + 3] > Integer.MAX_VALUE) { + s[off] = 0; + s[off + 1] = 0; + s[off + 2] = 0; + s[off + 3] = 0; return successResponse; } - final int offset = startWord.toInt(); - if (offset < 0) { - frame.pushStackItem(Bytes.EMPTY); - return successResponse; - } + final int offset = (int) s[off + 3]; final Bytes data = frame.getInputData(); - final MutableBytes32 res = MutableBytes32.create(); if (offset < data.size()) { - final Bytes toCopy = data.slice(offset, Math.min(Bytes32.SIZE, data.size() - offset)); - toCopy.copyTo(res, 0); - frame.pushStackItem(res.copy()); + 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); + StackMath.fromBytesAt(s, top, 0, result, 0, 32); } else { - frame.pushStackItem(Bytes.EMPTY); + s[off] = 0; + s[off + 1] = 0; + s[off + 2] = 0; + s[off + 3] = 0; } return successResponse; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallDataSizeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallDataSizeOperation.java index 4fc0ba5295e..0c2004cf961 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallDataSizeOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallDataSizeOperation.java @@ -17,9 +17,7 @@ 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.internal.Words; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The Call data size operation. */ public class CallDataSizeOperation extends AbstractFixedCostOperation { @@ -36,8 +34,9 @@ public CallDataSizeOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - final Bytes callData = frame.getInputData(); - frame.pushStackItem(Words.intBytes(callData.size())); + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTop( + StackMath.pushLong(frame.stackData(), frame.stackTop(), frame.getInputData().size())); return successResponse; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallOperation.java index 2133eadfef4..aae2795fcbb 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallOperation.java @@ -14,15 +14,13 @@ */ package org.hyperledger.besu.evm.operation; -import static org.hyperledger.besu.evm.internal.Words.clampedToLong; - 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.internal.Words; +import org.hyperledger.besu.evm.internal.StackMath; /** The Call operation. */ public class CallOperation extends AbstractCallOperation { @@ -38,12 +36,14 @@ public CallOperation(final GasCalculator gasCalculator) { @Override protected Address to(final MessageFrame frame) { - return Words.toAddress(frame.getStackItem(1)); + return StackMath.toAddressAt(frame.stackData(), frame.stackTop(), 1); } @Override protected Wei value(final MessageFrame frame) { - return Wei.wrap(frame.getStackItem(2)); + return Wei.wrap( + org.apache.tuweni.bytes.Bytes.wrap( + StackMath.getAt(frame.stackData(), frame.stackTop(), 2).toBytesBE())); } @Override @@ -53,22 +53,22 @@ protected Wei apparentValue(final MessageFrame frame) { @Override protected long inputDataOffset(final MessageFrame frame) { - return clampedToLong(frame.getStackItem(3)); + return StackMath.clampedToLong(frame.stackData(), frame.stackTop(), 3); } @Override protected long inputDataLength(final MessageFrame frame) { - return clampedToLong(frame.getStackItem(4)); + return StackMath.clampedToLong(frame.stackData(), frame.stackTop(), 4); } @Override protected long outputDataOffset(final MessageFrame frame) { - return clampedToLong(frame.getStackItem(5)); + return StackMath.clampedToLong(frame.stackData(), frame.stackTop(), 5); } @Override protected long outputDataLength(final MessageFrame frame) { - return clampedToLong(frame.getStackItem(6)); + return StackMath.clampedToLong(frame.stackData(), frame.stackTop(), 6); } @Override diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallValueOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallValueOperation.java index f54da6f03c6..aa38e14438b 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallValueOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallValueOperation.java @@ -14,10 +14,10 @@ */ package org.hyperledger.besu.evm.operation; -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.internal.StackMath; /** The Call value operation. */ public class CallValueOperation extends AbstractFixedCostOperation { @@ -34,8 +34,10 @@ public CallValueOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - final Wei value = frame.getApparentValue(); - frame.pushStackItem(value.toBytes()); + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + final byte[] bytes = frame.getApparentValue().toBytes().toArrayUnsafe(); + frame.setTop( + StackMath.pushFromBytes(frame.stackData(), frame.stackTop(), bytes, 0, bytes.length)); return successResponse; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallerOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallerOperation.java index 18b6a007179..5ceed58386d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallerOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallerOperation.java @@ -17,6 +17,7 @@ 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.internal.StackMath; /** The Caller operation. */ public class CallerOperation extends AbstractFixedCostOperation { @@ -33,7 +34,9 @@ public CallerOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - frame.pushStackItem(frame.getSenderAddress().getBytes()); + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTop( + StackMath.pushAddress(frame.stackData(), frame.stackTop(), frame.getSenderAddress())); return successResponse; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ChainIdOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ChainIdOperation.java index c4836c00f6d..61d76481f2e 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ChainIdOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ChainIdOperation.java @@ -17,6 +17,7 @@ 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.internal.StackMath; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; @@ -28,6 +29,7 @@ public class ChainIdOperation extends AbstractFixedCostOperation { public static final int OPCODE = 0x46; private final Bytes32 chainId; + private final byte[] chainIdBytes; /** * Instantiates a new Chain id operation. @@ -38,6 +40,7 @@ public class ChainIdOperation extends AbstractFixedCostOperation { public ChainIdOperation(final GasCalculator gasCalculator, final Bytes32 chainId) { super(OPCODE, "CHAINID", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); this.chainId = chainId; + this.chainIdBytes = chainId.toArrayUnsafe(); } /** @@ -52,7 +55,8 @@ public Bytes getChainId() { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - frame.pushStackItem(chainId); + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTop(StackMath.pushFromBytes(frame.stackData(), frame.stackTop(), chainIdBytes, 0, 32)); return successResponse; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CodeCopyOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CodeCopyOperation.java index 02da96a428f..97bb228e356 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CodeCopyOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CodeCopyOperation.java @@ -14,13 +14,12 @@ */ package org.hyperledger.besu.evm.operation; -import static org.hyperledger.besu.evm.internal.Words.clampedToLong; - import org.hyperledger.besu.evm.Code; 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.internal.StackMath; /** The Code copy operation. */ public class CodeCopyOperation extends AbstractOperation { @@ -36,9 +35,15 @@ public CodeCopyOperation(final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - final long memOffset = clampedToLong(frame.popStackItem()); - final long sourceOffset = clampedToLong(frame.popStackItem()); - final long numBytes = clampedToLong(frame.popStackItem()); + if (!frame.stackHasItems(3)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final long memOffset = StackMath.clampedToLong(s, top, 0); + final long sourceOffset = StackMath.clampedToLong(s, top, 1); + final long numBytes = StackMath.clampedToLong(s, top, 2); + frame.setTop(top - 3); final long cost = gasCalculator().dataCopyOperationGasCost(frame, memOffset, numBytes); if (frame.getRemainingGas() < cost) { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CodeSizeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CodeSizeOperation.java index d6dbb9ca36f..de8957b6e69 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CodeSizeOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CodeSizeOperation.java @@ -14,11 +14,10 @@ */ package org.hyperledger.besu.evm.operation; -import org.hyperledger.besu.evm.Code; 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.internal.Words; +import org.hyperledger.besu.evm.internal.StackMath; /** The Code size operation. */ public class CodeSizeOperation extends AbstractFixedCostOperation { @@ -35,8 +34,9 @@ public CodeSizeOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - final Code code = frame.getCode(); - frame.pushStackItem(Words.intBytes(code.getSize())); + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTop( + StackMath.pushLong(frame.stackData(), frame.stackTop(), frame.getCode().getSize())); return successResponse; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CoinbaseOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CoinbaseOperation.java index 2aa2a9a60a5..40df4a89a7b 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CoinbaseOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CoinbaseOperation.java @@ -18,6 +18,7 @@ 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.internal.StackMath; /** The Coinbase operation. */ public class CoinbaseOperation extends AbstractFixedCostOperation { @@ -34,8 +35,9 @@ public CoinbaseOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; final Address coinbase = frame.getMiningBeneficiary(); - frame.pushStackItem(coinbase.getBytes()); + frame.setTop(StackMath.pushAddress(frame.stackData(), frame.stackTop(), coinbase)); return successResponse; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CountLeadingZerosOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CountLeadingZerosOperation.java index ce162598b94..8db52eb7ca2 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CountLeadingZerosOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CountLeadingZerosOperation.java @@ -17,10 +17,7 @@ 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.internal.Words; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; +import org.hyperledger.besu.evm.internal.StackMath; /** The CLZ operation. */ public class CountLeadingZerosOperation extends AbstractFixedCostOperation { @@ -39,7 +36,7 @@ public CountLeadingZerosOperation(final GasCalculator gasCalculator) { @Override public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -48,15 +45,9 @@ public OperationResult executeFixedCostOperation(final MessageFrame frame, final * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - Bytes value = frame.popStackItem(); - final int numberOfLeadingZeros; - if (value.size() > Bytes32.SIZE) { - // should not happen but trim just in case - value = value.slice(value.size() - Bytes32.SIZE, Bytes32.SIZE); - } - numberOfLeadingZeros = value.numberOfLeadingZeros() + (Bytes32.SIZE - value.size()) * 8; - frame.pushStackItem(Words.intBytes(numberOfLeadingZeros)); + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(1)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.clz(s, frame.stackTop())); return clzSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java index 299ca135ccf..89341f8514f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java @@ -16,14 +16,13 @@ import static org.hyperledger.besu.crypto.Hash.keccak256; import static org.hyperledger.besu.evm.internal.Words.clampedAdd; -import static org.hyperledger.besu.evm.internal.Words.clampedToInt; -import static org.hyperledger.besu.evm.internal.Words.clampedToLong; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.Code; 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.internal.StackMath; import java.util.function.Supplier; @@ -46,8 +45,10 @@ public Create2Operation(final GasCalculator gasCalculator) { @Override public long cost(final MessageFrame frame, final Supplier unused) { - final int inputOffset = clampedToInt(frame.getStackItem(1)); - final int inputSize = clampedToInt(frame.getStackItem(2)); + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final int inputOffset = StackMath.clampedToInt(s, top, 1); + final int inputSize = StackMath.clampedToInt(s, top, 2); return clampedAdd( clampedAdd( gasCalculator().txCreateCost(), @@ -59,7 +60,9 @@ public long cost(final MessageFrame frame, final Supplier unused) { @Override public Address generateTargetContractAddress(final MessageFrame frame, final Code initcode) { final Address sender = frame.getRecipientAddress(); - final Bytes32 salt = Bytes32.leftPad(frame.getStackItem(3)); + final byte[] saltBytes = new byte[32]; + StackMath.toBytesAt(frame.stackData(), frame.stackTop(), 3, saltBytes); + final Bytes32 salt = Bytes32.wrap(saltBytes); final Bytes32 hash = keccak256( Bytes.concatenate(PREFIX, sender.getBytes(), salt, initcode.getCodeHash().getBytes())); @@ -68,8 +71,10 @@ public Address generateTargetContractAddress(final MessageFrame frame, final Cod @Override protected Code getInitCode(final MessageFrame frame, final EVM evm) { - final long inputOffset = clampedToLong(frame.getStackItem(1)); - final long inputSize = clampedToLong(frame.getStackItem(2)); + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final long inputOffset = StackMath.clampedToLong(s, top, 1); + final long inputSize = StackMath.clampedToLong(s, top, 2); final Bytes inputData = frame.readMemory(inputOffset, inputSize); // Never cache CREATEx initcode. The amount of reuse is very low, and caching mostly // addresses disk loading delay, and we already have the code. diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java index 464422bc636..421f0b95d89 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java @@ -15,8 +15,6 @@ package org.hyperledger.besu.evm.operation; import static org.hyperledger.besu.evm.internal.Words.clampedAdd; -import static org.hyperledger.besu.evm.internal.Words.clampedToInt; -import static org.hyperledger.besu.evm.internal.Words.clampedToLong; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.Code; @@ -24,6 +22,7 @@ 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.internal.StackMath; import java.util.function.Supplier; @@ -43,8 +42,10 @@ public CreateOperation(final GasCalculator gasCalculator) { @Override public long cost(final MessageFrame frame, final Supplier unused) { - final int inputOffset = clampedToInt(frame.getStackItem(1)); - final int inputSize = clampedToInt(frame.getStackItem(2)); + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final int inputOffset = StackMath.clampedToInt(s, top, 1); + final int inputSize = StackMath.clampedToInt(s, top, 2); return clampedAdd( clampedAdd( gasCalculator().txCreateCost(), @@ -61,8 +62,10 @@ protected Address generateTargetContractAddress(final MessageFrame frame, final @Override protected Code getInitCode(final MessageFrame frame, final EVM evm) { - final long inputOffset = clampedToLong(frame.getStackItem(1)); - final long inputSize = clampedToLong(frame.getStackItem(2)); + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final long inputOffset = StackMath.clampedToLong(s, top, 1); + final long inputSize = StackMath.clampedToLong(s, top, 2); final Bytes inputData = frame.readMemory(inputOffset, inputSize); // Never cache CREATEx initcode. The amount of reuse is very low, and caching mostly // addresses disk loading delay, and we already have the code. diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DelegateCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DelegateCallOperation.java index e7fb3f7c626..dd0cb5ae670 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/DelegateCallOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DelegateCallOperation.java @@ -14,13 +14,11 @@ */ package org.hyperledger.besu.evm.operation; -import static org.hyperledger.besu.evm.internal.Words.clampedToLong; - import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.internal.StackMath; /** The Delegate call operation. */ public class DelegateCallOperation extends AbstractCallOperation { @@ -36,7 +34,7 @@ public DelegateCallOperation(final GasCalculator gasCalculator) { @Override protected Address to(final MessageFrame frame) { - return Words.toAddress(frame.getStackItem(1)); + return StackMath.toAddressAt(frame.stackData(), frame.stackTop(), 1); } @Override @@ -51,22 +49,22 @@ protected Wei apparentValue(final MessageFrame frame) { @Override protected long inputDataOffset(final MessageFrame frame) { - return clampedToLong(frame.getStackItem(2)); + return StackMath.clampedToLong(frame.stackData(), frame.stackTop(), 2); } @Override protected long inputDataLength(final MessageFrame frame) { - return clampedToLong(frame.getStackItem(3)); + return StackMath.clampedToLong(frame.stackData(), frame.stackTop(), 3); } @Override protected long outputDataOffset(final MessageFrame frame) { - return clampedToLong(frame.getStackItem(4)); + return StackMath.clampedToLong(frame.stackData(), frame.stackTop(), 4); } @Override protected long outputDataLength(final MessageFrame frame) { - return clampedToLong(frame.getStackItem(5)); + return StackMath.clampedToLong(frame.stackData(), frame.stackTop(), 5); } @Override diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DifficultyOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DifficultyOperation.java index b2a9c0913b5..ce0c3c68708 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/DifficultyOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DifficultyOperation.java @@ -17,6 +17,7 @@ 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.internal.StackMath; /** The Difficulty operation. */ public class DifficultyOperation extends AbstractFixedCostOperation { @@ -33,7 +34,10 @@ public DifficultyOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - frame.pushStackItem(frame.getBlockValues().getDifficultyBytes()); + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + final byte[] bytes = frame.getBlockValues().getDifficultyBytes().toArrayUnsafe(); + frame.setTop( + StackMath.pushFromBytes(frame.stackData(), frame.stackTop(), bytes, 0, bytes.length)); return successResponse; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DivOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DivOperation.java index 41db7e17a6f..197ef000efb 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/DivOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DivOperation.java @@ -17,10 +17,7 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import java.math.BigInteger; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The Div operation. */ public class DivOperation extends AbstractFixedCostOperation { @@ -40,7 +37,7 @@ public DivOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -49,29 +46,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - - final Bytes value0 = frame.popStackItem(); - final Bytes value1 = frame.popStackItem(); - - if (value1.isZero()) { - frame.pushStackItem(Bytes.EMPTY); - } else { - BigInteger b1 = new BigInteger(1, value0.toArrayUnsafe()); - BigInteger b2 = new BigInteger(1, value1.toArrayUnsafe()); - final BigInteger result = b1.divide(b2); - - // because it's unsigned there is a change a 33 byte result will occur - // there is no toByteArrayUnsigned so we have to check and trim - byte[] resultArray = result.toByteArray(); - int length = resultArray.length; - if (length > 32) { - frame.pushStackItem(Bytes.wrap(resultArray, length - 32, 32)); - } else { - frame.pushStackItem(Bytes.wrap(resultArray)); - } - } - + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.div(s, frame.stackTop())); return divSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DupNOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DupNOperation.java index 5dff2511c4b..453f1076eee 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/DupNOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DupNOperation.java @@ -18,8 +18,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.OverflowException; -import org.hyperledger.besu.evm.internal.UnderflowException; +import org.hyperledger.besu.evm.internal.StackMath; /** * The DUPN operation (EIP-8024). @@ -62,19 +61,21 @@ public DupNOperation(final GasCalculator gasCalculator) { @Override public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { - return staticOperation(frame, frame.getCode().getBytes().toArrayUnsafe(), frame.getPC()); + return staticOperation( + frame, frame.stackData(), frame.getCode().getBytes().toArrayUnsafe(), frame.getPC()); } /** * Performs DUPN operation directly for hot-path execution. * * @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 byte[] code, final int pc) { + final MessageFrame frame, final long[] s, final byte[] code, final int pc) { // Get immediate byte, treating end-of-code as 0 final int imm = (pc + 1 >= code.length) ? 0 : code[pc + 1] & 0xFF; @@ -85,16 +86,15 @@ public static OperationResult staticOperation( final int n = Eip8024Decoder.DECODE_SINGLE[imm]; - try { - // Duplicate the n'th stack item (1-indexed) to the top - // In Besu's 0-indexed stack, the n'th item is at index n-1 - frame.pushStackItem(frame.getStackItem(n - 1)); - return DUPN_SUCCESS; - } catch (final UnderflowException ufe) { + // Need n items to read from, plus space for 1 more + if (!frame.stackHasItems(n)) { return UNDERFLOW_RESPONSE; - } catch (final OverflowException ofe) { + } + if (!frame.stackHasSpace(1)) { return OVERFLOW_RESPONSE; } + frame.setTop(StackMath.dup(s, frame.stackTop(), n)); + return DUPN_SUCCESS; } /** diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DupOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DupOperation.java index d0b44c917cd..3eba4f971eb 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/DupOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DupOperation.java @@ -18,6 +18,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.internal.StackMath; /** The Dup operation. */ public class DupOperation extends AbstractFixedCostOperation { @@ -55,19 +56,22 @@ public DupOperation(final int index, final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame, index); + return staticOperation(frame, frame.stackData(), index); } /** * Performs Dup operation. * * @param frame the frame + * @param s the stack data array * @param index the index * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame, final int index) { - frame.pushStackItem(frame.getStackItem(index - 1)); - + 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.setTop(StackMath.dup(s, frame.stackTop(), index)); return dupSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/EqOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/EqOperation.java index a0248437751..31218c35cfe 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/EqOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/EqOperation.java @@ -17,17 +17,11 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import java.util.Arrays; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.units.bigints.UInt256; +import org.hyperledger.besu.evm.internal.StackMath; /** The Eq operation. */ public class EqOperation extends AbstractFixedCostOperation { - private static final byte[] ZEROS = new byte[32]; - /** The Eq operation success result. */ static final OperationResult eqSuccess = new OperationResult(3, null); @@ -43,7 +37,7 @@ public EqOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -52,22 +46,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - final byte[] a = frame.popStackItem().toArrayUnsafe(); - final byte[] b = frame.popStackItem().toArrayUnsafe(); - final int nonZeroA = firstNonZeroIndex(a); - final int nonZeroB = firstNonZeroIndex(b); - Bytes result = UInt256.ZERO; - if (Arrays.equals(a, nonZeroA, a.length, b, nonZeroB, b.length)) { - result = UInt256.ONE; - } - - frame.pushStackItem(result); + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.eq(s, frame.stackTop())); return eqSuccess; } - - private static int firstNonZeroIndex(final byte[] value) { - final int m = Arrays.mismatch(value, 0, value.length, ZEROS, 0, value.length); - return m == -1 ? value.length : m; - } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExchangeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExchangeOperation.java index 0560933633a..d50f77fc185 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExchangeOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExchangeOperation.java @@ -18,9 +18,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.UnderflowException; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** * The EXCHANGE operation (EIP-8024). @@ -59,19 +57,21 @@ public ExchangeOperation(final GasCalculator gasCalculator) { @Override public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { - return staticOperation(frame, frame.getCode().getBytes().toArrayUnsafe(), frame.getPC()); + return staticOperation( + frame, frame.stackData(), frame.getCode().getBytes().toArrayUnsafe(), frame.getPC()); } /** * Performs EXCHANGE operation directly for hot-path execution. * * @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 byte[] code, final int pc) { + final MessageFrame frame, final long[] s, final byte[] code, final int pc) { // Get immediate byte, treating end-of-code as 0 final int imm = (pc + 1 >= code.length) ? 0 : code[pc + 1] & 0xFF; @@ -87,17 +87,12 @@ public static OperationResult staticOperation( final int n = packed & 0xFF; final int m = (packed >>> 8) & 0xFF; - try { - // Swap the (n+1)'th item (index n) with the (m+1)'th item (index m) - // In Besu's 0-indexed stack, (n+1)'th is index n, (m+1)'th is index m - final Bytes itemN = frame.getStackItem(n); - final Bytes itemM = frame.getStackItem(m); - frame.setStackItem(n, itemM); - frame.setStackItem(m, itemN); - return EXCHANGE_SUCCESS; - } catch (final UnderflowException ufe) { + final int maxIdx = Math.max(n, m); + if (!frame.stackHasItems(maxIdx + 1)) { return UNDERFLOW_RESPONSE; } + StackMath.exchange(s, frame.stackTop(), n, m); + return EXCHANGE_SUCCESS; } /** diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExpOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExpOperation.java index 0d0592b2d54..d0ae40929e3 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExpOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExpOperation.java @@ -15,14 +15,14 @@ package org.hyperledger.besu.evm.operation; import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.UInt256; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.internal.StackMath; import java.math.BigInteger; -import org.apache.tuweni.bytes.Bytes; - /** The Exp operation. */ public class ExpOperation extends AbstractOperation { @@ -39,42 +39,42 @@ public ExpOperation(final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - return staticOperation(frame, gasCalculator()); + return staticOperation(frame, frame.stackData(), gasCalculator()); } /** * Performs exp 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 GasCalculator gasCalculator) { - final Bytes number = frame.popStackItem(); - final Bytes power = frame.popStackItem(); + 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.stackTop(); + final UInt256 number = StackMath.getAt(s, top, 0); + final UInt256 power = StackMath.getAt(s, top, 1); - final int numBytes = (power.bitLength() + 7) / 8; + final int numBytes = StackMath.byteLengthAt(s, top, 1); final long cost = gasCalculator.expOperationGasCost(numBytes); if (frame.getRemainingGas() < cost) { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); } - byte[] numberBytes = number.toArrayUnsafe(); - BigInteger numBI = numberBytes.length > 0 ? new BigInteger(1, numberBytes) : BigInteger.ZERO; - byte[] powBytes = power.toArrayUnsafe(); - BigInteger powBI = powBytes.length > 0 ? new BigInteger(1, powBytes) : BigInteger.ZERO; - + // Use BigInteger for modPow - complex to implement natively + final BigInteger numBI = number.toBigInteger(); + final BigInteger powBI = power.toBigInteger(); final BigInteger result = numBI.modPow(powBI, MOD_BASE); + // Pop 2, push 1: net effect is top - 1 + final int newTop = top - 1; + frame.setTop(newTop); + StackMath.putAt(s, newTop, 0, UInt256.fromBigInteger(result)); - byte[] resultArray = result.toByteArray(); - int length = resultArray.length; - if (length > 32) { - frame.pushStackItem(Bytes.wrap(resultArray, length - 32, 32)); - } else { - frame.pushStackItem(Bytes.wrap(resultArray)); - } return new OperationResult(cost, null); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java index 9945263896a..474702aed3d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java @@ -15,7 +15,6 @@ package org.hyperledger.besu.evm.operation; import static org.hyperledger.besu.evm.internal.Words.clampedAdd; -import static org.hyperledger.besu.evm.internal.Words.clampedToLong; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.EVM; @@ -23,7 +22,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.internal.StackMath; import org.apache.tuweni.bytes.Bytes; @@ -62,10 +61,16 @@ protected long cost( @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - final Address address = Words.toAddress(frame.popStackItem()); - final long memOffset = clampedToLong(frame.popStackItem()); - final long sourceOffset = clampedToLong(frame.popStackItem()); - final long numBytes = clampedToLong(frame.popStackItem()); + if (!frame.stackHasItems(4)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final Address address = StackMath.toAddressAt(s, top, 0); + final long memOffset = StackMath.clampedToLong(s, top, 1); + final long sourceOffset = StackMath.clampedToLong(s, top, 2); + final long numBytes = StackMath.clampedToLong(s, top, 3); + frame.setTop(top - 4); final boolean accountIsWarm = frame.warmUpAddress(address) || gasCalculator().isPrecompile(address); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java index 48de0d2391d..34d3571841e 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java @@ -20,11 +20,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.OverflowException; -import org.hyperledger.besu.evm.internal.UnderflowException; -import org.hyperledger.besu.evm.internal.Words; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The Ext code hash operation. */ public class ExtCodeHashOperation extends AbstractOperation { @@ -52,28 +48,28 @@ protected long cost(final boolean accountIsWarm) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - try { - final Address address = Words.toAddress(frame.popStackItem()); - final boolean accountIsWarm = - frame.warmUpAddress(address) || gasCalculator().isPrecompile(address); - final long cost = cost(accountIsWarm); - if (frame.getRemainingGas() < cost) { - return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); - } - - final Account account = getAccount(address, frame); + if (!frame.stackHasItems(1)) { + return new OperationResult(cost(true), ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final Address address = StackMath.toAddressAt(s, top, 0); + final boolean accountIsWarm = + frame.warmUpAddress(address) || gasCalculator().isPrecompile(address); + final long cost = cost(accountIsWarm); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } - if (account == null || account.isEmpty()) { - frame.pushStackItem(Bytes.EMPTY); - } else { - frame.pushStackItem(account.getCodeHash().getBytes()); - } - return new OperationResult(cost, null); + final Account account = getAccount(address, frame); - } catch (final UnderflowException ufe) { - return new OperationResult(cost(true), ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); - } catch (final OverflowException ofe) { - return new OperationResult(cost(true), ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); + // Overwrite in place (pop 1, push 1) + if (account == null || account.isEmpty()) { + StackMath.putAt(s, top, 0, 0L, 0L, 0L, 0L); + } else { + final byte[] hashBytes = account.getCodeHash().getBytes().toArrayUnsafe(); + StackMath.fromBytesAt(s, top, 0, hashBytes, 0, hashBytes.length); } + return new OperationResult(cost, null); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java index 29c1d6fd814..08caba0a735 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java @@ -20,11 +20,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.OverflowException; -import org.hyperledger.besu.evm.internal.UnderflowException; -import org.hyperledger.besu.evm.internal.Words; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The Ext code size operation. */ public class ExtCodeSizeOperation extends AbstractOperation { @@ -52,25 +48,22 @@ protected long cost(final boolean accountIsWarm) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - try { - final Address address = Words.toAddress(frame.popStackItem()); - final boolean accountIsWarm = - frame.warmUpAddress(address) || gasCalculator().isPrecompile(address); - final long cost = cost(accountIsWarm); - if (frame.getRemainingGas() < cost) { - return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); - } else { - final Account account = getAccount(address, frame); - - Bytes codeSize = (account == null) ? Bytes.EMPTY : Words.intBytes(account.getCode().size()); - frame.pushStackItem(codeSize); - - return new OperationResult(cost, null); - } - } catch (final UnderflowException ufe) { + if (!frame.stackHasItems(1)) { return new OperationResult(cost(true), ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); - } catch (final OverflowException ofe) { - return new OperationResult(cost(true), ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); } + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final Address address = StackMath.toAddressAt(s, top, 0); + final boolean accountIsWarm = + frame.warmUpAddress(address) || gasCalculator().isPrecompile(address); + final long cost = cost(accountIsWarm); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + final Account account = getAccount(address, frame); + // Overwrite in place (pop 1, push 1) + final long codeSize = account == null ? 0L : account.getCode().size(); + StackMath.putAt(s, top, 0, 0L, 0L, 0L, codeSize); + return new OperationResult(cost, null); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/GasLimitOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/GasLimitOperation.java index a65900d88bc..d4358df4996 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/GasLimitOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/GasLimitOperation.java @@ -17,7 +17,7 @@ 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.internal.Words; +import org.hyperledger.besu.evm.internal.StackMath; /** The Gas limit operation. */ public class GasLimitOperation extends AbstractFixedCostOperation { @@ -34,7 +34,10 @@ public GasLimitOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - frame.pushStackItem(Words.longBytes(frame.getBlockValues().getGasLimit())); + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTop( + StackMath.pushLong( + frame.stackData(), frame.stackTop(), frame.getBlockValues().getGasLimit())); return successResponse; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/GasOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/GasOperation.java index 10d41f8b4ec..a510932269c 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/GasOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/GasOperation.java @@ -17,9 +17,7 @@ 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.internal.Words; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The Gas operation. */ public class GasOperation extends AbstractFixedCostOperation { @@ -36,9 +34,9 @@ public GasOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; final long gasRemaining = frame.getRemainingGas() - gasCost; - final Bytes value = Words.longBytes(gasRemaining); - frame.pushStackItem(value); + frame.setTop(StackMath.pushLong(frame.stackData(), frame.stackTop(), gasRemaining)); return successResponse; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/GasPriceOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/GasPriceOperation.java index 2a2d5b1a9b7..44f0bed6e75 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/GasPriceOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/GasPriceOperation.java @@ -14,10 +14,10 @@ */ package org.hyperledger.besu.evm.operation; -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.internal.StackMath; /** The Gas price operation. */ public class GasPriceOperation extends AbstractFixedCostOperation { @@ -34,8 +34,10 @@ public GasPriceOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - final Wei gasPrice = frame.getGasPrice(); - frame.pushStackItem(gasPrice.toBytes()); + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + final byte[] bytes = frame.getGasPrice().toBytes().toArrayUnsafe(); + frame.setTop( + StackMath.pushFromBytes(frame.stackData(), frame.stackTop(), bytes, 0, bytes.length)); return successResponse; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/GtOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/GtOperation.java index 859584fc1fc..42608e50806 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/GtOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/GtOperation.java @@ -17,8 +17,7 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The GT operation. */ public class GtOperation extends AbstractFixedCostOperation { @@ -38,7 +37,7 @@ public GtOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -47,14 +46,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - final Bytes value0 = frame.popStackItem().trimLeadingZeros(); - final Bytes value1 = frame.popStackItem().trimLeadingZeros(); - - final Bytes result = (value0.compareTo(value1) > 0 ? BYTES_ONE : Bytes.EMPTY); - - frame.pushStackItem(result); - + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.gt(s, frame.stackTop())); return gtSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/IsZeroOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/IsZeroOperation.java index 165481957e3..eb80e1dd3d7 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/IsZeroOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/IsZeroOperation.java @@ -17,8 +17,7 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The Is zero operation. */ public class IsZeroOperation extends AbstractFixedCostOperation { @@ -38,7 +37,7 @@ public IsZeroOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -47,11 +46,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - final Bytes value = frame.popStackItem().trimLeadingZeros(); - - frame.pushStackItem((value.size() == 0) ? BYTES_ONE : Bytes.EMPTY); - + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(1)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.isZero(s, frame.stackTop())); return isZeroSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/JumpOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/JumpOperation.java index 6dc7998b4c7..468ac28a1a3 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/JumpOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/JumpOperation.java @@ -34,23 +34,27 @@ public class JumpOperation extends AbstractFixedCostOperation { * @param gasCalculator the gas calculator */ public JumpOperation(final GasCalculator gasCalculator) { - super(0x56, "JUMP", 2, 0, gasCalculator, gasCalculator.getMidTierGasCost()); + super(0x56, "JUMP", 1, 0, gasCalculator, gasCalculator.getMidTierGasCost()); } @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** * Performs Jump operation. * * @param frame the frame + * @param s the stack data array * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - return jumpService.performJump( - frame, frame.popStackItem().trimLeadingZeros(), jumpResponse, invalidJumpResponse); + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(1)) return UNDERFLOW_RESPONSE; + final int top = frame.stackTop(); + final int destOff = (top - 1) << 2; + frame.setTop(top - 1); + return jumpService.performJump(frame, s, destOff, jumpResponse, invalidJumpResponse); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/JumpService.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/JumpService.java index a16df8beab3..d3841f4d037 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/JumpService.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/JumpService.java @@ -56,4 +56,72 @@ public Operation.OperationResult performJump( frame.setPC(jumpDestination); return validJumpResponse; } + + /** + * Performs the jump operation with a native UInt256 destination. + * + * @param frame the MessageFrame containing the code and PC + * @param dest the jump destination as UInt256 + * @param validJumpResponse the response to return in case the jump is successful + * @param invalidJumpResponse the response to return in case the jump failed + * @return either validJumpResponse or invalidJumpResponse depending on the result + */ + public Operation.OperationResult performJump( + final MessageFrame frame, + final org.hyperledger.besu.evm.UInt256 dest, + final Operation.OperationResult validJumpResponse, + final Operation.OperationResult invalidJumpResponse) { + if (dest.u3() != 0 + || dest.u2() != 0 + || dest.u1() != 0 + || dest.u0() < 0 + || dest.u0() > Integer.MAX_VALUE) { + return invalidJumpResponse; + } + final int jumpDestination = (int) dest.u0(); + + final Code code = frame.getCode(); + + if (code.isJumpDestInvalid(jumpDestination)) { + return invalidJumpResponse; + } + + frame.setPC(jumpDestination); + return validJumpResponse; + } + + /** + * Performs the jump operation reading the destination directly from the raw stack array. + * + * @param frame the MessageFrame containing the code and PC + * @param s the raw stack long array + * @param off the offset of the destination slot in the array (4 longs: u3, u2, u1, u0) + * @param validJumpResponse the response to return in case the jump is successful + * @param invalidJumpResponse the response to return in case the jump failed + * @return either validJumpResponse or invalidJumpResponse depending on the result + */ + public Operation.OperationResult performJump( + final MessageFrame frame, + final long[] s, + final int off, + final Operation.OperationResult validJumpResponse, + final Operation.OperationResult invalidJumpResponse) { + if (s[off] != 0 + || s[off + 1] != 0 + || s[off + 2] != 0 + || s[off + 3] < 0 + || s[off + 3] > Integer.MAX_VALUE) { + return invalidJumpResponse; + } + final int jumpDestination = (int) s[off + 3]; + + final Code code = frame.getCode(); + + if (code.isJumpDestInvalid(jumpDestination)) { + return invalidJumpResponse; + } + + frame.setPC(jumpDestination); + return validJumpResponse; + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/JumpiOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/JumpiOperation.java index 55025f58edf..fb0f4797fb1 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/JumpiOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/JumpiOperation.java @@ -19,8 +19,6 @@ import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.apache.tuweni.bytes.Bytes; - /** The JUMPI operation. */ public class JumpiOperation extends AbstractFixedCostOperation { @@ -42,24 +40,28 @@ public JumpiOperation(final GasCalculator gasCalculator) { @Override public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** * 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 Bytes dest = frame.popStackItem().trimLeadingZeros(); - final Bytes condition = frame.popStackItem().trimLeadingZeros(); + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + final int top = frame.stackTop(); + final int destOff = (top - 1) << 2; + final int condOff = (top - 2) << 2; + frame.setTop(top - 2); - // If condition is zero (false), no jump is will be performed. Therefore, skip the test. - if (condition.size() == 0) { + // 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 nojumpResponse; } - return jumpService.performJump(frame, dest, jumpiResponse, invalidJumpResponse); + return jumpService.performJump(frame, s, destOff, jumpiResponse, invalidJumpResponse); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/Keccak256Operation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/Keccak256Operation.java index b06c5b7aa44..692e43b5c7f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/Keccak256Operation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/Keccak256Operation.java @@ -15,12 +15,12 @@ package org.hyperledger.besu.evm.operation; import static org.hyperledger.besu.crypto.Hash.keccak256; -import static org.hyperledger.besu.evm.internal.Words.clampedToLong; 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.internal.StackMath; import org.apache.tuweni.bytes.Bytes; @@ -38,8 +38,13 @@ public Keccak256Operation(final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - final long from = clampedToLong(frame.popStackItem()); - final long length = clampedToLong(frame.popStackItem()); + if (!frame.stackHasItems(2)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final long from = StackMath.clampedToLong(s, top, 0); + final long length = StackMath.clampedToLong(s, top, 1); final long cost = gasCalculator().keccak256OperationGasCost(frame, from, length); if (frame.getRemainingGas() < cost) { @@ -47,7 +52,11 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { } final Bytes bytes = frame.readMutableMemory(from, length); - frame.pushStackItem(keccak256(bytes)); + final byte[] hashBytes = keccak256(bytes).toArrayUnsafe(); + // Pop 2, push 1: net effect is top - 1 + final int newTop = top - 1; + frame.setTop(newTop); + StackMath.fromBytesAt(s, newTop, 0, hashBytes, 0, hashBytes.length); return new OperationResult(cost, null); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/LogOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/LogOperation.java index c3a3b3bb21c..8e3dcf0a168 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/LogOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/LogOperation.java @@ -14,9 +14,6 @@ */ package org.hyperledger.besu.evm.operation; -import static org.apache.tuweni.bytes.Bytes32.leftPad; -import static org.hyperledger.besu.evm.internal.Words.clampedToLong; - import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Log; import org.hyperledger.besu.datatypes.LogTopic; @@ -24,9 +21,11 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.internal.StackMath; import com.google.common.collect.ImmutableList; import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; /** The Log operation. */ public class LogOperation extends AbstractOperation { @@ -46,8 +45,13 @@ public LogOperation(final int numTopics, final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - final long dataLocation = clampedToLong(frame.popStackItem()); - final long numBytes = clampedToLong(frame.popStackItem()); + if (!frame.stackHasItems(2 + numTopics)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final long dataLocation = StackMath.clampedToLong(s, top, 0); + final long numBytes = StackMath.clampedToLong(s, top, 1); if (frame.isStatic()) { return new OperationResult(0, ExceptionalHaltReason.ILLEGAL_STATE_CHANGE); @@ -65,8 +69,11 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { final ImmutableList.Builder builder = ImmutableList.builderWithExpectedSize(numTopics); for (int i = 0; i < numTopics; i++) { - builder.add(LogTopic.create(leftPad(frame.popStackItem()))); + final byte[] buf = new byte[32]; + StackMath.toBytesAt(s, top, 2 + i, buf); + builder.add(LogTopic.create(Bytes32.wrap(buf))); } + frame.setTop(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/operation/LtOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/LtOperation.java index e300c3baa2a..6a59934b9e8 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/LtOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/LtOperation.java @@ -17,8 +17,7 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The LT operation. */ public class LtOperation extends AbstractFixedCostOperation { @@ -38,7 +37,7 @@ public LtOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -47,14 +46,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - final Bytes value0 = frame.popStackItem().trimLeadingZeros(); - final Bytes value1 = frame.popStackItem().trimLeadingZeros(); - - final Bytes result = value0.compareTo(value1) < 0 ? BYTES_ONE : Bytes.EMPTY; - - frame.pushStackItem(result); - + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.lt(s, frame.stackTop())); return ltSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/MCopyOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/MCopyOperation.java index 21c2737f363..04058ca2b11 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/MCopyOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/MCopyOperation.java @@ -14,12 +14,11 @@ */ package org.hyperledger.besu.evm.operation; -import static org.hyperledger.besu.evm.internal.Words.clampedToLong; - 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.internal.StackMath; /** The Memory copy operation. */ public class MCopyOperation extends AbstractOperation { @@ -35,9 +34,15 @@ public MCopyOperation(final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - final long dst = clampedToLong(frame.popStackItem()); - final long src = clampedToLong(frame.popStackItem()); - final long length = clampedToLong(frame.popStackItem()); + if (!frame.stackHasItems(3)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final long dst = StackMath.clampedToLong(s, top, 0); + final long src = StackMath.clampedToLong(s, top, 1); + final long length = StackMath.clampedToLong(s, top, 2); + frame.setTop(top - 3); final long cost = gasCalculator().dataCopyOperationGasCost(frame, Math.max(src, dst), length); if (frame.getRemainingGas() < cost) { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/MLoadOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/MLoadOperation.java index cd2e482cd4b..38f7fa6c295 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/MLoadOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/MLoadOperation.java @@ -14,14 +14,11 @@ */ package org.hyperledger.besu.evm.operation; -import static org.hyperledger.besu.evm.internal.Words.clampedToLong; - 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.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The M load operation. */ public class MLoadOperation extends AbstractOperation { @@ -37,16 +34,19 @@ public MLoadOperation(final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - final long location = clampedToLong(frame.popStackItem()); + if (!frame.stackHasItems(1)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final long location = StackMath.clampedToLong(s, top, 0); final long cost = gasCalculator().mLoadOperationGasCost(frame, location); if (frame.getRemainingGas() < cost) { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); } - final Bytes value = frame.readMutableMemory(location, 32, true).copy(); - - frame.pushStackItem(value); + frame.mloadDirect(location, s, (top - 1) << 2); return new OperationResult(cost, null); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/MSizeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/MSizeOperation.java index b2ec67632eb..2a6416400bd 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/MSizeOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/MSizeOperation.java @@ -17,7 +17,7 @@ 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.internal.Words; +import org.hyperledger.besu.evm.internal.StackMath; /** The M size operation. */ public class MSizeOperation extends AbstractFixedCostOperation { @@ -34,7 +34,8 @@ public MSizeOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - frame.pushStackItem(Words.longBytes(frame.memoryByteSize())); + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTop(StackMath.pushLong(frame.stackData(), frame.stackTop(), frame.memoryByteSize())); return successResponse; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/MStore8Operation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/MStore8Operation.java index 8b0ac3a70ec..43b33d590e6 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/MStore8Operation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/MStore8Operation.java @@ -14,14 +14,11 @@ */ package org.hyperledger.besu.evm.operation; -import static org.hyperledger.besu.evm.internal.Words.clampedToLong; - 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.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The M store8 operation. */ public class MStore8Operation extends AbstractOperation { @@ -37,9 +34,14 @@ public MStore8Operation(final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - final long location = clampedToLong(frame.popStackItem()); - final Bytes value = frame.popStackItem(); - final byte theByte = (value.size() > 0) ? value.get(value.size() - 1) : 0; + if (!frame.stackHasItems(2)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final long location = StackMath.clampedToLong(s, top, 0); + // Low byte of value slot + final byte theByte = (byte) (s[((top - 2) << 2) + 3] & 0xFF); final long cost = gasCalculator().mStore8OperationGasCost(frame, location); if (frame.getRemainingGas() < cost) { @@ -47,6 +49,7 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { } frame.writeMemory(location, theByte, true); + frame.setTop(top - 2); return new OperationResult(cost, null); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/MStoreOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/MStoreOperation.java index 012d7d99c11..7e928dbb544 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/MStoreOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/MStoreOperation.java @@ -14,14 +14,11 @@ */ package org.hyperledger.besu.evm.operation; -import static org.hyperledger.besu.evm.internal.Words.clampedToLong; - 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.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The M store operation. */ public class MStoreOperation extends AbstractOperation { @@ -37,15 +34,20 @@ public MStoreOperation(final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - final long location = clampedToLong(frame.popStackItem()); - final Bytes value = frame.popStackItem(); + if (!frame.stackHasItems(2)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final long location = StackMath.clampedToLong(s, top, 0); final long cost = gasCalculator().mStoreOperationGasCost(frame, location); if (frame.getRemainingGas() < cost) { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); } - frame.writeMemoryRightAligned(location, 32, value, true); + frame.mstoreDirect(location, s, (top - 2) << 2); + frame.setTop(top - 2); return new OperationResult(cost, null); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ModOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ModOperation.java index 876fbf35e93..c3e94c890e4 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ModOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ModOperation.java @@ -17,12 +17,7 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import java.math.BigInteger; -import java.util.Arrays; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; +import org.hyperledger.besu.evm.internal.StackMath; /** The Mod operation. */ public class ModOperation extends AbstractFixedCostOperation { @@ -41,7 +36,7 @@ public ModOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -50,26 +45,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - final Bytes value0 = frame.popStackItem(); - final Bytes value1 = frame.popStackItem(); - if (value1.isZero()) { - frame.pushStackItem(Bytes32.ZERO); - } else { - BigInteger b1 = new BigInteger(1, value0.toArrayUnsafe()); - BigInteger b2 = new BigInteger(1, value1.toArrayUnsafe()); - final BigInteger result = b1.mod(b2); - - Bytes resultBytes = Bytes.wrap(result.toByteArray()); - if (resultBytes.size() > 32) { - resultBytes = resultBytes.slice(resultBytes.size() - 32, 32); - } - - final byte[] padding = new byte[32 - resultBytes.size()]; - Arrays.fill(padding, result.signum() < 0 ? (byte) 0xFF : 0x00); - - frame.pushStackItem(Bytes.concatenate(Bytes.wrap(padding), resultBytes)); - } + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.mod(s, frame.stackTop())); return modSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ModOperationOptimized.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ModOperationOptimized.java deleted file mode 100644 index dfa4e9a0daa..00000000000 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ModOperationOptimized.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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.operation; - -import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.UInt256; -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; - -/** The Mod operation. */ -public class ModOperationOptimized extends AbstractFixedCostOperation { - - private static final OperationResult modSuccess = new OperationResult(5, null); - - /** - * Instantiates a new Mod operation. - * - * @param gasCalculator the gas calculator - */ - public ModOperationOptimized(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); - } - - /** - * Performs Mod operation. - * - * @param frame the frame - * @return the operation result - */ - public static OperationResult staticOperation(final MessageFrame frame) { - final Bytes value0 = frame.popStackItem(); - final Bytes value1 = frame.popStackItem(); - Bytes resultBytes; - if (value1.isZero()) { - resultBytes = (Bytes) Bytes32.ZERO; - } else { - UInt256 b0 = UInt256.fromBytesBE(value0.toArrayUnsafe()); - UInt256 b1 = UInt256.fromBytesBE(value1.toArrayUnsafe()); - resultBytes = Bytes.wrap(b0.mod(b1).toBytesBE()); - } - frame.pushStackItem(resultBytes); - return modSuccess; - } -} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/MulModOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/MulModOperation.java index a4ba0962c92..7ab9bf7a4dd 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/MulModOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/MulModOperation.java @@ -17,11 +17,7 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import java.math.BigInteger; -import java.util.Arrays; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The Mul mod operation. */ public class MulModOperation extends AbstractFixedCostOperation { @@ -40,7 +36,7 @@ public MulModOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -49,30 +45,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - final Bytes value0 = frame.popStackItem(); - final Bytes value1 = frame.popStackItem(); - final Bytes value2 = frame.popStackItem(); - - if (value2.isZero()) { - frame.pushStackItem(Bytes.EMPTY); - } else { - BigInteger b0 = new BigInteger(1, value0.toArrayUnsafe()); - BigInteger b1 = new BigInteger(1, value1.toArrayUnsafe()); - BigInteger b2 = new BigInteger(1, value2.toArrayUnsafe()); - - BigInteger result = b0.multiply(b1).mod(b2); - Bytes resultBytes = Bytes.wrap(result.toByteArray()); - if (resultBytes.size() > 32) { - resultBytes = resultBytes.slice(resultBytes.size() - 32, 32); - } - - final byte[] padding = new byte[32 - resultBytes.size()]; - Arrays.fill(padding, result.signum() < 0 ? (byte) 0xFF : 0x00); - - frame.pushStackItem(Bytes.concatenate(Bytes.wrap(padding), resultBytes)); - } - + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(3)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.mulMod(s, frame.stackTop())); return mulModSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/MulModOperationOptimized.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/MulModOperationOptimized.java deleted file mode 100644 index 06bd1291505..00000000000 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/MulModOperationOptimized.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.operation; - -import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.UInt256; -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; - -/** The Mul mod operation. */ -public class MulModOperationOptimized extends AbstractFixedCostOperation { - - private static final OperationResult mulModSuccess = new OperationResult(8, null); - - /** - * Instantiates a new Mul mod operation. - * - * @param gasCalculator the gas calculator - */ - public MulModOperationOptimized(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); - } - - /** - * Performs MulMod operation. - * - * @param frame the frame - * @return the operation result - */ - public static OperationResult staticOperation(final MessageFrame frame) { - final Bytes value0 = frame.popStackItem(); - final Bytes value1 = frame.popStackItem(); - final Bytes value2 = frame.popStackItem(); - - Bytes resultBytes; - if (value2.isZero()) { - resultBytes = Bytes.EMPTY; - } else { - UInt256 b0 = UInt256.fromBytesBE(value0.toArrayUnsafe()); - UInt256 b1 = UInt256.fromBytesBE(value1.toArrayUnsafe()); - UInt256 b2 = UInt256.fromBytesBE(value2.toArrayUnsafe()); - resultBytes = Bytes.wrap(b0.mulMod(b1, b2).toBytesBE()); - } - - frame.pushStackItem(resultBytes); - return mulModSuccess; - } -} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/MulOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/MulOperation.java index da9e091d8f9..ece83294a35 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/MulOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/MulOperation.java @@ -17,10 +17,7 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import java.math.BigInteger; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The Mul operation. */ public class MulOperation extends AbstractFixedCostOperation { @@ -40,7 +37,7 @@ public MulOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -49,17 +46,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - BigInteger a = new BigInteger(1, frame.popStackItem().toArrayUnsafe()); - BigInteger b = new BigInteger(1, frame.popStackItem().toArrayUnsafe()); - BigInteger c = a.multiply(b); - byte[] cBytes = c.toByteArray(); - Bytes result = Bytes.wrap(cBytes); - if (cBytes.length > 32) { - result = result.slice(cBytes.length - 32, 32); - } - - frame.pushStackItem(result); + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.mul(s, frame.stackTop())); return mulSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/NotOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/NotOperation.java index 0b684365956..e79c4323451 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/NotOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/NotOperation.java @@ -17,9 +17,7 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; +import org.hyperledger.besu.evm.internal.StackMath; /** The Not operation. */ public class NotOperation extends AbstractFixedCostOperation { @@ -39,7 +37,7 @@ public NotOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -48,13 +46,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - final Bytes value = Bytes32.leftPad(frame.popStackItem()); - - final Bytes result = value.not(); - - frame.pushStackItem(result); - + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(1)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.not(s, frame.stackTop())); return notSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/NotOperationOptimized.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/NotOperationOptimized.java deleted file mode 100644 index b23f622c55c..00000000000 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/NotOperationOptimized.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * 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.operation; - -import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.UInt256; -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; - -/** The Not operation. */ -public class NotOperationOptimized extends AbstractFixedCostOperation { - - /** The Not operation success result. */ - static final OperationResult notSuccess = new OperationResult(3, null); - - /** - * Instantiates a new Not operation. - * - * @param gasCalculator the gas calculator - */ - public NotOperationOptimized(final GasCalculator gasCalculator) { - super(0x19, "NOT", 1, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); - } - - @Override - public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { - return staticOperation(frame); - } - - /** - * Performs Not operation. - * - * @param frame the frame - * @return the operation result - */ - public static OperationResult staticOperation(final MessageFrame frame) { - final Bytes value = frame.popStackItem(); - UInt256 uint256 = UInt256.fromBytesBE(value.toArrayUnsafe()); - - final UInt256 result = uint256.not(); - byte[] resultArray = result.toBytesBE(); - frame.pushStackItem(Bytes.wrap(resultArray)); - return notSuccess; - } -} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/NumberOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/NumberOperation.java index 4f82562dadd..ad9272e346f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/NumberOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/NumberOperation.java @@ -17,7 +17,7 @@ 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.internal.Words; +import org.hyperledger.besu.evm.internal.StackMath; /** The Number operation. */ public class NumberOperation extends AbstractFixedCostOperation { @@ -34,8 +34,10 @@ public NumberOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - final long number = frame.getBlockValues().getNumber(); - frame.pushStackItem(Words.longBytes(number)); + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTop( + StackMath.pushLong( + frame.stackData(), frame.stackTop(), frame.getBlockValues().getNumber())); return successResponse; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/OrOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/OrOperation.java index a8d4382f319..12e8221f20d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/OrOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/OrOperation.java @@ -17,8 +17,7 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The Or operation. */ public class OrOperation extends AbstractFixedCostOperation { @@ -38,7 +37,7 @@ public OrOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -47,14 +46,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - final Bytes value0 = frame.popStackItem(); - final Bytes value1 = frame.popStackItem(); - - final Bytes result = value0.or(value1); - - frame.pushStackItem(result); - + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.or(s, frame.stackTop())); return orSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/OrOperationOptimized.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/OrOperationOptimized.java deleted file mode 100644 index ec7bd3a1ecb..00000000000 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/OrOperationOptimized.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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.operation; - -import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.UInt256; -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; - -/** The XOR operation. */ -public class OrOperationOptimized extends AbstractFixedCostOperation { - - /** The XOR operation success result. */ - static final OperationResult xorSuccess = new OperationResult(3, null); - - /** - * Instantiates a new Xor operation. - * - * @param gasCalculator the gas calculator - */ - public OrOperationOptimized(final GasCalculator gasCalculator) { - super(0x17, "OR", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); - } - - @Override - public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { - return staticOperation(frame); - } - - /** - * Performs XOR operation. - * - * @param frame the frame - * @return the operation result - */ - public static OperationResult staticOperation(final MessageFrame frame) { - final Bytes value0 = frame.popStackItem(); - final Bytes value1 = frame.popStackItem(); - UInt256 b0 = UInt256.fromBytesBE(value0.toArrayUnsafe()); - UInt256 b1 = UInt256.fromBytesBE(value1.toArrayUnsafe()); - UInt256 result = b0.or(b1); - byte[] resultArray = result.toBytesBE(); - frame.pushStackItem(Bytes.wrap(resultArray)); - return xorSuccess; - } -} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/OriginOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/OriginOperation.java index c4f7e45a59a..6e0f0699b65 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/OriginOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/OriginOperation.java @@ -17,6 +17,7 @@ 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.internal.StackMath; /** The Origin operation. */ public class OriginOperation extends AbstractFixedCostOperation { @@ -33,7 +34,9 @@ public OriginOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - frame.pushStackItem(frame.getOriginatorAddress().getBytes()); + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTop( + StackMath.pushAddress(frame.stackData(), frame.stackTop(), frame.getOriginatorAddress())); return successResponse; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/PCOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/PCOperation.java index 71c3b3ed917..7a5d4a3a41a 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/PCOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/PCOperation.java @@ -17,7 +17,7 @@ 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.internal.Words; +import org.hyperledger.besu.evm.internal.StackMath; /** The PC operation. */ public class PCOperation extends AbstractFixedCostOperation { @@ -34,7 +34,8 @@ public PCOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - frame.pushStackItem(Words.intBytes(frame.getPC())); + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTop(StackMath.pushLong(frame.stackData(), frame.stackTop(), frame.getPC())); return successResponse; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/PayOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/PayOperation.java index 08196414fd9..96d13b083b6 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/PayOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/PayOperation.java @@ -15,8 +15,6 @@ package org.hyperledger.besu.evm.operation; import static org.hyperledger.besu.evm.internal.Words.clampedAdd; -import static org.hyperledger.besu.evm.operation.AbstractCallOperation.LEGACY_FAILURE_STACK_ITEM; -import static org.hyperledger.besu.evm.operation.AbstractCallOperation.LEGACY_SUCCESS_STACK_ITEM; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; @@ -26,7 +24,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.internal.StackMath; import java.util.Objects; @@ -46,18 +44,25 @@ public PayOperation(final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { + if (!frame.stackHasItems(2)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } if (frame.isStatic()) { return new OperationResult(0, ExceptionalHaltReason.ILLEGAL_STATE_CHANGE); } - final Bytes toAddressBytes = frame.getStackItem(0); - final int numberOfLowBytes = toAddressBytes.size() - toAddressBytes.numberOfLeadingZeroBytes(); - if (numberOfLowBytes > 20) { + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final int addrOff = (top - 1) << 2; + + // Check if value 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 = Words.toAddress(toAddressBytes); - final Wei value = Wei.wrap(frame.getStackItem(1)); + final Address to = StackMath.toAddressAt(s, top, 0); + final Wei value = Wei.wrap(Bytes.wrap(StackMath.getAt(s, top, 1).toBytesBE())); final boolean hasValue = value.greaterThan(Wei.ZERO); final Account recipient = getAccount(to, frame); @@ -69,15 +74,15 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { } if (!hasValue || Objects.equals(frame.getSenderAddress(), to)) { - frame.popStackItems(getStackItemsConsumed()); - frame.pushStackItem(LEGACY_SUCCESS_STACK_ITEM); + StackMath.putAt(s, top, 1, 0, 0, 0, 1); + frame.setTop(top - 1); return new OperationResult(cost, null); } final MutableAccount senderAccount = getSenderAccount(frame); if (value.compareTo(senderAccount.getBalance()) > 0) { - frame.popStackItems(getStackItemsConsumed()); - frame.pushStackItem(LEGACY_FAILURE_STACK_ITEM); + StackMath.putAt(s, top, 1, 0, 0, 0, 0); + frame.setTop(top - 1); return new OperationResult(cost, null); } @@ -85,8 +90,8 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { senderAccount.decrementBalance(value); recipientAccount.incrementBalance(value); - frame.popStackItems(getStackItemsConsumed()); - frame.pushStackItem(LEGACY_SUCCESS_STACK_ITEM); + StackMath.putAt(s, top, 1, 0, 0, 0, 1); + frame.setTop(top - 1); return new OperationResult(cost, null); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/PopOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/PopOperation.java index 88e66a44ed0..d23bc7e99d2 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/PopOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/PopOperation.java @@ -46,7 +46,8 @@ public Operation.OperationResult executeFixedCostOperation( * @return the operation result */ public static OperationResult staticOperation(final MessageFrame frame) { - frame.popStackItem(); + if (!frame.stackHasItems(1)) return UNDERFLOW_RESPONSE; + frame.setTop(frame.stackTop() - 1); return popSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/PrevRanDaoOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/PrevRanDaoOperation.java index 3b3f805941f..7928d62a7c3 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/PrevRanDaoOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/PrevRanDaoOperation.java @@ -17,6 +17,7 @@ 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.internal.StackMath; /** The Prev randao operation. */ public class PrevRanDaoOperation extends AbstractFixedCostOperation { @@ -32,7 +33,10 @@ public PrevRanDaoOperation(final GasCalculator gasCalculator) { @Override public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { - frame.pushStackItem(frame.getBlockValues().getMixHashOrPrevRandao()); + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + final byte[] bytes = frame.getBlockValues().getMixHashOrPrevRandao().toArrayUnsafe(); + frame.setTop( + StackMath.pushFromBytes(frame.stackData(), frame.stackTop(), bytes, 0, bytes.length)); return successResponse; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/Push0Operation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/Push0Operation.java index a9748994eb4..6d1871537ac 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/Push0Operation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/Push0Operation.java @@ -19,8 +19,7 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The Push0 operation. */ public class Push0Operation extends AbstractFixedCostOperation { @@ -39,17 +38,19 @@ public Push0Operation(final GasCalculator gasCalculator) { @Override public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** * Performs push0 operation. * * @param frame the frame + * @param s the stack data array * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - frame.pushStackItem(Bytes.EMPTY); + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTop(StackMath.pushZero(s, frame.stackTop())); return push0Success; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/PushOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/PushOperation.java index 0462fb9775e..dbc48ccd9c4 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/PushOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/PushOperation.java @@ -17,8 +17,7 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The Push operation. */ public class PushOperation extends AbstractFixedCostOperation { @@ -54,37 +53,27 @@ public PushOperation(final int length, final GasCalculator gasCalculator) { @Override public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { final byte[] code = frame.getCode().getBytes().toArrayUnsafe(); - return staticOperation(frame, code, frame.getPC(), length); + return staticOperation(frame, frame.stackData(), code, frame.getPC(), length); } /** * Performs Push operation. * * @param frame the frame + * @param s the stack data array * @param code the code * @param pc the pc * @param pushSize the push size * @return the operation result */ public static OperationResult staticOperation( - final MessageFrame frame, final byte[] code, final int pc, final int pushSize) { - final int copyStart = pc + 1; - Bytes push; - if (code.length <= copyStart) { - push = Bytes.EMPTY; - } else { - final int copyLength = Math.min(pushSize, code.length - pc - 1); - final int rightPad = pushSize - copyLength; - if (rightPad == 0) { - push = Bytes.wrap(code, copyStart, copyLength); - } else { - // Right Pad the push with 0s up to pushSize if greater than the copyLength - var bytecodeLocal = new byte[pushSize]; - System.arraycopy(code, copyStart, bytecodeLocal, 0, copyLength); - push = Bytes.wrap(bytecodeLocal); - } - } - frame.pushStackItem(push); + final MessageFrame frame, + final long[] s, + final byte[] code, + final int pc, + final int pushSize) { + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTop(StackMath.pushFromBytes(s, frame.stackTop(), code, pc + 1, pushSize)); frame.setPC(pc + pushSize); return pushSuccess; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataCopyOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataCopyOperation.java index c7c69149db7..eda36d12644 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataCopyOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataCopyOperation.java @@ -14,12 +14,11 @@ */ package org.hyperledger.besu.evm.operation; -import static org.hyperledger.besu.evm.internal.Words.clampedToLong; - 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.internal.StackMath; import org.apache.tuweni.bytes.Bytes; @@ -45,9 +44,15 @@ public ReturnDataCopyOperation(final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - final long memOffset = clampedToLong(frame.popStackItem()); - final long sourceOffset = clampedToLong(frame.popStackItem()); - final long numBytes = clampedToLong(frame.popStackItem()); + if (!frame.stackHasItems(3)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final long memOffset = StackMath.clampedToLong(s, top, 0); + final long sourceOffset = StackMath.clampedToLong(s, top, 1); + final long numBytes = StackMath.clampedToLong(s, top, 2); + frame.setTop(top - 3); final Bytes returnData = frame.getReturnData(); final int returnDataLength = returnData.size(); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataSizeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataSizeOperation.java index c3f75e32754..30f59014786 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataSizeOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataSizeOperation.java @@ -17,9 +17,7 @@ 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.internal.Words; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The Return data size operation. */ public class ReturnDataSizeOperation extends AbstractFixedCostOperation { @@ -36,8 +34,9 @@ public ReturnDataSizeOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - final Bytes returnData = frame.getReturnData(); - frame.pushStackItem(Words.longBytes(returnData.size())); + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTop( + StackMath.pushLong(frame.stackData(), frame.stackTop(), frame.getReturnData().size())); return successResponse; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnOperation.java index 2e23e9b6df3..fdcb0f29475 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnOperation.java @@ -14,12 +14,11 @@ */ package org.hyperledger.besu.evm.operation; -import static org.hyperledger.besu.evm.internal.Words.clampedToLong; - 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.internal.StackMath; /** The Return operation. */ public class ReturnOperation extends AbstractOperation { @@ -38,8 +37,14 @@ public ReturnOperation(final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - final long from = clampedToLong(frame.popStackItem()); - final long length = clampedToLong(frame.popStackItem()); + if (!frame.stackHasItems(2)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final long from = StackMath.clampedToLong(s, top, 0); + final long length = StackMath.clampedToLong(s, top, 1); + frame.setTop(top - 2); final long cost = gasCalculator().memoryExpansionGasCost(frame, from, length); if (frame.getRemainingGas() < cost) { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/RevertOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/RevertOperation.java index 6c681a5ce7f..67534823d7f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/RevertOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/RevertOperation.java @@ -14,12 +14,11 @@ */ package org.hyperledger.besu.evm.operation; -import static org.hyperledger.besu.evm.internal.Words.clampedToLong; - 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.internal.StackMath; import org.apache.tuweni.bytes.Bytes; @@ -40,8 +39,14 @@ public RevertOperation(final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - final long from = clampedToLong(frame.popStackItem()); - final long length = clampedToLong(frame.popStackItem()); + if (!frame.stackHasItems(2)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final long from = StackMath.clampedToLong(s, top, 0); + final long length = StackMath.clampedToLong(s, top, 1); + frame.setTop(top - 2); final long cost = gasCalculator().memoryExpansionGasCost(frame, from, length); if (frame.getRemainingGas() < cost) { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SDivOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SDivOperation.java index e4907a98e60..c27fba5da2c 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SDivOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SDivOperation.java @@ -17,11 +17,7 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import java.math.BigInteger; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; +import org.hyperledger.besu.evm.internal.StackMath; /** The SDiv operation. */ public class SDivOperation extends AbstractFixedCostOperation { @@ -40,7 +36,7 @@ public SDivOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -49,30 +45,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - final Bytes value0 = frame.popStackItem(); - final Bytes value1 = frame.popStackItem(); - - if (value1.isZero()) { - frame.pushStackItem(Bytes.EMPTY); - } else { - final BigInteger b1 = - value0.size() < 32 - ? new BigInteger(1, value0.toArrayUnsafe()) - : new BigInteger(value0.toArrayUnsafe()); - final BigInteger b2 = - value1.size() < 32 - ? new BigInteger(1, value1.toArrayUnsafe()) - : new BigInteger(value1.toArrayUnsafe()); - final BigInteger result = b1.divide(b2); - Bytes resultBytes = Bytes.wrap(result.toByteArray()); - if (resultBytes.size() > 32) { - resultBytes = resultBytes.slice(resultBytes.size() - 32, 32); - } - - frame.pushStackItem(Bytes32.leftPad(resultBytes, result.signum() < 0 ? (byte) 0xFF : 0x00)); - } - + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.signedDiv(s, frame.stackTop())); return sdivSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SGtOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SGtOperation.java index 8625f1fcc3a..ce0adc27d19 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SGtOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SGtOperation.java @@ -17,10 +17,7 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import java.math.BigInteger; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The SGt operation. */ public class SGtOperation extends AbstractFixedCostOperation { @@ -40,7 +37,7 @@ public SGtOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -49,23 +46,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - final Bytes value0 = frame.popStackItem(); - final Bytes value1 = frame.popStackItem(); - - final BigInteger b0 = - value0.size() < 32 - ? new BigInteger(1, value0.toArrayUnsafe()) - : new BigInteger(value0.toArrayUnsafe()); - final BigInteger b1 = - value1.size() < 32 - ? new BigInteger(1, value1.toArrayUnsafe()) - : new BigInteger(value1.toArrayUnsafe()); - - final Bytes result = b0.compareTo(b1) > 0 ? BYTES_ONE : Bytes.EMPTY; - - frame.pushStackItem(result); - + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.sgt(s, frame.stackTop())); return sgtSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SLoadOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SLoadOperation.java index b0388144477..1313bf675b4 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SLoadOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SLoadOperation.java @@ -16,15 +16,12 @@ 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.internal.OverflowException; -import org.hyperledger.besu.evm.internal.UnderflowException; - -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; +import org.hyperledger.besu.evm.internal.StackMath; /** The SLoad operation. */ public class SLoadOperation extends AbstractOperation { @@ -52,22 +49,22 @@ public SLoadOperation(final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - try { - final Account account = getAccount(frame.getRecipientAddress(), frame); - final Address address = account.getAddress(); - final Bytes32 key = UInt256.fromBytes(frame.popStackItem()); - final boolean slotIsWarm = frame.warmUpStorage(address, key); - final long cost = slotIsWarm ? warmCost : coldCost; - if (frame.getRemainingGas() < cost) { - return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); - } else { - frame.pushStackItem(getStorageValue(account, UInt256.fromBytes(key), frame)); - return slotIsWarm ? warmSuccess : coldSuccess; - } - } catch (final UnderflowException ufe) { + if (!frame.stackHasItems(1)) { return new OperationResult(warmCost, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); - } catch (final OverflowException ofe) { - return new OperationResult(warmCost, ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); } + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final Account account = getAccount(frame.getRecipientAddress(), frame); + final Address address = account.getAddress(); + final UInt256 key = StackMath.getAt(s, top, 0); + final boolean slotIsWarm = frame.warmUpStorage(address, key.toBytes32()); + final long cost = slotIsWarm ? warmCost : coldCost; + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + // Overwrite in place (pop 1, push 1) + final UInt256 result = UInt256.fromTuweni(getStorageValue(account, key.toTuweni(), frame)); + StackMath.putAt(s, top, 0, result); + return slotIsWarm ? warmSuccess : coldSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SLtOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SLtOperation.java index 4e8aaff490b..556b5228922 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SLtOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SLtOperation.java @@ -17,10 +17,7 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import java.math.BigInteger; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The SLT operation. */ public class SLtOperation extends AbstractFixedCostOperation { @@ -40,7 +37,7 @@ public SLtOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -49,23 +46,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - final Bytes value0 = frame.popStackItem(); - final Bytes value1 = frame.popStackItem(); - - final BigInteger b0 = - value0.size() < 32 - ? new BigInteger(1, value0.toArrayUnsafe()) - : new BigInteger(value0.toArrayUnsafe()); - final BigInteger b1 = - value1.size() < 32 - ? new BigInteger(1, value1.toArrayUnsafe()) - : new BigInteger(value1.toArrayUnsafe()); - - final Bytes result = b0.compareTo(b1) < 0 ? BYTES_ONE : Bytes.EMPTY; - - frame.pushStackItem(result); - + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.slt(s, frame.stackTop())); return sltSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SModOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SModOperation.java index 45780646a19..b607f747154 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SModOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SModOperation.java @@ -17,11 +17,7 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import java.math.BigInteger; -import java.util.Arrays; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The SMod operation. */ public class SModOperation extends AbstractFixedCostOperation { @@ -40,7 +36,7 @@ public SModOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -49,37 +45,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - final Bytes value0 = frame.popStackItem(); - final Bytes value1 = frame.popStackItem(); - - if (value1.isZero()) { - frame.pushStackItem(Bytes.EMPTY); - } else { - final BigInteger b1 = - value0.size() < 32 - ? new BigInteger(1, value0.toArrayUnsafe()) - : new BigInteger(value0.toArrayUnsafe()); - final BigInteger b2 = - value1.size() < 32 - ? new BigInteger(1, value1.toArrayUnsafe()) - : new BigInteger(value1.toArrayUnsafe()); - BigInteger result = b1.abs().mod(b2.abs()); - if (b1.signum() < 0) { - result = result.negate(); - } - - Bytes resultBytes = Bytes.wrap(result.toByteArray()); - if (resultBytes.size() > 32) { - resultBytes = resultBytes.slice(resultBytes.size() - 32, 32); - } - - final byte[] padding = new byte[32 - resultBytes.size()]; - Arrays.fill(padding, result.signum() < 0 ? (byte) 0xFF : 0x00); - - frame.pushStackItem(Bytes.concatenate(Bytes.wrap(padding), resultBytes)); - } - + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.signedMod(s, frame.stackTop())); return smodSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SModOperationOptimized.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SModOperationOptimized.java deleted file mode 100644 index 8d98851e492..00000000000 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SModOperationOptimized.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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.operation; - -import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.UInt256; -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; - -/** The SMod operation. */ -public class SModOperationOptimized extends AbstractFixedCostOperation { - - private static final OperationResult smodSuccess = new OperationResult(5, null); - - /** - * Instantiates a new SMod operation. - * - * @param gasCalculator the gas calculator - */ - public SModOperationOptimized(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); - } - - /** - * Performs SMod operation. - * - * @param frame the frame - * @return the operation result - */ - public static OperationResult staticOperation(final MessageFrame frame) { - final Bytes value0 = frame.popStackItem(); - final Bytes value1 = frame.popStackItem(); - - Bytes resultBytes; - if (value1.isZero()) { - resultBytes = (Bytes) Bytes32.ZERO; - } else { - UInt256 b0 = UInt256.fromBytesBE(value0.toArrayUnsafe()); - UInt256 b1 = UInt256.fromBytesBE(value1.toArrayUnsafe()); - resultBytes = Bytes.wrap(b0.signedMod(b1).toBytesBE()); - } - frame.pushStackItem(resultBytes); - - return smodSuccess; - } -} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SStoreOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SStoreOperation.java index 9d0a7e2aa54..8f275183b63 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SStoreOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SStoreOperation.java @@ -20,10 +20,12 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.internal.StackMath; import java.util.function.Supplier; import com.google.common.base.Suppliers; +import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; /** The SStore operation. */ @@ -64,8 +66,15 @@ public long getMinimumGasRemaining() { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - final UInt256 key = UInt256.fromBytes(frame.popStackItem()); - final UInt256 newValue = UInt256.fromBytes(frame.popStackItem()); + if (!frame.stackHasItems(2)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final org.hyperledger.besu.evm.UInt256 keyNative = StackMath.getAt(s, top, 0); + final org.hyperledger.besu.evm.UInt256 newValueNative = StackMath.getAt(s, top, 1); + // Pop 2 + frame.setTop(top - 2); final MutableAccount account = getMutableAccount(frame.getRecipientAddress(), frame); if (account == null) { @@ -82,8 +91,12 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { return new OperationResult(minimumGasRemaining, ExceptionalHaltReason.INSUFFICIENT_GAS); } + // Convert to tuweni types for storage API boundary + final UInt256 key = keyNative.toTuweni(); + final UInt256 newValue = newValueNative.toTuweni(); + final Address address = account.getAddress(); - final boolean slotIsWarm = frame.warmUpStorage(address, key); + final boolean slotIsWarm = frame.warmUpStorage(address, keyNative.toBytes32()); final Supplier currentValueSupplier = Suppliers.memoize(() -> getStorageValue(account, key, frame)); final Supplier originalValueSupplier = @@ -102,7 +115,7 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { .calculateStorageRefundAmount(newValue, currentValueSupplier, originalValueSupplier)); account.setStorageValue(key, newValue); - frame.storageWasUpdated(key, newValue); + frame.storageWasUpdated(key, Bytes.wrap(newValueNative.toBytesBE())); frame.getEip7928AccessList().ifPresent(t -> t.addSlotAccessForAccount(address, key)); return new OperationResult(cost, null); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperation.java index f40881db99e..ebeef091cc5 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperation.java @@ -14,13 +14,10 @@ */ package org.hyperledger.besu.evm.operation; -import static org.apache.tuweni.bytes.Bytes32.leftPad; - import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The Sar operation. */ public class SarOperation extends AbstractFixedCostOperation { @@ -28,9 +25,6 @@ public class SarOperation extends AbstractFixedCostOperation { /** The Sar operation success result. */ static final OperationResult sarSuccess = new OperationResult(3, null); - private static final Bytes ALL_BITS = - Bytes.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - /** * Instantiates a new Sar operation. * @@ -43,7 +37,7 @@ public SarOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -52,29 +46,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - Bytes shiftAmount = frame.popStackItem(); - final Bytes value = leftPad(frame.popStackItem()); - final boolean negativeNumber = value.get(0) < 0; - if (shiftAmount.size() > 4 && (shiftAmount = shiftAmount.trimLeadingZeros()).size() > 4) { - frame.pushStackItem(negativeNumber ? ALL_BITS : Bytes.EMPTY); - } else { - final int shiftAmountInt = shiftAmount.toInt(); - - if (shiftAmountInt >= 256 || shiftAmountInt < 0) { - frame.pushStackItem(negativeNumber ? ALL_BITS : Bytes.EMPTY); - } else { - // first perform standard shift right. - Bytes result = value.shiftRight(shiftAmountInt); - - // if a negative number, carry through the sign. - if (negativeNumber) { - final Bytes significantBits = ALL_BITS.shiftLeft(256 - shiftAmountInt); - result = result.or(significantBits); - } - frame.pushStackItem(result); - } - } + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.sar(s, frame.stackTop())); return sarSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java deleted file mode 100644 index adbea0cb47a..00000000000 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * 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.operation; - -import static org.hyperledger.besu.evm.operation.Shift256Operations.ALL_ONES; -import static org.hyperledger.besu.evm.operation.Shift256Operations.ALL_ONES_BYTES; -import static org.hyperledger.besu.evm.operation.Shift256Operations.isShiftOverflow; - -import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import java.util.Arrays; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; - -/** The Sar operation. */ -public class SarOperationOptimized extends AbstractFixedCostOperation { - - /** The Sar operation success result. */ - static final OperationResult sarSuccess = new OperationResult(3, null); - - /** - * Instantiates a new Sar operation. - * - * @param gasCalculator the gas calculator - */ - public SarOperationOptimized(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); - } - - /** - * Performs sar operation. - * - * @param frame the frame - * @return the operation result - */ - public static OperationResult staticOperation(final MessageFrame frame) { - final Bytes shiftAmount = frame.popStackItem(); - final Bytes value = frame.popStackItem(); - byte[] valueBytes = value.toArrayUnsafe(); - if (Arrays.equals(valueBytes, ALL_ONES_BYTES)) { - frame.pushStackItem(ALL_ONES); - return sarSuccess; - } - valueBytes = Bytes32.leftPad(value).toArrayUnsafe(); - final byte[] shiftBytes = shiftAmount.toArrayUnsafe(); - final boolean negative = (valueBytes[0] & 0x80) != 0; - - // shift >= 256, push All 1s if negative, All 0s otherwise - if (isShiftOverflow(shiftBytes)) { - frame.pushStackItem(negative ? ALL_ONES : Bytes.EMPTY); - return sarSuccess; - } - final int shift = shiftBytes.length == 0 ? 0 : (shiftBytes[shiftBytes.length - 1] & 0xFF); - - frame.pushStackItem(sar256(valueBytes, shift, negative)); - return sarSuccess; - } - - /** - * Performs a 256-bit arithmetic right shift (EVM SAR). - * - *

The input value is treated as a signed 256-bit integer in two's complement representation. - * The shift amount is in the range {@code [0..255]} and is assumed to have been validated by the - * caller. - * - *

For shift values greater than or equal to 256, the result is fully sign-extended and handled - * by the caller. - * - * @param in the raw 32-byte array of the input value - * @param shift the right shift amount in bits (0–255) - * @param negative whether the input value is negative (sign bit set) - * @return the shifted 256-bit value - */ - private static Bytes sar256(final byte[] in, final int shift, final boolean negative) { - if (shift == 0) return Bytes.wrap(in); - - final int shiftBytes = shift >>> 3; // /8 - final int shiftBits = shift & 7; // %8 - final int fill = negative ? 0xFF : 0x00; - - final byte[] out = new byte[32]; - - // Pre-fill sign-extended bytes (indices below shiftBytes are fully sign-extended) - if (negative && shiftBytes > 0) { - Arrays.fill(out, 0, shiftBytes, (byte) 0xFF); - } - - // Only iterate bytes that receive shifted data from the input - for (int i = 31; i >= shiftBytes; i--) { - final int srcIndex = i - shiftBytes; - final int curr = in[srcIndex] & 0xFF; - if (shiftBits == 0) { - out[i] = (byte) curr; - } else { - final int prev = (srcIndex - 1 >= 0) ? (in[srcIndex - 1] & 0xFF) : fill; - out[i] = (byte) ((curr >>> shiftBits) | (prev << (8 - shiftBits))); - } - } - - return Bytes.wrap(out); - } -} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SelfBalanceOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SelfBalanceOperation.java index 3ac6773f066..36370c6f52d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SelfBalanceOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SelfBalanceOperation.java @@ -14,13 +14,11 @@ */ package org.hyperledger.besu.evm.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.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The Self balance operation. */ public class SelfBalanceOperation extends AbstractFixedCostOperation { @@ -37,9 +35,15 @@ public SelfBalanceOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - final Address accountAddress = frame.getRecipientAddress(); - final Account account = getAccount(accountAddress, frame); - frame.pushStackItem(account == null ? Bytes.EMPTY : account.getBalance()); + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + final Account account = getAccount(frame.getRecipientAddress(), frame); + if (account == null) { + frame.setTop(StackMath.pushZero(frame.stackData(), frame.stackTop())); + } else { + final byte[] bytes = account.getBalance().toBytes().toArrayUnsafe(); + frame.setTop( + StackMath.pushFromBytes(frame.stackData(), frame.stackTop(), bytes, 0, bytes.length)); + } return successResponse; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SelfDestructOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SelfDestructOperation.java index ed7ea612330..2b26cb2567f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SelfDestructOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SelfDestructOperation.java @@ -22,7 +22,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.internal.StackMath; import org.hyperledger.besu.evm.log.TransferLogEmitter; /** The Self destruct operation. */ @@ -76,8 +76,16 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { return new OperationResult(0, ExceptionalHaltReason.ILLEGAL_STATE_CHANGE); } + if (!frame.stackHasItems(1)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + // First calculate cost. There's a bit of yak shaving getting values to calculate the cost. - final Address beneficiaryAddress = Words.toAddress(frame.popStackItem()); + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final Address beneficiaryAddress = StackMath.toAddressAt(s, top, 0); + // Pop 1 + frame.setTop(top - 1); final boolean beneficiaryIsWarm = frame.warmUpAddress(beneficiaryAddress) || gasCalculator().isPrecompile(beneficiaryAddress); final long beneficiaryAccessCost = diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperation.java index ec152ae778c..cfc7440cf4c 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperation.java @@ -14,13 +14,10 @@ */ package org.hyperledger.besu.evm.operation; -import static org.apache.tuweni.bytes.Bytes32.leftPad; - import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The Shl (Shift Left) operation. */ public class ShlOperation extends AbstractFixedCostOperation { @@ -40,7 +37,7 @@ public ShlOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -49,21 +46,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - Bytes shiftAmount = frame.popStackItem(); - if (shiftAmount.size() > 4 && (shiftAmount = shiftAmount.trimLeadingZeros()).size() > 4) { - frame.popStackItem(); - frame.pushStackItem(Bytes.EMPTY); - } else { - final int shiftAmountInt = shiftAmount.toInt(); - final Bytes value = leftPad(frame.popStackItem()); - - if (shiftAmountInt >= 256 || shiftAmountInt < 0) { - frame.pushStackItem(Bytes.EMPTY); - } else { - frame.pushStackItem(value.shiftLeft(shiftAmountInt)); - } - } + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.shl(s, frame.stackTop())); return shlSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperationOptimized.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperationOptimized.java deleted file mode 100644 index a691636e060..00000000000 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperationOptimized.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * 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.operation; - -import static org.hyperledger.besu.evm.operation.Shift256Operations.isShiftOverflow; - -import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; - -/** - * The optimized SHL (Shift Left) operation. - * - *

This implementation uses direct byte[] manipulation instead of Tuweni's Bytes.shiftLeft() to - * avoid intermediate object allocation and improve performance. - */ -public class ShlOperationOptimized extends AbstractFixedCostOperation { - - /** The Shl operation success result. */ - static final OperationResult shlSuccess = new OperationResult(3, null); - - /** - * Instantiates a new optimized Shl operation. - * - * @param gasCalculator the gas calculator - */ - public ShlOperationOptimized(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); - } - - /** - * Performs optimized Shift Left operation. - * - * @param frame the frame - * @return the operation result - */ - public static OperationResult staticOperation(final MessageFrame frame) { - final Bytes shiftAmount = frame.popStackItem(); - final Bytes value = frame.popStackItem(); - if (value.isZero()) { - frame.pushStackItem(Bytes.EMPTY); - return shlSuccess; - } - - final byte[] valueBytes = Bytes32.leftPad(value).toArrayUnsafe(); - final byte[] shiftBytes = shiftAmount.toArrayUnsafe(); - - // shift >= 256, push All 0s - if (isShiftOverflow(shiftBytes)) { - frame.pushStackItem(Bytes.EMPTY); - return shlSuccess; - } - - final int shift = shiftBytes.length == 0 ? 0 : (shiftBytes[shiftBytes.length - 1] & 0xFF); - - frame.pushStackItem(shl256(valueBytes, shift)); - return shlSuccess; - } - - /** - * Performs a 256-bit logical left shift (EVM SHL). - * - *

The shift amount is in the range {@code [0..255]} and is assumed to have been validated by - * the caller. For shift values >= 256, zero is returned by the caller. - * - * @param in the raw 32-byte array of the input value - * @param shift the left shift amount in bits (0–255) - * @return the shifted 256-bit value - */ - private static Bytes shl256(final byte[] in, final int shift) { - if (shift == 0) { - return Bytes.wrap(in); - } - - final int shiftBytes = shift >>> 3; // /8 - final int shiftBits = shift & 7; // %8 - - final byte[] out = new byte[32]; - - // Shift left: bytes move to lower indices (towards index 0) - // Bytes at index >= (32 - shiftBytes) are guaranteed zero (already from new byte[32]) - final int limit = 32 - shiftBytes; - for (int i = 0; i < limit; i++) { - final int srcIndex = i + shiftBytes; - final int curr = in[srcIndex] & 0xFF; - if (shiftBits == 0) { - out[i] = (byte) curr; - } else { - final int next = (srcIndex + 1 < 32) ? (in[srcIndex + 1] & 0xFF) : 0; - out[i] = (byte) ((curr << shiftBits) | (next >>> (8 - shiftBits))); - } - } - - return Bytes.wrap(out); - } -} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperation.java index 9b2c9ea5aa9..94db5a8927a 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperation.java @@ -14,13 +14,10 @@ */ package org.hyperledger.besu.evm.operation; -import static org.apache.tuweni.bytes.Bytes32.leftPad; - import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The Shr (Shift Right) operation. */ public class ShrOperation extends AbstractFixedCostOperation { @@ -40,7 +37,7 @@ public ShrOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -49,21 +46,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - Bytes shiftAmount = frame.popStackItem(); - if (shiftAmount.size() > 4 && (shiftAmount = shiftAmount.trimLeadingZeros()).size() > 4) { - frame.popStackItem(); - frame.pushStackItem(Bytes.EMPTY); - } else { - final int shiftAmountInt = shiftAmount.toInt(); - final Bytes value = leftPad(frame.popStackItem()); - - if (shiftAmountInt >= 256 || shiftAmountInt < 0) { - frame.pushStackItem(Bytes.EMPTY); - } else { - frame.pushStackItem(value.shiftRight(shiftAmountInt)); - } - } + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.shr(s, frame.stackTop())); return shrSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperationOptimized.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperationOptimized.java deleted file mode 100644 index 1eeb1b1ca5c..00000000000 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperationOptimized.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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.operation; - -import static org.hyperledger.besu.evm.operation.Shift256Operations.isShiftOverflow; - -import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; - -/** - * The optimized SHR (Shift Right Logical) operation. - * - *

This implementation uses direct byte[] manipulation instead of Tuweni's Bytes.shiftRight() to - * avoid intermediate object allocation and improve performance. - */ -public class ShrOperationOptimized extends AbstractFixedCostOperation { - - /** The Shr operation success result. */ - static final OperationResult shrSuccess = new OperationResult(3, null); - - /** - * Instantiates a new optimized Shr operation. - * - * @param gasCalculator the gas calculator - */ - public ShrOperationOptimized(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); - } - - /** - * Performs optimized Shift Right Logical operation. - * - * @param frame the frame - * @return the operation result - */ - public static OperationResult staticOperation(final MessageFrame frame) { - final Bytes shiftAmount = frame.popStackItem(); - final Bytes value = frame.popStackItem(); - if (value.isZero()) { - frame.pushStackItem(Bytes.EMPTY); - return shrSuccess; - } - - final byte[] valueBytes = Bytes32.leftPad(value).toArrayUnsafe(); - final byte[] shiftBytes = shiftAmount.toArrayUnsafe(); - - // shift >= 256, push All 0s - if (isShiftOverflow(shiftBytes)) { - frame.pushStackItem(Bytes.EMPTY); - return shrSuccess; - } - - final int shift = shiftBytes.length == 0 ? 0 : (shiftBytes[shiftBytes.length - 1] & 0xFF); - - frame.pushStackItem(shr256(valueBytes, shift)); - return shrSuccess; - } - - /** - * Performs a 256-bit logical right shift (EVM SHR). - * - *

The shift amount is in the range {@code [0..255]} and is assumed to have been validated by - * the caller. For shift values >= 256, zero is returned by the caller. - * - * @param in the raw 32-byte array of the input value - * @param shift the right shift amount in bits (0–255) - * @return the shifted 256-bit value - */ - private static Bytes shr256(final byte[] in, final int shift) { - if (shift == 0) { - return Bytes.wrap(in); - } - - final int shiftBytes = shift >>> 3; // /8 - final int shiftBits = shift & 7; // %8 - - final byte[] out = new byte[32]; - - // Shift right: bytes move to higher indices (towards index 31) - // Bytes below shiftBytes are guaranteed zero (already from new byte[32]) - for (int i = 31; i >= shiftBytes; i--) { - final int srcIndex = i - shiftBytes; - final int curr = in[srcIndex] & 0xFF; - if (shiftBits == 0) { - out[i] = (byte) curr; - } else { - final int prev = (srcIndex - 1 >= 0) ? (in[srcIndex - 1] & 0xFF) : 0; - out[i] = (byte) ((curr >>> shiftBits) | (prev << (8 - shiftBits))); - } - } - - return Bytes.wrap(out); - } -} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SignExtendOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SignExtendOperation.java index bd9d9178aa0..76c2c0a2cd7 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SignExtendOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SignExtendOperation.java @@ -17,10 +17,7 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.bytes.MutableBytes32; +import org.hyperledger.besu.evm.internal.StackMath; /** The Sign extend operation. */ public class SignExtendOperation extends AbstractFixedCostOperation { @@ -39,7 +36,7 @@ public SignExtendOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -48,32 +45,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - final Bytes value0 = frame.popStackItem().trimLeadingZeros(); - final Bytes value1 = Bytes32.leftPad(frame.popStackItem()); - - final MutableBytes32 result = MutableBytes32.create(); - - // Any value >= 31 imply an index <= 0, so no work to do (note that 0 itself is a valid index, - // but copying the 0th byte to itself is only so useful). - int value0size = value0.size(); - if (value0size > 1) { - frame.pushStackItem(value1); - return signExtendSuccess; - } - - int value0Value = value0.toInt(); - if (value0Value >= 31) { - frame.pushStackItem(value1); - return signExtendSuccess; - } - - final int byteIndex = 31 - value0.toInt(); - final byte toSet = value1.get(byteIndex) < 0 ? (byte) 0xFF : 0x00; - result.mutableSlice(0, byteIndex).fill(toSet); - value1.slice(byteIndex).copyTo(result, byteIndex); - frame.pushStackItem(result); - + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.signExtend(s, frame.stackTop())); return signExtendSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SlotNumOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SlotNumOperation.java index e17145a2252..20749570e41 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SlotNumOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SlotNumOperation.java @@ -17,7 +17,7 @@ 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.internal.Words; +import org.hyperledger.besu.evm.internal.StackMath; /** The SLOTNUM operation (EIP-7843). */ public class SlotNumOperation extends AbstractFixedCostOperation { @@ -34,8 +34,10 @@ public SlotNumOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - final long slotNumber = frame.getBlockValues().getSlotNumber(); - frame.pushStackItem(Words.longBytes(slotNumber)); + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTop( + StackMath.pushLong( + frame.stackData(), frame.stackTop(), frame.getBlockValues().getSlotNumber())); return successResponse; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/StaticCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/StaticCallOperation.java index 29a131f889f..3716532c56d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/StaticCallOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/StaticCallOperation.java @@ -14,13 +14,11 @@ */ package org.hyperledger.besu.evm.operation; -import static org.hyperledger.besu.evm.internal.Words.clampedToLong; - import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.internal.StackMath; /** The Static call operation. */ public class StaticCallOperation extends AbstractCallOperation { @@ -36,7 +34,7 @@ public StaticCallOperation(final GasCalculator gasCalculator) { @Override protected Address to(final MessageFrame frame) { - return Words.toAddress(frame.getStackItem(1)); + return StackMath.toAddressAt(frame.stackData(), frame.stackTop(), 1); } @Override @@ -51,22 +49,22 @@ protected Wei apparentValue(final MessageFrame frame) { @Override protected long inputDataOffset(final MessageFrame frame) { - return clampedToLong(frame.getStackItem(2)); + return StackMath.clampedToLong(frame.stackData(), frame.stackTop(), 2); } @Override protected long inputDataLength(final MessageFrame frame) { - return clampedToLong(frame.getStackItem(3)); + return StackMath.clampedToLong(frame.stackData(), frame.stackTop(), 3); } @Override protected long outputDataOffset(final MessageFrame frame) { - return clampedToLong(frame.getStackItem(4)); + return StackMath.clampedToLong(frame.stackData(), frame.stackTop(), 4); } @Override protected long outputDataLength(final MessageFrame frame) { - return clampedToLong(frame.getStackItem(5)); + return StackMath.clampedToLong(frame.stackData(), frame.stackTop(), 5); } @Override diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SubOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SubOperation.java index 491f1ff5980..e3e95c02374 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SubOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SubOperation.java @@ -17,11 +17,7 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import java.math.BigInteger; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; +import org.hyperledger.besu.evm.internal.StackMath; /** The Sub (Subtract) operation. */ public class SubOperation extends AbstractFixedCostOperation { @@ -41,7 +37,7 @@ public SubOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -50,22 +46,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - final BigInteger value0 = new BigInteger(1, frame.popStackItem().toArrayUnsafe()); - final BigInteger value1 = new BigInteger(1, frame.popStackItem().toArrayUnsafe()); - - final BigInteger result = value0.subtract(value1); - - byte[] resultArray = result.toByteArray(); - int length = resultArray.length; - if (length >= 32) { - frame.pushStackItem(Bytes.wrap(resultArray, length - 32, 32)); - } else if (result.signum() < 0) { - frame.pushStackItem(Bytes32.leftPad(Bytes.wrap(resultArray), (byte) -1)); - } else { - frame.pushStackItem(Bytes.wrap(resultArray)); - } - + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.sub(s, frame.stackTop())); return subSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SwapNOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SwapNOperation.java index ed9654ebb93..180d6a44dd2 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SwapNOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SwapNOperation.java @@ -18,9 +18,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.UnderflowException; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** * The SWAPN operation (EIP-8024). @@ -59,19 +57,21 @@ public SwapNOperation(final GasCalculator gasCalculator) { @Override public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { - return staticOperation(frame, frame.getCode().getBytes().toArrayUnsafe(), frame.getPC()); + return staticOperation( + frame, frame.stackData(), frame.getCode().getBytes().toArrayUnsafe(), frame.getPC()); } /** * Performs SWAPN operation directly for hot-path execution. * * @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 byte[] code, final int pc) { + final MessageFrame frame, final long[] s, final byte[] code, final int pc) { // Get immediate byte, treating end-of-code as 0 final int imm = (pc + 1 >= code.length) ? 0 : code[pc + 1] & 0xFF; @@ -82,17 +82,11 @@ public static OperationResult staticOperation( final int n = Eip8024Decoder.DECODE_SINGLE[imm]; - try { - // Swap the top of stack (index 0) with the (n+1)'th item (index n) - // In Besu's 0-indexed stack, top is index 0, (n+1)'th is index n - final Bytes top = frame.getStackItem(0); - final Bytes nthItem = frame.getStackItem(n); - frame.setStackItem(0, nthItem); - frame.setStackItem(n, top); - return SWAPN_SUCCESS; - } catch (final UnderflowException ufe) { + if (!frame.stackHasItems(n + 1)) { return UNDERFLOW_RESPONSE; } + StackMath.swap(s, frame.stackTop(), n); + return SWAPN_SUCCESS; } /** diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SwapOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SwapOperation.java index 12a389dcc5c..954b61b174b 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SwapOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SwapOperation.java @@ -18,8 +18,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The Swap operation. */ public class SwapOperation extends AbstractFixedCostOperation { @@ -57,21 +56,21 @@ public SwapOperation(final int index, final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame, index); + return staticOperation(frame, frame.stackData(), index); } /** * Performs swap operation. * * @param frame the frame + * @param s the stack data array * @param index the index * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame, final int index) { - final Bytes tmp = frame.getStackItem(0); - frame.setStackItem(0, frame.getStackItem(index)); - frame.setStackItem(index, tmp); - + public static OperationResult staticOperation( + final MessageFrame frame, final long[] s, final int index) { + if (!frame.stackHasItems(index + 1)) return UNDERFLOW_RESPONSE; + StackMath.swap(s, frame.stackTop(), index); return swapSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/TLoadOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/TLoadOperation.java index 684d18f987a..176fdc03f06 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/TLoadOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/TLoadOperation.java @@ -18,10 +18,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.UnderflowException; - -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; +import org.hyperledger.besu.evm.internal.StackMath; /** Implements the TLOAD operation defined in EIP-1153 */ public class TLoadOperation extends AbstractOperation { @@ -38,17 +35,21 @@ public TLoadOperation(final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { final long cost = gasCalculator().getTransientLoadOperationGasCost(); - try { - final Bytes32 slot = UInt256.fromBytes(frame.popStackItem()); - if (frame.getRemainingGas() < cost) { - return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); - } else { - frame.pushStackItem(frame.getTransientStorageValue(frame.getRecipientAddress(), slot)); - - return new OperationResult(cost, null); - } - } catch (final UnderflowException ufe) { + if (!frame.stackHasItems(1)) { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); } + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final org.hyperledger.besu.evm.UInt256 slot = StackMath.getAt(s, top, 0); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + // Overwrite in place (pop 1, push 1) + final byte[] result = + frame + .getTransientStorageValue(frame.getRecipientAddress(), slot.toBytes32()) + .toArrayUnsafe(); + StackMath.fromBytesAt(s, top, 0, result, 0, result.length); + return new OperationResult(cost, null); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/TStoreOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/TStoreOperation.java index 9eac70f58a9..f9757250fb1 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/TStoreOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/TStoreOperation.java @@ -18,8 +18,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.units.bigints.UInt256; +import org.hyperledger.besu.evm.internal.StackMath; /** Implements the TLOAD operation defined in EIP-1153 */ public class TStoreOperation extends AbstractOperation { @@ -35,8 +34,15 @@ public TStoreOperation(final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - final UInt256 key = UInt256.fromBytes(frame.popStackItem()); - final UInt256 value = UInt256.fromBytes(frame.popStackItem()); + if (!frame.stackHasItems(2)) { + return new OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } + final long[] s = frame.stackData(); + final int top = frame.stackTop(); + final org.hyperledger.besu.evm.UInt256 key = StackMath.getAt(s, top, 0); + final org.hyperledger.besu.evm.UInt256 value = StackMath.getAt(s, top, 1); + // Pop 2 + frame.setTop(top - 2); final long cost = gasCalculator().getTransientStoreOperationGasCost(); @@ -46,7 +52,8 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { } else if (remainingGas < cost) { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); } else { - frame.setTransientStorageValue(frame.getRecipientAddress(), key, value); + frame.setTransientStorageValue( + frame.getRecipientAddress(), key.toBytes32(), value.toBytes32()); return new OperationResult(cost, null); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/TimestampOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/TimestampOperation.java index 55863d0d69e..18496daadb8 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/TimestampOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/TimestampOperation.java @@ -17,7 +17,7 @@ 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.internal.Words; +import org.hyperledger.besu.evm.internal.StackMath; /** The Timestamp operation. */ public class TimestampOperation extends AbstractFixedCostOperation { @@ -34,8 +34,10 @@ public TimestampOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - final long timestamp = frame.getBlockValues().getTimestamp(); - frame.pushStackItem(Words.longBytes(timestamp)); + if (!frame.stackHasSpace(1)) return OVERFLOW_RESPONSE; + frame.setTop( + StackMath.pushLong( + frame.stackData(), frame.stackTop(), frame.getBlockValues().getTimestamp())); return successResponse; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/XorOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/XorOperation.java index f1f78d9f3ea..b4ac798a0aa 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/XorOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/XorOperation.java @@ -17,8 +17,7 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.internal.StackMath; /** The XOR operation. */ public class XorOperation extends AbstractFixedCostOperation { @@ -38,7 +37,7 @@ public XorOperation(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { - return staticOperation(frame); + return staticOperation(frame, frame.stackData()); } /** @@ -47,14 +46,9 @@ public Operation.OperationResult executeFixedCostOperation( * @param frame the frame * @return the operation result */ - public static OperationResult staticOperation(final MessageFrame frame) { - final Bytes value0 = frame.popStackItem(); - final Bytes value1 = frame.popStackItem(); - - final Bytes result = value0.xor(value1); - - frame.pushStackItem(result); - + public static OperationResult staticOperation(final MessageFrame frame, final long[] s) { + if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + frame.setTop(StackMath.xor(s, frame.stackTop())); return xorSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/XorOperationOptimized.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/XorOperationOptimized.java deleted file mode 100644 index 03151d021e1..00000000000 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/XorOperationOptimized.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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.operation; - -import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.UInt256; -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.GasCalculator; - -import org.apache.tuweni.bytes.Bytes; - -/** The XOR operation. */ -public class XorOperationOptimized extends AbstractFixedCostOperation { - - /** The XOR operation success result. */ - static final OperationResult xorSuccess = new OperationResult(3, null); - - /** - * Instantiates a new Xor operation. - * - * @param gasCalculator the gas calculator - */ - public XorOperationOptimized(final GasCalculator gasCalculator) { - super(0x18, "XOR", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); - } - - @Override - public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { - return staticOperation(frame); - } - - /** - * Performs XOR operation. - * - * @param frame the frame - * @return the operation result - */ - public static OperationResult staticOperation(final MessageFrame frame) { - final Bytes value0 = frame.popStackItem(); - final Bytes value1 = frame.popStackItem(); - UInt256 b0 = UInt256.fromBytesBE(value0.toArrayUnsafe()); - UInt256 b1 = UInt256.fromBytesBE(value1.toArrayUnsafe()); - UInt256 result = b0.xor(b1); - byte[] resultArray = result.toBytesBE(); - frame.pushStackItem(Bytes.wrap(resultArray)); - return xorSuccess; - } -} 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 eb9d974ae78..04f9784f243 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 @@ -164,6 +164,7 @@ protected void revert(final MessageFrame frame) { private void completedSuccess(final MessageFrame frame) { frame.getWorldUpdater().commit(); frame.getMessageFrameStack().removeFirst(); + frame.returnStackToPool(); frame.notifyCompletion(); } @@ -174,6 +175,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/tracing/EthTransferLogOperationTracer.java b/evm/src/main/java/org/hyperledger/besu/evm/tracing/EthTransferLogOperationTracer.java index 2934ba8199e..34bab2b3e91 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/tracing/EthTransferLogOperationTracer.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/tracing/EthTransferLogOperationTracer.java @@ -82,7 +82,7 @@ public void traceContextExit(final MessageFrame frame) { } private void emitSelfDestructLog(final MessageFrame frame) { - final Address beneficiaryAddress = Words.toAddress(frame.getStackItem(0)); + final Address beneficiaryAddress = Words.toAddress(frame.getStackBytes(0)); final Address originatorAddress = frame.getRecipientAddress(); final MutableAccount originatorAccount = frame.getWorldUpdater().getAccount(originatorAddress); final Wei originatorBalance = originatorAccount.getBalance(); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/tracing/StreamingOperationTracer.java b/evm/src/main/java/org/hyperledger/besu/evm/tracing/StreamingOperationTracer.java index 2854d629763..a0d8e9c7aeb 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/tracing/StreamingOperationTracer.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/tracing/StreamingOperationTracer.java @@ -108,7 +108,7 @@ public void tracePreExecution(final MessageFrame messageFrame) { } stack = new ArrayList<>(messageFrame.stackSize()); for (int i = messageFrame.stackSize() - 1; i >= 0; i--) { - stack.add("\"" + shortBytes(messageFrame.getStackItem(i)) + "\""); + stack.add("\"" + shortBytes(messageFrame.getStackBytes(i)) + "\""); } pc = messageFrame.getPC(); gas = messageFrame.getRemainingGas(); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/CodeTest.java b/evm/src/test/java/org/hyperledger/besu/evm/CodeTest.java index be7d42d53fe..e070bb50c6e 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/CodeTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/CodeTest.java @@ -26,13 +26,13 @@ import org.hyperledger.besu.evm.frame.BlockValues; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.internal.StackMath; import org.hyperledger.besu.evm.operation.JumpOperation; import org.hyperledger.besu.evm.operation.Operation.OperationResult; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import jakarta.validation.constraints.NotNull; import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -90,7 +90,7 @@ private MessageFrame createJumpFrame(final Code getsCached) { .build(); frame.setPC(CURRENT_PC); - frame.pushStackItem(UInt256.fromHexString("0x03")); + frame.setTop(StackMath.pushLong(frame.stackData(), frame.stackTop(), 3)); return frame; } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/UInt256MulDivTest.java b/evm/src/test/java/org/hyperledger/besu/evm/UInt256MulDivTest.java new file mode 100644 index 00000000000..cedac1f1f0c --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/UInt256MulDivTest.java @@ -0,0 +1,591 @@ +/* + * 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; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigInteger; + +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; +import org.junit.jupiter.api.Test; + +/** + * Comprehensive property-based and edge-case tests for UInt256 mul() and div() operations, + * comparing against BigInteger to find correctness bugs. + */ +public class UInt256MulDivTest { + + private static final BigInteger TWO_256 = BigInteger.ONE.shiftLeft(256); + private static final BigInteger MAX_UINT256 = TWO_256.subtract(BigInteger.ONE); + + // region Providers + + @Provide + Arbitrary unsigned1to32() { + return Arbitraries.bytes() + .array(byte[].class) + .ofMinSize(1) + .ofMaxSize(32) + .map(UInt256MulDivTest::clampUnsigned32); + } + + @Provide + Arbitrary oneLimbDivisor() { + return Arbitraries.bytes() + .array(byte[].class) + .ofMinSize(1) + .ofMaxSize(8) + .map(UInt256MulDivTest::clampUnsigned32); + } + + @Provide + Arbitrary twoLimbDivisor() { + return Arbitraries.bytes() + .array(byte[].class) + .ofMinSize(9) + .ofMaxSize(16) + .map(UInt256MulDivTest::clampUnsigned32); + } + + @Provide + Arbitrary threeLimbDivisor() { + return Arbitraries.bytes() + .array(byte[].class) + .ofMinSize(17) + .ofMaxSize(24) + .map(UInt256MulDivTest::clampUnsigned32); + } + + @Provide + Arbitrary fourLimbDivisor() { + return Arbitraries.bytes() + .array(byte[].class) + .ofMinSize(25) + .ofMaxSize(32) + .map(UInt256MulDivTest::clampUnsigned32); + } + + // endregion + + // region 1. Property-based: mul() vs BigInteger + + @Property(tries = 10000) + void property_mul_matchesBigInteger( + @ForAll("unsigned1to32") final byte[] a, @ForAll("unsigned1to32") final byte[] b) { + final UInt256 ua = UInt256.fromBytesBE(a); + final UInt256 ub = UInt256.fromBytesBE(b); + + final byte[] got = ua.mul(ub).toBytesBE(); + + BigInteger A = toBigUnsigned(a); + BigInteger B = toBigUnsigned(b); + byte[] expected = bigUnsignedToBytes32(A.multiply(B).mod(TWO_256)); + assertThat(got) + .as("mul mismatch: %s * %s", ua.toHexString(), ub.toHexString()) + .containsExactly(expected); + } + + @Property(tries = 10000) + void property_mul_commutative( + @ForAll("unsigned1to32") final byte[] a, @ForAll("unsigned1to32") final byte[] b) { + final UInt256 ua = UInt256.fromBytesBE(a); + final UInt256 ub = UInt256.fromBytesBE(b); + assertThat(ua.mul(ub)).isEqualTo(ub.mul(ua)); + } + + // endregion + + // region 2. Property-based: div() vs BigInteger + + @Property(tries = 10000) + void property_div_matchesBigInteger( + @ForAll("unsigned1to32") final byte[] a, @ForAll("unsigned1to32") final byte[] b) { + final UInt256 ua = UInt256.fromBytesBE(a); + final UInt256 ub = UInt256.fromBytesBE(b); + + final byte[] got = ua.div(ub).toBytesBE(); + + BigInteger A = toBigUnsigned(a); + BigInteger B = toBigUnsigned(b); + byte[] expected; + if (B.signum() == 0) { + expected = Bytes32.ZERO.toArrayUnsafe(); + } else { + expected = bigUnsignedToBytes32(A.divide(B)); + } + assertThat(got) + .as("div mismatch: %s / %s", ua.toHexString(), ub.toHexString()) + .containsExactly(expected); + } + + // endregion + + // region 3. Property-based: div() with specific divisor sizes + + @Property(tries = 5000) + void property_div_oneLimbDivisor( + @ForAll("unsigned1to32") final byte[] a, @ForAll("oneLimbDivisor") final byte[] b) { + final UInt256 ua = UInt256.fromBytesBE(a); + final UInt256 ub = UInt256.fromBytesBE(b); + + final byte[] got = ua.div(ub).toBytesBE(); + + BigInteger A = toBigUnsigned(a); + BigInteger B = toBigUnsigned(b); + byte[] expected = + (B.signum() == 0) ? Bytes32.ZERO.toArrayUnsafe() : bigUnsignedToBytes32(A.divide(B)); + assertThat(got) + .as("div1 mismatch: %s / %s", ua.toHexString(), ub.toHexString()) + .containsExactly(expected); + } + + @Property(tries = 5000) + void property_div_twoLimbDivisor( + @ForAll("unsigned1to32") final byte[] a, @ForAll("twoLimbDivisor") final byte[] b) { + final UInt256 ua = UInt256.fromBytesBE(a); + final UInt256 ub = UInt256.fromBytesBE(b); + + final byte[] got = ua.div(ub).toBytesBE(); + + BigInteger A = toBigUnsigned(a); + BigInteger B = toBigUnsigned(b); + byte[] expected = bigUnsignedToBytes32(A.divide(B)); + assertThat(got) + .as("div2 mismatch: %s / %s", ua.toHexString(), ub.toHexString()) + .containsExactly(expected); + } + + @Property(tries = 5000) + void property_div_threeLimbDivisor( + @ForAll("unsigned1to32") final byte[] a, @ForAll("threeLimbDivisor") final byte[] b) { + final UInt256 ua = UInt256.fromBytesBE(a); + final UInt256 ub = UInt256.fromBytesBE(b); + + final byte[] got = ua.div(ub).toBytesBE(); + + BigInteger A = toBigUnsigned(a); + BigInteger B = toBigUnsigned(b); + byte[] expected = bigUnsignedToBytes32(A.divide(B)); + assertThat(got) + .as("div3 mismatch: %s / %s", ua.toHexString(), ub.toHexString()) + .containsExactly(expected); + } + + @Property(tries = 5000) + void property_div_fourLimbDivisor( + @ForAll("unsigned1to32") final byte[] a, @ForAll("fourLimbDivisor") final byte[] b) { + final UInt256 ua = UInt256.fromBytesBE(a); + final UInt256 ub = UInt256.fromBytesBE(b); + + final byte[] got = ua.div(ub).toBytesBE(); + + BigInteger A = toBigUnsigned(a); + BigInteger B = toBigUnsigned(b); + byte[] expected = bigUnsignedToBytes32(A.divide(B)); + assertThat(got) + .as("div4 mismatch: %s / %s", ua.toHexString(), ub.toHexString()) + .containsExactly(expected); + } + + // endregion + + // region 4. Property-based: signedDiv() vs BigInteger + + @Property(tries = 10000) + void property_signedDiv_matchesBigInteger( + @ForAll("unsigned1to32") final byte[] a, @ForAll("unsigned1to32") final byte[] b) { + final byte[] a32 = Bytes32.leftPad(Bytes.wrap(a)).toArrayUnsafe(); + final byte[] b32 = Bytes32.leftPad(Bytes.wrap(b)).toArrayUnsafe(); + final BigInteger A = new BigInteger(a32); // signed interpretation + final BigInteger B = new BigInteger(b32); // signed interpretation + final UInt256 ua = UInt256.fromBytesBE(a32); + final UInt256 ub = UInt256.fromBytesBE(b32); + + final byte[] got = ua.signedDiv(ub).toBytesBE(); + + byte[] expected; + if (B.signum() == 0) { + expected = Bytes32.ZERO.toArrayUnsafe(); + } else if (A.equals(minSigned256()) && B.equals(BigInteger.valueOf(-1))) { + // Special case: MIN_INT256 / -1 = MIN_INT256 (overflow) + expected = bigSignedToBytes32(A); + } else { + expected = bigSignedToBytes32(A.divide(B)); + } + assertThat(got) + .as("signedDiv mismatch: %s / %s", ua.toHexString(), ub.toHexString()) + .containsExactly(expected); + } + + // endregion + + // region 5. Property-based: division invariant a == (a/b)*b + (a%b) + + @Property(tries = 10000) + void property_div_mod_invariant( + @ForAll("unsigned1to32") final byte[] a, @ForAll("unsigned1to32") final byte[] b) { + final UInt256 ua = UInt256.fromBytesBE(a); + final UInt256 ub = UInt256.fromBytesBE(b); + + if (ub.equals(UInt256.ZERO)) { + return; // skip div by zero + } + + UInt256 q = ua.div(ub); + UInt256 r = ua.mod(ub); + UInt256 reconstructed = q.mul(ub).add(r); + + assertThat(reconstructed) + .as( + "invariant a=(a/b)*b+(a%%b) failed: a=%s b=%s q=%s r=%s", + ua.toHexString(), ub.toHexString(), q.toHexString(), r.toHexString()) + .isEqualTo(ua); + } + + // endregion + + // region 6. Edge case tests for div() + + @Test + void div_maxBy1() { + assertDivMatchesBigInteger(MAX_UINT256, BigInteger.ONE); + } + + @Test + void div_maxBy2() { + assertDivMatchesBigInteger(MAX_UINT256, BigInteger.TWO); + } + + @Test + void div_maxByMax() { + assertDivMatchesBigInteger(MAX_UINT256, MAX_UINT256); + } + + @Test + void div_maxByMaxMinus1() { + assertDivMatchesBigInteger(MAX_UINT256, MAX_UINT256.subtract(BigInteger.ONE)); + } + + @Test + void div_pow128ByPow64() { + assertDivMatchesBigInteger(BigInteger.ONE.shiftLeft(128), BigInteger.ONE.shiftLeft(64)); + } + + @Test + void div_pow192ByPow128() { + assertDivMatchesBigInteger(BigInteger.ONE.shiftLeft(192), BigInteger.ONE.shiftLeft(128)); + } + + @Test + void div_pow255ByPow127() { + assertDivMatchesBigInteger(BigInteger.ONE.shiftLeft(255), BigInteger.ONE.shiftLeft(127)); + } + + @Test + void div_allOnesByAllOnesMinus1() { + assertDivMatchesBigInteger(MAX_UINT256, MAX_UINT256.subtract(BigInteger.ONE)); + } + + @Test + void div_allOnesByAllFsMinus1InLowerHalf() { + // 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF / 0xFFFFFFFFFFFFFFFE + BigInteger dividend = BigInteger.ONE.shiftLeft(128).subtract(BigInteger.ONE); + BigInteger divisor = BigInteger.ONE.shiftLeft(64).subtract(BigInteger.TWO); + assertDivMatchesBigInteger(dividend, divisor); + } + + @Test + void div_nearOverflowQuotientEstimate() { + // Construct case where top 2 limbs of dividend / top limb of divisor ~ 2^64 + // dividend = (2^64 - 1) << 192 | (2^64 - 1) << 128 + // divisor = (2^64 - 1) << 64 | 1 + BigInteger limb = BigInteger.ONE.shiftLeft(64).subtract(BigInteger.ONE); + BigInteger dividend = limb.shiftLeft(192).or(limb.shiftLeft(128)); + BigInteger divisor = limb.shiftLeft(64).or(BigInteger.ONE); + assertDivMatchesBigInteger(dividend, divisor); + } + + @Test + void div_maxDividendSmallMultiLimbDivisor() { + // MAX / (2^64 + 1) + BigInteger divisor = BigInteger.ONE.shiftLeft(64).add(BigInteger.ONE); + assertDivMatchesBigInteger(MAX_UINT256, divisor); + } + + @Test + void div_maxDividend3LimbDivisor() { + // MAX / (2^128 + 1) + BigInteger divisor = BigInteger.ONE.shiftLeft(128).add(BigInteger.ONE); + assertDivMatchesBigInteger(MAX_UINT256, divisor); + } + + @Test + void div_addBackTriggerCase() { + // Specifically constructed to trigger the add-back step in Knuth's Algorithm D. + // When the trial quotient digit is too large by 1, the algorithm must subtract 1 + // and add back the divisor. This happens when: + // - The normalized dividend's top 2 digits / top divisor digit gives a quotient + // that, when multiplied by the full divisor, exceeds the dividend portion. + // + // Using known add-back trigger: dividend close to divisor * (2^64 - 1) + BigInteger divisor = new BigInteger("FFFFFFFFFFFFFFFF0000000000000001", 16); + BigInteger qHigh = BigInteger.ONE.shiftLeft(64).subtract(BigInteger.ONE); // 2^64 - 1 + BigInteger dividend = divisor.multiply(qHigh).add(divisor.subtract(BigInteger.ONE)); + if (dividend.bitLength() <= 256) { + assertDivMatchesBigInteger(dividend, divisor); + } + } + + @Test + void div_addBackTriggerCase2() { + // Another add-back case: divisor with high bit pattern that causes overestimate + BigInteger divisor = new BigInteger("80000000000000000000000000000001", 16); + BigInteger quotient = new BigInteger("FFFFFFFFFFFFFFFE", 16); + BigInteger remainder = divisor.subtract(BigInteger.ONE); + BigInteger dividend = divisor.multiply(quotient).add(remainder); + if (dividend.bitLength() <= 256) { + assertDivMatchesBigInteger(dividend, divisor); + } + } + + @Test + void div_largeDividendSmall2LimbDivisor() { + // A very large dividend divided by a small 2-limb divisor, producing large quotient + BigInteger divisor = BigInteger.ONE.shiftLeft(64).add(BigInteger.valueOf(3)); + assertDivMatchesBigInteger(MAX_UINT256, divisor); + } + + @Test + void div_borrowOverflowCase() { + // Case designed to trigger combined borrow overflow in divGeneral multiply-subtract loop + // prodHi near 2^64-1 AND subtraction underflow simultaneously + BigInteger divisor = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16); // 128-bit all-ones + BigInteger dividend = MAX_UINT256; // 256-bit all-ones + assertDivMatchesBigInteger(dividend, divisor); + } + + @Test + void div_borrowOverflowCase2() { + // Divisor with top limb = 0xFFFFFFFFFFFFFFFF and dividend = MAX + BigInteger divisor = new BigInteger("FFFFFFFFFFFFFFFF0000000000000000", 16); + assertDivMatchesBigInteger(MAX_UINT256, divisor); + } + + @Test + void div_borrowOverflowCase3() { + // 4-limb divisor with all-ones pattern + BigInteger divisor = + new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE", 16); + assertDivMatchesBigInteger(MAX_UINT256, divisor); + } + + @Test + void div_oneByOne() { + assertDivMatchesBigInteger(BigInteger.ONE, BigInteger.ONE); + } + + @Test + void div_zeroByAny() { + assertDivMatchesBigInteger(BigInteger.ZERO, BigInteger.valueOf(42)); + } + + @Test + void div_byZero() { + UInt256 result = UInt256.fromBytesBE(new byte[] {1}).div(UInt256.ZERO); + assertThat(result).isEqualTo(UInt256.ZERO); + } + + @Test + void div_powersOf2() { + for (int i = 1; i < 256; i++) { + BigInteger dividend = BigInteger.ONE.shiftLeft(255); + BigInteger divisor = BigInteger.ONE.shiftLeft(i); + assertDivMatchesBigInteger(dividend, divisor); + } + } + + @Test + void div_consecutiveValues() { + // Test divisions of consecutive large values to stress boundary conditions + for (int offset = 0; offset < 16; offset++) { + BigInteger base = BigInteger.ONE.shiftLeft(128); + BigInteger dividend = base.add(BigInteger.valueOf(offset)); + BigInteger divisor = BigInteger.ONE.shiftLeft(64).add(BigInteger.valueOf(offset)); + assertDivMatchesBigInteger(dividend, divisor); + } + } + + // endregion + + // region 7. Edge case tests for mul() + + @Test + void mul_maxTimesMax() { + assertMulMatchesBigInteger(MAX_UINT256, MAX_UINT256); + } + + @Test + void mul_maxTimes2() { + assertMulMatchesBigInteger(MAX_UINT256, BigInteger.TWO); + } + + @Test + void mul_maxTimesMaxMinus1() { + assertMulMatchesBigInteger(MAX_UINT256, MAX_UINT256.subtract(BigInteger.ONE)); + } + + @Test + void mul_powersOf2() { + for (int i = 0; i < 256; i++) { + BigInteger a = BigInteger.ONE.shiftLeft(i); + BigInteger b = BigInteger.ONE.shiftLeft(255 - i); + assertMulMatchesBigInteger(a, b); + } + } + + @Test + void mul_maxTimesOne() { + assertMulMatchesBigInteger(MAX_UINT256, BigInteger.ONE); + } + + @Test + void mul_maxTimesZero() { + assertMulMatchesBigInteger(MAX_UINT256, BigInteger.ZERO); + } + + @Test + void mul_halfMaxSquared() { + BigInteger half = BigInteger.ONE.shiftLeft(128).subtract(BigInteger.ONE); + assertMulMatchesBigInteger(half, half); + } + + @Test + void mul_limbBoundaries() { + // Values at limb boundaries: 2^64-1, 2^64, 2^128-1, etc. + BigInteger[] boundaries = { + BigInteger.ONE.shiftLeft(64).subtract(BigInteger.ONE), + BigInteger.ONE.shiftLeft(64), + BigInteger.ONE.shiftLeft(128).subtract(BigInteger.ONE), + BigInteger.ONE.shiftLeft(128), + BigInteger.ONE.shiftLeft(192).subtract(BigInteger.ONE), + BigInteger.ONE.shiftLeft(192), + }; + for (BigInteger a : boundaries) { + for (BigInteger b : boundaries) { + assertMulMatchesBigInteger(a, b); + } + } + } + + @Test + void mul_allOnesPattern() { + // 0xFFFFFFFFFFFFFFFF * 0xFFFFFFFFFFFFFFFF (single limb max * single limb max) + BigInteger limbMax = BigInteger.ONE.shiftLeft(64).subtract(BigInteger.ONE); + assertMulMatchesBigInteger(limbMax, limbMax); + } + + // endregion + + // region Helpers + + private void assertDivMatchesBigInteger(final BigInteger a, final BigInteger b) { + UInt256 ua = bigIntegerToUInt256(a); + UInt256 ub = bigIntegerToUInt256(b); + + byte[] got = ua.div(ub).toBytesBE(); + + byte[] expected; + if (b.signum() == 0) { + expected = Bytes32.ZERO.toArrayUnsafe(); + } else { + expected = bigUnsignedToBytes32(a.divide(b)); + } + assertThat(got) + .as("div mismatch: %s / %s", ua.toHexString(), ub.toHexString()) + .containsExactly(expected); + } + + private void assertMulMatchesBigInteger(final BigInteger a, final BigInteger b) { + UInt256 ua = bigIntegerToUInt256(a); + UInt256 ub = bigIntegerToUInt256(b); + + byte[] got = ua.mul(ub).toBytesBE(); + + byte[] expected = bigUnsignedToBytes32(a.multiply(b).mod(TWO_256)); + assertThat(got) + .as("mul mismatch: %s * %s", ua.toHexString(), ub.toHexString()) + .containsExactly(expected); + } + + private static UInt256 bigIntegerToUInt256(final BigInteger val) { + return UInt256.fromBytesBE(bigUnsignedToBytes32(val.mod(TWO_256))); + } + + private static byte[] clampUnsigned32(final byte[] any) { + if (any.length == 0) { + return new byte[] {0}; + } + int len = Math.max(1, Math.min(32, any.length)); + byte[] out = new byte[len]; + System.arraycopy(any, 0, out, 0, len); + return out; + } + + private static byte[] bigUnsignedToBytes32(final BigInteger x) { + BigInteger y = x.mod(TWO_256); + + byte[] ba = y.toByteArray(); + if (ba.length == 0) { + return new byte[] {0}; + } + + if (ba.length == 32) { + return ba; + } + + if (ba.length < 32) { + byte[] out = new byte[32]; + System.arraycopy(ba, 0, out, 32 - ba.length, ba.length); + return out; + } + + byte[] out = new byte[32]; + System.arraycopy(ba, ba.length - 32, out, 0, 32); + return out; + } + + private static BigInteger toBigUnsigned(final byte[] be) { + return new BigInteger(1, be); + } + + private static BigInteger minSigned256() { + // -2^255 in two's complement = 0x8000...0000 + return BigInteger.ONE.shiftLeft(255).negate(); + } + + private static byte[] bigSignedToBytes32(final BigInteger val) { + // Convert signed BigInteger to 256-bit two's complement bytes + BigInteger mod = val.mod(TWO_256); + return bigUnsignedToBytes32(mod); + } + + // endregion +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/UInt256Prop.java b/evm/src/test/java/org/hyperledger/besu/evm/UInt256Prop.java new file mode 100644 index 00000000000..3cfc4a7ad13 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/UInt256Prop.java @@ -0,0 +1,273 @@ +/* + * 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; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigInteger; +import java.util.Arrays; + +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; + +public class UInt256Prop { + @Provide + Arbitrary unsigned1to32() { + return Arbitraries.bytes() + .array(byte[].class) + .ofMinSize(1) + .ofMaxSize(32) + .map(UInt256Prop::clampUnsigned32); + } + + @Provide + Arbitrary unsigned0to64() { + return Arbitraries.bytes() + .array(byte[].class) + .ofMinSize(0) + .ofMaxSize(64) + .map(UInt256Prop::clampUnsigned32); + } + + @Provide + Arbitrary singleLimbUnsigned1to4() { + return Arbitraries.bytes() + .array(byte[].class) + .ofMinSize(1) + .ofMaxSize(4) + .map(UInt256Prop::clampUnsigned32); + } + + @Provide + Arbitrary shifts() { + return Arbitraries.integers().between(-512, 512); + } + + @Property + void property_roundTripUnsigned_toFromBytesBE(@ForAll("unsigned0to64") final byte[] any) { + // Arrange + final byte[] be = clampUnsigned32(any); + + // Act + final UInt256 u = UInt256.fromBytesBE(be); + final byte[] back = u.toBytesBE(); + + // Assert + assertThat(back).hasSize(32); + byte[] expected = bigUnsignedToBytes32(toBigUnsigned(be)); + assertThat(back).containsExactly(expected); + } + + @Property + void property_equals_compare_consistent( + @ForAll("unsigned1to32") final byte[] a, @ForAll("unsigned1to32") final byte[] b) { + // Arrange + final UInt256 ua = UInt256.fromBytesBE(a); + final UInt256 ub = UInt256.fromBytesBE(b); + + // Act + final int cmp = UInt256.compare(ua, ub); + final boolean eq = ua.equals(ub); + + // Assert + assertThat(cmp == 0).isEqualTo(eq); + + BigInteger ba = toBigUnsigned(a); + BigInteger bb = toBigUnsigned(b); + int bc = ba.compareTo(bb); + assertThat(Integer.signum(cmp)).isEqualTo(Integer.signum(bc)); + } + + @Property + void property_mod_matchesBigInteger( + @ForAll("unsigned1to32") final byte[] a, @ForAll("unsigned1to32") final byte[] m) { + // Arrange + final UInt256 ua = UInt256.fromBytesBE(a); + final UInt256 um = UInt256.fromBytesBE(m); + + // Act + final byte[] got = ua.mod(um).toBytesBE(); + + // Assert + BigInteger A = toBigUnsigned(a); + BigInteger M = toBigUnsigned(m); + byte[] exp = (M.signum() == 0) ? Bytes32.ZERO.toArrayUnsafe() : bigUnsignedToBytes32(A.mod(M)); + assertThat(got).containsExactly(exp); + } + + @Property + void property_mod_singleLimb_matchesBigInteger( + @ForAll("singleLimbUnsigned1to4") final byte[] a, + @ForAll("singleLimbUnsigned1to4") final byte[] m) { + + // Arrange + final UInt256 ua = UInt256.fromBytesBE(a); + final UInt256 um = UInt256.fromBytesBE(m); + + // Act + final byte[] got = ua.mod(um).toBytesBE(); + + // Assert + BigInteger A = toBigUnsigned(a); + BigInteger M = toBigUnsigned(m); + byte[] exp = (M.signum() == 0) ? Bytes32.ZERO.toArrayUnsafe() : bigUnsignedToBytes32(A.mod(M)); + assertThat(got).containsExactly(exp); + } + + @Property + void property_signedMod_matchesEvmSemantics( + @ForAll("unsigned1to32") final byte[] a, @ForAll("unsigned1to32") final byte[] m) { + + // Arrange + final byte[] a32 = Bytes32.leftPad(Bytes.wrap(a)).toArrayUnsafe(); + final byte[] m32 = Bytes32.leftPad(Bytes.wrap(m)).toArrayUnsafe(); + final BigInteger A = new BigInteger(a32); + final BigInteger M = new BigInteger(m32); + final UInt256 ua = UInt256.fromBytesBE(a32); + final UInt256 um = UInt256.fromBytesBE(m32); + + // Act + byte[] got = ua.signedMod(um).toBytesBE(); + + // Assert + byte[] expected = + (M.signum() == 0) ? Bytes32.ZERO.toArrayUnsafe() : computeSignedModExpected(A, M); + + assertThat(got).containsExactly(expected); + } + + @Property + void property_addMod_matchesBigInteger( + @ForAll("unsigned1to32") final byte[] a, + @ForAll("unsigned1to32") final byte[] b, + @ForAll("unsigned1to32") final byte[] m) { + // Arrange + final UInt256 ua = UInt256.fromBytesBE(a); + final UInt256 ub = UInt256.fromBytesBE(b); + final UInt256 um = UInt256.fromBytesBE(m); + + // Act + byte[] got = ua.addMod(ub, um).toBytesBE(); + + // Assert + BigInteger A = toBigUnsigned(a); + BigInteger B = toBigUnsigned(b); + BigInteger M = toBigUnsigned(m); + byte[] exp = + (M.signum() == 0) ? Bytes32.ZERO.toArrayUnsafe() : bigUnsignedToBytes32(A.add(B).mod(M)); + assertThat(got).containsExactly(exp); + } + + @Property + void property_mulMod_matchesBigInteger( + @ForAll("unsigned1to32") final byte[] a, + @ForAll("unsigned1to32") final byte[] b, + @ForAll("unsigned1to32") final byte[] m) { + // Arrange + final UInt256 ua = UInt256.fromBytesBE(a); + final UInt256 ub = UInt256.fromBytesBE(b); + final UInt256 um = UInt256.fromBytesBE(m); + + // Act + byte[] got = ua.mulMod(ub, um).toBytesBE(); + + // Assert + BigInteger A = toBigUnsigned(a); + BigInteger B = toBigUnsigned(b); + BigInteger M = toBigUnsigned(m); + byte[] exp = + (M.signum() == 0) + ? Bytes32.ZERO.toArrayUnsafe() + : bigUnsignedToBytes32(A.multiply(B).mod(M)); + assertThat(got).containsExactly(exp); + } + + @Property + void property_divByZero_invariants() { + // Arrange + UInt256 x = UInt256.fromBytesBE(new byte[] {1, 2, 3, 4}); + UInt256 zero = UInt256.ZERO; + + // Act & Assert + assertThat(x.mod(zero).toBytesBE()).containsExactly(Bytes32.ZERO.toArrayUnsafe()); + assertThat(x.signedMod(zero).toBytesBE()).containsExactly(Bytes32.ZERO.toArrayUnsafe()); + assertThat(x.addMod(x, zero).toBytesBE()).containsExactly(Bytes32.ZERO.toArrayUnsafe()); + assertThat(x.mulMod(x, zero).toBytesBE()).containsExactly(Bytes32.ZERO.toArrayUnsafe()); + } + + private static byte[] clampUnsigned32(final byte[] any) { + if (any.length == 0) { + return new byte[] {0}; + } + int len = Math.max(1, Math.min(32, any.length)); + byte[] out = new byte[len]; + System.arraycopy(any, 0, out, 0, len); + return out; + } + + private static byte[] bigUnsignedToBytes32(final BigInteger x) { + BigInteger y = x.mod(BigInteger.ONE.shiftLeft(256)); + + byte[] ba = y.toByteArray(); + if (ba.length == 0) { + return new byte[32]; + } + + if (ba.length == 32) { + return ba; + } + + if (ba.length < 32) { + byte[] out = new byte[32]; + System.arraycopy(ba, 0, out, 32 - ba.length, ba.length); + return out; + } + + // If bigger than 32, take lower 32 bytes. + byte[] out = new byte[32]; + System.arraycopy(ba, ba.length - 32, out, 0, 32); + + return out; + } + + private static BigInteger toBigUnsigned(final byte[] be) { + return new BigInteger(1, be); + } + + private static byte[] computeSignedModExpected(final BigInteger A, final BigInteger M) { + + BigInteger r = A.abs().mod(M.abs()); + + if (A.signum() < 0 && r.signum() != 0) { + return padNegative(r); + } + + return bigUnsignedToBytes32(r); + } + + private static byte[] padNegative(final BigInteger r) { + BigInteger neg = r.negate(); + byte[] rb = neg.toByteArray(); + byte[] padded = new byte[32]; + Arrays.fill(padded, (byte) 0xFF); + System.arraycopy(rb, 0, padded, 32 - rb.length, rb.length); + return padded; + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/UInt256PropertyBasedTest.java b/evm/src/test/java/org/hyperledger/besu/evm/UInt256PropertyBasedTest.java index 97744492bbe..3cf8d04cc20 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/UInt256PropertyBasedTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/UInt256PropertyBasedTest.java @@ -123,6 +123,10 @@ void property_mod_matchesBigInteger( BigInteger A = toBigUnsigned(a); BigInteger M = toBigUnsigned(m); byte[] exp = (M.signum() == 0) ? Bytes32.ZERO.toArrayUnsafe() : bigUnsignedToBytes32(A.mod(M)); + if (!Arrays.equals(got, exp)) + System.out.println( + String.format( + "%s %% %s == %s", ua.toHexString(), um.toHexString(), ua.mod(um).toHexString())); assertThat(got).containsExactly(exp); } @@ -511,7 +515,7 @@ void property_xor_involutive( void property_xor_with_allOnes_is_complement(@ForAll("unsigned1to32") final byte[] a) { // Arrange final UInt256 ua = UInt256.fromBytesBE(a); - final UInt256 allOnes = UInt256.ALL_ONES; + final UInt256 allOnes = UInt256.MAX; // Act final UInt256 result = ua.xor(allOnes); @@ -574,7 +578,7 @@ void property_xor_specific_patterns() { (byte) 0x55, (byte) 0x55, (byte) 0x55, (byte) 0x55 }); // 01010101... - final UInt256 allOnes = UInt256.ALL_ONES; + final UInt256 allOnes = UInt256.MAX; // Act & Assert - 0xAA XOR 0x55 = 0xFF assertThat(pattern1.xor(pattern2)).isEqualTo(allOnes); @@ -687,7 +691,7 @@ void property_or_idempotent(@ForAll("unsigned1to32") final byte[] a) { void property_or_with_allOnes(@ForAll("unsigned1to32") final byte[] a) { // Arrange final UInt256 ua = UInt256.fromBytesBE(a); - final UInt256 allOnes = UInt256.ALL_ONES; + final UInt256 allOnes = UInt256.MAX; // Act & Assert - A | 0xFF...FF = 0xFF...FF (domination) assertThat(ua.or(allOnes)).isEqualTo(allOnes); assertThat(allOnes.or(ua)).isEqualTo(allOnes); @@ -742,7 +746,7 @@ void property_or_with_complement_is_allOnes(@ForAll("unsigned1to32") final byte[ complementBytes[i] = (byte) ~aBytes32[i]; } final UInt256 complement = UInt256.fromBytesBE(complementBytes); - final UInt256 allOnes = UInt256.ALL_ONES; + final UInt256 allOnes = UInt256.MAX; // Act & Assert - A | ~A = 0xFF...FF assertThat(ua.or(complement)).isEqualTo(allOnes); } @@ -774,7 +778,7 @@ void property_or_specific_patterns() { (byte) 0x55, (byte) 0x55, (byte) 0x55, (byte) 0x55, (byte) 0x55, (byte) 0x55, (byte) 0x55, (byte) 0x55 }); // 01010101... - final UInt256 allOnes = UInt256.ALL_ONES; + final UInt256 allOnes = UInt256.MAX; // Act & Assert - 0xAA OR 0x55 = 0xFF assertThat(pattern1.or(pattern2)).isEqualTo(allOnes); // Verify with Bytes implementation @@ -896,7 +900,7 @@ void property_not_involutive(@ForAll("unsigned1to32") final byte[] a) { void property_not_different_from_original(@ForAll("unsigned1to32") final byte[] a) { // Arrange final UInt256 ua = UInt256.fromBytesBE(a); - final UInt256 allOnes = UInt256.ALL_ONES; + final UInt256 allOnes = UInt256.MAX; // Act final UInt256 notA = ua.not(); @@ -922,7 +926,7 @@ void property_not_with_or_is_allOnes(@ForAll("unsigned1to32") final byte[] a) { // Arrange final UInt256 ua = UInt256.fromBytesBE(a); // Act & Assert - A | ~A = 0xFF...FF - assertThat(ua.or(ua.not())).isEqualTo(UInt256.ALL_ONES); + assertThat(ua.or(ua.not())).isEqualTo(UInt256.MAX); } @Property @@ -930,7 +934,7 @@ void property_not_with_xor_is_allOnes(@ForAll("unsigned1to32") final byte[] a) { // Arrange final UInt256 ua = UInt256.fromBytesBE(a); // Act & Assert - A ^ ~A = 0xFF...FF - assertThat(ua.xor(ua.not())).isEqualTo(UInt256.ALL_ONES); + assertThat(ua.xor(ua.not())).isEqualTo(UInt256.MAX); } @Property @@ -963,7 +967,7 @@ void property_not_de_morgans_or( void property_not_zero_is_allOnes() { // Arrange final UInt256 zero = UInt256.ZERO; - final UInt256 allOnes = UInt256.ALL_ONES; + final UInt256 allOnes = UInt256.MAX; // Act & Assert - ~0 = 0xFF...FF assertThat(zero.not()).isEqualTo(allOnes); @@ -972,7 +976,7 @@ void property_not_zero_is_allOnes() { @Property void property_not_allOnes_is_zero() { // Arrange - final UInt256 allOnes = UInt256.ALL_ONES; + final UInt256 allOnes = UInt256.MAX; final UInt256 zero = UInt256.ZERO; // Act & Assert - ~0xFF...FF = 0 @@ -1036,7 +1040,7 @@ void property_not_each_bit_flipped(@ForAll("unsigned1to32") final byte[] a) { void property_not_xor_equivalence(@ForAll("unsigned1to32") final byte[] a) { // Arrange final UInt256 ua = UInt256.fromBytesBE(a); - final UInt256 allOnes = UInt256.ALL_ONES; + final UInt256 allOnes = UInt256.MAX; // Act & Assert - ~A = A ^ 0xFF...FF assertThat(ua.not()).isEqualTo(ua.xor(allOnes)); @@ -1046,7 +1050,7 @@ void property_not_xor_equivalence(@ForAll("unsigned1to32") final byte[] a) { void property_not_sum_with_original(@ForAll("unsigned1to32") final byte[] a) { // Arrange final UInt256 ua = UInt256.fromBytesBE(a); - final UInt256 allOnes = UInt256.ALL_ONES; + final UInt256 allOnes = UInt256.MAX; // Act // When we add A + ~A (bitwise), we should get all 1s in each bit position diff --git a/evm/src/test/java/org/hyperledger/besu/evm/UInt256Test.java b/evm/src/test/java/org/hyperledger/besu/evm/UInt256Test.java index 080b684ac34..16320fb54c4 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/UInt256Test.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/UInt256Test.java @@ -15,7 +15,6 @@ package org.hyperledger.besu.evm; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; import java.math.BigInteger; import java.util.Arrays; @@ -24,24 +23,23 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; public class UInt256Test { - static final int SAMPLE_SIZE = 300; + static final int SAMPLE_SIZE = 3; - private Bytes32 bigIntTo32B(final BigInteger x) { - byte[] a = x.toByteArray(); + private Bytes32 bigIntTo32B(final BigInteger y) { + byte[] a = y.toByteArray(); if (a.length > 32) return Bytes32.wrap(a, a.length - 32); return Bytes32.leftPad(Bytes.wrap(a)); } - private Bytes32 bigIntToSigned32B(final BigInteger x) { - if (x.signum() >= 0) return bigIntTo32B(x); + private Bytes32 bigIntTo32B(final BigInteger x, final int sign) { + if (sign >= 0) return bigIntTo32B(x); byte[] a = new byte[32]; Arrays.fill(a, (byte) 0xFF); byte[] b = x.toByteArray(); System.arraycopy(b, 0, a, 32 - b.length, b.length); + if (a.length > 32) return Bytes32.wrap(a, a.length - 32); return Bytes32.leftPad(Bytes.wrap(a)); } @@ -63,22 +61,22 @@ public void fromInts() { public void fromBytesBE() { byte[] input; UInt256 result; - int[] expectedLimbs; + UInt256 expected; input = new byte[] {-128, 0, 0, 0}; result = UInt256.fromBytesBE(input); - expectedLimbs = new int[] {-2147483648, 0, 0, 0, 0, 0, 0, 0}; - assertThat(result.limbs()).as("4b-neg-limbs").isEqualTo(expectedLimbs); + expected = new UInt256(0, 0, 0, 2147483648L); + assertThat(result).as("4b-neg-limbs").isEqualTo(expected); input = new byte[] {0, 0, 1, 1, 1}; result = UInt256.fromBytesBE(input); - expectedLimbs = new int[] {1 + 256 + 65536, 0, 0, 0, 0, 0, 0, 0}; - assertThat(result.limbs()).as("3b-limbs").isEqualTo(expectedLimbs); + expected = new UInt256(0, 0, 0, 1 + 256 + 65536); + assertThat(result).as("3b-limbs").isEqualTo(expected); - input = new byte[] {1, 0, 0, 0, 0, 1, 1, 1}; + input = new byte[] {1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1}; result = UInt256.fromBytesBE(input); - expectedLimbs = new int[] {1 + 256 + 65536, 16777216, 0, 0, 0, 0, 0, 0}; - assertThat(result.limbs()).as("8b-limbs").isEqualTo(expectedLimbs); + expected = new UInt256(0, 0, 16777216, 1 + 256 + 65536); + assertThat(result).as("8b-limbs").isEqualTo(expected); input = new byte[] { @@ -86,8 +84,8 @@ public void fromBytesBE() { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; result = UInt256.fromBytesBE(input); - expectedLimbs = new int[] {0, 0, 0, 0, 0, 0, 0, 16777216}; - assertThat(result.limbs()).as("32b-limbs").isEqualTo(expectedLimbs); + expected = new UInt256(72057594037927936L, 0, 0, 0); + assertThat(result).as("32b-limbs").isEqualTo(expected); input = new byte[] { @@ -95,8 +93,15 @@ public void fromBytesBE() { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; result = UInt256.fromBytesBE(input); - expectedLimbs = new int[] {0, 0, 0, 0, 0, 0, 257, 0}; - assertThat(result.limbs()).as("32b-padded-limbs").isEqualTo(expectedLimbs); + expected = new UInt256(257, 0, 0, 0); + assertThat(result).as("32b-padded-limbs").isEqualTo(expected); + + Bytes inputBytes = + Bytes.fromHexString("0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff"); + input = inputBytes.toArrayUnsafe(); + result = UInt256.fromBytesBE(input); + expected = new UInt256(0, 4294967295L, -1L, -1L); + assertThat(result).as("32b-case2-limbs").isEqualTo(expected); } @Test @@ -208,6 +213,112 @@ public void modB() { assertThat(remainder).isEqualTo(expected); } + @Test + public void modC() { + BigInteger big_number = new BigInteger("1000000000000000000000000000000000000000000000000", 16); + BigInteger big_modulus = new BigInteger("ff00000000000000", 16); + UInt256 number = UInt256.fromBytesBE(big_number.toByteArray()); + UInt256 modulus = UInt256.fromBytesBE(big_modulus.toByteArray()); + Bytes32 remainder = Bytes32.leftPad(Bytes.wrap(number.mod(modulus).toBytesBE())); + Bytes32 expected = Bytes32.leftPad(Bytes.wrap(big_number.mod(big_modulus).toByteArray())); + assertThat(remainder).isEqualTo(expected); + } + + @Test + public void modD() { + BigInteger big_number = new BigInteger("ff00000000000000000000000000000000", 16); + BigInteger big_modulus = new BigInteger("100000000000000000000000000000000", 16); + UInt256 number = UInt256.fromBytesBE(big_number.toByteArray()); + UInt256 modulus = UInt256.fromBytesBE(big_modulus.toByteArray()); + Bytes32 remainder = Bytes32.leftPad(Bytes.wrap(number.mod(modulus).toBytesBE())); + Bytes32 expected = Bytes32.leftPad(Bytes.wrap(big_number.mod(big_modulus).toByteArray())); + assertThat(remainder).isEqualTo(expected); + } + + @Test + public void modE() { + BigInteger big_number = new BigInteger("ff00000000000000000000000000000000", 16); + BigInteger big_modulus = new BigInteger("100000000000000000000000000000001", 16); + UInt256 number = UInt256.fromBytesBE(big_number.toByteArray()); + UInt256 modulus = UInt256.fromBytesBE(big_modulus.toByteArray()); + Bytes32 remainder = Bytes32.leftPad(Bytes.wrap(number.mod(modulus).toBytesBE())); + Bytes32 expected = Bytes32.leftPad(Bytes.wrap(big_number.mod(big_modulus).toByteArray())); + assertThat(remainder).isEqualTo(expected); + } + + @Test + public void modF() { + BigInteger big_number = new BigInteger("1000000000000000000000000000000000000000000000000", 16); + BigInteger big_modulus = new BigInteger("ff000000000000000000000000000000", 16); + UInt256 number = UInt256.fromBytesBE(big_number.toByteArray()); + UInt256 modulus = UInt256.fromBytesBE(big_modulus.toByteArray()); + Bytes32 remainder = Bytes32.leftPad(Bytes.wrap(number.mod(modulus).toBytesBE())); + Bytes32 expected = Bytes32.leftPad(Bytes.wrap(big_number.mod(big_modulus).toByteArray())); + assertThat(remainder).isEqualTo(expected); + } + + @Test + public void modG() { + BigInteger big_number = new BigInteger("1000000000000000000000000000000000000000000000000", 16); + BigInteger big_modulus = new BigInteger("100000000000000000000000000000001", 16); + UInt256 number = UInt256.fromBytesBE(big_number.toByteArray()); + UInt256 modulus = UInt256.fromBytesBE(big_modulus.toByteArray()); + Bytes32 remainder = Bytes32.leftPad(Bytes.wrap(number.mod(modulus).toBytesBE())); + Bytes32 expected = Bytes32.leftPad(Bytes.wrap(big_number.mod(big_modulus).toByteArray())); + assertThat(remainder).isEqualTo(expected); + } + + @Test + public void modH() { + BigInteger big_number = + new BigInteger("000000000000000000ff00000000000000000000000000000000000000000000", 16); + BigInteger big_modulus = + new BigInteger("0000000000000000000000000000000000fe0000000000000000000000000001", 16); + UInt256 number = UInt256.fromBytesBE(big_number.toByteArray()); + UInt256 modulus = UInt256.fromBytesBE(big_modulus.toByteArray()); + Bytes32 remainder = Bytes32.leftPad(Bytes.wrap(number.mod(modulus).toBytesBE())); + Bytes32 expected = Bytes32.leftPad(Bytes.wrap(big_number.mod(big_modulus).toByteArray())); + assertThat(remainder).isEqualTo(expected); + } + + @Test + public void modI() { + // modulus 128 with overflow case + BigInteger big_number = new BigInteger("020000000000000000000000000000000000", 16); + BigInteger big_modulus = new BigInteger("02000000000000000000", 16); + UInt256 number = UInt256.fromBytesBE(big_number.toByteArray()); + UInt256 modulus = UInt256.fromBytesBE(big_modulus.toByteArray()); + Bytes32 remainder = Bytes32.leftPad(Bytes.wrap(number.mod(modulus).toBytesBE())); + Bytes32 expected = Bytes32.leftPad(Bytes.wrap(big_number.mod(big_modulus).toByteArray())); + assertThat(remainder).isEqualTo(expected); + } + + @Test + public void modJ() { + // modulus 128 with overflow case -> 2 add back in quotient estimate div2by1. + BigInteger big_number = new BigInteger("10000000000000000010000000000000000", 16); + BigInteger big_modulus = new BigInteger("200000000000000ff", 16); + UInt256 number = UInt256.fromBytesBE(big_number.toByteArray()); + UInt256 modulus = UInt256.fromBytesBE(big_modulus.toByteArray()); + Bytes32 remainder = Bytes32.leftPad(Bytes.wrap(number.mod(modulus).toBytesBE())); + Bytes32 expected = Bytes32.leftPad(Bytes.wrap(big_number.mod(big_modulus).toByteArray())); + assertThat(remainder).isEqualTo(expected); + } + + @Test + public void modK() { + // modulus 128 with overflow case -> 2 add back in quotient estimate div2by1. + BigInteger big_number = + new BigInteger("ff000000000000000000000000000000000000000000000000000000", 16); + BigInteger big_modulus = + new BigInteger("1000000000000000000000002000000000000000000000000", 16); + UInt256 number = UInt256.fromBytesBE(big_number.toByteArray()); + UInt256 modulus = UInt256.fromBytesBE(big_modulus.toByteArray()); + Bytes32 remainder = Bytes32.leftPad(Bytes.wrap(number.mod(modulus).toBytesBE())); + Bytes32 expected = Bytes32.leftPad(Bytes.wrap(big_number.mod(big_modulus).toByteArray())); + assertThat(remainder).isEqualTo(expected); + } + @Test public void modGeneralState() { BigInteger big_number = new BigInteger("cea0c5cc171fa61277e5604a3bc8aef4de3d3882", 16); @@ -268,6 +379,30 @@ public void referenceTest459() { assertThat(remainder).isEqualTo(expected); } + @Test + public void ExecutionSpecStateTest_453() { + byte[] xArr = + new byte[] { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -2 + }; + byte[] mArr = + new byte[] { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 + }; + BigInteger xbig = new BigInteger(1, xArr); + BigInteger ybig = new BigInteger(1, xArr); + BigInteger mbig = new BigInteger(1, mArr); + UInt256 x = UInt256.fromBytesBE(xArr); + UInt256 y = UInt256.fromBytesBE(xArr); + UInt256 m = UInt256.fromBytesBE(mArr); + Bytes32 remainder = Bytes32.leftPad(Bytes.wrap(x.addMod(y, m).toBytesBE())); + Bytes32 expected = + BigInteger.ZERO.compareTo(mbig) == 0 ? Bytes32.ZERO : bigIntTo32B(xbig.add(ybig).mod(mbig)); + assertThat(remainder).isEqualTo(expected); + } + @Test public void addMod() { final Random random = new Random(42); @@ -292,10 +427,47 @@ public void addMod() { BigInteger.ZERO.compareTo(cInt) == 0 ? Bytes32.ZERO : bigIntTo32B(aInt.add(bInt).mod(cInt)); + if (!remainder.equals(expected)) + System.out.println(String.format("%s + %s == %s (mod %s)", a, b, a.add(b), c)); assertThat(remainder).isEqualTo(expected); } } + @Test + public void mulMod_ExecutionSpecStateTest_457() { + Bytes value0 = + Bytes.fromHexString("0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff"); + Bytes value1 = + Bytes.fromHexString("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"); + Bytes value2 = + Bytes.fromHexString("0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff"); + BigInteger aInt = new BigInteger(1, value0.toArrayUnsafe()); + BigInteger bInt = new BigInteger(1, value1.toArrayUnsafe()); + BigInteger cInt = new BigInteger(1, value2.toArrayUnsafe()); + UInt256 a = UInt256.fromBytesBE(value0.toArrayUnsafe()); + UInt256 b = UInt256.fromBytesBE(value1.toArrayUnsafe()); + UInt256 c = UInt256.fromBytesBE(value2.toArrayUnsafe()); + Bytes32 remainder = Bytes32.leftPad(Bytes.wrap(a.mulMod(b, c).toBytesBE())); + Bytes32 expected = bigIntTo32B(aInt.multiply(bInt).mod(cInt)); + assertThat(remainder).isEqualTo(expected); + + value0 = + Bytes.fromHexString("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"); + value1 = + Bytes.fromHexString("0xffffffffffffffffffffffffb195148ca348dc57a7331852b390ccefa7b0c18b"); + value2 = + Bytes.fromHexString("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"); + aInt = new BigInteger(1, value0.toArrayUnsafe()); + bInt = new BigInteger(1, value1.toArrayUnsafe()); + cInt = new BigInteger(1, value2.toArrayUnsafe()); + a = UInt256.fromBytesBE(value0.toArrayUnsafe()); + b = UInt256.fromBytesBE(value1.toArrayUnsafe()); + c = UInt256.fromBytesBE(value2.toArrayUnsafe()); + remainder = Bytes32.leftPad(Bytes.wrap(a.mulMod(b, c).toBytesBE())); + expected = bigIntTo32B(aInt.multiply(bInt).mod(cInt)); + assertThat(remainder).isEqualTo(expected); + } + @Test public void mulMod() { final Random random = new Random(123); @@ -320,40 +492,34 @@ public void mulMod() { BigInteger.ZERO.compareTo(cInt) == 0 ? Bytes32.ZERO : bigIntTo32B(aInt.multiply(bInt).mod(cInt)); + if (!remainder.equals(expected)) + System.out.println( + String.format("%s * %s (mod %s)", a.toHexString(), b.toHexString(), c.toHexString())); assertThat(remainder).isEqualTo(expected); } } - @Test - public void signedMod_no_padding() { - Bytes aBytes = - Bytes.fromHexString("0xe8e8e8e2000100000009ea02000000000000ff3ffffff80000001000220000"); - Bytes bBytes = - Bytes.fromHexString("0x8000000000000000000000000000000000000000000000000000000000000000"); - Bytes32 expected = - Bytes32.leftPad( - Bytes.fromHexString( - "0x00e8e8e8e2000100000009ea02000000000000ff3ffffff80000001000220000")); - UInt256 a = UInt256.fromBytesBE(aBytes.toArrayUnsafe()); - UInt256 b = UInt256.fromBytesBE(bBytes.toArrayUnsafe()); - Bytes32 remainder = Bytes32.leftPad(Bytes.wrap(a.signedMod(b).toBytesBE())); - assertThat(remainder).isEqualTo(expected); - } - @Test public void signedMod() { final Random random = new Random(432); for (int i = 0; i < SAMPLE_SIZE; i++) { int aSize = random.nextInt(1, 33); int bSize = random.nextInt(1, 33); + boolean neg = random.nextBoolean(); byte[] aArray = new byte[aSize]; byte[] bArray = new byte[bSize]; random.nextBytes(aArray); random.nextBytes(bArray); + if ((aSize < 32) && (neg)) { + byte[] tmp = new byte[32]; + Arrays.fill(tmp, (byte) 0xFF); + System.arraycopy(aArray, 0, tmp, 32 - aArray.length, aArray.length); + aArray = tmp; + } UInt256 a = UInt256.fromBytesBE(aArray); UInt256 b = UInt256.fromBytesBE(bArray); - BigInteger aInt = aArray.length < 32 ? new BigInteger(1, aArray) : new BigInteger(aArray); - BigInteger bInt = bArray.length < 32 ? new BigInteger(1, bArray) : new BigInteger(bArray); + BigInteger aInt = a.isNegative() ? new BigInteger(aArray) : new BigInteger(1, aArray); + BigInteger bInt = b.isNegative() ? new BigInteger(bArray) : new BigInteger(1, bArray); Bytes32 remainder = Bytes32.leftPad(Bytes.wrap(a.signedMod(b).toBytesBE())); Bytes32 expected; BigInteger rem = BigInteger.ZERO; @@ -362,285 +528,12 @@ public void signedMod() { rem = aInt.abs().mod(bInt.abs()); if ((aInt.compareTo(BigInteger.ZERO) < 0) && (rem.compareTo(BigInteger.ZERO) != 0)) { rem = rem.negate(); - expected = bigIntToSigned32B(rem); + expected = bigIntTo32B(rem, -1); } else { - expected = bigIntTo32B(rem); + expected = bigIntTo32B(rem, 1); } } assertThat(remainder).isEqualTo(expected); } } - - @Test - void testFromBytesBE_emptyArray() { - UInt256 result = UInt256.fromBytesBE(new byte[0]); - assertThat(result).isEqualTo(UInt256.ZERO); - assertThat(result.isZero()).isTrue(); - } - - @Test - void testFromBytesBE_singleZeroByte() { - UInt256 result = UInt256.fromBytesBE(new byte[] {0}); - assertThat(result).isEqualTo(UInt256.ZERO); - assertThat(result.intValue()).isEqualTo(0); - } - - @Test - void testFromBytesBE_singleByte() { - UInt256 result = UInt256.fromBytesBE(new byte[] {0x42}); - assertThat(result.intValue()).isEqualTo(0x42); - assertThat(result.longValue()).isEqualTo(0x42L); - } - - @Test - void testFromBytesBE_twoBytesFF() { - UInt256 result = UInt256.fromBytesBE(new byte[] {(byte) 0xFF, (byte) 0xFF}); - assertThat(result.intValue()).isEqualTo(0xFFFF); - assertThat(result.longValue()).isEqualTo(0xFFFFL); - } - - @Test - void testFromBytesBE_fourBytes() { - UInt256 result = UInt256.fromBytesBE(new byte[] {0x01, 0x02, 0x03, 0x04}); - assertThat(result.intValue()).isEqualTo(0x01020304); - } - - @Test - void testFromBytesBE_eightBytes() { - UInt256 result = - UInt256.fromBytesBE(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}); - assertThat(result.longValue()).isEqualTo(0x0102030405060708L); - } - - @Test - void testFromBytesBE_exactly32Bytes_allZeros() { - byte[] bytes = new byte[32]; // all zeros - UInt256 result = UInt256.fromBytesBE(bytes); - assertThat(result).isEqualTo(UInt256.ZERO); - assertThat(result.isZero()).isTrue(); - } - - @Test - void testFromBytesBE_exactly32Bytes_allOnes() { - byte[] bytes = new byte[32]; - for (int i = 0; i < 32; i++) { - bytes[i] = (byte) 0xFF; - } - UInt256 result = UInt256.fromBytesBE(bytes); - - // Should be MAX_UINT256 (2^256 - 1) - byte[] resultBytes = result.toBytesBE(); - assertArrayEquals(bytes, resultBytes); - } - - @Test - void testFromBytesBE_exactly32Bytes_one() { - byte[] bytes = new byte[32]; - bytes[31] = 0x01; // least significant byte - UInt256 result = UInt256.fromBytesBE(bytes); - - assertThat(result.intValue()).isEqualTo(1); - assertThat(result.longValue()).isEqualTo(1L); - } - - @Test - void testFromBytesBE_exactly32Bytes_pattern() { - byte[] bytes = new byte[32]; - // Create pattern: 0x0102030405060708...1F20 - for (int i = 0; i < 32; i++) { - bytes[i] = (byte) (i + 1); - } - UInt256 result = UInt256.fromBytesBE(bytes); - - // Verify round-trip - byte[] resultBytes = result.toBytesBE(); - assertArrayEquals(bytes, resultBytes); - } - - @Test - void testFromBytesBE_exactly32Bytes_highBitSet() { - byte[] bytes = new byte[32]; - bytes[0] = (byte) 0x80; // high bit set (but still unsigned) - UInt256 result = UInt256.fromBytesBE(bytes); - - // Verify it's treated as unsigned (not negative) - byte[] resultBytes = result.toBytesBE(); - assertArrayEquals(bytes, resultBytes); - } - - @Test - void testFromBytesBE_roundTrip_variousLengths() { - for (int len = 1; len <= 32; len++) { - byte[] original = new byte[len]; - for (int i = 0; i < len; i++) { - original[i] = (byte) (i + 1); - } - - UInt256 value = UInt256.fromBytesBE(original); - byte[] result = value.toBytesBE(); - - // Result is always 32 bytes, so compare with left-padded original - byte[] expected = new byte[32]; - System.arraycopy(original, 0, expected, 32 - len, len); - - assertArrayEquals(expected, result, "Failed for length " + len); - } - } - - @Test - void testFromBytesBE_leadingZeros() { - // Leading zeros should be handled correctly - byte[] bytes = new byte[] {0x00, 0x00, 0x00, 0x01, 0x02, 0x03}; - UInt256 result = UInt256.fromBytesBE(bytes); - - assertThat(result.intValue()).isEqualTo(0x010203); - } - - @Test - void testFromBytesBE_maxInt() { - byte[] bytes = new byte[] {0x7F, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}; - UInt256 result = UInt256.fromBytesBE(bytes); - - assertThat(result.intValue()).isEqualTo(Integer.MAX_VALUE); - } - - @Test - void testFromBytesBE_maxLong() { - byte[] bytes = - new byte[] { - 0x7F, - (byte) 0xFF, - (byte) 0xFF, - (byte) 0xFF, - (byte) 0xFF, - (byte) 0xFF, - (byte) 0xFF, - (byte) 0xFF - }; - UInt256 result = UInt256.fromBytesBE(bytes); - - assertThat(result.longValue()).isEqualTo(Long.MAX_VALUE); - } - - @Test - void testFromBytesBE_unsignedIntMax() { - // 0xFFFFFFFF as unsigned = 4294967295 - byte[] bytes = new byte[] {(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}; - UInt256 result = UInt256.fromBytesBE(bytes); - - assertThat(result.longValue()).isEqualTo(0xFFFFFFFFL); - } - - @Test - void testFromBytesBE_unsignedLongMax() { - // 0xFFFFFFFFFFFFFFFF as unsigned - byte[] bytes = - new byte[] { - (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, - (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF - }; - UInt256 result = UInt256.fromBytesBE(bytes); - - // When converted back to long, should get the bit pattern - assertThat(result.longValue()).isEqualTo(-1L); // all bits set - } - - @Test - void testFromBytesBE_boundaryValues() { - // Test 1, 2, 3, 4, 8, 16, 32 bytes - int[] lengths = {1, 2, 3, 4, 8, 16, 32}; - - for (int len : lengths) { - byte[] bytes = new byte[len]; - bytes[len - 1] = (byte) 0xFF; // set last byte - - UInt256 result = UInt256.fromBytesBE(bytes); - assertThat(result.intValue() & 0xFF).isEqualTo(0xFF); - } - } - - @Test - void testFromBytesBE_comparisonWithBigInteger() { - byte[] bytes = - new byte[] {0x12, 0x34, 0x56, 0x78, (byte) 0x9A, (byte) 0xBC, (byte) 0xDE, (byte) 0xF0}; - - UInt256 result = UInt256.fromBytesBE(bytes); - java.math.BigInteger expected = new java.math.BigInteger(1, bytes); - - assertThat(result.toBigInteger()).isEqualTo(expected); - } - - @ParameterizedTest - @ValueSource(ints = {0, 1, 127, 128, 255, 256, 65535, 65536, Integer.MAX_VALUE}) - void testFromBytesBE_knownIntegers(final int value) { - // Convert int to bytes (big-endian) - byte[] bytes = new byte[4]; - bytes[0] = (byte) (value >>> 24); - bytes[1] = (byte) (value >>> 16); - bytes[2] = (byte) (value >>> 8); - bytes[3] = (byte) value; - - UInt256 result = UInt256.fromBytesBE(bytes); - assertThat(result.intValue()).isEqualTo(value); - } - - @Test - void testFromBytesBE_powerOfTwo() { - // Test 2^8, 2^16, 2^32, 2^64, 2^128, 2^255 - - // 2^8 = 256 - byte[] bytes8 = new byte[] {0x01, 0x00}; - assertThat(UInt256.fromBytesBE(bytes8).intValue()).isEqualTo(256); - - // 2^16 = 65536 - byte[] bytes16 = new byte[] {0x01, 0x00, 0x00}; - assertThat(UInt256.fromBytesBE(bytes16).intValue()).isEqualTo(65536); - - // 2^32 - byte[] bytes32 = new byte[] {0x01, 0x00, 0x00, 0x00, 0x00}; - assertThat(UInt256.fromBytesBE(bytes32).longValue()).isEqualTo(0x100000000L); - } - - @Test - void testFromBytesBE_alternatingPattern() { - // 0xAA pattern - byte[] bytesAA = new byte[32]; - for (int i = 0; i < 32; i++) { - bytesAA[i] = (byte) 0xAA; - } - UInt256 resultAA = UInt256.fromBytesBE(bytesAA); - assertArrayEquals(bytesAA, resultAA.toBytesBE()); - - // 0x55 pattern - byte[] bytes55 = new byte[32]; - for (int i = 0; i < 32; i++) { - bytes55[i] = (byte) 0x55; - } - UInt256 result55 = UInt256.fromBytesBE(bytes55); - assertArrayEquals(bytes55, result55.toBytesBE()); - } - - @Test - void testFromBytesBE_consistency() { - // Verify same bytes always produce same result - byte[] bytes = new byte[] {0x01, 0x02, 0x03, 0x04, 0x05}; - - UInt256 result1 = UInt256.fromBytesBE(bytes); - UInt256 result2 = UInt256.fromBytesBE(bytes); - - assertThat(result1).isEqualTo(result2); - assertThat(result1.hashCode()).isEqualTo(result2.hashCode()); - } - - @Test - void testFromBytesBE_differentLengthsSameValue() { - // Leading zeros should not affect value - byte[] bytes1 = new byte[] {0x01, 0x02, 0x03}; - byte[] bytes2 = new byte[] {0x00, 0x00, 0x01, 0x02, 0x03}; - - UInt256 result1 = UInt256.fromBytesBE(bytes1); - UInt256 result2 = UInt256.fromBytesBE(bytes2); - - assertThat(result1).isEqualTo(result2); - } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/internal/OperandStackTest.java b/evm/src/test/java/org/hyperledger/besu/evm/internal/OperandStackTest.java index 9a3e21f134e..2fa18dc6852 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/internal/OperandStackTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/internal/OperandStackTest.java @@ -15,171 +15,77 @@ package org.hyperledger.besu.evm.internal; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; class OperandStackTest { - @Test - void construction() { - final OperandStack stack = new OperandStack(1); - assertThat(stack.size()).isZero(); - } - - @Test - void construction_NegativeMaximumSize() { - assertThatThrownBy(() -> new OperandStack(-1)).isInstanceOf(IllegalArgumentException.class); - } - - @Test - void push_StackOverflow() { - final OperandStack stack = new OperandStack(1); - stack.push(UInt256.fromHexString("0x01")); - final UInt256 operand = UInt256.fromHexString("0x02"); - assertThatThrownBy(() -> stack.push(operand)).isInstanceOf(OverflowException.class); - } + // --- StackPool tests --- @Test - void pop_StackUnderflow() { - final OperandStack stack = new OperandStack(1); - assertThatThrownBy(stack::pop).isInstanceOf(UnderflowException.class); + void pool_borrowAndRelease_reusesArrays() { + final StackPool pool = new StackPool(); + long[] s1 = pool.stacks[--pool.size]; // simulate borrow + pool.outstanding++; + pool.stacks[pool.size++] = s1; // simulate release + pool.outstanding--; + long[] s2 = pool.stacks[--pool.size]; + pool.outstanding++; + assertThat(s2).isSameAs(s1); } @Test - void pushPop() { - final OperandStack stack = new OperandStack(1); - stack.push(UInt256.fromHexString("0x01")); - assertThat(stack.size()).isEqualTo(1); - assertThat(stack.pop()).isEqualTo(Bytes32.fromHexString("0x01")); + void pool_borrowMultiple_tracksOutstanding() { + long[] s1 = StackPool.borrow(1024); + long[] s2 = StackPool.borrow(1024); + StackPool.release(s2, 1024); + StackPool.release(s1, 1024); + // Should not throw } @Test - void get_NegativeOffset() { - final OperandStack stack = new OperandStack(1); - assertThatThrownBy(() -> stack.get(-1)).isInstanceOf(UnderflowException.class); + void pool_nonDefaultSize_allocatesNew() { + long[] s = StackPool.borrow(512); + assertThat(s.length).isEqualTo(512 << 2); } @Test - void get_IndexGreaterThanSize() { - final OperandStack stack = new OperandStack(1); - stack.push(UInt256.fromHexString("0x01")); - assertThatThrownBy(() -> stack.get(2)).isInstanceOf(UnderflowException.class); - } - - @Test - void get() { - final OperandStack stack = new OperandStack(3); - stack.push(UInt256.fromHexString("0x01")); - stack.push(UInt256.fromHexString("0x02")); - stack.push(UInt256.fromHexString("0x03")); - assertThat(stack.size()).isEqualTo(3); - assertThat(stack.get(0)).isEqualTo(Bytes32.fromHexString("0x03")); - assertThat(stack.get(1)).isEqualTo(Bytes32.fromHexString("0x02")); - assertThat(stack.get(2)).isEqualTo(Bytes32.fromHexString("0x01")); - } - - @Test - void set_NegativeOffset() { - final OperandStack stack = new OperandStack(1); - final Bytes32 operand = Bytes32.fromHexString("0x01"); - assertThatThrownBy(() -> stack.set(-1, operand)).isInstanceOf(UnderflowException.class); - } - - @Test - void set_IndexGreaterThanSize() { - final OperandStack stack = new OperandStack(1); - stack.push(UInt256.fromHexString("0x01")); - final Bytes32 operand = Bytes32.fromHexString("0x01"); - assertThatThrownBy(() -> stack.set(2, operand)).isInstanceOf(OverflowException.class); - } - - @Test - void set_IndexGreaterThanCurrentSize() { - final OperandStack stack = new OperandStack(1024); - stack.push(UInt256.fromHexString("0x01")); - final Bytes32 operand = Bytes32.fromHexString("0x01"); - assertThatThrownBy(() -> stack.set(2, operand)).isInstanceOf(OverflowException.class); - } - - @Test - void set() { - final OperandStack stack = new OperandStack(3); - stack.push(UInt256.fromHexString("0x01")); - stack.push(UInt256.fromHexString("0x02")); - stack.push(UInt256.fromHexString("0x03")); - stack.set(2, UInt256.fromHexString("0x04")); - assertThat(stack.size()).isEqualTo(3); - assertThat(stack.get(0)).isEqualTo(Bytes32.fromHexString("0x03")); - assertThat(stack.get(1)).isEqualTo(Bytes32.fromHexString("0x02")); - assertThat(stack.get(2)).isEqualTo(Bytes32.fromHexString("0x04")); + void pool_maintain_shrinksAfterLowUsage() { + final StackPool pool = new StackPool(); + for (int cycle = 0; cycle < 10; cycle++) { + pool.peakThisCycle = 1; + pool.idleCount = 256; + pool.maintain(); + } + assertThat(pool.capacity).isEqualTo(16); } @Test - void bulkPop() { - final OperandStack stack = new OperandStack(8); - stack.push(UInt256.fromHexString("0x01")); - stack.push(UInt256.fromHexString("0x02")); - stack.push(UInt256.fromHexString("0x03")); - stack.push(UInt256.fromHexString("0x04")); - stack.push(UInt256.fromHexString("0x05")); - stack.push(UInt256.fromHexString("0x06")); - stack.push(UInt256.fromHexString("0x07")); - stack.push(UInt256.fromHexString("0x08")); - assertThat(stack.size()).isEqualTo(8); - stack.bulkPop(2); - assertThat(stack.get(0)).isEqualTo(Bytes32.fromHexString("0x06")); - stack.bulkPop(6); - assertThat(stack.isEmpty()).isTrue(); + void pool_maintain_growsAfterHighUsage() { + final StackPool pool = new StackPool(); + for (int cycle = 0; cycle < 10; cycle++) { + pool.peakThisCycle = 100; + pool.idleCount = 256; + pool.maintain(); + } + assertThat(pool.capacity).isEqualTo(256); } @Test - void preserveTop() { - final OperandStack stack = new OperandStack(8); - stack.push(UInt256.fromHexString("0x01")); - stack.push(UInt256.fromHexString("0x02")); - stack.push(UInt256.fromHexString("0x03")); - stack.push(UInt256.fromHexString("0x04")); - stack.push(UInt256.fromHexString("0x05")); - stack.push(UInt256.fromHexString("0x06")); - stack.push(UInt256.fromHexString("0x07")); - stack.push(UInt256.fromHexString("0x08")); - assertThat(stack.size()).isEqualTo(8); - stack.preserveTop(6, 1); - assertThat(stack.get(0)).isEqualTo(Bytes32.fromHexString("0x08")); - assertThat(stack.get(1)).isEqualTo(Bytes32.fromHexString("0x06")); - assertThat(stack.size()).isEqualTo(7); - stack.preserveTop(1, 3); - assertThat(stack.get(0)).isEqualTo(Bytes32.fromHexString("0x08")); - assertThat(stack.get(1)).isEqualTo(Bytes32.fromHexString("0x06")); - assertThat(stack.get(2)).isEqualTo(Bytes32.fromHexString("0x05")); - assertThat(stack.get(3)).isEqualTo(Bytes32.fromHexString("0x01")); - assertThat(stack.size()).isEqualTo(4); - - stack.preserveTop(4, 0); - assertThat(stack.size()).isEqualTo(4); - assertThatThrownBy(() -> stack.preserveTop(4, 2)).isInstanceOf(UnderflowException.class); - stack.preserveTop(2, 2); - assertThat(stack.size()).isEqualTo(4); - stack.preserveTop(0, 2); - assertThat(stack.get(0)).isEqualTo(Bytes32.fromHexString("0x08")); - assertThat(stack.get(1)).isEqualTo(Bytes32.fromHexString("0x06")); - - assertThatThrownBy(() -> stack.preserveTop(5, 1)).isInstanceOf(UnderflowException.class); - assertThatThrownBy(() -> stack.preserveTop(1, 5)).isInstanceOf(UnderflowException.class); - } - - @ParameterizedTest - @ValueSource(ints = {5, 31, 32, 33, 1023, 1024, 1025}) - void largeOverflows(final int n) { - final OperandStack stack = new OperandStack(n); - for (int i = 0; i < n; i++) { - stack.push(UInt256.ONE); + void pool_maintain_shrinksFromSpike() { + final StackPool pool = new StackPool(); + pool.peakThisCycle = 512; + pool.idleCount = 256; + pool.maintain(); + int capacityAfterSpike = pool.capacity; + assertThat(capacityAfterSpike).isGreaterThan(16); + + for (int cycle = 0; cycle < 50; cycle++) { + pool.peakThisCycle = 1; + pool.idleCount = 256; + pool.maintain(); } - assertThatThrownBy(() -> stack.push(UInt256.ONE)).isInstanceOf(OverflowException.class); + assertThat(pool.capacity).isLessThan(capacityAfterSpike); + assertThat(pool.capacity).isEqualTo(16); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/AbstractCreateOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/AbstractCreateOperationTest.java index eff84c517a7..f0b0bc2444c 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/AbstractCreateOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/AbstractCreateOperationTest.java @@ -36,6 +36,7 @@ import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.internal.StackMath; import org.hyperledger.besu.evm.processor.ContractCreationProcessor; import org.hyperledger.besu.evm.tracing.OperationTracer; import org.hyperledger.besu.evm.worldstate.WorldUpdater; @@ -46,7 +47,6 @@ import java.util.function.Supplier; import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.Test; class AbstractCreateOperationTest { @@ -135,7 +135,6 @@ protected void onFailure( } private void executeOperation(final Bytes contract, final EVM evm) { - final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); final MessageFrame messageFrame = MessageFrame.builder() .type(MessageFrame.Type.CONTRACT_CREATION) @@ -156,11 +155,13 @@ private void executeOperation(final Bytes contract, final EVM evm) { .worldUpdater(worldUpdater) .build(); final Deque messageFrameStack = messageFrame.getMessageFrameStack(); - messageFrame.pushStackItem(Bytes.ofUnsignedLong(contract.size())); - messageFrame.pushStackItem(memoryOffset); - messageFrame.pushStackItem(Bytes.EMPTY); + messageFrame.setTop( + StackMath.pushLong(messageFrame.stackData(), messageFrame.stackTop(), contract.size())); + messageFrame.setTop( + StackMath.pushLong(messageFrame.stackData(), messageFrame.stackTop(), 0xFF)); + messageFrame.setTop(StackMath.pushZero(messageFrame.stackData(), messageFrame.stackTop())); messageFrame.expandMemory(0, 500); - messageFrame.writeMemory(memoryOffset.trimLeadingZeros().toInt(), contract.size(), contract); + messageFrame.writeMemory(0xFF, contract.size(), contract); when(account.getNonce()).thenReturn(55L); when(account.getBalance()).thenReturn(Wei.ZERO); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/BaseFeeOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/BaseFeeOperationTest.java index 13f04849887..17a3c0d428d 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/BaseFeeOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/BaseFeeOperationTest.java @@ -15,23 +15,18 @@ package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.Wei; -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.BerlinGasCalculator; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.Words; import org.hyperledger.besu.evm.operation.Operation.OperationResult; +import org.hyperledger.besu.evm.testutils.FakeBlockValues; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; import java.util.Optional; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.Test; class BaseFeeOperationTest { @@ -39,47 +34,35 @@ class BaseFeeOperationTest { @Test void shouldReturnGasCost() { - final MessageFrame frame = createMessageFrame(100, Optional.of(Wei.of(5L))); + final MessageFrame frame = + new TestMessageFrameBuilder() + .blockValues(new FakeBlockValues(Optional.of(Wei.of(5L)))) + .build(); final Operation operation = new BaseFeeOperation(gasCalculator); final OperationResult result = operation.execute(frame, null); assertThat(result.getGasCost()).isEqualTo(gasCalculator.getBaseTierGasCost()); - assertSuccessResult(result); + assertThat(result.getHaltReason()).isNull(); } @Test void shouldWriteBaseFeeToStack() { - final MessageFrame frame = createMessageFrame(100, Optional.of(Wei.of(5L))); + final MessageFrame frame = + new TestMessageFrameBuilder() + .blockValues(new FakeBlockValues(Optional.of(Wei.of(5L)))) + .build(); final Operation operation = new BaseFeeOperation(gasCalculator); - final OperationResult result = operation.execute(frame, null); - verify(frame).pushStackItem(UInt256.fromBytes(Bytes32.leftPad(Words.longBytes(5L)))); - assertSuccessResult(result); + operation.execute(frame, null); + assertThat(frame.getStackItem(0)) + .isEqualTo( + org.hyperledger.besu.evm.UInt256.fromBytesBE(Wei.of(5L).toBytes().toArrayUnsafe())); } @Test void shouldHaltIfNoBaseFeeInBlockHeader() { - final MessageFrame frame = createMessageFrame(100, Optional.empty()); + final MessageFrame frame = + new TestMessageFrameBuilder().blockValues(new FakeBlockValues(Optional.empty())).build(); final Operation operation = new BaseFeeOperation(gasCalculator); final OperationResult result = operation.execute(frame, null); - assertExceptionalHalt(result, ExceptionalHaltReason.INVALID_OPERATION); - } - - private void assertSuccessResult(final OperationResult result) { - assertThat(result).isNotNull(); - assertThat(result.getHaltReason()).isNull(); - } - - private void assertExceptionalHalt( - final OperationResult result, final ExceptionalHaltReason reason) { - assertThat(result).isNotNull(); - assertThat(result.getHaltReason()).isEqualTo(reason); - } - - private MessageFrame createMessageFrame(final long initialGas, final Optional baseFee) { - final MessageFrame frame = mock(MessageFrame.class); - when(frame.getRemainingGas()).thenReturn(initialGas); - final BlockValues blockHeader = mock(BlockValues.class); - when(blockHeader.getBaseFee()).thenReturn(baseFee); - when(frame.getBlockValues()).thenReturn(blockHeader); - return frame; + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INVALID_OPERATION); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/BlobHashOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/BlobHashOperationTest.java index ec09a1c2291..a217a3b210d 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/BlobHashOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/BlobHashOperationTest.java @@ -16,22 +16,21 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.VersionedHash; import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.UInt256; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator; import org.hyperledger.besu.evm.gascalculator.LondonGasCalculator; +import org.hyperledger.besu.evm.internal.StackMath; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; -import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.Test; @@ -40,19 +39,35 @@ class BlobHashOperationTest { private static final String testVersionedHash = "0x01cafebabeb0b0facedeadbeefbeef0001cafebabeb0b0facedeadbeefbeef00"; + /** Helper: create a stack with one UInt256 value pushed. Returns stack with top=1. */ + private static long[] stackWithValue(final UInt256 val) { + final long[] s = new long[4 * 4]; // room for 4 slots + StackMath.putAt(s, 1, 0, val); + return s; + } + + /** Helper: read the top slot as UInt256. */ + private static UInt256 readTop(final long[] s, final int top) { + return StackMath.getAt(s, top, 0); + } + @Test void putsHashOnStack() { VersionedHash version0Hash = new VersionedHash(Bytes32.fromHexStringStrict(testVersionedHash)); List versionedHashes = Arrays.asList(version0Hash); BlobHashOperation getHash = new BlobHashOperation(new LondonGasCalculator()); MessageFrame frame = mock(MessageFrame.class); - when(frame.popStackItem()).thenReturn(Bytes.of(0)); + long[] s = stackWithValue(UInt256.ZERO); + when(frame.stackHasItems(1)).thenReturn(true); + when(frame.stackData()).thenReturn(s); + when(frame.stackTop()).thenReturn(1); when(frame.getVersionedHashes()).thenReturn(Optional.of(versionedHashes)); EVM fakeEVM = mock(EVM.class); Operation.OperationResult r = getHash.execute(frame, fakeEVM); assertThat(r.getGasCost()).isEqualTo(3); assertThat(r.getHaltReason()).isNull(); - verify(frame).pushStackItem(version0Hash.getBytes()); + assertThat(readTop(s, 1)) + .isEqualTo(UInt256.fromBytesBE(version0Hash.getBytes().toArrayUnsafe())); } @Test @@ -62,19 +77,26 @@ void pushesZeroOnBloblessTx() { BlobHashOperation getHash = new BlobHashOperation(new CancunGasCalculator()); MessageFrame frame = mock(MessageFrame.class); - when(frame.popStackItem()).thenReturn(Bytes.of(0)); + long[] s = stackWithValue(UInt256.ZERO); + when(frame.stackHasItems(1)).thenReturn(true); + when(frame.stackData()).thenReturn(s); + when(frame.stackTop()).thenReturn(1); when(frame.getVersionedHashes()).thenReturn(Optional.empty()); Operation.OperationResult failed1 = getHash.execute(frame, fakeEVM); assertThat(failed1.getGasCost()).isEqualTo(3); assertThat(failed1.getHaltReason()).isNull(); + assertThat(readTop(s, 1)).isEqualTo(UInt256.ZERO); - when(frame.popStackItem()).thenReturn(Bytes.of(0)); + // Reset stack for second call + s = stackWithValue(UInt256.ZERO); + when(frame.stackData()).thenReturn(s); + when(frame.stackTop()).thenReturn(1); when(frame.getVersionedHashes()).thenReturn(Optional.of(new ArrayList<>())); Operation.OperationResult failed2 = getHash.execute(frame, fakeEVM); assertThat(failed2.getGasCost()).isEqualTo(3); assertThat(failed2.getHaltReason()).isNull(); - verify(frame, times(2)).pushStackItem(Bytes.EMPTY); + assertThat(readTop(s, 1)).isEqualTo(UInt256.ZERO); } @Test @@ -83,13 +105,16 @@ void pushZeroOnVersionIndexOutOFBounds() { List versionedHashes = Arrays.asList(version0Hash); BlobHashOperation getHash = new BlobHashOperation(new CancunGasCalculator()); MessageFrame frame = mock(MessageFrame.class); - when(frame.popStackItem()).thenReturn(Bytes.of(1)); + long[] s = stackWithValue(UInt256.fromInt(1)); + when(frame.stackHasItems(1)).thenReturn(true); + when(frame.stackData()).thenReturn(s); + when(frame.stackTop()).thenReturn(1); when(frame.getVersionedHashes()).thenReturn(Optional.of(versionedHashes)); EVM fakeEVM = mock(EVM.class); Operation.OperationResult r = getHash.execute(frame, fakeEVM); assertThat(r.getGasCost()).isEqualTo(3); assertThat(r.getHaltReason()).isNull(); - verify(frame).pushStackItem(Bytes.EMPTY); + assertThat(readTop(s, 1)).isEqualTo(UInt256.ZERO); } @Test @@ -98,12 +123,15 @@ public void pushZeroWhenPopsMissingUint256SizedIndex() { List versionedHashes = Arrays.asList(version0Hash); BlobHashOperation getHash = new BlobHashOperation(new CancunGasCalculator()); MessageFrame frame = mock(MessageFrame.class); - when(frame.popStackItem()).thenReturn(Bytes32.repeat((byte) 0x2C)); + long[] s = stackWithValue(UInt256.fromBytesBE(Bytes32.repeat((byte) 0x2C).toArrayUnsafe())); + when(frame.stackHasItems(1)).thenReturn(true); + when(frame.stackData()).thenReturn(s); + when(frame.stackTop()).thenReturn(1); when(frame.getVersionedHashes()).thenReturn(Optional.of(versionedHashes)); EVM fakeEVM = mock(EVM.class); Operation.OperationResult r = getHash.execute(frame, fakeEVM); assertThat(r.getGasCost()).isEqualTo(3); assertThat(r.getHaltReason()).isNull(); - verify(frame).pushStackItem(Bytes.EMPTY); + assertThat(readTop(s, 1)).isEqualTo(UInt256.ZERO); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/BlockHashOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/BlockHashOperationTest.java index 9f81a63d696..f27458de79f 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/BlockHashOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/BlockHashOperationTest.java @@ -21,12 +21,12 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.FrontierGasCalculator; +import org.hyperledger.besu.evm.internal.StackMath; import org.hyperledger.besu.evm.testutils.FakeBlockValues; import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.Test; class BlockHashOperationTest { @@ -73,7 +73,7 @@ private void assertBlockHash( final BlockHashLookup blockHashLookup, final long initialGas) { assertBlockHash( - UInt256.valueOf(requestedBlock), + Bytes32.leftPad(Bytes.ofUnsignedLong(requestedBlock)), expectedOutput, currentBlockNumber, blockHashLookup, @@ -90,13 +90,15 @@ private void assertBlockHash( new TestMessageFrameBuilder() .blockHashLookup(blockHashLookup) .blockValues(new FakeBlockValues(currentBlockNumber)) - .pushStackItem(UInt256.fromBytes(input)) + .pushStackItem(input) .initialGas(initialGas) .build(); blockHashOperation.execute(frame, null); - final Bytes result = frame.popStackItem(); - assertThat(result).isEqualTo(expectedOutput); - assertThat(frame.stackSize()).isZero(); + final org.hyperledger.besu.evm.UInt256 result = + StackMath.getAt(frame.stackData(), frame.stackTop(), 0); + assertThat(result) + .isEqualTo(org.hyperledger.besu.evm.UInt256.fromBytesBE(expectedOutput.toArrayUnsafe())); + assertThat(frame.stackSize()).isOne(); } private void assertFailure( @@ -109,7 +111,7 @@ private void assertFailure( new TestMessageFrameBuilder() .blockHashLookup(blockHashLookup) .blockValues(new FakeBlockValues(currentBlockNumber)) - .pushStackItem(UInt256.fromBytes(input)) + .pushStackItem(input) .initialGas(initialGas) .build(); Operation.OperationResult operationResult = blockHashOperation.execute(frame, null); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/ChainIdOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/ChainIdOperationTest.java index acae6249c4d..95c84c9c7ba 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/ChainIdOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/ChainIdOperationTest.java @@ -15,28 +15,22 @@ package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; import org.hyperledger.besu.evm.operation.Operation.OperationResult; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; import java.util.List; -import org.apache.tuweni.bytes.Bytes; 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; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; class ChainIdOperationTest { - private final MessageFrame messageFrame = mock(MessageFrame.class); - static Iterable params() { return List.of( Arguments.of("0x01", 2), @@ -48,24 +42,15 @@ static Iterable params() { @ParameterizedTest @MethodSource("params") void shouldReturnChainId(final String chainIdString, final int expectedGas) { - Bytes32 chainId = Bytes32.fromHexString(chainIdString); - ChainIdOperation operation = new ChainIdOperation(new ConstantinopleGasCalculator(), chainId); - final ArgumentCaptor arg = ArgumentCaptor.forClass(Bytes.class); - when(messageFrame.getRemainingGas()).thenReturn(100L); - operation.execute(messageFrame, null); - Mockito.verify(messageFrame).getRemainingGas(); - Mockito.verify(messageFrame).pushStackItem(arg.capture()); - Mockito.verifyNoMoreInteractions(messageFrame); - assertThat(arg.getValue()).isEqualTo(chainId); - } - - @ParameterizedTest - @MethodSource("params") - void shouldCalculateGasPrice(final String chainIdString, final int expectedGas) { - Bytes32 chainId = Bytes32.fromHexString(chainIdString); - ChainIdOperation operation = new ChainIdOperation(new ConstantinopleGasCalculator(), chainId); - final OperationResult result = operation.execute(messageFrame, null); + final Bytes32 chainId = Bytes32.fromHexString(chainIdString); + final ChainIdOperation operation = + new ChainIdOperation(new ConstantinopleGasCalculator(), chainId); + final MessageFrame frame = new TestMessageFrameBuilder().build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); assertThat(result.getGasCost()).isEqualTo(expectedGas); + assertThat(frame.getStackItem(0)) + .isEqualTo(org.hyperledger.besu.evm.UInt256.fromBytesBE(chainId.toArrayUnsafe())); } @Test diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/CountLeadingZerosOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/CountLeadingZerosOperationTest.java index d6a8afa1798..0395d679c72 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/CountLeadingZerosOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/CountLeadingZerosOperationTest.java @@ -14,33 +14,27 @@ */ package org.hyperledger.besu.evm.operation; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.assertj.core.api.Assertions.assertThat; -import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.UInt256; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.gascalculator.SpuriousDragonGasCalculator; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; -import org.junit.jupiter.api.BeforeEach; +import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; class CountLeadingZerosOperationTest { - private CountLeadingZerosOperation operation; - private MessageFrame frame; - - @BeforeEach - void setUp() { - operation = new CountLeadingZerosOperation(mock(GasCalculator.class)); - frame = mock(MessageFrame.class); - } + private final GasCalculator gasCalculator = new SpuriousDragonGasCalculator(); + private final CountLeadingZerosOperation operation = + new CountLeadingZerosOperation(gasCalculator); static Stream provideClzTestCases() { return Stream.of( @@ -58,12 +52,16 @@ static Stream provideClzTestCases() { @ParameterizedTest @MethodSource("provideClzTestCases") void testClzOperation(final String value, final int expectedLeadingZeros) { - Bytes input = Bytes.fromHexString(value); - Bytes expected = Words.intBytes(expectedLeadingZeros); - - when(frame.popStackItem()).thenReturn(input); + Bytes raw = Bytes.fromHexString(value); + // Truncate to lower 32 bytes if longer (as the EVM stack would) + byte[] padded = + raw.size() >= 32 + ? raw.slice(raw.size() - 32, 32).toArrayUnsafe() + : Bytes32.leftPad(raw).toArrayUnsafe(); - operation.executeFixedCostOperation(frame, mock(EVM.class)); - verify(frame).pushStackItem(expected); + final MessageFrame frame = + new TestMessageFrameBuilder().pushStackItem(Bytes.wrap(padded)).build(); + operation.execute(frame, null); + assertThat(frame.getStackItem(0)).isEqualTo(UInt256.fromInt(expectedLeadingZeros)); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/Create2OperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/Create2OperationTest.java index ffbe7a16b2e..b0d693d36c8 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/Create2OperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/Create2OperationTest.java @@ -33,6 +33,7 @@ import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.internal.StackMath; import org.hyperledger.besu.evm.operation.Operation.OperationResult; import org.hyperledger.besu.evm.processor.ContractCreationProcessor; import org.hyperledger.besu.evm.tracing.OperationTracer; @@ -43,7 +44,6 @@ import jakarta.validation.constraints.NotNull; import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -132,9 +132,8 @@ public static Object[][] params() { public void setUp(final String sender, final String salt, final String code) { - final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); + final int memoryOffset = 0xFF; final Bytes codeBytes = Bytes.fromHexString(code); - final UInt256 memoryLength = UInt256.valueOf(codeBytes.size()); messageFrame = MessageFrame.builder() .type(MessageFrame.Type.CONTRACT_CREATION) @@ -154,12 +153,19 @@ public void setUp(final String sender, final String salt, final String code) { .initialGas(100_000L) .worldUpdater(worldUpdater) .build(); - messageFrame.pushStackItem(UInt256.fromHexString(salt)); - messageFrame.pushStackItem(memoryLength); - messageFrame.pushStackItem(memoryOffset); - messageFrame.pushStackItem(UInt256.ZERO); + { + final byte[] saltBytes = Bytes.fromHexString(salt).toArrayUnsafe(); + messageFrame.setTop( + StackMath.pushFromBytes( + messageFrame.stackData(), messageFrame.stackTop(), saltBytes, 0, saltBytes.length)); + } + messageFrame.setTop( + StackMath.pushLong(messageFrame.stackData(), messageFrame.stackTop(), codeBytes.size())); + messageFrame.setTop( + StackMath.pushLong(messageFrame.stackData(), messageFrame.stackTop(), 0xFF)); + messageFrame.setTop(StackMath.pushZero(messageFrame.stackData(), messageFrame.stackTop())); messageFrame.expandMemory(0, 500); - messageFrame.writeMemory(memoryOffset.trimLeadingZeros().toInt(), code.length(), codeBytes); + messageFrame.writeMemory(memoryOffset, code.length(), codeBytes); when(account.getBalance()).thenReturn(Wei.ZERO); when(worldUpdater.getAccount(any())).thenReturn(account); @@ -196,8 +202,8 @@ void shouldCalculateGasPrice( @Test void shanghaiMaxInitCodeSizeCreate() { - final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); - final UInt256 memoryLength = UInt256.fromHexString("0xc000"); + final Bytes memoryOffset = Bytes.fromHexString("0xFF"); + final Bytes memoryLength = Bytes.fromHexString("0xc000"); final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength); when(account.getNonce()).thenReturn(55L); @@ -224,8 +230,8 @@ void shanghaiMaxInitCodeSizeCreate() { @Test void shanghaiMaxInitCodeSizePlus1Create() { - final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); - final UInt256 memoryLength = UInt256.fromHexString("0xc001"); + final Bytes memoryOffset = Bytes.fromHexString("0xFF"); + final Bytes memoryLength = Bytes.fromHexString("0xc001"); final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength); when(account.getNonce()).thenReturn(55L); @@ -245,8 +251,8 @@ void shanghaiMaxInitCodeSizePlus1Create() { @Test void amsterdamMaxInitCodeSizeCreate() { - final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); - final UInt256 memoryLength = UInt256.fromHexString("0x10000"); + final Bytes memoryOffset = Bytes.fromHexString("0xFF"); + final Bytes memoryLength = Bytes.fromHexString("0x010000"); final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength); when(account.getNonce()).thenReturn(55L); @@ -266,8 +272,8 @@ void amsterdamMaxInitCodeSizeCreate() { @Test void amsterdamMaxInitCodeSizePlus1Create() { - final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); - final UInt256 memoryLength = UInt256.fromHexString("0x10001"); + final Bytes memoryOffset = Bytes.fromHexString("0xFF"); + final Bytes memoryLength = Bytes.fromHexString("0x010001"); final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength); when(account.getNonce()).thenReturn(55L); @@ -287,8 +293,8 @@ void amsterdamMaxInitCodeSizePlus1Create() { @Test void amsterdamBetweenOldAndNewInitCodeLimitCreate() { - final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); - final UInt256 memoryLength = UInt256.fromHexString("0xC001"); + final Bytes memoryOffset = Bytes.fromHexString("0xFF"); + final Bytes memoryLength = Bytes.fromHexString("0xC001"); final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength); when(account.getNonce()).thenReturn(55L); @@ -307,7 +313,7 @@ void amsterdamBetweenOldAndNewInitCodeLimitCreate() { } @NotNull - private MessageFrame testMemoryFrame(final UInt256 memoryOffset, final UInt256 memoryLength) { + private MessageFrame testMemoryFrame(final Bytes memoryOffset, final Bytes memoryLength) { final MessageFrame messageFrame = MessageFrame.builder() .type(MessageFrame.Type.CONTRACT_CREATION) @@ -327,13 +333,22 @@ private MessageFrame testMemoryFrame(final UInt256 memoryOffset, final UInt256 m .initialGas(100000L) .worldUpdater(worldUpdater) .build(); - messageFrame.pushStackItem(Bytes.EMPTY); - messageFrame.pushStackItem(memoryLength); - messageFrame.pushStackItem(memoryOffset); - messageFrame.pushStackItem(UInt256.ZERO); + messageFrame.setTop(StackMath.pushZero(messageFrame.stackData(), messageFrame.stackTop())); + { + final byte[] lenBytes = memoryLength.toArrayUnsafe(); + messageFrame.setTop( + StackMath.pushFromBytes( + messageFrame.stackData(), messageFrame.stackTop(), lenBytes, 0, lenBytes.length)); + } + { + final byte[] offBytes = memoryOffset.toArrayUnsafe(); + messageFrame.setTop( + StackMath.pushFromBytes( + messageFrame.stackData(), messageFrame.stackTop(), offBytes, 0, offBytes.length)); + } + messageFrame.setTop(StackMath.pushZero(messageFrame.stackData(), messageFrame.stackTop())); messageFrame.expandMemory(0, 500); - messageFrame.writeMemory( - memoryOffset.trimLeadingZeros().toInt(), SIMPLE_CREATE.size(), SIMPLE_CREATE); + messageFrame.writeMemory(memoryOffset.toInt(), SIMPLE_CREATE.size(), SIMPLE_CREATE); final Deque messageFrameStack = messageFrame.getMessageFrameStack(); if (messageFrameStack.isEmpty()) { messageFrameStack.push(messageFrame); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/CreateOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/CreateOperationTest.java index 1c9c5534b46..a1256d41e61 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/CreateOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/CreateOperationTest.java @@ -33,6 +33,7 @@ import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.internal.StackMath; import org.hyperledger.besu.evm.processor.ContractCreationProcessor; import org.hyperledger.besu.evm.tracing.OperationTracer; import org.hyperledger.besu.evm.worldstate.WorldUpdater; @@ -42,7 +43,6 @@ import jakarta.validation.constraints.NotNull; import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.Test; class CreateOperationTest { @@ -74,9 +74,9 @@ class CreateOperationTest { void createFromMemoryMutationSafe() { // Given: Execute a CREATE operation with a contract that logs in the constructor - final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); - final UInt256 memoryLength = UInt256.valueOf(SIMPLE_CREATE.size()); - final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1); + final Bytes memoryOffset = Bytes.fromHexString("0xFF"); + final Bytes memoryLength = Bytes.ofUnsignedInt(SIMPLE_CREATE.size()); + final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, Bytes.EMPTY, 1); when(account.getNonce()).thenReturn(55L); when(account.getBalance()).thenReturn(Wei.ZERO); @@ -100,9 +100,7 @@ void createFromMemoryMutationSafe() { // WHEN the memory that the create operation was executed from is altered. messageFrame.writeMemory( - memoryOffset.trimLeadingZeros().toInt(), - SIMPLE_CREATE.size(), - Bytes.random(SIMPLE_CREATE.size())); + memoryOffset.toInt(), SIMPLE_CREATE.size(), Bytes.random(SIMPLE_CREATE.size())); // THEN the logs still have the expected topic final String calculatedTopicAfter = log.getTopics().get(0).getBytes().toUnprefixedHexString(); @@ -111,9 +109,9 @@ void createFromMemoryMutationSafe() { @Test void nonceTooLarge() { - final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); - final UInt256 memoryLength = UInt256.valueOf(SIMPLE_CREATE.size()); - final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1); + final Bytes memoryOffset = Bytes.fromHexString("0xFF"); + final Bytes memoryLength = Bytes.ofUnsignedInt(SIMPLE_CREATE.size()); + final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, Bytes.EMPTY, 1); when(worldUpdater.getAccount(any())).thenReturn(account); when(account.getBalance()).thenReturn(Wei.ZERO); @@ -122,15 +120,15 @@ void nonceTooLarge() { final EVM evm = MainnetEVMs.london(EvmConfiguration.DEFAULT); operation.execute(messageFrame, evm); - assertThat(messageFrame.getStackItem(0).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); + assertThat(messageFrame.getStackItem(0)).isEqualTo(org.hyperledger.besu.evm.UInt256.ZERO); } @Test void messageFrameStackTooDeep() { - final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); - final UInt256 memoryLength = UInt256.valueOf(SIMPLE_CREATE.size()); + final Bytes memoryOffset = Bytes.fromHexString("0xFF"); + final Bytes memoryLength = Bytes.ofUnsignedInt(SIMPLE_CREATE.size()); final MessageFrame messageFrame = - testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1025); + testMemoryFrame(memoryOffset, memoryLength, Bytes.EMPTY, 1025); when(worldUpdater.getAccount(any())).thenReturn(account); when(account.getBalance()).thenReturn(Wei.ZERO); @@ -139,15 +137,14 @@ void messageFrameStackTooDeep() { final EVM evm = MainnetEVMs.london(EvmConfiguration.DEFAULT); operation.execute(messageFrame, evm); - assertThat(messageFrame.getStackItem(0).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); + assertThat(messageFrame.getStackItem(0)).isEqualTo(org.hyperledger.besu.evm.UInt256.ZERO); } @Test void notEnoughValue() { - final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); - final UInt256 memoryLength = UInt256.valueOf(SIMPLE_CREATE.size()); - final MessageFrame messageFrame = - testMemoryFrame(memoryOffset, memoryLength, UInt256.valueOf(1), 1); + final Bytes memoryOffset = Bytes.fromHexString("0xFF"); + final Bytes memoryLength = Bytes.ofUnsignedInt(SIMPLE_CREATE.size()); + final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, Bytes.of(1), 1); final Deque messageFrameStack = messageFrame.getMessageFrameStack(); for (int i = 0; i < 1025; i++) { messageFrameStack.add(messageFrame); @@ -160,14 +157,14 @@ void notEnoughValue() { final EVM evm = MainnetEVMs.london(EvmConfiguration.DEFAULT); operation.execute(messageFrame, evm); - assertThat(messageFrame.getStackItem(0).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); + assertThat(messageFrame.getStackItem(0)).isEqualTo(org.hyperledger.besu.evm.UInt256.ZERO); } @Test void shanghaiMaxInitCodeSizeCreate() { - final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); - final UInt256 memoryLength = UInt256.fromHexString("0xc000"); - final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1); + final Bytes memoryOffset = Bytes.fromHexString("0xFF"); + final Bytes memoryLength = Bytes.fromHexString("0xc000"); + final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, Bytes.EMPTY, 1); when(account.getNonce()).thenReturn(55L); when(account.getBalance()).thenReturn(Wei.ZERO); @@ -193,9 +190,9 @@ void shanghaiMaxInitCodeSizeCreate() { @Test void shanghaiMaxInitCodeSizePlus1Create() { - final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); - final UInt256 memoryLength = UInt256.fromHexString("0xc001"); - final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1); + final Bytes memoryOffset = Bytes.fromHexString("0xFF"); + final Bytes memoryLength = Bytes.fromHexString("0xc001"); + final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, Bytes.EMPTY, 1); when(account.getNonce()).thenReturn(55L); when(account.getBalance()).thenReturn(Wei.ZERO); @@ -213,9 +210,9 @@ void shanghaiMaxInitCodeSizePlus1Create() { @Test void amsterdamMaxInitCodeSizeCreate() { - final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); - final UInt256 memoryLength = UInt256.fromHexString("0x10000"); - final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1); + final Bytes memoryOffset = Bytes.fromHexString("0xFF"); + final Bytes memoryLength = Bytes.fromHexString("0x010000"); + final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, Bytes.EMPTY, 1); when(account.getNonce()).thenReturn(55L); when(account.getBalance()).thenReturn(Wei.ZERO); @@ -234,9 +231,9 @@ void amsterdamMaxInitCodeSizeCreate() { @Test void amsterdamMaxInitCodeSizePlus1Create() { - final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); - final UInt256 memoryLength = UInt256.fromHexString("0x10001"); - final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1); + final Bytes memoryOffset = Bytes.fromHexString("0xFF"); + final Bytes memoryLength = Bytes.fromHexString("0x010001"); + final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, Bytes.EMPTY, 1); when(account.getNonce()).thenReturn(55L); when(account.getBalance()).thenReturn(Wei.ZERO); @@ -254,9 +251,9 @@ void amsterdamMaxInitCodeSizePlus1Create() { @Test void amsterdamBetweenOldAndNewInitCodeLimitCreate() { - final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); - final UInt256 memoryLength = UInt256.fromHexString("0xC001"); - final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1); + final Bytes memoryOffset = Bytes.fromHexString("0xFF"); + final Bytes memoryLength = Bytes.fromHexString("0xC001"); + final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, Bytes.EMPTY, 1); when(account.getNonce()).thenReturn(55L); when(account.getBalance()).thenReturn(Wei.ZERO); @@ -275,10 +272,7 @@ void amsterdamBetweenOldAndNewInitCodeLimitCreate() { @NotNull private MessageFrame testMemoryFrame( - final UInt256 memoryOffset, - final UInt256 memoryLength, - final UInt256 value, - final int depth) { + final Bytes memoryOffset, final Bytes memoryLength, final Bytes value, final int depth) { final MessageFrame messageFrame = MessageFrame.builder() .type(MessageFrame.Type.CONTRACT_CREATION) @@ -298,12 +292,26 @@ private MessageFrame testMemoryFrame( .initialGas(100000L) .worldUpdater(worldUpdater) .build(); - messageFrame.pushStackItem(memoryLength); - messageFrame.pushStackItem(memoryOffset); - messageFrame.pushStackItem(value); + { + final byte[] lenBytes = memoryLength.toArrayUnsafe(); + messageFrame.setTop( + StackMath.pushFromBytes( + messageFrame.stackData(), messageFrame.stackTop(), lenBytes, 0, lenBytes.length)); + } + { + final byte[] offBytes = memoryOffset.toArrayUnsafe(); + messageFrame.setTop( + StackMath.pushFromBytes( + messageFrame.stackData(), messageFrame.stackTop(), offBytes, 0, offBytes.length)); + } + { + final byte[] valBytes = value.toArrayUnsafe(); + messageFrame.setTop( + StackMath.pushFromBytes( + messageFrame.stackData(), messageFrame.stackTop(), valBytes, 0, valBytes.length)); + } messageFrame.expandMemory(0, 500); - messageFrame.writeMemory( - memoryOffset.trimLeadingZeros().toInt(), SIMPLE_CREATE.size(), SIMPLE_CREATE); + messageFrame.writeMemory(memoryOffset.toInt(), SIMPLE_CREATE.size(), SIMPLE_CREATE); final Deque messageFrameStack = messageFrame.getMessageFrameStack(); while (messageFrameStack.size() < depth) { messageFrameStack.push(messageFrame); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/DupNOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/DupNOperationTest.java index ace174670e6..9a0d68334c3 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/DupNOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/DupNOperationTest.java @@ -17,6 +17,7 @@ import static org.assertj.core.api.Assertions.assertThat; import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.UInt256; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.PragueGasCalculator; @@ -80,7 +81,7 @@ void testDupN_basicOperation() { assertThat(result.getGasCost()).isEqualTo(3); assertThat(result.getPcIncrement()).isEqualTo(2); // Top of stack should now be a copy of item 17 (value 17) - assertThat(frame.getStackItem(0)).isEqualTo(Bytes.of(17)); + assertThat(frame.getStackItem(0)).isEqualTo(UInt256.fromInt(17)); // Stack should have 18 items now assertThat(frame.stackSize()).isEqualTo(18); } @@ -104,7 +105,7 @@ void testDupN_immediate90() { assertThat(result.getHaltReason()).isNull(); assertThat(result.getPcIncrement()).isEqualTo(2); // Should duplicate item 107 (value 107) - assertThat(frame.getStackItem(0).toInt()).isEqualTo(107); + assertThat(frame.getStackItem(0).intValue()).isEqualTo(107); } @Test @@ -123,7 +124,7 @@ void testDupN_immediate128() { final OperationResult result = operation.execute(frame, null); assertThat(result.getHaltReason()).isNull(); - assertThat(frame.getStackItem(0).toInt()).isEqualTo(108); + assertThat(frame.getStackItem(0).intValue()).isEqualTo(108); } @ParameterizedTest @@ -183,7 +184,7 @@ void testDupN_endOfCode() { // Immediate 0 is valid, should succeed assertThat(result.getHaltReason()).isNull(); - assertThat(frame.getStackItem(0)).isEqualTo(Bytes.of(17)); + assertThat(frame.getStackItem(0)).isEqualTo(UInt256.fromInt(17)); } @Test @@ -247,8 +248,8 @@ void testSpecVector_dupn17With18Items() { final MessageFrame frame = builder.build(); assertThat(frame.stackSize()).isEqualTo(18); - assertThat(frame.getStackItem(0)).isEqualTo(Bytes.of(0)); // top - assertThat(frame.getStackItem(17)).isEqualTo(Bytes.of(1)); // bottom + assertThat(frame.getStackItem(0)).isEqualTo(UInt256.ZERO); // top + assertThat(frame.getStackItem(17)).isEqualTo(UInt256.fromInt(1)); // bottom final OperationResult result = operation.execute(frame, null); @@ -256,7 +257,7 @@ void testSpecVector_dupn17With18Items() { assertThat(result.getPcIncrement()).isEqualTo(2); // DUPN 0 -> n=17, duplicates stack[16] which is 0 assertThat(frame.stackSize()).isEqualTo(19); - assertThat(frame.getStackItem(0)).isEqualTo(Bytes.of(0)); // duplicated value + assertThat(frame.getStackItem(0)).isEqualTo(UInt256.ZERO); // duplicated value } /** @@ -285,7 +286,7 @@ void testSpecVector_dupnEndOfCode() { // Implicit immediate 0 is valid, should behave same as explicit 0 assertThat(result.getHaltReason()).isNull(); assertThat(frame.stackSize()).isEqualTo(19); - assertThat(frame.getStackItem(0)).isEqualTo(Bytes.of(0)); + assertThat(frame.getStackItem(0)).isEqualTo(UInt256.ZERO); } /** diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/EqOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/EqOperationTest.java index 7235e7cc561..2266a2efdd9 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/EqOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/EqOperationTest.java @@ -20,6 +20,7 @@ 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.internal.StackMath; import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; import java.util.Arrays; @@ -28,7 +29,6 @@ import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -100,6 +100,7 @@ void testEqOperation(final String value0, final String value1, final Condition e operation.executeFixedCostOperation(frame, mock(EVM.class)); - assertThat(UInt256.valueOf(expectedResult.ordinal())).isEqualTo(frame.popStackItem()); + assertThat(StackMath.getAt(frame.stackData(), frame.stackTop(), 0)) + .isEqualTo(org.hyperledger.besu.evm.UInt256.fromInt(expectedResult.ordinal())); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/ExchangeOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/ExchangeOperationTest.java index 740f11351cc..058f80239a9 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/ExchangeOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/ExchangeOperationTest.java @@ -17,6 +17,7 @@ import static org.assertj.core.api.Assertions.assertThat; import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.UInt256; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.PragueGasCalculator; @@ -98,10 +99,10 @@ void testExchange_basicOperation() { final MessageFrame frame = builder.build(); // Before - assertThat(frame.getStackItem(0)).isEqualTo(Bytes.of(1)); - assertThat(frame.getStackItem(1)).isEqualTo(Bytes.of(2)); - assertThat(frame.getStackItem(2)).isEqualTo(Bytes.of(3)); - assertThat(frame.getStackItem(3)).isEqualTo(Bytes.of(4)); + assertThat(frame.getStackItem(0)).isEqualTo(UInt256.fromInt(1)); + assertThat(frame.getStackItem(1)).isEqualTo(UInt256.fromInt(2)); + assertThat(frame.getStackItem(2)).isEqualTo(UInt256.fromInt(3)); + assertThat(frame.getStackItem(3)).isEqualTo(UInt256.fromInt(4)); final OperationResult result = operation.execute(frame, null); @@ -110,10 +111,10 @@ void testExchange_basicOperation() { assertThat(result.getPcIncrement()).isEqualTo(2); // After: stack[2] and stack[3] swapped - assertThat(frame.getStackItem(0)).isEqualTo(Bytes.of(1)); - assertThat(frame.getStackItem(1)).isEqualTo(Bytes.of(2)); - assertThat(frame.getStackItem(2)).isEqualTo(Bytes.of(4)); // was 3 - assertThat(frame.getStackItem(3)).isEqualTo(Bytes.of(3)); // was 4 + assertThat(frame.getStackItem(0)).isEqualTo(UInt256.fromInt(1)); + assertThat(frame.getStackItem(1)).isEqualTo(UInt256.fromInt(2)); + assertThat(frame.getStackItem(2)).isEqualTo(UInt256.fromInt(4)); // was 3 + assertThat(frame.getStackItem(3)).isEqualTo(UInt256.fromInt(3)); // was 4 assertThat(frame.stackSize()).isEqualTo(4); } @@ -132,16 +133,16 @@ void testExchange_immediate0xd0() { final MessageFrame frame = builder.build(); // Before: stack[1]=2, stack[19]=20 - assertThat(frame.getStackItem(1).toInt()).isEqualTo(2); - assertThat(frame.getStackItem(19).toInt()).isEqualTo(20); + assertThat(frame.getStackItem(1).intValue()).isEqualTo(2); + assertThat(frame.getStackItem(19).intValue()).isEqualTo(20); final OperationResult result = operation.execute(frame, null); assertThat(result.getHaltReason()).isNull(); // After: swapped - assertThat(frame.getStackItem(1).toInt()).isEqualTo(20); - assertThat(frame.getStackItem(19).toInt()).isEqualTo(2); + assertThat(frame.getStackItem(1).intValue()).isEqualTo(20); + assertThat(frame.getStackItem(19).intValue()).isEqualTo(2); } @ParameterizedTest @@ -193,15 +194,15 @@ void testExchange_endOfCode() { final MessageFrame frame = builder.build(); // Before: stack[1]=2, stack[29]=30 - assertThat(frame.getStackItem(1)).isEqualTo(Bytes.of(2)); - assertThat(frame.getStackItem(29)).isEqualTo(Bytes.of(30)); + assertThat(frame.getStackItem(1)).isEqualTo(UInt256.fromInt(2)); + assertThat(frame.getStackItem(29)).isEqualTo(UInt256.fromInt(30)); final OperationResult result = operation.execute(frame, null); assertThat(result.getHaltReason()).isNull(); // Verify swap - assertThat(frame.getStackItem(1)).isEqualTo(Bytes.of(30)); - assertThat(frame.getStackItem(29)).isEqualTo(Bytes.of(2)); + assertThat(frame.getStackItem(1)).isEqualTo(UInt256.fromInt(30)); + assertThat(frame.getStackItem(29)).isEqualTo(UInt256.fromInt(2)); } @Test @@ -243,9 +244,9 @@ void testSpecVector_exchange_e801() { // Verify initial state assertThat(frame.stackSize()).isEqualTo(3); - assertThat(frame.getStackItem(0)).isEqualTo(Bytes.of(2)); // top - assertThat(frame.getStackItem(1)).isEqualTo(Bytes.of(1)); - assertThat(frame.getStackItem(2)).isEqualTo(Bytes.of(0)); // bottom + assertThat(frame.getStackItem(0)).isEqualTo(UInt256.fromInt(2)); // top + assertThat(frame.getStackItem(1)).isEqualTo(UInt256.fromInt(1)); + assertThat(frame.getStackItem(2)).isEqualTo(UInt256.ZERO); // bottom final OperationResult result = operation.execute(frame, null); @@ -254,9 +255,9 @@ void testSpecVector_exchange_e801() { // After EXCHANGE 01: stack[1] and stack[2] swapped assertThat(frame.stackSize()).isEqualTo(3); // size unchanged - assertThat(frame.getStackItem(0)).isEqualTo(Bytes.of(2)); // unchanged - assertThat(frame.getStackItem(1)).isEqualTo(Bytes.of(0)); // swapped - assertThat(frame.getStackItem(2)).isEqualTo(Bytes.of(1)); // swapped + assertThat(frame.getStackItem(0)).isEqualTo(UInt256.fromInt(2)); // unchanged + assertThat(frame.getStackItem(1)).isEqualTo(UInt256.ZERO); // swapped + assertThat(frame.getStackItem(2)).isEqualTo(UInt256.fromInt(1)); // swapped } /** @@ -280,9 +281,9 @@ void testSpecVector_exchange30Items() { final MessageFrame frame = builder.build(); assertThat(frame.stackSize()).isEqualTo(30); - assertThat(frame.getStackItem(0)).isEqualTo(Bytes.of(2)); // top - assertThat(frame.getStackItem(1)).isEqualTo(Bytes.of(99)); - assertThat(frame.getStackItem(29)).isEqualTo(Bytes.of(1)); // bottom + assertThat(frame.getStackItem(0)).isEqualTo(UInt256.fromInt(2)); // top + assertThat(frame.getStackItem(1)).isEqualTo(UInt256.fromInt(99)); + assertThat(frame.getStackItem(29)).isEqualTo(UInt256.fromInt(1)); // bottom final OperationResult result = operation.execute(frame, null); @@ -290,9 +291,9 @@ void testSpecVector_exchange30Items() { // After EXCHANGE: stack[1] and stack[29] swapped assertThat(frame.stackSize()).isEqualTo(30); - assertThat(frame.getStackItem(0)).isEqualTo(Bytes.of(2)); // unchanged - assertThat(frame.getStackItem(1)).isEqualTo(Bytes.of(1)); // was 99, now 1 - assertThat(frame.getStackItem(29)).isEqualTo(Bytes.of(99)); // was 1, now 99 + assertThat(frame.getStackItem(0)).isEqualTo(UInt256.fromInt(2)); // unchanged + assertThat(frame.getStackItem(1)).isEqualTo(UInt256.fromInt(1)); // was 99, now 1 + assertThat(frame.getStackItem(29)).isEqualTo(UInt256.fromInt(99)); // was 1, now 99 } /** diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperationTest.java index 54b1bc8fac0..5911045d2bd 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperationTest.java @@ -24,6 +24,7 @@ import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; import org.hyperledger.besu.evm.gascalculator.IstanbulGasCalculator; +import org.hyperledger.besu.evm.internal.StackMath; import org.hyperledger.besu.evm.internal.Words; import org.hyperledger.besu.evm.operation.Operation.OperationResult; import org.hyperledger.besu.evm.testutils.FakeBlockValues; @@ -62,32 +63,33 @@ void istanbulShouldCharge700Gas() { @Test void shouldReturnZeroWhenAccountDoesNotExist() { - final Bytes result = executeOperation(REQUESTED_ADDRESS); - assertThat(result.trimLeadingZeros()).isEqualTo(Bytes.EMPTY); + final org.hyperledger.besu.evm.UInt256 result = executeOperation(REQUESTED_ADDRESS); + assertThat(result).isEqualTo(org.hyperledger.besu.evm.UInt256.ZERO); } @Test void shouldReturnHashOfEmptyDataWhenAccountExistsButDoesNotHaveCode() { worldStateUpdater.getOrCreate(REQUESTED_ADDRESS).setBalance(Wei.of(1)); - assertThat(executeOperation(REQUESTED_ADDRESS)).isEqualTo(Hash.EMPTY.getBytes()); + assertThat(executeOperation(REQUESTED_ADDRESS).toBytes32()).isEqualTo(Hash.EMPTY.getBytes()); } @Test void shouldReturnZeroWhenAccountExistsButIsEmpty() { worldStateUpdater.getOrCreate(REQUESTED_ADDRESS); - assertThat(executeOperation(REQUESTED_ADDRESS).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); + assertThat(executeOperation(REQUESTED_ADDRESS)) + .isEqualTo(org.hyperledger.besu.evm.UInt256.ZERO); } @Test void shouldReturnZeroWhenPrecompiledContractHasNoBalance() { - assertThat(executeOperation(Address.ECREC).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); + assertThat(executeOperation(Address.ECREC)).isEqualTo(org.hyperledger.besu.evm.UInt256.ZERO); } @Test void shouldReturnEmptyCodeHashWhenPrecompileHasBalance() { // Sending money to a precompile causes it to exist in the world state archive. worldStateUpdater.getOrCreate(Address.ECREC).setBalance(Wei.of(10)); - assertThat(executeOperation(Address.ECREC)).isEqualTo(Hash.EMPTY.getBytes()); + assertThat(executeOperation(Address.ECREC).toBytes32()).isEqualTo(Hash.EMPTY.getBytes()); } @Test @@ -95,7 +97,8 @@ void shouldGetHashOfAccountCodeWhenCodeIsPresent() { final Bytes code = Bytes.fromHexString("0xabcdef"); final MutableAccount account = worldStateUpdater.getOrCreate(REQUESTED_ADDRESS); account.setCode(code); - assertThat(executeOperation(REQUESTED_ADDRESS)).isEqualTo(Hash.hash(code).getBytes()); + assertThat(executeOperation(REQUESTED_ADDRESS).toBytes32()) + .isEqualTo(Hash.hash(code).getBytes()); } @Test @@ -109,7 +112,7 @@ void shouldZeroOutLeftMostBitsToGetAddress() { .add(UInt256.valueOf(2).pow(UInt256.valueOf(160))); final MessageFrame frame = createMessageFrame(value); operation.execute(frame, null); - assertThat(frame.getStackItem(0)).isEqualTo(Hash.hash(code).getBytes()); + assertThat(frame.getStackItem(0).toBytes32()).isEqualTo(Hash.hash(code).getBytes()); } @Test @@ -123,14 +126,14 @@ void shouldGetHash() { final MessageFrame frame = createMessageFrame(value); operation.execute(frame, null); - assertThat(frame.getStackItem(0)).isEqualTo(Hash.hash(code).getBytes()); + assertThat(frame.getStackItem(0).toBytes32()).isEqualTo(Hash.hash(code).getBytes()); final MessageFrame frameIstanbul = createMessageFrame(value); operationIstanbul.execute(frameIstanbul, null); - assertThat(frameIstanbul.getStackItem(0)).isEqualTo(Hash.hash(code).getBytes()); + assertThat(frameIstanbul.getStackItem(0).toBytes32()).isEqualTo(Hash.hash(code).getBytes()); } - private Bytes executeOperation(final Address requestedAddress) { + private org.hyperledger.besu.evm.UInt256 executeOperation(final Address requestedAddress) { final MessageFrame frame = createMessageFrame(requestedAddress); operation.execute(frame, null); return frame.getStackItem(0); @@ -141,7 +144,7 @@ private MessageFrame createMessageFrame(final Address requestedAddress) { return createMessageFrame(stackItem); } - private MessageFrame createMessageFrame(final UInt256 stackItem) { + private MessageFrame createMessageFrame(final Bytes stackItem) { final BlockValues blockValues = new FakeBlockValues(1337); final MessageFrame frame = new TestMessageFrameBuilder() @@ -149,7 +152,9 @@ private MessageFrame createMessageFrame(final UInt256 stackItem) { .blockValues(blockValues) .build(); - frame.pushStackItem(stackItem); + final byte[] padded = org.apache.tuweni.bytes.Bytes32.leftPad(stackItem).toArrayUnsafe(); + frame.setTop( + StackMath.pushFromBytes(frame.stackData(), frame.stackTop(), padded, 0, padded.length)); return frame; } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperationTest.java index 2290e319814..2b6e6948d3c 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperationTest.java @@ -23,6 +23,7 @@ import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; import org.hyperledger.besu.evm.gascalculator.IstanbulGasCalculator; +import org.hyperledger.besu.evm.internal.StackMath; import org.hyperledger.besu.evm.internal.Words; import org.hyperledger.besu.evm.operation.Operation.OperationResult; import org.hyperledger.besu.evm.testutils.FakeBlockValues; @@ -61,32 +62,33 @@ void istanbulShouldCharge700Gas() { @Test void shouldReturnZeroWhenAccountDoesNotExist() { - final Bytes result = executeOperation(REQUESTED_ADDRESS); - assertThat(result.trimLeadingZeros()).isEqualTo(Bytes.EMPTY); + final org.hyperledger.besu.evm.UInt256 result = executeOperation(REQUESTED_ADDRESS); + assertThat(result).isEqualTo(org.hyperledger.besu.evm.UInt256.ZERO); } @Test void shouldReturnSizeOfEmptyDataWhenAccountExistsButDoesNotHaveCode() { worldStateUpdater.getOrCreate(REQUESTED_ADDRESS).setBalance(Wei.of(1)); - assertThat(executeOperation(REQUESTED_ADDRESS).toInt()).isZero(); + assertThat(executeOperation(REQUESTED_ADDRESS).intValue()).isZero(); } @Test void shouldReturnZeroWhenAccountExistsButIsEmpty() { worldStateUpdater.getOrCreate(REQUESTED_ADDRESS); - assertThat(executeOperation(REQUESTED_ADDRESS).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); + assertThat(executeOperation(REQUESTED_ADDRESS)) + .isEqualTo(org.hyperledger.besu.evm.UInt256.ZERO); } @Test void shouldReturnZeroWhenPrecompiledContractHasNoBalance() { - assertThat(executeOperation(Address.ECREC).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); + assertThat(executeOperation(Address.ECREC)).isEqualTo(org.hyperledger.besu.evm.UInt256.ZERO); } @Test void shouldReturnEmptyCodeSizeWhenPrecompileHasBalance() { // Sending money to a precompile causes it to exist in the world state archive. worldStateUpdater.getOrCreate(Address.ECREC).setBalance(Wei.of(10)); - assertThat(executeOperation(Address.ECREC).toInt()).isZero(); + assertThat(executeOperation(Address.ECREC).intValue()).isZero(); } @Test @@ -94,7 +96,7 @@ void shouldGetSizeOfAccountCodeWhenCodeIsPresent() { final Bytes code = Bytes.fromHexString("0xabcdef"); final MutableAccount account = worldStateUpdater.getOrCreate(REQUESTED_ADDRESS); account.setCode(code); - assertThat(executeOperation(REQUESTED_ADDRESS).toInt()).isEqualTo(3); + assertThat(executeOperation(REQUESTED_ADDRESS).intValue()).isEqualTo(3); } @Test @@ -108,7 +110,7 @@ void shouldZeroOutLeftMostBitsToGetAddress() { .add(UInt256.valueOf(2).pow(UInt256.valueOf(160))); final MessageFrame frame = createMessageFrame(value); operation.execute(frame, null); - assertThat(frame.getStackItem(0).toInt()).isEqualTo(3); + assertThat(frame.getStackItem(0).intValue()).isEqualTo(3); } @Test @@ -122,14 +124,14 @@ void shouldGetSize() { final MessageFrame frame = createMessageFrame(value); operation.execute(frame, null); - assertThat(frame.getStackItem(0).toInt()).isEqualTo(9); + assertThat(frame.getStackItem(0).intValue()).isEqualTo(9); final MessageFrame frameIstanbul = createMessageFrame(value); operationIstanbul.execute(frameIstanbul, null); - assertThat(frame.getStackItem(0).toInt()).isEqualTo(9); + assertThat(frame.getStackItem(0).intValue()).isEqualTo(9); } - private Bytes executeOperation(final Address requestedAddress) { + private org.hyperledger.besu.evm.UInt256 executeOperation(final Address requestedAddress) { final MessageFrame frame = createMessageFrame(requestedAddress); operation.execute(frame, null); return frame.getStackItem(0); @@ -140,7 +142,7 @@ private MessageFrame createMessageFrame(final Address requestedAddress) { return createMessageFrame(stackItem); } - private MessageFrame createMessageFrame(final UInt256 stackItem) { + private MessageFrame createMessageFrame(final Bytes stackItem) { final BlockValues blockValues = new FakeBlockValues(1337); final MessageFrame frame = new TestMessageFrameBuilder() @@ -148,7 +150,9 @@ private MessageFrame createMessageFrame(final UInt256 stackItem) { .blockValues(blockValues) .build(); - frame.pushStackItem(stackItem); + final byte[] padded = org.apache.tuweni.bytes.Bytes32.leftPad(stackItem).toArrayUnsafe(); + frame.setTop( + StackMath.pushFromBytes(frame.stackData(), frame.stackTop(), padded, 0, padded.length)); return frame; } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/PayOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/PayOperationTest.java index 66e41c0a75b..9a5dafdf012 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/PayOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/PayOperationTest.java @@ -30,6 +30,7 @@ import java.util.List; import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -130,7 +131,10 @@ void noValueTest( assertThat(result.getGasCost()).isEqualTo(chargedGas); assertThat(result.getHaltReason()).isEqualTo(haltReason); - assertThat(frame.getStackItem(0)).isEqualTo(stackItem); + assertThat(frame.getStackItem(0)) + .isEqualTo( + org.hyperledger.besu.evm.UInt256.fromBytesBE( + Bytes32.leftPad(stackItem).toArrayUnsafe())); Account recipientAccount = worldUpdater.get(recipientAddress); if (recipientAccount != null) { @@ -284,7 +288,10 @@ void valueTest( assertThat(result.getGasCost()).isEqualTo(chargedGas); assertThat(result.getHaltReason()).isEqualTo(haltReason); - assertThat(frame.getStackItem(0)).isEqualTo(stackItem); + assertThat(frame.getStackItem(0)) + .isEqualTo( + org.hyperledger.besu.evm.UInt256.fromBytesBE( + Bytes32.leftPad(stackItem).toArrayUnsafe())); assertThat(senderAccount.getBalance()).isEqualTo(senderBalance); assertThat(worldUpdater.getAccount(recipientAddress).getBalance()).isEqualTo(recipientBalance); @@ -394,7 +401,10 @@ void staticCallContext( assertThat(result.getGasCost()).isEqualTo(chargedGas); assertThat(result.getHaltReason()).isEqualTo(haltReason); - assertThat(frame.getStackItem(0)).isEqualTo(stackItem); + assertThat(frame.getStackItem(0)) + .isEqualTo( + org.hyperledger.besu.evm.UInt256.fromBytesBE( + Bytes32.leftPad(stackItem).toArrayUnsafe())); assertThat(senderAccount.getBalance()).isEqualTo(senderBalance); assertThat(recipientAccount.getBalance()).isEqualTo(recipientBalance); @@ -469,6 +479,9 @@ void tooBigAddress( assertThat(result.getGasCost()).isEqualTo(gasCost); assertThat(result.getHaltReason()).isEqualTo(haltReason); - assertThat(frame.getStackItem(0)).isEqualTo(stackItem); + assertThat(frame.getStackItem(0)) + .isEqualTo( + org.hyperledger.besu.evm.UInt256.fromBytesBE( + Bytes32.leftPad(stackItem).toArrayUnsafe())); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/PrevRanDaoOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/PrevRanDaoOperationTest.java index 28340920a07..8afd99d0fb5 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/PrevRanDaoOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/PrevRanDaoOperationTest.java @@ -16,13 +16,12 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.BlockValues; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.LondonGasCalculator; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; @@ -33,32 +32,30 @@ class PrevRanDaoOperationTest { @Test void pushesPrevRandaoWhenDifficultyZero() { - PrevRanDaoOperation op = new PrevRanDaoOperation(new LondonGasCalculator()); - MessageFrame messageFrame = mock(MessageFrame.class); - BlockValues blockHeader = mock(BlockValues.class); - Bytes32 prevRandao = Bytes32.fromHexString("0xb0b0face"); + final PrevRanDaoOperation op = new PrevRanDaoOperation(new LondonGasCalculator()); + final Bytes32 prevRandao = Bytes32.fromHexString("0xb0b0face"); + final BlockValues blockHeader = mock(BlockValues.class); when(blockHeader.getDifficultyBytes()).thenReturn(UInt256.ZERO); when(blockHeader.getMixHashOrPrevRandao()).thenReturn(prevRandao); - when(messageFrame.getBlockValues()).thenReturn(blockHeader); - EVM evm = mock(EVM.class); - Operation.OperationResult r = op.executeFixedCostOperation(messageFrame, evm); + final MessageFrame frame = new TestMessageFrameBuilder().blockValues(blockHeader).build(); + final Operation.OperationResult r = op.executeFixedCostOperation(frame, null); assertThat(r.getHaltReason()).isNull(); - verify(messageFrame).pushStackItem(prevRandao); + assertThat(frame.getStackItem(0)) + .isEqualTo(org.hyperledger.besu.evm.UInt256.fromBytesBE(prevRandao.toArrayUnsafe())); } @Test void pushesPrevRandDaoWhenDifficultyPresent() { - PrevRanDaoOperation op = new PrevRanDaoOperation(new LondonGasCalculator()); - MessageFrame messageFrame = mock(MessageFrame.class); - BlockValues blockHeader = mock(BlockValues.class); - Bytes32 prevRandao = Bytes32.fromHexString("0xb0b0face"); - Bytes difficulty = Bytes.random(32); + final PrevRanDaoOperation op = new PrevRanDaoOperation(new LondonGasCalculator()); + final Bytes32 prevRandao = Bytes32.fromHexString("0xb0b0face"); + final Bytes difficulty = Bytes.random(32); + final BlockValues blockHeader = mock(BlockValues.class); when(blockHeader.getDifficultyBytes()).thenReturn(difficulty); when(blockHeader.getMixHashOrPrevRandao()).thenReturn(prevRandao); - when(messageFrame.getBlockValues()).thenReturn(blockHeader); - EVM evm = mock(EVM.class); - Operation.OperationResult r = op.executeFixedCostOperation(messageFrame, evm); + final MessageFrame frame = new TestMessageFrameBuilder().blockValues(blockHeader).build(); + final Operation.OperationResult r = op.executeFixedCostOperation(frame, null); assertThat(r.getHaltReason()).isNull(); - verify(messageFrame).pushStackItem(prevRandao); + assertThat(frame.getStackItem(0)) + .isEqualTo(org.hyperledger.besu.evm.UInt256.fromBytesBE(prevRandao.toArrayUnsafe())); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/Push0OperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/Push0OperationTest.java index eb19d5cb3e7..58ca77639ac 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/Push0OperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/Push0OperationTest.java @@ -15,23 +15,15 @@ package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.evm.Code; -import org.hyperledger.besu.evm.frame.BlockValues; +import org.hyperledger.besu.evm.UInt256; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.BerlinGasCalculator; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.operation.Operation.OperationResult; -import org.hyperledger.besu.evm.testutils.FakeBlockValues; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; -import java.util.Optional; - -import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; class Push0OperationTest { @@ -39,25 +31,12 @@ class Push0OperationTest { @Test void shouldPush0OntoStack() { - final MessageFrame frame = createMessageFrame(100, Optional.of(Wei.of(5L))); + final MessageFrame frame = new TestMessageFrameBuilder().build(); final Operation operation = new Push0Operation(gasCalculator); final OperationResult result = operation.execute(frame, null); - Mockito.verify(frame).pushStackItem(Bytes.EMPTY); + assertThat(frame.getStackItem(0)).isEqualTo(UInt256.ZERO); assertThat(result.getGasCost()).isEqualTo(gasCalculator.getBaseTierGasCost()); - assertSuccessResult(result); - } - - private void assertSuccessResult(final OperationResult result) { assertThat(result).isNotNull(); assertThat(result.getHaltReason()).isNull(); } - - private MessageFrame createMessageFrame(final long initialGas, final Optional baseFee) { - final MessageFrame frame = mock(MessageFrame.class); - when(frame.getRemainingGas()).thenReturn(initialGas); - final BlockValues blockValues = new FakeBlockValues(baseFee); - when(frame.getBlockValues()).thenReturn(blockValues); - when(frame.getCode()).thenReturn(Code.EMPTY_CODE); - return frame; - } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/PushOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/PushOperationTest.java index f446c4e0e29..abd642a6109 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/PushOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/PushOperationTest.java @@ -17,67 +17,49 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.evm.operation.PushOperation.staticOperation; -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.frame.MessageFrame; -import org.hyperledger.besu.evm.toy.ToyBlockValues; -import org.hyperledger.besu.evm.toy.ToyWorld; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; -@ExtendWith(MockitoExtension.class) public class PushOperationTest { private static final byte[] byteCode = new byte[] {0x00, 0x01, 0x02, 0x03}; - private static final MessageFrame frame = - MessageFrame.builder() - .worldUpdater(new ToyWorld()) - .originator(Address.ZERO) - .gasPrice(Wei.ONE) - .blobGasPrice(Wei.ONE) - .blockValues(new ToyBlockValues()) - .miningBeneficiary(Address.ZERO) - .blockHashLookup((__, ___) -> Hash.ZERO) - .type(MessageFrame.Type.MESSAGE_CALL) - .initialGas(1) - .address(Address.ZERO) - .contract(Address.ZERO) - .inputData(Bytes32.ZERO) - .sender(Address.ZERO) - .value(Wei.ZERO) - .apparentValue(Wei.ZERO) - .code(Code.EMPTY_CODE) - .completer(messageFrame -> {}) - .build(); - ; + + private MessageFrame createFrame() { + return new TestMessageFrameBuilder().build(); + } @Test void unpaddedPushDoesntReachEndCode() { - staticOperation(frame, byteCode, 0, byteCode.length - 2); - assertThat(frame.getStackItem(0).equals(Bytes.fromHexString("0x0102"))).isTrue(); + final MessageFrame frame = createFrame(); + staticOperation(frame, frame.stackData(), byteCode, 0, byteCode.length - 2); + assertThat(frame.getStackItem(0)) + .isEqualTo(UInt256.fromBytesBE(Bytes.fromHexString("0x0102").toArrayUnsafe())); } @Test void unpaddedPushUpReachesEndCode() { - staticOperation(frame, byteCode, 0, byteCode.length - 1); - assertThat(frame.getStackItem(0).equals(Bytes.fromHexString("0x010203"))).isTrue(); + final MessageFrame frame = createFrame(); + staticOperation(frame, frame.stackData(), byteCode, 0, byteCode.length - 1); + assertThat(frame.getStackItem(0)) + .isEqualTo(UInt256.fromBytesBE(Bytes.fromHexString("0x010203").toArrayUnsafe())); } @Test void paddedPush() { - staticOperation(frame, byteCode, 1, byteCode.length - 1); - assertThat(frame.getStackItem(0).equals(Bytes.fromHexString("0x020300"))).isTrue(); + final MessageFrame frame = createFrame(); + staticOperation(frame, frame.stackData(), byteCode, 1, byteCode.length - 1); + assertThat(frame.getStackItem(0)) + .isEqualTo(UInt256.fromBytesBE(Bytes.fromHexString("0x020300").toArrayUnsafe())); } @Test void oobPush() { - staticOperation(frame, byteCode, byteCode.length, byteCode.length - 1); - assertThat(frame.getStackItem(0).equals(Bytes.EMPTY)).isTrue(); + final MessageFrame frame = createFrame(); + staticOperation(frame, frame.stackData(), byteCode, byteCode.length, byteCode.length - 1); + assertThat(frame.getStackItem(0)).isEqualTo(UInt256.ZERO); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/RevertOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/RevertOperationTest.java index d5094494a3c..abe7c70174a 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/RevertOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/RevertOperationTest.java @@ -15,46 +15,33 @@ package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.when; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.units.bigints.UInt256; -import org.junit.jupiter.api.BeforeEach; +import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; -@ExtendWith(MockitoExtension.class) class RevertOperationTest { - @Mock private MessageFrame messageFrame; private final RevertOperation operation = new RevertOperation(new ConstantinopleGasCalculator()); - private final Bytes revertReasonBytes = Bytes.fromHexString("726576657274206d657373616765"); - - @BeforeEach - void setUp() { - when(messageFrame.popStackItem()) - .thenReturn(UInt256.fromHexString("0x00")) - .thenReturn(UInt256.fromHexString("0x0e")); - when(messageFrame.readMemory(0, 14)).thenReturn(revertReasonBytes); - when(messageFrame.memoryWordSize()).thenReturn(0); - when(messageFrame.calculateMemoryExpansion(anyLong(), anyLong())).thenReturn(14L); - when(messageFrame.getRemainingGas()).thenReturn(10_000L); - } - @Test void shouldReturnReason() { - final ArgumentCaptor arg = ArgumentCaptor.forClass(Bytes.class); - operation.execute(messageFrame, null); - Mockito.verify(messageFrame).setRevertReason(arg.capture()); - assertThat(arg.getValue()).isEqualTo(revertReasonBytes); + final Bytes revertReasonBytes = Bytes.fromHexString("726576657274206d657373616765"); + final MessageFrame frame = + new TestMessageFrameBuilder() + .pushStackItem(Bytes32.fromHexStringLenient("0x0e")) // length = 14 + .pushStackItem(Bytes32.fromHexStringLenient("0x00")) // from = 0 + .initialGas(10_000L) + .build(); + // Write revert reason to memory + frame.writeMemory(0, 14, revertReasonBytes, true); + operation.execute(frame, null); + assertThat(frame.getRevertReason()).isPresent(); + assertThat(frame.getRevertReason().get()).isEqualTo(revertReasonBytes); + assertThat(frame.getState()).isEqualTo(MessageFrame.State.REVERT); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/SStoreOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/SStoreOperationTest.java index 6f3c7fd21f6..5a6d0b384ed 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/SStoreOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/SStoreOperationTest.java @@ -24,6 +24,7 @@ import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.internal.StackMath; import org.hyperledger.besu.evm.operation.Operation.OperationResult; import org.hyperledger.besu.evm.testutils.FakeBlockValues; import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; @@ -32,7 +33,6 @@ import java.util.List; -import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -82,8 +82,8 @@ void storeOperation( final SStoreOperation operation = new SStoreOperation(gasCalculator, minimumGasAvailable); final MessageFrame frame = createMessageFrame(Address.fromHexString("0x18675309"), initialGas, remainingGas); - frame.pushStackItem(UInt256.ZERO); - frame.pushStackItem(UInt256.fromHexString("0x01")); + frame.setTop(StackMath.pushZero(frame.stackData(), frame.stackTop())); + frame.setTop(StackMath.pushLong(frame.stackData(), frame.stackTop(), 1)); final OperationResult result = operation.execute(frame, null); assertThat(result.getHaltReason()).isEqualTo(expectedHalt); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/SarOperationPropertyBasedTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/SarOperationPropertyBasedTest.java deleted file mode 100644 index 8f89f50bbeb..00000000000 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/SarOperationPropertyBasedTest.java +++ /dev/null @@ -1,405 +0,0 @@ -/* - * 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.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.frame.MessageFrame; - -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 original SAR operation with the optimized version. - * - *

Tests verify that SarOperationOptimized produces identical results to SarOperation for all - * possible inputs, including edge cases for negative values and sign extension. - */ -public class SarOperationPropertyBasedTest { - - // 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); // set sign bit of 256-bit word - return bytes; - }); - } - - @Provide - Arbitrary positiveValues() { - // Generate values with sign bit clear (first byte < 0x80) - return Arbitraries.bytes() - .array(byte[].class) - .ofMinSize(1) - .ofMaxSize(32) - .map( - bytes -> { - if (bytes.length > 0) { - // Ensure sign bit is clear by ANDing with 0x7F - bytes[0] = (byte) (bytes[0] & 0x7F); - } - return bytes; - }); - } - - // endregion - - // region SAR Property Tests - Random Inputs - - @Property(tries = 10000) - void property_sarOptimized_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 Bytes originalResult = runSarOperation(shift, value); - final Bytes optimizedResult = runSarOperationOptimized(shift, value); - - assertThat(Bytes32.leftPad(optimizedResult)) - .as( - "SAR mismatch for shift=%s, value=%s", - shift.toHexString(), Bytes32.leftPad(value).toHexString()) - .isEqualTo(Bytes32.leftPad(originalResult)); - } - - @Property(tries = 5000) - void property_sarOptimized_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 Bytes originalResult = runSarOperation(shiftBytes, value); - final Bytes optimizedResult = runSarOperationOptimized(shiftBytes, value); - - assertThat(Bytes32.leftPad(optimizedResult)) - .as("SAR mismatch for shift=%d", shift) - .isEqualTo(Bytes32.leftPad(originalResult)); - } - - @Property(tries = 1000) - void property_sarOptimized_matchesOriginal_overflowShifts( - @ForAll("values1to32") final byte[] valueBytes, @ForAll("overflowShifts") final int shift) { - - final Bytes value = Bytes.wrap(valueBytes); - final Bytes shiftBytes = intToMinimalBytes(shift); - - final Bytes originalResult = runSarOperation(shiftBytes, value); - final Bytes optimizedResult = runSarOperationOptimized(shiftBytes, value); - - assertThat(Bytes32.leftPad(optimizedResult)) - .as("SAR overflow mismatch for shift=%d", shift) - .isEqualTo(Bytes32.leftPad(originalResult)); - } - - // endregion - - // region SAR Property Tests - Negative Values (Sign Extension) - - @Property(tries = 5000) - void property_sarOptimized_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 Bytes originalResult = runSarOperation(shiftBytes, value); - final Bytes optimizedResult = runSarOperationOptimized(shiftBytes, value); - - assertThat(Bytes32.leftPad(optimizedResult)) - .as("SAR negative mismatch for shift=%d, value=%s", shift, value.toHexString()) - .isEqualTo(Bytes32.leftPad(originalResult)); - } - - @Property(tries = 1000) - void property_sarOptimized_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 Bytes originalResult = runSarOperation(shiftBytes, value); - final Bytes optimizedResult = runSarOperationOptimized(shiftBytes, value); - - // For negative values with overflow shift, result should be all ones - assertThat(Bytes32.leftPad(optimizedResult)) - .as("SAR negative overflow mismatch for shift=%d", shift) - .isEqualTo(Bytes32.leftPad(originalResult)); - } - - // endregion - - // region SAR Property Tests - Positive Values - - @Property(tries = 5000) - void property_sarOptimized_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 Bytes originalResult = runSarOperation(shiftBytes, value); - final Bytes optimizedResult = runSarOperationOptimized(shiftBytes, value); - - assertThat(Bytes32.leftPad(optimizedResult)) - .as("SAR positive mismatch for shift=%d, value=%s", shift, value.toHexString()) - .isEqualTo(Bytes32.leftPad(originalResult)); - } - - @Property(tries = 1000) - void property_sarOptimized_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 Bytes originalResult = runSarOperation(shiftBytes, value); - final Bytes optimizedResult = runSarOperationOptimized(shiftBytes, value); - - // For positive values with overflow shift, result should be all zeros - assertThat(Bytes32.leftPad(optimizedResult)) - .as("SAR positive overflow mismatch for shift=%d", shift) - .isEqualTo(Bytes32.leftPad(originalResult)); - } - - // endregion - - // region Edge Case Tests - - @Property(tries = 1000) - void property_sar_shiftByZero_returnsOriginalValue( - @ForAll("values1to32") final byte[] valueBytes) { - - final Bytes value = Bytes.wrap(valueBytes); - final Bytes shift = Bytes.of(0); - - final Bytes originalResult = runSarOperation(shift, value); - final Bytes optimizedResult = runSarOperationOptimized(shift, value); - - assertThat(Bytes32.leftPad(optimizedResult)).isEqualTo(Bytes32.leftPad(value)); - assertThat(Bytes32.leftPad(originalResult)).isEqualTo(Bytes32.leftPad(value)); - } - - @Property(tries = 500) - void property_sar_negativeValue_largeShift_returnsAllOnes( - @ForAll("negativeValues") final byte[] valueBytes) { - - final Bytes value = Bytes.wrap(valueBytes); - final Bytes largeShift = Bytes.fromHexString("0x010000000000"); - - final Bytes originalResult = runSarOperation(largeShift, value); - final Bytes optimizedResult = runSarOperationOptimized(largeShift, value); - - // Both should return all ones for negative value with large shift - final Bytes32 allOnes = - Bytes32.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - assertThat(Bytes32.leftPad(optimizedResult)).isEqualTo(allOnes); - assertThat(Bytes32.leftPad(originalResult)).isEqualTo(allOnes); - } - - @Property(tries = 500) - void property_sar_positiveValue_largeShift_returnsZero( - @ForAll("positiveValues") final byte[] valueBytes) { - - final Bytes value = Bytes.wrap(valueBytes); - final Bytes largeShift = Bytes.fromHexString("0x010000000000"); - - final Bytes originalResult = runSarOperation(largeShift, value); - final Bytes optimizedResult = runSarOperationOptimized(largeShift, value); - - // Both should return zero for positive value with large shift - assertThat(Bytes32.leftPad(optimizedResult)).isEqualTo(Bytes32.ZERO); - assertThat(Bytes32.leftPad(originalResult)).isEqualTo(Bytes32.ZERO); - } - - @Property(tries = 500) - void property_sar_allOnes_anyShift_returnsAllOnes(@ForAll("smallShifts") final int shift) { - - // -1 in two's complement (all bits set) - final Bytes value = - Bytes.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - final Bytes shiftBytes = Bytes.of(shift); - - final Bytes originalResult = runSarOperation(shiftBytes, value); - final Bytes optimizedResult = runSarOperationOptimized(shiftBytes, value); - - // SAR of -1 by any amount should still be -1 (all ones) - assertThat(Bytes32.leftPad(optimizedResult)).isEqualTo(Bytes32.leftPad(value)); - assertThat(Bytes32.leftPad(originalResult)).isEqualTo(Bytes32.leftPad(value)); - } - - @Property(tries = 500) - void property_sar_minValue_shift255_returnsAllOnes() { - - // MIN_VALUE: 0x8000...0000 (only sign bit set) - final Bytes value = - Bytes.fromHexString("0x8000000000000000000000000000000000000000000000000000000000000000"); - final Bytes shift = Bytes.of(255); - - final Bytes originalResult = runSarOperation(shift, value); - final Bytes optimizedResult = runSarOperationOptimized(shift, value); - - // SAR of MIN_VALUE by 255 should be all ones (-1) - final Bytes32 allOnes = - Bytes32.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - assertThat(Bytes32.leftPad(optimizedResult)).isEqualTo(allOnes); - assertThat(Bytes32.leftPad(originalResult)).isEqualTo(allOnes); - } - - @Property(tries = 500) - void property_sar_maxPositive_shift255_returnsZero() { - - // MAX_VALUE: 0x7fff...ffff (all bits except sign bit set) - final Bytes value = - Bytes.fromHexString("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - final Bytes shift = Bytes.of(255); - - final Bytes originalResult = runSarOperation(shift, value); - final Bytes optimizedResult = runSarOperationOptimized(shift, value); - - // SAR of MAX_VALUE by 255 should be 0 - assertThat(Bytes32.leftPad(optimizedResult)).isEqualTo(Bytes32.ZERO); - assertThat(Bytes32.leftPad(originalResult)).isEqualTo(Bytes32.ZERO); - } - - @Property(tries = 3000) - void property_sarOptimized_matchesOriginal_negativeValues_shift255( - @ForAll("negativeValues") final byte[] valueBytes) { - - final Bytes value = Bytes.wrap(valueBytes); - final Bytes shift = Bytes.of(255); - - final Bytes originalResult = runSarOperation(shift, value); - final Bytes optimizedResult = runSarOperationOptimized(shift, value); - - assertThat(Bytes32.leftPad(optimizedResult)) - .as("SAR negative shift=255 mismatch for value=%s", value.toHexString()) - .isEqualTo(Bytes32.leftPad(originalResult)); - } - - @Property(tries = 3000) - void property_sarOptimized_matchesOriginal_positiveValues_shift255( - @ForAll("positiveValues") final byte[] valueBytes) { - - final Bytes value = Bytes.wrap(valueBytes); - final Bytes shift = Bytes.of(255); - - final Bytes originalResult = runSarOperation(shift, value); - final Bytes optimizedResult = runSarOperationOptimized(shift, value); - - assertThat(Bytes32.leftPad(optimizedResult)) - .as("SAR positive shift=255 mismatch for value=%s", value.toHexString()) - .isEqualTo(Bytes32.leftPad(originalResult)); - } - - // endregion - - // region Helper Methods - - private Bytes runSarOperation(final Bytes shift, final Bytes value) { - return runOperation(shift, value, SarOperation::staticOperation); - } - - private Bytes runSarOperationOptimized(final Bytes shift, final Bytes value) { - return runOperation(shift, value, SarOperationOptimized::staticOperation); - } - - @FunctionalInterface - interface OperationExecutor { - Operation.OperationResult execute(MessageFrame frame); - } - - private Bytes runOperation( - final Bytes shift, final Bytes value, final OperationExecutor 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]; - } - - 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/operation/SarOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/SarOperationTest.java index c7d7cad00dc..73432bfd5dd 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/SarOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/SarOperationTest.java @@ -15,17 +15,15 @@ package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +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.testutils.TestMessageFrameBuilder; import java.util.List; -import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -116,66 +114,47 @@ static Iterable data() { "0xff", "0x0000000000000000000000000000000000000000000000000000000000000000"), Arguments.of( - "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0x100", "0x"), - Arguments.of( - "0x0000000000000000000000000000000000000000000000000000000000000400", - "0x80", + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x100", "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"), + "0x0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0xfc", + "0x0000000000000000000000000000000000000000000000000000000000000000"), Arguments.of( - "0x8000000000000000000000000000000000000000000000000000000000000400", - "0x80000000", - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xfe", + "0x0000000000000000000000000000000000000000000000000000000000000000"), Arguments.of( - "0x8000000000000000000000000000000000000000000000000000000000000400", - "0x8000000000000000", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0xfe", "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), Arguments.of( - "0x8000000000000000000000000000000000000000000000000000000000000400", - "0x80000000000000000000000000000000", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0xff", "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), Arguments.of( - "0x8000000000000000000000000000000000000000000000000000000000000400", - "0x8000000000000000000000000000000000000000000000000000000000000000", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x100", "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); } @ParameterizedTest(name = "{index}: {0}, {1}, {2}") @MethodSource("data") void shiftOperation(final String number, final String shift, final String expectedResult) { - final MessageFrame frame = mock(MessageFrame.class); - when(frame.stackSize()).thenReturn(2); - when(frame.getRemainingGas()).thenReturn(100L); - when(frame.popStackItem()) - .thenReturn(Bytes32.fromHexStringLenient(shift)) - .thenReturn(Bytes.fromHexString(number)); + final MessageFrame frame = + new TestMessageFrameBuilder() + .pushStackItem(Bytes32.fromHexStringLenient(number)) + .pushStackItem(Bytes32.fromHexStringLenient(shift)) + .build(); operation.execute(frame, null); - verify(frame).pushStackItem(Bytes.fromHexString(expectedResult)); + UInt256 expected; + if (expectedResult.equals("0x") || expectedResult.equals("0x0")) { + expected = UInt256.ZERO; + } else { + expected = UInt256.fromBytesBE(Bytes32.fromHexStringLenient(expectedResult).toArrayUnsafe()); + } + assertThat(frame.getStackItem(0)).isEqualTo(expected); } @Test diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/SelfDestructOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/SelfDestructOperationTest.java index c03be40f00b..271b252cca5 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/SelfDestructOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/SelfDestructOperationTest.java @@ -31,6 +31,7 @@ import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.internal.StackMath; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.apache.tuweni.bytes.Bytes; @@ -90,7 +91,16 @@ void checkContractDeletionCommon( .initialGas(100_000L) .worldUpdater(worldUpdater) .build(); - messageFrame.pushStackItem(Bytes.fromHexString(beneficiary)); + { + final byte[] beneficiaryBytes = Bytes.fromHexString(beneficiary).toArrayUnsafe(); + messageFrame.setTop( + StackMath.pushFromBytes( + messageFrame.stackData(), + messageFrame.stackTop(), + beneficiaryBytes, + 0, + beneficiaryBytes.length)); + } if (newContract) { messageFrame.addCreate(originatorAddress); } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/ShiftOperationsPropertyBasedTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/ShiftOperationsPropertyBasedTest.java deleted file mode 100644 index 004acfd7ba6..00000000000 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/ShiftOperationsPropertyBasedTest.java +++ /dev/null @@ -1,292 +0,0 @@ -/* - * 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.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.frame.MessageFrame; - -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 original shift operations with their optimized versions. - * - *

Tests verify that SHL/SHR optimized implementations produce identical results to the original - * implementations for all possible inputs. - */ -public class ShiftOperationsPropertyBasedTest { - - // 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); - } - - // endregion - - // region SHL Property Tests - - @Property(tries = 10000) - void property_shlOptimized_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 Bytes originalResult = runShlOperation(shift, value); - final Bytes optimizedResult = runShlOperationOptimized(shift, value); - - assertThat(Bytes32.leftPad(optimizedResult)) - .as( - "SHL mismatch for shift=%s, value=%s", - shift.toHexString(), Bytes32.leftPad(value).toHexString()) - .isEqualTo(Bytes32.leftPad(originalResult)); - } - - @Property(tries = 5000) - void property_shlOptimized_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 Bytes originalResult = runShlOperation(shiftBytes, value); - final Bytes optimizedResult = runShlOperationOptimized(shiftBytes, value); - - assertThat(Bytes32.leftPad(optimizedResult)) - .as("SHL mismatch for shift=%d", shift) - .isEqualTo(Bytes32.leftPad(originalResult)); - } - - @Property(tries = 1000) - void property_shlOptimized_matchesOriginal_overflowShifts( - @ForAll("values1to32") final byte[] valueBytes, @ForAll("overflowShifts") final int shift) { - - final Bytes value = Bytes.wrap(valueBytes); - final Bytes shiftBytes = intToMinimalBytes(shift); - - final Bytes originalResult = runShlOperation(shiftBytes, value); - final Bytes optimizedResult = runShlOperationOptimized(shiftBytes, value); - - assertThat(Bytes32.leftPad(optimizedResult)) - .as("SHL overflow mismatch for shift=%d", shift) - .isEqualTo(Bytes32.leftPad(originalResult)); - } - - // endregion - - // region SHR Property Tests - - @Property(tries = 10000) - void property_shrOptimized_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 Bytes originalResult = runShrOperation(shift, value); - final Bytes optimizedResult = runShrOperationOptimized(shift, value); - - assertThat(Bytes32.leftPad(optimizedResult)) - .as( - "SHR mismatch for shift=%s, value=%s", - shift.toHexString(), Bytes32.leftPad(value).toHexString()) - .isEqualTo(Bytes32.leftPad(originalResult)); - } - - @Property(tries = 5000) - void property_shrOptimized_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 Bytes originalResult = runShrOperation(shiftBytes, value); - final Bytes optimizedResult = runShrOperationOptimized(shiftBytes, value); - - assertThat(Bytes32.leftPad(optimizedResult)) - .as("SHR mismatch for shift=%d", shift) - .isEqualTo(Bytes32.leftPad(originalResult)); - } - - @Property(tries = 1000) - void property_shrOptimized_matchesOriginal_overflowShifts( - @ForAll("values1to32") final byte[] valueBytes, @ForAll("overflowShifts") final int shift) { - - final Bytes value = Bytes.wrap(valueBytes); - final Bytes shiftBytes = intToMinimalBytes(shift); - - final Bytes originalResult = runShrOperation(shiftBytes, value); - final Bytes optimizedResult = runShrOperationOptimized(shiftBytes, value); - - assertThat(Bytes32.leftPad(optimizedResult)) - .as("SHR overflow mismatch for shift=%d", shift) - .isEqualTo(Bytes32.leftPad(originalResult)); - } - - // endregion - - // region Edge Case Tests - - @Property(tries = 1000) - void property_shl_shiftByZero_returnsOriginalValue( - @ForAll("values1to32") final byte[] valueBytes) { - - final Bytes value = Bytes.wrap(valueBytes); - final Bytes shift = Bytes.of(0); - - final Bytes originalResult = runShlOperation(shift, value); - final Bytes optimizedResult = runShlOperationOptimized(shift, value); - - assertThat(Bytes32.leftPad(optimizedResult)).isEqualTo(Bytes32.leftPad(value)); - assertThat(Bytes32.leftPad(originalResult)).isEqualTo(Bytes32.leftPad(value)); - } - - @Property(tries = 1000) - void property_shr_shiftByZero_returnsOriginalValue( - @ForAll("values1to32") final byte[] valueBytes) { - - final Bytes value = Bytes.wrap(valueBytes); - final Bytes shift = Bytes.of(0); - - final Bytes originalResult = runShrOperation(shift, value); - final Bytes optimizedResult = runShrOperationOptimized(shift, value); - - assertThat(Bytes32.leftPad(optimizedResult)).isEqualTo(Bytes32.leftPad(value)); - assertThat(Bytes32.leftPad(originalResult)).isEqualTo(Bytes32.leftPad(value)); - } - - @Property(tries = 500) - void property_shl_largeShift_returnsZero(@ForAll("values1to32") final byte[] valueBytes) { - - final Bytes value = Bytes.wrap(valueBytes); - final Bytes largeShift = Bytes.fromHexString("0x010000000000"); - - final Bytes originalResult = runShlOperation(largeShift, value); - final Bytes optimizedResult = runShlOperationOptimized(largeShift, value); - - assertThat(Bytes32.leftPad(optimizedResult)).isEqualTo(Bytes32.ZERO); - assertThat(Bytes32.leftPad(originalResult)).isEqualTo(Bytes32.ZERO); - } - - @Property(tries = 500) - void property_shr_largeShift_returnsZero(@ForAll("values1to32") final byte[] valueBytes) { - - final Bytes value = Bytes.wrap(valueBytes); - final Bytes largeShift = Bytes.fromHexString("0x010000000000"); - - final Bytes originalResult = runShrOperation(largeShift, value); - final Bytes optimizedResult = runShrOperationOptimized(largeShift, value); - - assertThat(Bytes32.leftPad(optimizedResult)).isEqualTo(Bytes32.ZERO); - assertThat(Bytes32.leftPad(originalResult)).isEqualTo(Bytes32.ZERO); - } - - // endregion - - // region Helper Methods - - private Bytes runShlOperation(final Bytes shift, final Bytes value) { - return runOperation(shift, value, ShlOperation::staticOperation); - } - - private Bytes runShlOperationOptimized(final Bytes shift, final Bytes value) { - return runOperation(shift, value, ShlOperationOptimized::staticOperation); - } - - private Bytes runShrOperation(final Bytes shift, final Bytes value) { - return runOperation(shift, value, ShrOperation::staticOperation); - } - - private Bytes runShrOperationOptimized(final Bytes shift, final Bytes value) { - return runOperation(shift, value, ShrOperationOptimized::staticOperation); - } - - @FunctionalInterface - interface OperationExecutor { - Operation.OperationResult execute(MessageFrame frame); - } - - private Bytes runOperation( - final Bytes shift, final Bytes value, final OperationExecutor 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]; - } - - 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/operation/ShlOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/ShlOperationTest.java index a4d0b8f3104..9da06c4ab98 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/ShlOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/ShlOperationTest.java @@ -15,19 +15,16 @@ package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +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.testutils.TestMessageFrameBuilder; import java.util.Arrays; -import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -105,14 +102,19 @@ static Iterable data() { @ParameterizedTest @MethodSource("data") void shiftOperation(final String number, final String shift, final String expectedResult) { - final MessageFrame frame = mock(MessageFrame.class); - when(frame.stackSize()).thenReturn(2); - when(frame.getRemainingGas()).thenReturn(100L); - when(frame.popStackItem()) - .thenReturn(UInt256.fromBytes(Bytes32.fromHexStringLenient(shift))) - .thenReturn(UInt256.fromHexString(number)); + final MessageFrame frame = + new TestMessageFrameBuilder() + .pushStackItem(Bytes32.fromHexStringLenient(number)) + .pushStackItem(Bytes32.fromHexStringLenient(shift)) + .build(); operation.execute(frame, null); - verify(frame).pushStackItem(Bytes.fromHexString(expectedResult)); + UInt256 expected; + if (expectedResult.equals("0x") || expectedResult.equals("0x0")) { + expected = UInt256.ZERO; + } else { + expected = UInt256.fromBytesBE(Bytes32.fromHexStringLenient(expectedResult).toArrayUnsafe()); + } + assertThat(frame.getStackItem(0)).isEqualTo(expected); } @Test diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/ShrOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/ShrOperationTest.java index 5acc7466a2e..fc823e7aeef 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/ShrOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/ShrOperationTest.java @@ -15,19 +15,16 @@ package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +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.testutils.TestMessageFrameBuilder; import java.util.Arrays; -import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -116,14 +113,19 @@ static Iterable data() { @ParameterizedTest @MethodSource("data") void shiftOperation(final String number, final String shift, final String expectedResult) { - final MessageFrame frame = mock(MessageFrame.class); - when(frame.stackSize()).thenReturn(2); - when(frame.getRemainingGas()).thenReturn(100L); - when(frame.popStackItem()) - .thenReturn(UInt256.fromBytes(Bytes32.fromHexStringLenient(shift))) - .thenReturn(UInt256.fromHexString(number)); + final MessageFrame frame = + new TestMessageFrameBuilder() + .pushStackItem(Bytes32.fromHexStringLenient(number)) + .pushStackItem(Bytes32.fromHexStringLenient(shift)) + .build(); operation.execute(frame, null); - verify(frame).pushStackItem(Bytes.fromHexString(expectedResult)); + UInt256 expected; + if (expectedResult.equals("0x") || expectedResult.equals("0x0")) { + expected = UInt256.ZERO; + } else { + expected = UInt256.fromBytesBE(Bytes32.fromHexStringLenient(expectedResult).toArrayUnsafe()); + } + assertThat(frame.getStackItem(0)).isEqualTo(expected); } @Test diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/SlotNumOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/SlotNumOperationTest.java index 086efb1623a..89ade760476 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/SlotNumOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/SlotNumOperationTest.java @@ -16,15 +16,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import org.hyperledger.besu.evm.frame.BlockValues; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.BerlinGasCalculator; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.Words; import org.hyperledger.besu.evm.operation.Operation.OperationResult; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; import org.junit.jupiter.api.Test; @@ -33,40 +32,39 @@ class SlotNumOperationTest { @Test void shouldReturnGasCost() { - final MessageFrame frame = createMessageFrame(100L, 42L); + final MessageFrame frame = createMessageFrame(42L); final Operation operation = new SlotNumOperation(gasCalculator); final OperationResult result = operation.execute(frame, null); assertThat(result.getGasCost()).isEqualTo(gasCalculator.getBaseTierGasCost()); - assertSuccessResult(result); + assertThat(result.getHaltReason()).isNull(); } @Test void shouldWriteSlotNumberToStack() { final long expectedSlotNumber = 12345L; - final MessageFrame frame = createMessageFrame(100L, expectedSlotNumber); + final MessageFrame frame = createMessageFrame(expectedSlotNumber); final Operation operation = new SlotNumOperation(gasCalculator); - final OperationResult result = operation.execute(frame, null); - verify(frame).pushStackItem(Words.longBytes(expectedSlotNumber)); - assertSuccessResult(result); + operation.execute(frame, null); + assertThat(frame.getStackItem(0)) + .isEqualTo(org.hyperledger.besu.evm.UInt256.fromLong(expectedSlotNumber)); } @Test void shouldHandleZeroSlotNumber() { - final MessageFrame frame = createMessageFrame(100L, 0L); + final MessageFrame frame = createMessageFrame(0L); final Operation operation = new SlotNumOperation(gasCalculator); - final OperationResult result = operation.execute(frame, null); - verify(frame).pushStackItem(Words.longBytes(0L)); - assertSuccessResult(result); + operation.execute(frame, null); + assertThat(frame.getStackItem(0)).isEqualTo(org.hyperledger.besu.evm.UInt256.ZERO); } @Test void shouldHandleLargeSlotNumber() { final long maxSlotNumber = Long.MAX_VALUE; - final MessageFrame frame = createMessageFrame(100L, maxSlotNumber); + final MessageFrame frame = createMessageFrame(maxSlotNumber); final Operation operation = new SlotNumOperation(gasCalculator); - final OperationResult result = operation.execute(frame, null); - verify(frame).pushStackItem(Words.longBytes(maxSlotNumber)); - assertSuccessResult(result); + operation.execute(frame, null); + assertThat(frame.getStackItem(0)) + .isEqualTo(org.hyperledger.besu.evm.UInt256.fromLong(maxSlotNumber)); } @Test @@ -88,17 +86,9 @@ void shouldHaveCorrectStackBehavior() { assertThat(operation.getStackItemsProduced()).isEqualTo(1); } - private void assertSuccessResult(final OperationResult result) { - assertThat(result).isNotNull(); - assertThat(result.getHaltReason()).isNull(); - } - - private MessageFrame createMessageFrame(final long initialGas, final long slotNumber) { - final MessageFrame frame = mock(MessageFrame.class); - when(frame.getRemainingGas()).thenReturn(initialGas); + private MessageFrame createMessageFrame(final long slotNumber) { final BlockValues blockValues = mock(BlockValues.class); when(blockValues.getSlotNumber()).thenReturn(slotNumber); - when(frame.getBlockValues()).thenReturn(blockValues); - return frame; + return new TestMessageFrameBuilder().blockValues(blockValues).build(); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/SwapNOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/SwapNOperationTest.java index f065b54a4d3..5c127c1d0eb 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/SwapNOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/SwapNOperationTest.java @@ -17,6 +17,7 @@ import static org.assertj.core.api.Assertions.assertThat; import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.UInt256; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.PragueGasCalculator; @@ -69,8 +70,8 @@ void testSwapN_basicOperation() { final MessageFrame frame = builder.build(); // Before: stack[0]=1, stack[17]=18 - assertThat(frame.getStackItem(0)).isEqualTo(Bytes.of(1)); - assertThat(frame.getStackItem(17)).isEqualTo(Bytes.of(18)); + assertThat(frame.getStackItem(0)).isEqualTo(UInt256.fromInt(1)); + assertThat(frame.getStackItem(17)).isEqualTo(UInt256.fromInt(18)); final OperationResult result = operation.execute(frame, null); @@ -79,8 +80,8 @@ void testSwapN_basicOperation() { assertThat(result.getPcIncrement()).isEqualTo(2); // After: stack[0]=18, stack[17]=1 - assertThat(frame.getStackItem(0)).isEqualTo(Bytes.of(18)); - assertThat(frame.getStackItem(17)).isEqualTo(Bytes.of(1)); + assertThat(frame.getStackItem(0)).isEqualTo(UInt256.fromInt(18)); + assertThat(frame.getStackItem(17)).isEqualTo(UInt256.fromInt(1)); // Stack size unchanged assertThat(frame.stackSize()).isEqualTo(18); } @@ -99,16 +100,16 @@ void testSwapN_immediate128() { final MessageFrame frame = builder.build(); // Before: stack[0]=1, stack[108]=109 - assertThat(frame.getStackItem(0).toInt()).isEqualTo(1); - assertThat(frame.getStackItem(108).toInt()).isEqualTo(109); + assertThat(frame.getStackItem(0).intValue()).isEqualTo(1); + assertThat(frame.getStackItem(108).intValue()).isEqualTo(109); final OperationResult result = operation.execute(frame, null); assertThat(result.getHaltReason()).isNull(); // After: swapped - assertThat(frame.getStackItem(0).toInt()).isEqualTo(109); - assertThat(frame.getStackItem(108).toInt()).isEqualTo(1); + assertThat(frame.getStackItem(0).intValue()).isEqualTo(109); + assertThat(frame.getStackItem(108).intValue()).isEqualTo(1); } @ParameterizedTest @@ -164,8 +165,8 @@ void testSwapN_endOfCode() { // Immediate 0 is valid assertThat(result.getHaltReason()).isNull(); // Verify swap happened - assertThat(frame.getStackItem(0)).isEqualTo(Bytes.of(18)); - assertThat(frame.getStackItem(17)).isEqualTo(Bytes.of(1)); + assertThat(frame.getStackItem(0)).isEqualTo(UInt256.fromInt(18)); + assertThat(frame.getStackItem(17)).isEqualTo(UInt256.fromInt(1)); } @Test @@ -206,8 +207,8 @@ void testSpecVector_swapn17With18Items() { final MessageFrame frame = builder.build(); assertThat(frame.stackSize()).isEqualTo(18); - assertThat(frame.getStackItem(0)).isEqualTo(Bytes.of(2)); // top - assertThat(frame.getStackItem(17)).isEqualTo(Bytes.of(1)); // bottom + assertThat(frame.getStackItem(0)).isEqualTo(UInt256.fromInt(2)); // top + assertThat(frame.getStackItem(17)).isEqualTo(UInt256.fromInt(1)); // bottom final OperationResult result = operation.execute(frame, null); @@ -215,8 +216,8 @@ void testSpecVector_swapn17With18Items() { assertThat(result.getPcIncrement()).isEqualTo(2); // After SWAPN: stack[0]=1, stack[17]=2 assertThat(frame.stackSize()).isEqualTo(18); // size unchanged - assertThat(frame.getStackItem(0)).isEqualTo(Bytes.of(1)); // swapped - assertThat(frame.getStackItem(17)).isEqualTo(Bytes.of(2)); // swapped + assertThat(frame.getStackItem(0)).isEqualTo(UInt256.fromInt(1)); // swapped + assertThat(frame.getStackItem(17)).isEqualTo(UInt256.fromInt(2)); // swapped } /** @@ -243,8 +244,8 @@ void testSpecVector_swapnEndOfCode() { // Implicit immediate 0 is valid assertThat(result.getHaltReason()).isNull(); - assertThat(frame.getStackItem(0)).isEqualTo(Bytes.of(1)); - assertThat(frame.getStackItem(17)).isEqualTo(Bytes.of(2)); + assertThat(frame.getStackItem(0)).isEqualTo(UInt256.fromInt(1)); + assertThat(frame.getStackItem(17)).isEqualTo(UInt256.fromInt(2)); } /** diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/TStoreOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/TStoreOperationTest.java index c7c3470825a..809b119d225 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/TStoreOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/TStoreOperationTest.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.internal.StackMath; import org.hyperledger.besu.evm.operation.Operation.OperationResult; import org.hyperledger.besu.evm.testutils.ByteCodeBuilder; import org.hyperledger.besu.evm.testutils.FakeBlockValues; @@ -35,7 +36,6 @@ import java.util.stream.Stream; -import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -72,8 +72,8 @@ void tstoreInsufficientGas() { final TStoreOperation operation = new TStoreOperation(gasCalculator); final MessageFrame frame = createMessageFrame(Address.fromHexString("0x18675309"), initialGas, remainingGas); - frame.pushStackItem(UInt256.ZERO); - frame.pushStackItem(UInt256.fromHexString("0x01")); + frame.setTop(StackMath.pushZero(frame.stackData(), frame.stackTop())); + frame.setTop(StackMath.pushLong(frame.stackData(), frame.stackTop(), 1)); final OperationResult result = operation.execute(frame, null); assertThat(result.getHaltReason()).isEqualTo(INSUFFICIENT_GAS); @@ -86,8 +86,8 @@ void tStoreSimpleTest() { final TStoreOperation operation = new TStoreOperation(gasCalculator); final MessageFrame frame = createMessageFrame(Address.fromHexString("0x18675309"), initialGas, remainingGas); - frame.pushStackItem(UInt256.ZERO); - frame.pushStackItem(UInt256.fromHexString("0x01")); + frame.setTop(StackMath.pushZero(frame.stackData(), frame.stackTop())); + frame.setTop(StackMath.pushLong(frame.stackData(), frame.stackTop(), 1)); final OperationResult result = operation.execute(frame, null); assertThat(result.getHaltReason()).isNull(); @@ -101,11 +101,11 @@ void tLoadEmpty() { createMessageFrame(Address.fromHexString("0x18675309"), initialGas, remainingGas); final TLoadOperation tload = new TLoadOperation(gasCalculator); - frame.pushStackItem(UInt256.fromHexString("0x01")); + frame.setTop(StackMath.pushLong(frame.stackData(), frame.stackTop(), 1)); final OperationResult tloadResult = tload.execute(frame, null); assertThat(tloadResult.getHaltReason()).isNull(); - var tloadValue = frame.popStackItem(); - assertThat(tloadValue).isEqualTo(Bytes32.ZERO); + var tloadValue = StackMath.getAt(frame.stackData(), frame.stackTop(), 0); + assertThat(tloadValue).isEqualTo(org.hyperledger.besu.evm.UInt256.ZERO); } @Test @@ -115,25 +115,28 @@ void tStoreTLoad() { final TStoreOperation tstore = new TStoreOperation(gasCalculator); final MessageFrame frame = createMessageFrame(Address.fromHexString("0x18675309"), initialGas, remainingGas); - frame.pushStackItem(UInt256.ONE); - frame.pushStackItem(UInt256.fromHexString("0x01")); + frame.setTop(StackMath.pushLong(frame.stackData(), frame.stackTop(), 1)); + frame.setTop(StackMath.pushLong(frame.stackData(), frame.stackTop(), 1)); final OperationResult result = tstore.execute(frame, null); assertThat(result.getHaltReason()).isNull(); TLoadOperation tload = new TLoadOperation(gasCalculator); - frame.pushStackItem(UInt256.fromHexString("0x01")); + frame.setTop(StackMath.pushLong(frame.stackData(), frame.stackTop(), 1)); OperationResult tloadResult = tload.execute(frame, null); assertThat(tloadResult.getHaltReason()).isNull(); - UInt256 tloadValue = UInt256.fromBytes(frame.popStackItem()); - assertThat(tloadValue).isEqualTo(UInt256.ONE); + org.hyperledger.besu.evm.UInt256 tloadValue = + StackMath.getAt(frame.stackData(), frame.stackTop(), 0); + frame.setTop(frame.stackTop() - 1); + assertThat(tloadValue).isEqualTo(org.hyperledger.besu.evm.UInt256.fromLong(1)); // Loading from a different location returns default value - frame.pushStackItem(UInt256.fromHexString("0x02")); + frame.setTop(StackMath.pushLong(frame.stackData(), frame.stackTop(), 2)); tloadResult = tload.execute(frame, null); assertThat(tloadResult.getHaltReason()).isNull(); - tloadValue = UInt256.fromBytes(frame.popStackItem()); - assertThat(tloadValue).isEqualTo(UInt256.ZERO); + tloadValue = StackMath.getAt(frame.stackData(), frame.stackTop(), 0); + frame.setTop(frame.stackTop() - 1); + assertThat(tloadValue).isEqualTo(org.hyperledger.besu.evm.UInt256.ZERO); } @Test @@ -143,25 +146,27 @@ void tStoreUpdate() { final TStoreOperation tstore = new TStoreOperation(gasCalculator); final MessageFrame frame = createMessageFrame(Address.fromHexString("0x18675309"), initialGas, remainingGas); - frame.pushStackItem(UInt256.ONE); - frame.pushStackItem(UInt256.fromHexString("0x01")); + frame.setTop(StackMath.pushLong(frame.stackData(), frame.stackTop(), 1)); + frame.setTop(StackMath.pushLong(frame.stackData(), frame.stackTop(), 1)); OperationResult result = tstore.execute(frame, null); assertThat(result.getHaltReason()).isNull(); // Store 2 at position 1 - frame.pushStackItem(UInt256.fromHexString("0x02")); - frame.pushStackItem(UInt256.fromHexString("0x01")); + frame.setTop(StackMath.pushLong(frame.stackData(), frame.stackTop(), 2)); + frame.setTop(StackMath.pushLong(frame.stackData(), frame.stackTop(), 1)); result = tstore.execute(frame, null); assertThat(result.getHaltReason()).isNull(); final TLoadOperation tload = new TLoadOperation(gasCalculator); - frame.pushStackItem(UInt256.fromHexString("0x01")); + frame.setTop(StackMath.pushLong(frame.stackData(), frame.stackTop(), 1)); final OperationResult tloadResult = tload.execute(frame, null); assertThat(tloadResult.getHaltReason()).isNull(); - UInt256 tloadValue = UInt256.fromBytes(frame.popStackItem()); - assertThat(tloadValue).isEqualTo(UInt256.fromHexString("0x02")); + org.hyperledger.besu.evm.UInt256 tloadValue = + StackMath.getAt(frame.stackData(), frame.stackTop(), 0); + frame.setTop(frame.stackTop() - 1); + assertThat(tloadValue).isEqualTo(org.hyperledger.besu.evm.UInt256.fromLong(2)); } // Zeroing out a transient storage slot does not result in gas refund @@ -172,15 +177,15 @@ void noGasRefundFromTransientState() { final TStoreOperation tstore = new TStoreOperation(gasCalculator); final MessageFrame frame = createMessageFrame(Address.fromHexString("0x18675309"), initialGas, remainingGas); - frame.pushStackItem(UInt256.ONE); - frame.pushStackItem(UInt256.fromHexString("0x01")); + frame.setTop(StackMath.pushLong(frame.stackData(), frame.stackTop(), 1)); + frame.setTop(StackMath.pushLong(frame.stackData(), frame.stackTop(), 1)); OperationResult result = tstore.execute(frame, null); assertThat(result.getHaltReason()).isNull(); // Reset value to 0 - frame.pushStackItem(UInt256.fromHexString("0x00")); - frame.pushStackItem(UInt256.fromHexString("0x01")); + frame.setTop(StackMath.pushZero(frame.stackData(), frame.stackTop())); + frame.setTop(StackMath.pushLong(frame.stackData(), frame.stackTop(), 1)); result = tstore.execute(frame, null); assertThat(result.getHaltReason()).isNull(); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestMessageFrameBuilder.java b/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestMessageFrameBuilder.java index 178a1408b8b..d88c327e042 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestMessageFrameBuilder.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestMessageFrameBuilder.java @@ -23,6 +23,7 @@ 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.StackMath; import org.hyperledger.besu.evm.internal.Words; import org.hyperledger.besu.evm.toy.ToyWorld; import org.hyperledger.besu.evm.worldstate.WorldUpdater; @@ -166,7 +167,12 @@ public MessageFrame build() { .isStatic(isStatic) .build(); frame.setPC(pc); - stackItems.forEach(frame::pushStackItem); + stackItems.forEach( + item -> { + final byte[] bytes = item.toArrayUnsafe(); + frame.setTop( + StackMath.pushFromBytes(frame.stackData(), frame.stackTop(), bytes, 0, bytes.length)); + }); frame.writeMemory(0, memory.size(), memory); return frame; }