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 48d7a72b300..ab72ccf76a7 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 @@ -18,8 +18,149 @@ 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; + public class AddModOperationBenchmark extends TernaryOperationBenchmark { + // Benches for (a + b) % c + + // Define available scenarios + public enum Case { + ADDMOD_32_32_32(1, 1, 1), + ADDMOD_64_32_32(2, 1, 1), + ADDMOD_64_64_32(2, 2, 1), + ADDMOD_64_64_64(2, 2, 2), + ADDMOD_128_32_32(4, 1, 1), + ADDMOD_128_64_32(4, 2, 1), + ADDMOD_128_64_64(4, 2, 2), + ADDMOD_128_128_32(4, 4, 1), + ADDMOD_128_128_64(4, 4, 2), + ADDMOD_128_128_128(4, 4, 3), + ADDMOD_192_32_32(6, 1, 1), + ADDMOD_192_64_32(6, 2, 1), + ADDMOD_192_64_64(6, 2, 2), + ADDMOD_192_128_32(6, 4, 1), + ADDMOD_192_128_64(6, 4, 2), + ADDMOD_192_128_128(6, 4, 4), + ADDMOD_192_192_32(6, 6, 1), + ADDMOD_192_192_64(6, 6, 2), + ADDMOD_192_192_128(6, 6, 4), + ADDMOD_192_192_192(6, 6, 6), + ADDMOD_256_32_32(8, 1, 1), + ADDMOD_256_64_32(8, 2, 1), + ADDMOD_256_64_64(8, 2, 2), + ADDMOD_256_128_32(8, 4, 1), + ADDMOD_256_128_64(8, 4, 2), + ADDMOD_256_128_128(8, 4, 4), + ADDMOD_256_192_32(8, 6, 1), + ADDMOD_256_192_64(8, 6, 2), + ADDMOD_256_192_128(8, 6, 4), + ADDMOD_256_192_192(8, 6, 6), + ADDMOD_256_256_32(8, 8, 1), + ADDMOD_256_256_64(8, 8, 2), + ADDMOD_256_256_128(8, 8, 4), + ADDMOD_256_256_192(8, 8, 6), + ADDMOD_256_256_256(8, 8, 8), + LARGER_ADDMOD_64_64_128(2, 2, 4), + LARGER_ADDMOD_192_192_256(6, 6, 8), + ZERO_ADDMOD_128_256_0(4, 8, 0), + FULL_RANDOM(-1, -1, -1); + + final int aSize; + final int bSize; + final int cSize; + + Case(final int aSize, final int bSize, final int cSize) { + this.aSize = aSize; + this.bSize = bSize; + this.cSize = cSize; + } + } + + @Param({ + "ADDMOD_32_32_32", + "ADDMOD_64_32_32", + "ADDMOD_64_64_32", + "ADDMOD_64_64_64", + "ADDMOD_128_32_32", + "ADDMOD_128_64_32", + "ADDMOD_128_64_64", + "ADDMOD_128_128_32", + "ADDMOD_128_128_64", + "ADDMOD_128_128_128", + "ADDMOD_192_32_32", + "ADDMOD_192_64_32", + "ADDMOD_192_64_64", + "ADDMOD_192_128_32", + "ADDMOD_192_128_64", + "ADDMOD_192_128_128", + "ADDMOD_192_192_32", + "ADDMOD_192_192_64", + "ADDMOD_192_192_128", + "ADDMOD_192_192_192", + "ADDMOD_256_32_32", + "ADDMOD_256_64_32", + "ADDMOD_256_64_64", + "ADDMOD_256_128_32", + "ADDMOD_256_128_64", + "ADDMOD_256_128_128", + "ADDMOD_256_192_32", + "ADDMOD_256_192_64", + "ADDMOD_256_192_128", + "ADDMOD_256_192_192", + "ADDMOD_256_256_32", + "ADDMOD_256_256_64", + "ADDMOD_256_256_128", + "ADDMOD_256_256_192", + "ADDMOD_256_256_256", + "LARGER_ADDMOD_64_64_128", + "LARGER_ADDMOD_192_192_256", + "ZERO_ADDMOD_128_256_0", + "FULL_RANDOM" + }) + private String caseName; + + @Setup(Level.Iteration) + @Override + 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]; + + final ThreadLocalRandom random = ThreadLocalRandom.current(); + int aSize; + int bSize; + int cSize; + + for (int i = 0; i < SAMPLE_SIZE; i++) { + if (scenario.aSize < 0) aSize = random.nextInt(1, 33); + else aSize = scenario.aSize * 4; + if (scenario.bSize < 0) bSize = random.nextInt(1, 33); + else bSize = scenario.bSize * 4; + if (scenario.cSize < 0) cSize = random.nextInt(1, 33); + else cSize = scenario.cSize * 4; + + final byte[] a = new byte[aSize]; + final byte[] b = new byte[bSize]; + final byte[] c = new byte[cSize]; + random.nextBytes(a); + random.nextBytes(b); + random.nextBytes(c); + aPool[i] = Bytes.wrap(a); + bPool[i] = Bytes.wrap(b); + cPool[i] = Bytes.wrap(c); + } + index = 0; + } + @Override protected Operation.OperationResult invoke(final MessageFrame frame) { return AddModOperation.staticOperation(frame); 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 6404764b7e3..bb18601286b 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 @@ -18,7 +18,113 @@ 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; + public class ModOperationBenchmark extends BinaryOperationBenchmark { + // Benches for a % b + + // Define available scenarios + public enum Case { + MOD_32_32(1, 1), + MOD_64_32(2, 1), + MOD_64_64(2, 2), + MOD_128_32(4, 1), + MOD_128_64(4, 2), + MOD_128_128(4, 4), + MOD_192_32(6, 1), + MOD_192_64(6, 2), + MOD_192_128(6, 4), + MOD_192_192(6, 6), + MOD_256_32(8, 1), + MOD_256_64(8, 2), + MOD_256_128(8, 4), + MOD_256_192(8, 6), + MOD_256_256(8, 8), + LARGER_MOD_64_128(2, 4), + LARGER_MOD_192_256(6, 8), + ZERO_MOD_128_0(4, 0), + FULL_RANDOM(-1, -1); + + final int divSize; + final int modSize; + + Case(final int divSize, final int modSize) { + this.divSize = divSize; + this.modSize = modSize; + } + } + + @Param({ + "MOD_32_32", + "MOD_64_32", + "MOD_64_64", + "MOD_128_32", + "MOD_128_64", + "MOD_128_128", + "MOD_192_32", + "MOD_192_64", + "MOD_192_128", + "MOD_192_192", + "MOD_256_32", + "MOD_256_64", + "MOD_256_128", + "MOD_256_192", + "MOD_256_256", + "LARGER_MOD_64_128", + "LARGER_MOD_192_256", + "ZERO_MOD_128_0", + "FULL_RANDOM" + }) + private String caseName; + + @Setup(Level.Iteration) + @Override + public void setUp() { + frame = BenchmarkHelper.createMessageCallFrame(); + + Case scenario = Case.valueOf(caseName); + aPool = new Bytes[SAMPLE_SIZE]; + bPool = new Bytes[SAMPLE_SIZE]; + + final ThreadLocalRandom random = ThreadLocalRandom.current(); + int aSize; + int bSize; + + for (int i = 0; i < SAMPLE_SIZE; i++) { + if (scenario.divSize < 0) aSize = random.nextInt(1, 33); + else aSize = scenario.divSize * 4; + if (scenario.modSize < 0) bSize = random.nextInt(1, 33); + else bSize = scenario.modSize * 4; + + final byte[] a = new byte[aSize]; + final byte[] b = new byte[bSize]; + random.nextBytes(a); + random.nextBytes(b); + + // Swap a and b if necessary + if ((scenario.divSize != scenario.modSize)) { + aPool[i] = Bytes.wrap(a); + bPool[i] = Bytes.wrap(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); + } else { + aPool[i] = Bytes.wrap(a); + bPool[i] = Bytes.wrap(b); + } + } + } + index = 0; + } @Override protected Operation.OperationResult invoke(final MessageFrame frame) { 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 be3771432e1..fbe5a3305cc 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 @@ -18,8 +18,149 @@ 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; + public class MulModOperationBenchmark extends TernaryOperationBenchmark { + // Benches for (a + b) % c + + // Define available scenarios + public enum Case { + MULMOD_32_32_32(1, 1, 1), + MULMOD_64_32_32(2, 1, 1), + MULMOD_64_64_32(2, 2, 1), + MULMOD_64_64_64(2, 2, 2), + MULMOD_128_32_32(4, 1, 1), + MULMOD_128_64_32(4, 2, 1), + MULMOD_128_64_64(4, 2, 2), + MULMOD_128_128_32(4, 4, 1), + MULMOD_128_128_64(4, 4, 2), + MULMOD_128_128_128(4, 4, 3), + MULMOD_192_32_32(6, 1, 1), + MULMOD_192_64_32(6, 2, 1), + MULMOD_192_64_64(6, 2, 2), + MULMOD_192_128_32(6, 4, 1), + MULMOD_192_128_64(6, 4, 2), + MULMOD_192_128_128(6, 4, 4), + MULMOD_192_192_32(6, 6, 1), + MULMOD_192_192_64(6, 6, 2), + MULMOD_192_192_128(6, 6, 4), + MULMOD_192_192_192(6, 6, 6), + MULMOD_256_32_32(8, 1, 1), + MULMOD_256_64_32(8, 2, 1), + MULMOD_256_64_64(8, 2, 2), + MULMOD_256_128_32(8, 4, 1), + MULMOD_256_128_64(8, 4, 2), + MULMOD_256_128_128(8, 4, 4), + MULMOD_256_192_32(8, 6, 1), + MULMOD_256_192_64(8, 6, 2), + MULMOD_256_192_128(8, 6, 4), + MULMOD_256_192_192(8, 6, 6), + MULMOD_256_256_32(8, 8, 1), + MULMOD_256_256_64(8, 8, 2), + MULMOD_256_256_128(8, 8, 4), + MULMOD_256_256_192(8, 8, 6), + MULMOD_256_256_256(8, 8, 8), + LARGER_MULMOD_64_64_128(2, 2, 4), + LARGER_MULMOD_192_192_256(6, 6, 8), + ZERO_MULMOD_128_256_0(4, 8, 0), + FULL_RANDOM(-1, -1, -1); + + final int aSize; + final int bSize; + final int cSize; + + Case(final int aSize, final int bSize, final int cSize) { + this.aSize = aSize; + this.bSize = bSize; + this.cSize = cSize; + } + } + + @Param({ + "MULMOD_32_32_32", + "MULMOD_64_32_32", + "MULMOD_64_64_32", + "MULMOD_64_64_64", + "MULMOD_128_32_32", + "MULMOD_128_64_32", + "MULMOD_128_64_64", + "MULMOD_128_128_32", + "MULMOD_128_128_64", + "MULMOD_128_128_128", + "MULMOD_192_32_32", + "MULMOD_192_64_32", + "MULMOD_192_64_64", + "MULMOD_192_128_32", + "MULMOD_192_128_64", + "MULMOD_192_128_128", + "MULMOD_192_192_32", + "MULMOD_192_192_64", + "MULMOD_192_192_128", + "MULMOD_192_192_192", + "MULMOD_256_32_32", + "MULMOD_256_64_32", + "MULMOD_256_64_64", + "MULMOD_256_128_32", + "MULMOD_256_128_64", + "MULMOD_256_128_128", + "MULMOD_256_192_32", + "MULMOD_256_192_64", + "MULMOD_256_192_128", + "MULMOD_256_192_192", + "MULMOD_256_256_32", + "MULMOD_256_256_64", + "MULMOD_256_256_128", + "MULMOD_256_256_192", + "MULMOD_256_256_256", + "LARGER_MULMOD_64_64_128", + "LARGER_MULMOD_192_192_256", + "ZERO_MULMOD_128_256_0", + "FULL_RANDOM" + }) + private String caseName; + + @Setup(Level.Iteration) + @Override + public void setUp() { + frame = BenchmarkHelper.createMessageCallFrame(); + + Case scenario = Case.valueOf(caseName); + aPool = new Bytes[SAMPLE_SIZE]; + bPool = new Bytes[SAMPLE_SIZE]; + cPool = new Bytes[SAMPLE_SIZE]; + + final ThreadLocalRandom random = ThreadLocalRandom.current(); + int aSize; + int bSize; + int cSize; + + for (int i = 0; i < SAMPLE_SIZE; i++) { + if (scenario.aSize < 0) aSize = random.nextInt(1, 33); + else aSize = scenario.aSize * 4; + if (scenario.bSize < 0) bSize = random.nextInt(1, 33); + else bSize = scenario.bSize * 4; + if (scenario.cSize < 0) cSize = random.nextInt(1, 33); + else cSize = scenario.cSize * 4; + + final byte[] a = new byte[aSize]; + final byte[] b = new byte[bSize]; + final byte[] c = new byte[cSize]; + random.nextBytes(a); + random.nextBytes(b); + random.nextBytes(c); + aPool[i] = Bytes.wrap(a); + bPool[i] = Bytes.wrap(b); + cPool[i] = Bytes.wrap(c); + } + index = 0; + } + @Override protected Operation.OperationResult invoke(final MessageFrame frame) { return MulModOperation.staticOperation(frame); 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 d09ecae1311..80ee76944e0 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 @@ -18,7 +18,113 @@ import org.hyperledger.besu.evm.operation.Operation; 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; + public class SModOperationBenchmark extends BinaryOperationBenchmark { + // Benches for a % b + + // Define available scenarios + public enum Case { + SMOD_32_32(1, 1), + SMOD_64_32(2, 1), + SMOD_64_64(2, 2), + SMOD_128_32(4, 1), + SMOD_128_64(4, 2), + SMOD_128_128(4, 4), + SMOD_192_32(6, 1), + SMOD_192_64(6, 2), + SMOD_192_128(6, 4), + SMOD_192_192(6, 6), + SMOD_256_32(8, 1), + SMOD_256_64(8, 2), + SMOD_256_128(8, 4), + SMOD_256_192(8, 6), + SMOD_256_256(8, 8), + LARGER_SMOD_64_128(2, 4), + LARGER_SMOD_192_256(6, 8), + ZERO_SMOD_128_0(4, 0), + FULL_RANDOM(-1, -1); + + final int divSize; + final int modSize; + + Case(final int divSize, final int modSize) { + this.divSize = divSize; + this.modSize = modSize; + } + } + + @Param({ + "SMOD_32_32", + "SMOD_64_32", + "SMOD_64_64", + "SMOD_128_32", + "SMOD_128_64", + "SMOD_128_128", + "SMOD_192_32", + "SMOD_192_64", + "SMOD_192_128", + "SMOD_192_192", + "SMOD_256_32", + "SMOD_256_64", + "SMOD_256_128", + "SMOD_256_192", + "SMOD_256_256", + "LARGER_SMOD_64_128", + "LARGER_SMOD_192_256", + "ZERO_SMOD_128_0", + "FULL_RANDOM" + }) + private String caseName; + + @Setup(Level.Iteration) + @Override + public void setUp() { + frame = BenchmarkHelper.createMessageCallFrame(); + + Case scenario = Case.valueOf(caseName); + aPool = new Bytes[SAMPLE_SIZE]; + bPool = new Bytes[SAMPLE_SIZE]; + + final ThreadLocalRandom random = ThreadLocalRandom.current(); + int aSize; + int bSize; + + for (int i = 0; i < SAMPLE_SIZE; i++) { + if (scenario.divSize < 0) aSize = random.nextInt(1, 33); + else aSize = scenario.divSize * 4; + if (scenario.modSize < 0) bSize = random.nextInt(1, 33); + else bSize = scenario.modSize * 4; + + final byte[] a = new byte[aSize]; + final byte[] b = new byte[bSize]; + random.nextBytes(a); + random.nextBytes(b); + + // Swap a and b if necessary + if ((scenario.divSize != scenario.modSize)) { + aPool[i] = Bytes.wrap(a); + bPool[i] = Bytes.wrap(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); + } else { + aPool[i] = Bytes.wrap(a); + bPool[i] = Bytes.wrap(b); + } + } + } + index = 0; + } @Override protected Operation.OperationResult invoke(final MessageFrame frame) { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/UInt256.java b/evm/src/main/java/org/hyperledger/besu/evm/UInt256.java new file mode 100644 index 00000000000..db366b280c5 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/UInt256.java @@ -0,0 +1,756 @@ +/* + * 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; + +import java.util.Arrays; + +/** + * 256-bits wide unsigned integer class. + * + *
This class is an optimised version of BigInteger for fixed width 256-bits integers. + */ +public final class UInt256 { + // region Internals + // -------------------------------------------------------------------------- + // UInt256 is a big-endian up to 256-bits integer. + // Internally, it is represented with int/long limbs in little-endian order. + // Wraps a view over limbs array from 0..length. + // Internally limbs are little-endian and can perhaps have more elements than length. + private final int[] limbs; + private final int length; + + // Maximum number of significant limbs + private static final int N_LIMBS = 8; + // Mask for long values + private static final long MASK_L = 0xFFFFFFFFL; + + // Getters for testing + int length() { + return length; + } + + int[] limbs() { + return limbs; + } + + // -------------------------------------------------------------------------- + // endregion + + // region Preallocating Small Integers + // -------------------------------------------------------------------------- + private static final int nSmallInts = 256; + private static final UInt256[] smallInts = new UInt256[nSmallInts]; + + static { + smallInts[0] = new UInt256(new int[] {}); + for (int i = 1; i < nSmallInts; i++) { + smallInts[i] = new UInt256(new int[] {i}); + } + } + + /** The constant 0. */ + public static final UInt256 ZERO = smallInts[0]; + + /** The constant 1. */ + public static final UInt256 ONE = smallInts[1]; + + /** The constant 2. */ + public static final UInt256 TWO = smallInts[2]; + + /** The constant 10. */ + public static final UInt256 TEN = smallInts[10]; + + /** The constant 16. */ + public static final UInt256 SIXTEEN = smallInts[16]; + + // -------------------------------------------------------------------------- + // endregion + + // region Constructors + // -------------------------------------------------------------------------- + + UInt256(final int[] limbs, final int length) { + this.limbs = limbs; + this.length = length; + // Unchecked length: assumes length is properly set. + } + + /** + * Instantiates a new UInt256 from limbs (int[]). + * + * @param limbs integer limbs in little-endian order. + */ + public UInt256(final int[] limbs) { + int i = Math.min(limbs.length, N_LIMBS) - 1; + while ((i >= 0) && (limbs[i] == 0)) i--; + this.limbs = limbs; + this.length = i + 1; + } + + /** + * Instantiates a new UInt256 from byte[]. + * + * @param bytes raw bytes in BigEndian order. + * @return Big-endian UInt256 represented by the bytes. + */ + public static UInt256 fromBytesBE(final byte[] bytes) { + int offset = 0; + while ((offset < bytes.length) && (bytes[offset] == 0x00)) ++offset; + int nBytes = bytes.length - offset; + if (nBytes == 0) return ZERO; + int len = (nBytes + 3) / 4; + int[] limbs = new int[len]; + // int[] limbs = new int[N_LIMBS]; + + int i; + int base; + // Up to most significant limb take 4 bytes. + for (i = 0, base = bytes.length - 4; i < len - 1; ++i, base = base - 4) { + limbs[i] = + (bytes[base] << 24) + | ((bytes[base + 1] & 0xFF) << 16) + | ((bytes[base + 2] & 0xFF) << 8) + | ((bytes[base + 3] & 0xFF)); + } + // Last effective limb + limbs[i] = + switch (nBytes - i * 4) { + case 1 -> ((bytes[offset] & 0xFF)); + case 2 -> (((bytes[offset] & 0xFF) << 8) | (bytes[offset + 1] & 0xFF)); + case 3 -> + (((bytes[offset] & 0xFF) << 16) + | ((bytes[offset + 1] & 0xFF) << 8) + | (bytes[offset + 2] & 0xFF)); + case 4 -> + ((bytes[offset] << 24) + | ((bytes[offset + 1] & 0xFF) << 16) + | ((bytes[offset + 2] & 0xFF) << 8) + | (bytes[offset + 3] & 0xFF)); + default -> throw new IllegalStateException("Unexpected value"); + }; + return new UInt256(limbs, len); + } + + /** + * Instantiates a new UInt256 from byte[]. + * + * @param bytes raw bytes in BigEndian order. + * @return Big-endian UInt256 represented by the bytes. + */ + public static UInt256 fromSignedBytesBE(final byte[] bytes) { + int nBytes = bytes.length; + if (nBytes == 0) return ZERO; + int len = (nBytes + 3) / 4; + int[] limbs = new int[len]; + // int[] limbs = new int[N_LIMBS]; + + int i; + int base; + // Up to most significant limb take 4 bytes. + for (i = 0, base = bytes.length - 4; i < len - 1; ++i, base = base - 4) { + limbs[i] = + (bytes[base] << 24) + | ((bytes[base + 1] & 0xFF) << 16) + | ((bytes[base + 2] & 0xFF) << 8) + | ((bytes[base + 3] & 0xFF)); + } + // Last effective limb + limbs[i] = + switch (nBytes - i * 4) { + case 1 -> ((bytes[0])); + case 2 -> (((bytes[0]) << 8) | (bytes[1] & 0xFF)); + case 3 -> (((bytes[0]) << 16) | ((bytes[1] & 0xFF) << 8) | (bytes[2] & 0xFF)); + case 4 -> + ((bytes[0] << 24) + | ((bytes[1] & 0xFF) << 16) + | ((bytes[2] & 0xFF) << 8) + | (bytes[3] & 0xFF)); + default -> throw new IllegalStateException("Unexpected value"); + }; + return new UInt256(limbs, len); + } + + /** + * Instantiates a new UInt256 from an int. + * + * @param value int value to convert to UInt256. + * @return The UInt256 equivalent of value. + */ + public static UInt256 fromInt(final int value) { + if (0 <= value && value < nSmallInts) return smallInts[value]; + return new UInt256(new int[] {value}, 1); + } + + /** + * Instantiates a new UInt256 from a long. + * + * @param value long value to convert to UInt256. + * @return The UInt256 equivalent of value. + */ + public static UInt256 fromLong(final long value) { + if (0 <= value && value < nSmallInts) return smallInts[(int) value]; + return new UInt256(new int[] {(int) value, (int) (value >>> 32)}); + } + + // -------------------------------------------------------------------------- + // endregion + + // region Conversions + // -------------------------------------------------------------------------- + /** + * Convert to int. + * + * @return Value truncated to an int, possibly lossy. + */ + public int intValue() { + return (limbs.length == 0 ? 0 : limbs[0]); + } + + /** + * Convert to long. + * + * @return Value truncated to a long, possibly lossy. + */ + public long longValue() { + switch (length) { + case 0 -> { + return 0L; + } + case 1 -> { + return (limbs[0] & MASK_L); + } + default -> { + return (limbs[0] & MASK_L) | ((limbs[1] & MASK_L) << 32); + } + } + } + + /** + * Convert to BigEndian byte array. + * + * @return Big-endian ordered bytes for this UInt256 value. + */ + public byte[] toBytesBE() { + byte[] out = new byte[32]; + for (int i = 0, offset = 28; i < length; i++, offset -= 4) { + int v = limbs[i]; + out[offset] = (byte) (v >>> 24); + out[offset + 1] = (byte) (v >>> 16); + out[offset + 2] = (byte) (v >>> 8); + out[offset + 3] = (byte) v; + } + return out; + } + + /** + * Convert to BigEndian byte array. + * + * @return Big-endian ordered bytes for this UInt256 value. + */ + public byte[] toSignedBytesBE() { + byte[] out = new byte[32]; + if (length > 0 && limbs[length - 1] < 0) Arrays.fill(out, (byte) 0xFF); + for (int i = 0, offset = 28; i < length; i++, offset -= 4) { + int v = limbs[i]; + out[offset] = (byte) (v >>> 24); + out[offset + 1] = (byte) (v >>> 16); + out[offset + 2] = (byte) (v >>> 8); + out[offset + 3] = (byte) v; + } + return out; + } + + /** + * Convert to BigEndian byte array. + * + * @param sign if sign is negative, pad with 1s, else pad with 0s. + * @return Big-endian ordered bytes for this UInt256 value. + */ + public byte[] toSignedBytesBE(final int sign) { + byte[] out = new byte[32]; + if (sign < 0) Arrays.fill(out, (byte) 0xFF); + for (int i = 0, offset = 28; i < length; i++, offset -= 4) { + int v = limbs[i]; + out[offset] = (byte) (v >>> 24); + out[offset + 1] = (byte) (v >>> 16); + out[offset + 2] = (byte) (v >>> 8); + out[offset + 3] = (byte) v; + } + return out; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("0x"); + byte[] out = new byte[length * 4]; + for (int i = 0, offset = 4 * (length - 1); i < length; i++, offset -= 4) { + int v = limbs[i]; + out[offset] = (byte) (v >>> 24); + out[offset + 1] = (byte) (v >>> 16); + out[offset + 2] = (byte) (v >>> 8); + out[offset + 3] = (byte) v; + } + for (byte b : out) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } + + /** + * Convert to hexstring according to sign. + * + *
If sign is negative, pad with 1s, else pad with 0s.
+ *
+ * @param sign padding with 0s or 1s whether sign is non-negative.
+ * @return HexString
+ */
+ public String toString(final int sign) {
+ if (sign >= 0) return toString();
+ StringBuilder sb = new StringBuilder("0x");
+ byte[] out = new byte[length * 4];
+ Arrays.fill(out, (byte) 0xFF);
+ for (int i = 0, offset = 4 * (length - 1); i < length; i++, offset -= 4) {
+ int v = limbs[i];
+ out[offset] = (byte) (v >>> 24);
+ out[offset + 1] = (byte) (v >>> 16);
+ out[offset + 2] = (byte) (v >>> 8);
+ out[offset + 3] = (byte) v;
+ }
+ for (byte b : out) {
+ sb.append(String.format("%02x", b));
+ }
+ return sb.toString();
+ }
+
+ // --------------------------------------------------------------------------
+ // endregion
+
+ // region Comparisons
+ // --------------------------------------------------------------------------
+
+ /**
+ * Is the value 0 ?
+ *
+ * @return true if this UInt256 value is 0.
+ */
+ public boolean isZero() {
+ if (length == 0) return true;
+ for (int i = 0; i < length; i++) {
+ if (limbs[i] != 0) return false;
+ }
+ return true;
+ }
+
+ /**
+ * Compares two UInt256.
+ *
+ * @param a left UInt256
+ * @param b right UInt256
+ * @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 = Integer.compare(a.length, b.length);
+ if (comp != 0) return comp;
+ for (int i = a.length - 1; i >= 0; i--) {
+ comp = Integer.compareUnsigned(a.limbs[i], b.limbs[i]);
+ if (comp != 0) return comp;
+ }
+ return 0;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) return true;
+ if (!(obj instanceof UInt256)) return false;
+ UInt256 other = (UInt256) obj;
+
+ // Compare lengths after trimming leading zero limbs
+ int cmp = UInt256.compare(this, other);
+ return cmp == 0;
+ }
+
+ @Override
+ public int hashCode() {
+ int h = 1;
+ for (int i = 0; i < length; i++) {
+ h = 31 * h + limbs[i];
+ }
+ return h;
+ }
+
+ // --------------------------------------------------------------------------
+ // endregion
+
+ // region Bitwise Operations
+ // --------------------------------------------------------------------------
+
+ /**
+ * Shifts value to the left.
+ *
+ * @param shift number of bits to shift. If negative, shift right instead.
+ * @return Shifted UInt256 value.
+ */
+ public UInt256 shiftLeft(final int shift) {
+ if (shift >= 256) return ZERO;
+ if (shift < 0) return shiftRight(-shift);
+ if (shift == 0 || isZero()) return this;
+ int nDiffBits = shift - numberOfLeadingZeros(this.limbs, this.length);
+ int size = Math.min(this.length + (nDiffBits + 31) / 32, N_LIMBS);
+ int[] shifted = new int[size];
+ shiftLeftInto(shifted, this.limbs, shift);
+ return new UInt256(shifted, size);
+ }
+
+ /**
+ * Shifts value to the right.
+ *
+ * @param shift number of bits to shift. If negative, shift left instead.
+ * @return Shifted UInt256 value.
+ */
+ public UInt256 shiftRight(final int shift) {
+ if (shift < 0) return shiftLeft(-shift);
+ if (isZero()) return ZERO;
+ int[] shifted = new int[this.length];
+ shiftRightInto(shifted, this.limbs, shift);
+ return new UInt256(shifted);
+ }
+
+ // --------------------------------------------------------------------------
+ // endregion
+
+ // region Arithmetic Operations
+ // --------------------------------------------------------------------------
+
+ /**
+ * (Signed) absolute value
+ *
+ * @return The absolute value of this signed integer.
+ */
+ public UInt256 abs() {
+ int[] newLimbs = Arrays.copyOf(limbs, length);
+ absInplace(newLimbs);
+ return new UInt256(newLimbs, length);
+ }
+
+ /**
+ * Addition (modulo 2**256).
+ *
+ * @param other The integer to add.
+ * @return The sum of this with other.
+ */
+ public UInt256 add(final UInt256 other) {
+ if (other.isZero()) return this;
+ if (this.isZero()) return other;
+ return new UInt256(addWithCarry(this.limbs, other.limbs));
+ }
+
+ /**
+ * Multiplication (modulo 2**256).
+ *
+ * @param other The integer to add.
+ * @return The sum of this with other.
+ */
+ public UInt256 mul(final UInt256 other) {
+ if (this.isZero() || other.isZero()) return ZERO;
+ int[] result = new int[this.length + other.length + 1];
+ addMul(result, this.limbs, other.limbs);
+ return new UInt256(result);
+ }
+
+ /**
+ * Unsigned modulo reduction.
+ *
+ * @param modulus The modulus of the reduction
+ * @return The remainder modulo {@code modulus}.
+ */
+ public UInt256 mod(final UInt256 modulus) {
+ int cmp = compare(this, modulus);
+ if (cmp < 0) return this;
+ if (cmp == 0 || modulus.isZero() || this.isZero()) return ZERO;
+ return new UInt256(knuthRemainder(this.limbs, modulus.limbs));
+ }
+
+ /**
+ * Signed modulo reduction.
+ *
+ * @param modulus The modulus of the reduction
+ * @return The remainder modulo {@code modulus}.
+ */
+ public UInt256 signedMod(final UInt256 modulus) {
+ if (this.isZero() || modulus.isZero()) return ZERO;
+ int[] x = new int[this.length];
+ int[] y = new int[modulus.length];
+ absInto(x, this.limbs);
+ absInto(y, modulus.limbs);
+ int[] r = knuthRemainder(x, y);
+ if (isNegative(this.limbs)) {
+ int[] s = new int[N_LIMBS];
+ System.arraycopy(r, 0, s, 0, r.length);
+ negate(s);
+ return new UInt256(s);
+ }
+ return new UInt256(r);
+ }
+
+ /**
+ * Modular addition.
+ *
+ * @param other The integer to add to this.
+ * @param modulus The modulus of the reduction.
+ * @return This integer this + other (mod modulus).
+ */
+ public UInt256 addMod(final UInt256 other, final UInt256 modulus) {
+ if (modulus.isZero()) return ZERO;
+ if (this.isZero()) return other.mod(modulus);
+ if (other.isZero()) return this.mod(modulus);
+ int[] sum = addWithCarry(this.limbs, other.limbs);
+ int[] rem = knuthRemainder(sum, modulus.limbs);
+ return new UInt256(rem);
+ }
+
+ /**
+ * Modular multiplication.
+ *
+ * @param other The integer to add to this.
+ * @param modulus The modulus of the reduction.
+ * @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 = new int[this.length + other.length + 1];
+ addMul(result, this.limbs, other.limbs);
+ result = knuthRemainder(result, modulus.limbs);
+ return new UInt256(result);
+ }
+
+ // --------------------------------------------------------------------------
+ // endregion
+
+ // region Support (private) Algorithms
+ // --------------------------------------------------------------------------
+ private static boolean isNegative(final int[] x) {
+ return x[x.length - 1] < 0;
+ }
+
+ private static void negate(final int[] x) {
+ int carry = 1;
+ for (int i = 0; i < x.length; i++) {
+ x[i] = ~x[i] + carry;
+ carry = (x[i] == 0 && carry == 1) ? 1 : 0;
+ }
+ }
+
+ private static void absInplace(final int[] x) {
+ if (isNegative(x)) negate(x);
+ }
+
+ private static void absInto(final int[] dst, final int[] src) {
+ System.arraycopy(src, 0, dst, 0, Math.min(dst.length, src.length));
+ absInplace(dst);
+ }
+
+ private static int numberOfLeadingZeros(final int[] x, final int limit) {
+ int leadingIndex = limit - 1;
+ while ((leadingIndex >= 0) && (x[leadingIndex] == 0)) leadingIndex--;
+ return 32 * (limit - leadingIndex - 1) + Integer.numberOfLeadingZeros(x[leadingIndex]);
+ }
+
+ private static int numberOfLeadingZeros(final int[] x) {
+ return numberOfLeadingZeros(x, x.length);
+ }
+
+ private static void shiftLeftInto(final int[] result, final int[] x, final int shift) {
+ // Unchecked: result should be initialised with zeroes
+ int limbShift = shift / 32;
+ int bitShift = shift % 32;
+ int nLimbs = Math.min(x.length, result.length - limbShift);
+ if (limbShift >= result.length) return;
+ if (bitShift == 0) {
+ System.arraycopy(x, 0, result, limbShift, nLimbs);
+ return;
+ }
+
+ int j = limbShift;
+ int carry = 0;
+ for (int i = 0; i < nLimbs; ++i, ++j) {
+ result[j] = (x[i] << bitShift) | carry;
+ carry = x[i] >>> (32 - bitShift);
+ }
+ if (carry != 0 && j < result.length) result[j] = carry; // last carry
+ }
+
+ private static void shiftRightInto(final int[] result, final int[] x, final int shift) {
+ int limbShift = shift / 32;
+ int bitShift = shift % 32;
+ int nLimbs = Math.min(x.length - limbShift, result.length);
+
+ if (limbShift >= x.length) return;
+ if (bitShift == 0) {
+ System.arraycopy(x, limbShift, result, 0, nLimbs);
+ return;
+ }
+
+ 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);
+ }
+ }
+
+ private static int[] addWithCarry(final int[] x, final int[] y) {
+ // Step 1: Add with carry
+ int[] a;
+ int[] b;
+ if (x.length < y.length) {
+ a = y;
+ b = x;
+ } else {
+ a = x;
+ b = y;
+ }
+ int maxLen = a.length;
+ int minLen = b.length;
+ int[] sum = new int[maxLen + 1];
+ long carry = 0;
+ for (int i = 0; i < minLen; 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 = minLen; i < maxLen; i++) {
+ sum[i] = a[i] + icarry;
+ icarry = (a[i] != 0 && sum[i] == 0) ? 1 : 0;
+ }
+ sum[maxLen] = icarry;
+ return sum;
+ }
+
+ private static void addMul(final int[] lhs, final int[] a, final int[] b) {
+ // Shortest in outer loop, swap if needed
+ int[] x;
+ int[] y;
+ if (a.length < b.length) {
+ x = b;
+ y = a;
+ } else {
+ x = a;
+ y = b;
+ }
+ // x: widening int -> long
+ long[] xl = new long[x.length];
+ for (int i = 0; i < xl.length; i++) {
+ xl[i] = x[i] & MASK_L;
+ }
+ // Main algo
+ for (int i = 0; i < y.length; i++) {
+ long carry = 0;
+ long yi = y[i] & MASK_L;
+
+ int k = i;
+ for (int j = 0; j < x.length; j++, k++) {
+ long prod = yi * xl[j];
+ long sum = (lhs[k] & MASK_L) + prod + carry;
+ lhs[k] = (int) sum;
+ carry = sum >>> 32;
+ }
+
+ // 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 static int[] knuthRemainder(final int[] dividend, final int[] modulus) {
+ int shift = numberOfLeadingZeros(modulus);
+ int limbShift = shift / 32;
+ int n = modulus.length - limbShift;
+ if (n == 0) return new int[0];
+ if (n == 1) {
+ if (dividend.length == 1)
+ return (new int[] {Integer.remainderUnsigned(dividend[0], modulus[0])});
+ long d = modulus[0] & MASK_L;
+ long rem = 0;
+ // Process from most significant limb downwards
+ for (int i = dividend.length - 1; i >= 0; i--) {
+ long cur = (rem << 32) | (dividend[i] & MASK_L);
+ rem = Long.remainderUnsigned(cur, d);
+ }
+ return (new int[] {(int) rem});
+ }
+ // Normalize
+ int m = dividend.length - n;
+ int bitShift = shift % 32;
+ int[] vLimbs = new int[n];
+ shiftLeftInto(vLimbs, modulus, bitShift);
+ int[] uLimbs = new int[dividend.length + 1];
+ shiftLeftInto(uLimbs, dividend, bitShift);
+
+ // Main division loop
+ long vn1 = vLimbs[n - 1] & MASK_L;
+ long vn2 = vLimbs[n - 2] & MASK_L;
+ 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);
+
+ 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);
+
+ while (qhat == 0x1_0000_0000L || Long.compareUnsigned(qhat * vn2, (rhat << 32) | ujn2) > 0) {
+ qhat--;
+ rhat += vn1;
+ if (rhat >= 0x1_0000_0000L) break;
+ }
+
+ // Multiply-subtract qhat*v from u slice
+ long borrow = 0;
+ for (int i = 0; i < n; i++) {
+ long prod = (vLimbs[i] & MASK_L) * qhat;
+ long sub = (uLimbs[i + j] & MASK_L) - (prod & MASK_L) - borrow;
+ uLimbs[i + j] = (int) sub;
+ borrow = (prod >>> 32) - (sub >> 32);
+ }
+ long sub = (uLimbs[j + n] & MASK_L) - borrow;
+ uLimbs[j + n] = (int) sub;
+
+ if (sub < 0) {
+ // Add back
+ long carry = 0;
+ for (int i = 0; i < n; i++) {
+ long sum = (uLimbs[i + j] & MASK_L) + (vLimbs[i] & MASK_L) + carry;
+ uLimbs[i + j] = (int) sum;
+ carry = sum >>> 32;
+ }
+ uLimbs[j + n] = (int) (uLimbs[j + n] + carry);
+ }
+ }
+ // Unnormalize remainder
+ int[] shifted = new int[n];
+ shiftRightInto(shifted, uLimbs, bitShift);
+ return shifted;
+ }
+ // --------------------------------------------------------------------------
+ // endregion
+}
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 4e21e36bc20..ceb5a6d0ab2 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
@@ -15,12 +15,10 @@
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 java.math.BigInteger;
-import java.util.Arrays;
-
import org.apache.tuweni.bytes.Bytes;
/** The Add mod operation. */
@@ -50,29 +48,22 @@ public Operation.OperationResult executeFixedCostOperation(
* @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()) {
- frame.pushStackItem(Bytes.EMPTY);
+ resultBytes = 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));
+ 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/ModOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ModOperation.java
index e61495e9f5b..0ed5a884d84 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
@@ -15,12 +15,10 @@
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 java.math.BigInteger;
-import java.util.Arrays;
-
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
@@ -53,24 +51,15 @@ public Operation.OperationResult executeFixedCostOperation(
public static OperationResult staticOperation(final MessageFrame frame) {
final Bytes value0 = frame.popStackItem();
final Bytes value1 = frame.popStackItem();
+ Bytes resultBytes;
if (value1.isZero()) {
- frame.pushStackItem(Bytes32.ZERO);
+ resultBytes = (Bytes) 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));
+ 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 589ef9570be..bbdd55c9c09 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
@@ -15,12 +15,10 @@
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 java.math.BigInteger;
-import java.util.Arrays;
-
import org.apache.tuweni.bytes.Bytes;
/** The Mul mod operation. */
@@ -54,25 +52,17 @@ public static OperationResult staticOperation(final MessageFrame frame) {
final Bytes value1 = frame.popStackItem();
final Bytes value2 = frame.popStackItem();
+ Bytes resultBytes;
if (value2.isZero()) {
- frame.pushStackItem(Bytes.EMPTY);
+ resultBytes = 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));
+ 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/SModOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SModOperation.java
index e210aec6069..88bd6eb31fb 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
@@ -15,13 +15,12 @@
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 java.math.BigInteger;
-import java.util.Arrays;
-
import org.apache.tuweni.bytes.Bytes;
+import org.apache.tuweni.bytes.Bytes32;
/** The SMod operation. */
public class SModOperation extends AbstractFixedCostOperation {
@@ -53,32 +52,15 @@ public static OperationResult staticOperation(final MessageFrame frame) {
final Bytes value0 = frame.popStackItem();
final Bytes value1 = frame.popStackItem();
+ Bytes resultBytes;
if (value1.isZero()) {
- frame.pushStackItem(Bytes.EMPTY);
+ resultBytes = (Bytes) Bytes32.ZERO;
} 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));
+ UInt256 b0 = UInt256.fromSignedBytesBE(value0.toArrayUnsafe());
+ UInt256 b1 = UInt256.fromSignedBytesBE(value1.toArrayUnsafe());
+ resultBytes = Bytes.wrap(b0.signedMod(b1).toBytesBE());
}
+ frame.pushStackItem(resultBytes);
return smodSuccess;
}
diff --git a/evm/src/test/java/org/hyperledger/besu/evm/UInt256ParameterisedTest.java b/evm/src/test/java/org/hyperledger/besu/evm/UInt256ParameterisedTest.java
new file mode 100644
index 00000000000..0377ee7c39f
--- /dev/null
+++ b/evm/src/test/java/org/hyperledger/besu/evm/UInt256ParameterisedTest.java
@@ -0,0 +1,455 @@
+/*
+ * 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.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+import java.util.stream.Stream;
+
+import org.apache.tuweni.bytes.Bytes;
+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;
+
+public class UInt256ParameterisedTest {
+
+ // Test constants
+ private static final BigInteger TWO_TO_64 = BigInteger.TWO.pow(64);
+ private static final BigInteger TWO_TO_128 = BigInteger.TWO.pow(128);
+ private static final BigInteger TWO_TO_192 = BigInteger.TWO.pow(192);
+ private static final BigInteger TWO_TO_256 = BigInteger.TWO.pow(256);
+ private static final BigInteger UINT256_MAX = TWO_TO_256.subtract(BigInteger.ONE);
+
+ private static final int RANDOM_TEST_COUNT = 6;
+
+ // region Test Data Providers
+
+ /** Provides unary test cases (single BigInteger values). */
+ static Stream