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..73b86cf4dd1 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SelfBalanceOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SelfBalanceOperationBenchmark.java @@ -30,11 +30,8 @@ import org.hyperledger.besu.evm.operation.SelfBalanceOperation; import org.hyperledger.besu.evm.worldstate.WorldUpdater; -import java.io.IOException; -import java.util.Random; import java.util.concurrent.TimeUnit; -import org.apache.tuweni.bytes.Bytes; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Measurement; @@ -44,6 +41,7 @@ import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; @State(Scope.Thread) @Warmup(iterations = 2, time = 3, timeUnit = TimeUnit.SECONDS) @@ -52,65 +50,41 @@ @BenchmarkMode(Mode.AverageTime) public class SelfBalanceOperationBenchmark { - private static final int NUMBER_ADDRESSES = 20000; - private static final Random RANDOM_GENERATOR = new Random(); private SelfBalanceOperation operation; - private MessageFrame[] frames; - private int executingFrameIndex = 0; - - private MessageFrame createMessageFrame( - final Blockchain blockchain, - final WorldUpdater worldUpdater, - final ExecutionContextTestFixture executionContextTestFixture, - final Address address) { - final BlockHeader blockHeader = new BlockHeaderTestFixture().buildHeader(); - return new MessageFrameTestFixture() - .address(address) - .worldUpdater(worldUpdater) - .blockHeader(blockHeader) - .executionContextTestFixture(executionContextTestFixture) - .blockchain(blockchain) - .build(); - } - - private WorldUpdater createWorldUpdater(final Blockchain blockchain, final Address[] addresses) - throws IOException { - final WorldUpdater worldStateUpdater; - try (WorldStateArchive worldStateArchive = createBonsaiInMemoryWorldStateArchive(blockchain)) { - worldStateUpdater = worldStateArchive.getWorldState().updater(); - } - for (Address address : addresses) { - worldStateUpdater.getOrCreate(address).setBalance(Wei.of(1)); - } - worldStateUpdater.commit(); - return worldStateUpdater; - } + private MessageFrame frame; @Setup public void prepare() throws Exception { operation = new SelfBalanceOperation(mock(GasCalculator.class)); final Blockchain blockchain = mock(Blockchain.class); - final Address[] addresses = new Address[NUMBER_ADDRESSES]; - for (int j = 0; j < addresses.length; j++) { - final byte[] address = new byte[Address.SIZE]; - RANDOM_GENERATOR.nextBytes(address); - addresses[j] = Address.wrap(Bytes.wrap(address)); + final Address address = Address.fromHexString("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + + final WorldUpdater worldUpdater; + try (WorldStateArchive archive = createBonsaiInMemoryWorldStateArchive(blockchain)) { + worldUpdater = archive.getWorldState().updater(); } - frames = new MessageFrame[NUMBER_ADDRESSES]; - final WorldUpdater worldUpdater = createWorldUpdater(blockchain, addresses); + worldUpdater.getOrCreate(address).setBalance(Wei.of(1)); + worldUpdater.commit(); + final ExecutionContextTestFixture executionContextTestFixture = ExecutionContextTestFixture.create(); - for (int i = 0; i < NUMBER_ADDRESSES; i++) { - frames[i] = - createMessageFrame(blockchain, worldUpdater, executionContextTestFixture, addresses[0]); - } + final BlockHeader blockHeader = new BlockHeaderTestFixture().buildHeader(); + frame = + new MessageFrameTestFixture() + .address(address) + .worldUpdater(worldUpdater) + .blockHeader(blockHeader) + .executionContextTestFixture(executionContextTestFixture) + .blockchain(blockchain) + .build(); + + // Pre-warm: force trie path into memory + worldUpdater.get(address); } @Benchmark - public void executeOperation() { - final MessageFrame executingFrame = frames[executingFrameIndex++]; - operation.execute(executingFrame, null); - executingFrame.popStackItem(); - executingFrameIndex = executingFrameIndex % NUMBER_ADDRESSES; + public void executeOperation(final Blackhole blackhole) { + blackhole.consume(operation.execute(frame, null)); + frame.popStackItem(); } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SelfBalanceOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SelfBalanceOperationBenchmarkV2.java new file mode 100644 index 00000000000..beb33076f0c --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/SelfBalanceOperationBenchmarkV2.java @@ -0,0 +1,100 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createBonsaiInMemoryWorldStateArchive; +import static org.mockito.Mockito.mock; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.frame.BlockValues; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.v2.operation.SelfBalanceOperationV2; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.concurrent.TimeUnit; + +import org.apache.tuweni.bytes.Bytes32; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +@State(Scope.Thread) +@Warmup(iterations = 2, time = 3, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(value = TimeUnit.NANOSECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@BenchmarkMode(Mode.AverageTime) +public class SelfBalanceOperationBenchmarkV2 { + + private SelfBalanceOperationV2 operation; + private MessageFrame frame; + + @Setup + public void setup() throws Exception { + operation = new SelfBalanceOperationV2(mock(GasCalculator.class)); + final Blockchain blockchain = mock(Blockchain.class); + final Address address = Address.fromHexString("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + + final WorldUpdater worldUpdater; + try (WorldStateArchive archive = createBonsaiInMemoryWorldStateArchive(blockchain)) { + worldUpdater = archive.getWorldState().updater(); + } + worldUpdater.getOrCreate(address).setBalance(Wei.of(1)); + worldUpdater.commit(); + + frame = + MessageFrame.builder() + .enableEvmV2(true) + .worldUpdater(worldUpdater) + .originator(Address.ZERO) + .gasPrice(Wei.ONE) + .blobGasPrice(Wei.ONE) + .blockValues(mock(BlockValues.class)) + .miningBeneficiary(Address.ZERO) + .blockHashLookup((__, ___) -> Hash.ZERO) + .type(MessageFrame.Type.MESSAGE_CALL) + .initialGas(Long.MAX_VALUE) + .address(address) + .contract(address) + .inputData(Bytes32.ZERO) + .sender(Address.ZERO) + .value(Wei.ZERO) + .apparentValue(Wei.ZERO) + .code(Code.EMPTY_CODE) + .completer(__ -> {}) + .build(); + + // Pre-warm: force trie path into memory + worldUpdater.get(address); + } + + @Benchmark + public void executeOperation(final Blackhole blackhole) { + blackhole.consume(operation.execute(frame, null)); + frame.setTopV2(frame.stackTopV2() - 1); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java index eb0edd52e33..42084f5db6b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java @@ -293,8 +293,8 @@ public void writeTo(final RLPOutput out) { out.writeBytes(mixHashOrPrevRandao); out.writeLong(nonce); do { - if (baseFee == null) break; - out.writeUInt256Scalar(baseFee); + if (maybeBaseFee.isEmpty()) break; + out.writeUInt256Scalar(maybeBaseFee.get()); if (withdrawalsRoot == null) break; out.writeBytes(withdrawalsRoot.getBytes()); @@ -488,7 +488,7 @@ public String toString() { sb.append("gasUsed=").append(gasUsed).append(", "); sb.append("timestamp=").append(timestamp).append(", "); sb.append("extraData=").append(extraData).append(", "); - sb.append("baseFee=").append(baseFee).append(", "); + sb.append("baseFee=").append(maybeBaseFee.orElse(null)).append(", "); sb.append("mixHashOrPrevRandao=").append(mixHashOrPrevRandao).append(", "); sb.append("nonce=").append(nonce).append(", "); if (withdrawalsRoot != null) { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java index e06faac0a72..4293fe6ce88 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java @@ -41,7 +41,8 @@ public class ProcessableBlockHeader // The block creation timestamp (seconds since the unix epoch) protected final long timestamp; // base fee is included for post EIP-1559 blocks - protected final Wei baseFee; + // store optional base fee to compute it at build time + protected final Optional maybeBaseFee; // prevRandao is included for post-merge blocks protected final Bytes32 mixHashOrPrevRandao; // parentBeaconBlockRoot is included for Cancun @@ -66,7 +67,7 @@ protected ProcessableBlockHeader( this.number = number; this.gasLimit = gasLimit; this.timestamp = timestamp; - this.baseFee = baseFee; + this.maybeBaseFee = Optional.ofNullable(baseFee); this.mixHashOrPrevRandao = mixHashOrPrevRandao; this.parentBeaconBlockRoot = parentBeaconBlockRoot; this.slotNumber = slotNumber; @@ -149,7 +150,7 @@ public long getTimestamp() { */ @Override public Optional getBaseFee() { - return Optional.ofNullable(baseFee); + return maybeBaseFee; } /** @@ -216,7 +217,9 @@ public String toString() { sb.append("difficulty=").append(difficulty).append(", "); sb.append("gasLimit=").append(gasLimit).append(", "); sb.append("timestamp=").append(timestamp).append(", "); - sb.append("baseFee=").append(baseFee).append(", "); + if (maybeBaseFee.isPresent()) { + sb.append("baseFee=").append(maybeBaseFee.get()).append(", "); + } sb.append("mixHashOrPrevRandao=").append(mixHashOrPrevRandao).append(", "); if (parentBeaconBlockRoot != null) { sb.append("parentBeaconBlockRoot=").append(parentBeaconBlockRoot).append(", "); diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestEnv.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestEnv.java index 2ed8174fa65..4d094ac114a 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestEnv.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestEnv.java @@ -207,7 +207,8 @@ public BlockHeader parentBlockHeader(final ProtocolSpec protocolSpec) { if (protocolSpec.getWithdrawalsProcessor().isPresent()) { builder.withdrawalsRoot(BodyValidation.withdrawalsRoot(withdrawals)); } - if ((baseFee == null || baseFee.isEmpty()) && protocolSpec.getFeeMarket().implementsBaseFee()) { + if ((maybeBaseFee.isEmpty() || maybeBaseFee.get().isEmpty()) + && protocolSpec.getFeeMarket().implementsBaseFee()) { builder.baseFee( ((BaseFeeMarket) protocolSpec.getFeeMarket()) .computeBaseFee( 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 954f4e1e955..94216289733 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java @@ -212,6 +212,7 @@ public enum Type { // significant) private final long[] stackDataV2; private int stackTopV2; + private final int stackMaxSizeV2; private Bytes output = Bytes.EMPTY; private Bytes returnData = Bytes.EMPTY; private Code createdCode = null; @@ -282,6 +283,7 @@ private MessageFrame( this.stack = new OperandStack(txValues.maxStackSize()); this.stackDataV2 = enableEvmV2 ? new long[txValues.maxStackSize() * 4] : null; this.stackTopV2 = 0; + this.stackMaxSizeV2 = txValues.maxStackSize(); this.pc = 0; this.recipient = recipient; this.contract = contract; @@ -518,10 +520,20 @@ public void setTopV2(final int newTop) { * @param n the number of items required * @return true if the stack contains at least n items */ - public boolean stackHasItems(final int n) { + public boolean stackHasItemsV2(final int n) { return stackTopV2 >= n; } + /** + * Returns true if the stack has space for {@code n} more items. + * + * @param n the number of additional items + * @return true if the stack can accommodate n more items + */ + public boolean stackHasSpaceV2(final int n) { + return stackTopV2 + n <= stackMaxSizeV2; + } + // --------------------------------------------------------------------------- // endregion diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java index 0480e77b067..3b42ccdbe9b 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractFixedCostOperationV2.java @@ -18,16 +18,9 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.OverflowException; -import org.hyperledger.besu.evm.internal.UnderflowException; -import org.hyperledger.besu.evm.operation.AbstractOperation; -/** The Abstract fixed cost operation. */ -abstract class AbstractFixedCostOperationV2 extends AbstractOperation { - - /** Shared underflow response for static operation methods. */ - static final OperationResult UNDERFLOW_RESPONSE = - new OperationResult(0L, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); +/** The Abstract fixed cost operation for V2 (long[] stack) operations. */ +abstract class AbstractFixedCostOperationV2 extends AbstractOperationV2 { /** The Success response. */ protected final OperationResult successResponse; @@ -35,9 +28,6 @@ abstract class AbstractFixedCostOperationV2 extends AbstractOperation { /** The Out of gas response. */ protected final OperationResult outOfGasResponse; - private final OperationResult underflowResponse; - private final OperationResult overflowResponse; - /** The Gas cost. */ protected final long gasCost; @@ -62,24 +52,14 @@ protected AbstractFixedCostOperationV2( gasCost = fixedCost; successResponse = new OperationResult(gasCost, null); outOfGasResponse = new OperationResult(gasCost, ExceptionalHaltReason.INSUFFICIENT_GAS); - underflowResponse = - new OperationResult(gasCost, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); - overflowResponse = new OperationResult(gasCost, ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); } @Override public final OperationResult execute(final MessageFrame frame, final EVM evm) { - try { - if (frame.getRemainingGas() < gasCost) { - return outOfGasResponse; - } else { - return executeFixedCostOperation(frame, evm); - } - } catch (final UnderflowException ufe) { - return underflowResponse; - } catch (final OverflowException ofe) { - return overflowResponse; + if (frame.getRemainingGas() < gasCost) { + return outOfGasResponse; } + return executeFixedCostOperation(frame, evm); } /** diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractOperationV2.java new file mode 100644 index 00000000000..1eb06678128 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AbstractOperationV2.java @@ -0,0 +1,178 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt256; + +/** + * All V2 {@link Operation} implementations should inherit from this class to get the setting of + * some members for free. + */ +public abstract class AbstractOperationV2 implements Operation { + static final Bytes BYTES_ONE = Bytes.of(1); + + /** Shared underflow response (zero gas cost). */ + public static final Operation.OperationResult UNDERFLOW_RESPONSE = + new Operation.OperationResult(0L, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + + /** Shared overflow response (zero gas cost). */ + public static final Operation.OperationResult OVERFLOW_RESPONSE = + new Operation.OperationResult(0L, ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); + + private final int opcode; + private final String name; + private final int stackItemsConsumed; + private final int stackItemsProduced; + private final GasCalculator gasCalculator; + + /** + * Instantiates a new Abstract operation. + * + * @param opcode the opcode + * @param name the name + * @param stackItemsConsumed the stack items consumed + * @param stackItemsProduced the stack items produced + * @param gasCalculator the gas calculator + */ + protected AbstractOperationV2( + final int opcode, + final String name, + final int stackItemsConsumed, + final int stackItemsProduced, + final GasCalculator gasCalculator) { + this.opcode = opcode & 0xff; + this.name = name; + this.stackItemsConsumed = stackItemsConsumed; + this.stackItemsProduced = stackItemsProduced; + this.gasCalculator = gasCalculator; + } + + /** + * Gets Gas calculator. + * + * @return the gas calculator + */ + protected GasCalculator gasCalculator() { + return gasCalculator; + } + + @Override + public int getOpcode() { + return opcode; + } + + @Override + public String getName() { + return name; + } + + @Override + public int getStackItemsConsumed() { + return stackItemsConsumed; + } + + @Override + public int getStackItemsProduced() { + return stackItemsProduced; + } + + /** + * Retrieves the {@link Account} at the specified address. + * + *

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

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

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

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

If an EIP-7928 Block Access List is active, the slot access is recorded for the account. + * + * @param account the account whose storage is being accessed + * @param slotKey the key of the storage slot + * @param frame the current message execution frame + * @return the value stored at the specified key + */ + protected UInt256 getStorageValue( + final Account account, final UInt256 slotKey, final MessageFrame frame) { + final UInt256 slotValue = account.getStorageValue(slotKey); + frame + .getEip7928AccessList() + .ifPresent(t -> t.addSlotAccessForAccount(account.getAddress(), slotKey)); + return slotValue; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddOperationV2.java index c544c830610..b0a9ea1de26 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/AddOperationV2.java @@ -56,7 +56,7 @@ public Operation.OperationResult executeFixedCostOperation( * @return the operation result */ public static Operation.OperationResult staticOperation(final MessageFrame frame) { - if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + if (!frame.stackHasItemsV2(2)) return UNDERFLOW_RESPONSE; long[] stack = frame.stackDataV2(); int top = frame.stackTopV2(); final int aOffset = (top - 1) << 2; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java new file mode 100644 index 00000000000..3834a4e3ccc --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2.java @@ -0,0 +1,74 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import static org.hyperledger.besu.evm.v2.operation.StackUtil.pushWei; +import static org.hyperledger.besu.evm.v2.operation.StackUtil.pushZero; +import static org.hyperledger.besu.evm.v2.operation.StackUtil.readAddressAt; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +/** The Balance operation. */ +public class BalanceOperationV2 extends AbstractOperationV2 { + + /** + * Instantiates a new Balance operation. + * + * @param gasCalculator the gas calculator + */ + public BalanceOperationV2(final GasCalculator gasCalculator) { + super(0x31, "BALANCE", 1, 1, gasCalculator); + } + + /** + * Gets Balance operation Gas Cost plus warm storage read cost or cold account access cost. + * + * @param accountIsWarm true to add warm storage read cost, false to add cold account access cost + * @return the long + */ + protected long cost(final boolean accountIsWarm) { + return gasCalculator().getBalanceOperationGasCost() + + (accountIsWarm + ? gasCalculator().getWarmStorageReadCost() + : gasCalculator().getColdAccountAccessCost()); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + if (!frame.stackHasItemsV2(1)) return UNDERFLOW_RESPONSE; + final long[] stack = frame.stackDataV2(); + final int top = frame.stackTopV2(); + final Address address = readAddressAt(stack, top, 0); + final boolean accountIsWarm = + frame.warmUpAddress(address) || gasCalculator().isPrecompile(address); + final long cost = cost(accountIsWarm); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + final Account account = getAccount(address, frame); + if (account == null) { + pushZero(stack, top - 1); + } else { + pushWei(account.getBalance(), stack, top - 1); + } + // no setTopV2 needed -- pop 1 + push 1 = net 0 + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java new file mode 100644 index 00000000000..58821149a32 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2.java @@ -0,0 +1,54 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import static org.hyperledger.besu.evm.v2.operation.StackUtil.pushWei; + +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; + +import java.util.Optional; + +/** The Base fee operation. */ +public class BaseFeeOperationV2 extends AbstractFixedCostOperationV2 { + + /** + * Instantiates a new Base fee operation. + * + * @param gasCalculator the gas calculator + */ + public BaseFeeOperationV2(final GasCalculator gasCalculator) { + super(0x48, "BASEFEE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + final Optional maybeBaseFee = frame.getBlockValues().getBaseFee(); + if (maybeBaseFee.isEmpty()) { + return new Operation.OperationResult(gasCost, ExceptionalHaltReason.INVALID_OPERATION); + } + if (!frame.stackHasSpaceV2(1)) return OVERFLOW_RESPONSE; + final long[] stack = frame.stackDataV2(); + final int top = frame.stackTopV2(); + pushWei(maybeBaseFee.get(), stack, top); + frame.setTopV2(top + 1); + return successResponse; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java new file mode 100644 index 00000000000..69b60f7ba84 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2.java @@ -0,0 +1,44 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import static org.hyperledger.besu.evm.v2.operation.StackUtil.pushWei; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +/** The Blob Base fee operation. */ +public class BlobBaseFeeOperationV2 extends AbstractFixedCostOperationV2 { + + /** + * Instantiates a new Blob Base fee operation. + * + * @param gasCalculator the gas calculator + */ + public BlobBaseFeeOperationV2(final GasCalculator gasCalculator) { + super(0x4a, "BLOBBASEFEE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { + if (!frame.stackHasSpaceV2(1)) return OVERFLOW_RESPONSE; + final long[] stack = frame.stackDataV2(); + final int top = frame.stackTopV2(); + pushWei(frame.getBlobGasPrice(), stack, top); + frame.setTopV2(top + 1); + return successResponse; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java new file mode 100644 index 00000000000..56bbb1cdc1f --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2.java @@ -0,0 +1,46 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import static org.hyperledger.besu.evm.v2.operation.StackUtil.pushWei; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; + +/** The Call value operation. */ +public class CallValueOperationV2 extends AbstractFixedCostOperationV2 { + + /** + * Instantiates a new Call value operation. + * + * @param gasCalculator the gas calculator + */ + public CallValueOperationV2(final GasCalculator gasCalculator) { + super(0x34, "CALLVALUE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + if (!frame.stackHasSpaceV2(1)) return OVERFLOW_RESPONSE; + final long[] stack = frame.stackDataV2(); + final int top = frame.stackTopV2(); + pushWei(frame.getApparentValue(), stack, top); + frame.setTopV2(top + 1); + return successResponse; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java new file mode 100644 index 00000000000..278ff41ff34 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2.java @@ -0,0 +1,46 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import static org.hyperledger.besu.evm.v2.operation.StackUtil.pushWei; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; + +/** The Gas price operation. */ +public class GasPriceOperationV2 extends AbstractFixedCostOperationV2 { + + /** + * Instantiates a new Gas price operation. + * + * @param gasCalculator the gas calculator + */ + public GasPriceOperationV2(final GasCalculator gasCalculator) { + super(0x3A, "GASPRICE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + if (!frame.stackHasSpaceV2(1)) return OVERFLOW_RESPONSE; + final long[] stack = frame.stackDataV2(); + final int top = frame.stackTopV2(); + pushWei(frame.getGasPrice(), stack, top); + frame.setTopV2(top + 1); + return successResponse; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2.java index 970c2c02698..b7f090e5ec1 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2.java @@ -49,7 +49,7 @@ public Operation.OperationResult executeFixedCostOperation( * @return the operation result */ public static OperationResult staticOperation(final MessageFrame frame) { - if (!frame.stackHasItems(3)) return UNDERFLOW_RESPONSE; + if (!frame.stackHasItemsV2(3)) return UNDERFLOW_RESPONSE; int top = frame.stackTopV2(); final int aOffset = (--top) << 2; final int bOffset = (--top) << 2; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2.java index 986a2d0655f..aa629ffa509 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2.java @@ -48,7 +48,7 @@ public Operation.OperationResult executeFixedCostOperation( * @return the operation result */ public static OperationResult staticOperation(final MessageFrame frame) { - if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + if (!frame.stackHasItemsV2(2)) return UNDERFLOW_RESPONSE; long[] stack = frame.stackDataV2(); int top = frame.stackTopV2(); final int shiftOffset = (--top) << 2; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java new file mode 100644 index 00000000000..d505a464bac --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2.java @@ -0,0 +1,53 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import static org.hyperledger.besu.evm.v2.operation.StackUtil.pushWei; +import static org.hyperledger.besu.evm.v2.operation.StackUtil.pushZero; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; + +/** The Self balance operation. */ +public class SelfBalanceOperationV2 extends AbstractFixedCostOperationV2 { + + /** + * Instantiates a new Self balance operation. + * + * @param gasCalculator the gas calculator + */ + public SelfBalanceOperationV2(final GasCalculator gasCalculator) { + super(0x47, "SELFBALANCE", 0, 1, gasCalculator, gasCalculator.getLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + if (!frame.stackHasSpaceV2(1)) return OVERFLOW_RESPONSE; + final long[] s = frame.stackDataV2(); + final int top = frame.stackTopV2(); + final Account account = getAccount(frame.getRecipientAddress(), frame); + if (account == null) { + pushZero(s, top); + } else { + pushWei(account.getBalance(), s, top); + } + frame.setTopV2(top + 1); + return successResponse; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ShlOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ShlOperationV2.java index 62b56aef7dd..2b17264cc42 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ShlOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ShlOperationV2.java @@ -48,7 +48,7 @@ public Operation.OperationResult executeFixedCostOperation( * @return the operation result */ public static OperationResult staticOperation(final MessageFrame frame) { - if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + if (!frame.stackHasItemsV2(2)) return UNDERFLOW_RESPONSE; long[] stack = frame.stackDataV2(); int top = frame.stackTopV2(); final int shiftOffset = (--top) << 2; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ShrOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ShrOperationV2.java index f8eb164b16f..7f7b9c000fd 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ShrOperationV2.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/ShrOperationV2.java @@ -48,7 +48,7 @@ public Operation.OperationResult executeFixedCostOperation( * @return the operation result */ public static OperationResult staticOperation(final MessageFrame frame) { - if (!frame.stackHasItems(2)) return UNDERFLOW_RESPONSE; + if (!frame.stackHasItemsV2(2)) return UNDERFLOW_RESPONSE; long[] stack = frame.stackDataV2(); int top = frame.stackTopV2(); final int shiftOffset = (--top) << 2; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/StackUtil.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/StackUtil.java new file mode 100644 index 00000000000..d14d64495d6 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/StackUtil.java @@ -0,0 +1,90 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.nio.ByteOrder; + +import org.apache.tuweni.bytes.Bytes; + +/** + * Static utility for reading/writing typed values on the flat {@code long[]} V2 operand stack. Each + * 256-bit word occupies 4 consecutive longs in big-endian limb order: {@code [u3, u2, u1, u0]} + * where u3 is the most-significant limb. This class has a default modifier (package-private) + * because it shouldn't be used outside the EVM operations + */ +final class StackUtil { + + private static final VarHandle LONG_BE = + MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN); + private static final VarHandle INT_BE = + MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.BIG_ENDIAN); + + private StackUtil() {} + + /** + * Writes a zero-valued 256-bit word at the given stack slot. + * + * @param stack the flat limb array (4 longs per 256-bit word) + * @param top the slot index to write to + */ + static void pushZero(final long[] stack, final int top) { + final int offset = top << 2; + stack[offset] = 0; + stack[offset + 1] = 0; + stack[offset + 2] = 0; + stack[offset + 3] = 0; + } + + /** + * Writes a {@link Wei} value as four big-endian limbs at the given stack slot. + * + * @param wei the Wei value to write + * @param stack the flat limb array + * @param top the slot index to write to + */ + static void pushWei(final Wei wei, final long[] stack, final int top) { + // TODO EVMv2 store this representation at Wei object construction time when switching from v2 + // to v1 + int offset = top << 2; + final byte[] b = wei.toArrayUnsafe(); + stack[offset] = (long) LONG_BE.get(b, 0); + stack[offset + 1] = (long) LONG_BE.get(b, 8); + stack[offset + 2] = (long) LONG_BE.get(b, 16); + stack[offset + 3] = (long) LONG_BE.get(b, 24); + } + + /** + * Extracts a 160-bit {@link Address} from a 256-bit stack word at the given depth below the top + * of stack. + * + * @param stack the flat limb array + * @param top current stack-top (item count) + * @param depth 0 for the topmost item, 1 for the item below, etc. + * @return the address formed from the lower 160 bits of the stack word + */ + static Address readAddressAt(final long[] stack, final int top, final int depth) { + final int off = (top - 1 - depth) << 2; + byte[] bytes = new byte[20]; + INT_BE.set(bytes, 0, (int) stack[off + 1]); + LONG_BE.set(bytes, 4, stack[off + 2]); + LONG_BE.set(bytes, 12, stack[off + 3]); + return Address.wrap(Bytes.wrap(bytes)); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2Test.java new file mode 100644 index 00000000000..57efd63d100 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BalanceOperationV2Test.java @@ -0,0 +1,143 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2.getV2StackItem; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.BerlinGasCalculator; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation.OperationResult; +import org.hyperledger.besu.evm.toy.ToyWorld; +import org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2; + +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.Test; + +class BalanceOperationV2Test { + + private static final Address TARGET = Address.fromHexString("0xABCDEF0123456789ABCD"); + private final GasCalculator gasCalculator = new BerlinGasCalculator(); + private final BalanceOperationV2 operation = new BalanceOperationV2(gasCalculator); + + @Test + void shouldPushBalanceToStack() { + final ToyWorld world = new ToyWorld(); + world.createAccount(TARGET, 0, Wei.of(99999L)); + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .worldUpdater(world) + .pushStackItem(Bytes32.leftPad(TARGET.getBytes())) + .build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)).isEqualTo(new UInt256(0, 0, 0, 99999L)); + } + + @Test + void shouldPushZeroForNonExistentAccount() { + final ToyWorld world = new ToyWorld(); + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .worldUpdater(world) + .pushStackItem(Bytes32.leftPad(TARGET.getBytes())) + .build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)).isEqualTo(UInt256.ZERO); + } + + @Test + void shouldPushLargeBalance() { + final Wei largeBalance = + Wei.fromHexString("0x00000000000000010000000000000002000000000000000300000000000000FF"); + final ToyWorld world = new ToyWorld(); + world.createAccount(TARGET, 0, largeBalance); + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .worldUpdater(world) + .pushStackItem(Bytes32.leftPad(TARGET.getBytes())) + .build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)) + .isEqualTo( + new UInt256( + 0x0000000000000001L, + 0x0000000000000002L, + 0x0000000000000003L, + 0x00000000000000FFL)); + } + + @Test + void shouldChargeColdAccessCost() { + final ToyWorld world = new ToyWorld(); + world.createAccount(TARGET, 0, Wei.of(1L)); + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .worldUpdater(world) + .pushStackItem(Bytes32.leftPad(TARGET.getBytes())) + .build(); + final OperationResult result = operation.execute(frame, null); + final long expectedCost = + gasCalculator.getBalanceOperationGasCost() + gasCalculator.getColdAccountAccessCost(); + assertThat(result.getGasCost()).isEqualTo(expectedCost); + assertThat(result.getHaltReason()).isNull(); + } + + @Test + void shouldChargeWarmAccessCost() { + final ToyWorld world = new ToyWorld(); + world.createAccount(TARGET, 0, Wei.of(1L)); + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .worldUpdater(world) + .pushStackItem(Bytes32.leftPad(TARGET.getBytes())) + .build(); + frame.warmUpAddress(TARGET); + final OperationResult result = operation.execute(frame, null); + final long expectedCost = + gasCalculator.getBalanceOperationGasCost() + gasCalculator.getWarmStorageReadCost(); + assertThat(result.getGasCost()).isEqualTo(expectedCost); + assertThat(result.getHaltReason()).isNull(); + } + + @Test + void shouldHaltOnInsufficientGas() { + final ToyWorld world = new ToyWorld(); + world.createAccount(TARGET, 0, Wei.of(1L)); + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .initialGas(1) + .worldUpdater(world) + .pushStackItem(Bytes32.leftPad(TARGET.getBytes())) + .build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + @Test + void shouldHaltOnStackUnderflow() { + final ToyWorld world = new ToyWorld(); + final MessageFrame frame = new TestMessageFrameBuilderV2().worldUpdater(world).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2Test.java new file mode 100644 index 00000000000..93bc64c9a45 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BaseFeeOperationV2Test.java @@ -0,0 +1,116 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2.getV2StackItem; + +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.BerlinGasCalculator; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation.OperationResult; +import org.hyperledger.besu.evm.testutils.FakeBlockValues; +import org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; + +class BaseFeeOperationV2Test { + + private final GasCalculator gasCalculator = new BerlinGasCalculator(); + private final BaseFeeOperationV2 operation = new BaseFeeOperationV2(gasCalculator); + + @Test + void shouldReturnGasCost() { + final MessageFrame frame = createFrame(100, Optional.of(Wei.of(5L))); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getGasCost()).isEqualTo(gasCalculator.getBaseTierGasCost()); + assertThat(result.getHaltReason()).isNull(); + } + + @Test + void shouldWriteBaseFeeToStack() { + final MessageFrame frame = createFrame(100, Optional.of(Wei.of(5L))); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)).isEqualTo(new UInt256(0, 0, 0, 5L)); + } + + @Test + void shouldWriteLargeBaseFeeToStack() { + final Wei largeFee = + Wei.fromHexString("0x00000000000000010000000000000002000000000000000300000000000000FF"); + final MessageFrame frame = createFrame(100, Optional.of(largeFee)); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + final UInt256 stackVal = getV2StackItem(frame, 0); + assertThat(stackVal) + .isEqualTo( + new UInt256( + 0x0000000000000001L, + 0x0000000000000002L, + 0x0000000000000003L, + 0x00000000000000FFL)); + } + + @Test + void shouldHaltIfNoBaseFeeInBlockHeader() { + final MessageFrame frame = createFrame(100, Optional.empty()); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INVALID_OPERATION); + } + + @Test + void shouldTriggerIvalidOperationEvenStackOverflow() { + final MessageFrame frame = createFrame(100, Optional.empty()); + frame.setTopV2(MessageFrame.DEFAULT_MAX_STACK_SIZE); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INVALID_OPERATION); + } + + @Test + void shouldHaltOnInsufficientGas() { + final MessageFrame frame = createFrame(1, Optional.of(Wei.of(5L))); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + @Test + void shouldHaltOnInsufficientGasEvenStackOverflow() { + final MessageFrame frame = createFrame(1, Optional.of(Wei.of(5L))); + frame.setTopV2(MessageFrame.DEFAULT_MAX_STACK_SIZE); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + @Test + void shouldHaltOnStackOverflow() { + final MessageFrame frame = createFrame(100, Optional.of(Wei.of(5L))); + frame.setTopV2(MessageFrame.DEFAULT_MAX_STACK_SIZE); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); + } + + private MessageFrame createFrame(final long initialGas, final Optional baseFee) { + return new TestMessageFrameBuilderV2() + .initialGas(initialGas) + .blockValues(new FakeBlockValues(baseFee)) + .build(); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2Test.java new file mode 100644 index 00000000000..2245d7cb8b9 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/BlobBaseFeeOperationV2Test.java @@ -0,0 +1,90 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2.getV2StackItem; + +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.BerlinGasCalculator; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation.OperationResult; +import org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2; + +import org.junit.jupiter.api.Test; + +class BlobBaseFeeOperationV2Test { + + private final GasCalculator gasCalculator = new BerlinGasCalculator(); + private final BlobBaseFeeOperationV2 operation = new BlobBaseFeeOperationV2(gasCalculator); + + @Test + void shouldPushBlobGasPriceToStack() { + final MessageFrame frame = new TestMessageFrameBuilderV2().blobGasPrice(Wei.of(42L)).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)).isEqualTo(new UInt256(0, 0, 0, 42L)); + } + + @Test + void shouldPushZeroBlobGasPrice() { + final MessageFrame frame = new TestMessageFrameBuilderV2().blobGasPrice(Wei.ZERO).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)).isEqualTo(UInt256.ZERO); + } + + @Test + void shouldPushLargeBlobGasPrice() { + final Wei largePrice = + Wei.fromHexString("0x00000000000000010000000000000002000000000000000300000000000000FF"); + final MessageFrame frame = new TestMessageFrameBuilderV2().blobGasPrice(largePrice).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)) + .isEqualTo( + new UInt256( + 0x0000000000000001L, + 0x0000000000000002L, + 0x0000000000000003L, + 0x00000000000000FFL)); + } + + @Test + void shouldReturnCorrectGasCost() { + final MessageFrame frame = new TestMessageFrameBuilderV2().blobGasPrice(Wei.of(1L)).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getGasCost()).isEqualTo(gasCalculator.getBaseTierGasCost()); + } + + @Test + void shouldHaltOnInsufficientGas() { + final MessageFrame frame = + new TestMessageFrameBuilderV2().initialGas(1).blobGasPrice(Wei.of(1L)).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + @Test + void shouldHaltOnStackOverflow() { + final MessageFrame frame = new TestMessageFrameBuilderV2().blobGasPrice(Wei.of(1L)).build(); + frame.setTopV2(MessageFrame.DEFAULT_MAX_STACK_SIZE); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2Test.java new file mode 100644 index 00000000000..f819541f435 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/CallValueOperationV2Test.java @@ -0,0 +1,90 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2.getV2StackItem; + +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.BerlinGasCalculator; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation.OperationResult; +import org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2; + +import org.junit.jupiter.api.Test; + +class CallValueOperationV2Test { + + private final GasCalculator gasCalculator = new BerlinGasCalculator(); + private final CallValueOperationV2 operation = new CallValueOperationV2(gasCalculator); + + @Test + void shouldPushCallValueToStack() { + final MessageFrame frame = new TestMessageFrameBuilderV2().value(Wei.of(1_000L)).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)).isEqualTo(new UInt256(0, 0, 0, 1_000L)); + } + + @Test + void shouldPushZeroCallValue() { + final MessageFrame frame = new TestMessageFrameBuilderV2().value(Wei.ZERO).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)).isEqualTo(UInt256.ZERO); + } + + @Test + void shouldPushLargeCallValue() { + final Wei largeValue = + Wei.fromHexString("0x00000000000000010000000000000002000000000000000300000000000000FF"); + final MessageFrame frame = new TestMessageFrameBuilderV2().value(largeValue).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)) + .isEqualTo( + new UInt256( + 0x0000000000000001L, + 0x0000000000000002L, + 0x0000000000000003L, + 0x00000000000000FFL)); + } + + @Test + void shouldReturnCorrectGasCost() { + final MessageFrame frame = new TestMessageFrameBuilderV2().value(Wei.of(1L)).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getGasCost()).isEqualTo(gasCalculator.getBaseTierGasCost()); + } + + @Test + void shouldHaltOnInsufficientGas() { + final MessageFrame frame = + new TestMessageFrameBuilderV2().initialGas(0).value(Wei.of(1L)).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + @Test + void shouldHaltOnStackOverflow() { + final MessageFrame frame = new TestMessageFrameBuilderV2().value(Wei.of(1L)).build(); + frame.setTopV2(MessageFrame.DEFAULT_MAX_STACK_SIZE); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2Test.java new file mode 100644 index 00000000000..c95b5a929bb --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/GasPriceOperationV2Test.java @@ -0,0 +1,75 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2.getV2StackItem; + +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.BerlinGasCalculator; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation.OperationResult; +import org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2; + +import org.junit.jupiter.api.Test; + +class GasPriceOperationV2Test { + + private final GasCalculator gasCalculator = new BerlinGasCalculator(); + private final GasPriceOperationV2 operation = new GasPriceOperationV2(gasCalculator); + + @Test + void shouldPushGasPriceToStack() { + final MessageFrame frame = + new TestMessageFrameBuilderV2().gasPrice(Wei.of(20_000_000_000L)).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)).isEqualTo(new UInt256(0, 0, 0, 20_000_000_000L)); + } + + @Test + void shouldPushZeroGasPrice() { + final MessageFrame frame = new TestMessageFrameBuilderV2().gasPrice(Wei.ZERO).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)).isEqualTo(UInt256.ZERO); + } + + @Test + void shouldReturnCorrectGasCost() { + final MessageFrame frame = new TestMessageFrameBuilderV2().gasPrice(Wei.of(1L)).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getGasCost()).isEqualTo(gasCalculator.getBaseTierGasCost()); + } + + @Test + void shouldHaltOnInsufficientGas() { + final MessageFrame frame = + new TestMessageFrameBuilderV2().initialGas(1L).gasPrice(Wei.of(10L)).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + @Test + void shouldHaltOnStackOverflow() { + final MessageFrame frame = new TestMessageFrameBuilderV2().gasPrice(Wei.of(1L)).build(); + frame.setTopV2(MessageFrame.DEFAULT_MAX_STACK_SIZE); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2Test.java index 0b0f801d2fb..31af52c4f65 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2Test.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/SarOperationV2Test.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.evm.v2.operation; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2.getV2StackItem; import org.hyperledger.besu.evm.UInt256; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -173,16 +174,10 @@ void shiftOperation(final String number, final String shift, final String expect .build(); operation.execute(frame, null); UInt256 expected = UInt256.fromBytesBE(Bytes32.fromHexString(expectedResult).toArrayUnsafe()); - assertThat(getStackItem(frame, 0)).isEqualTo(expected); + assertThat(getV2StackItem(frame, 0)).isEqualTo(expected); assertThat(frame.stackTopV2()).isEqualTo(1); } - private static UInt256 getStackItem(final MessageFrame frame, final int offset) { - final long[] s = frame.stackDataV2(); - final int idx = (frame.stackTopV2() - 1 - offset) << 2; - return new UInt256(s[idx], s[idx + 1], s[idx + 2], s[idx + 3]); - } - @Test void stackTopUpdated() { final MessageFrame frame = @@ -199,7 +194,7 @@ void stackTopUpdated() { .build(); operation.execute(frame, null); assertThat(frame.stackTopV2()).isEqualTo(4); - assertThat(getStackItem(frame, 0)) + assertThat(getV2StackItem(frame, 0)) .isEqualTo( UInt256.fromBytesBE( Bytes32.fromHexString( diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2Test.java new file mode 100644 index 00000000000..4680e730b75 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/SelfBalanceOperationV2Test.java @@ -0,0 +1,113 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.v2.operation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2.getV2StackItem; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.BerlinGasCalculator; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation.OperationResult; +import org.hyperledger.besu.evm.toy.ToyWorld; +import org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2; + +import org.junit.jupiter.api.Test; + +class SelfBalanceOperationV2Test { + + private static final Address RECIPIENT = Address.fromHexString("0xdeadbeef"); + private final GasCalculator gasCalculator = new BerlinGasCalculator(); + private final SelfBalanceOperationV2 operation = new SelfBalanceOperationV2(gasCalculator); + + @Test + void shouldPushSelfBalanceToStack() { + final ToyWorld world = new ToyWorld(); + world.createAccount(RECIPIENT, 0, Wei.of(12345L)); + final MessageFrame frame = + new TestMessageFrameBuilderV2().worldUpdater(world).address(RECIPIENT).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)).isEqualTo(new UInt256(0, 0, 0, 12345L)); + } + + @Test + void shouldPushZeroForNullAccount() { + final ToyWorld world = new ToyWorld(); + final MessageFrame frame = + new TestMessageFrameBuilderV2().worldUpdater(world).address(RECIPIENT).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)).isEqualTo(UInt256.ZERO); + } + + @Test + void shouldPushLargeBalance() { + final Wei largeBalance = + Wei.fromHexString("0x00000000000000010000000000000002000000000000000300000000000000FF"); + final ToyWorld world = new ToyWorld(); + world.createAccount(RECIPIENT, 0, largeBalance); + final MessageFrame frame = + new TestMessageFrameBuilderV2().worldUpdater(world).address(RECIPIENT).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isNull(); + assertThat(getV2StackItem(frame, 0)) + .isEqualTo( + new UInt256( + 0x0000000000000001L, + 0x0000000000000002L, + 0x0000000000000003L, + 0x00000000000000FFL)); + } + + @Test + void shouldReturnCorrectGasCost() { + final ToyWorld world = new ToyWorld(); + world.createAccount(RECIPIENT, 0, Wei.of(1L)); + final MessageFrame frame = + new TestMessageFrameBuilderV2().worldUpdater(world).address(RECIPIENT).build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getGasCost()).isEqualTo(gasCalculator.getLowTierGasCost()); + } + + @Test + void shouldHaltOnInsufficientGas() { + final ToyWorld world = new ToyWorld(); + world.createAccount(RECIPIENT, 0, Wei.of(1L)); + final MessageFrame frame = + new TestMessageFrameBuilderV2() + .initialGas(4) + .worldUpdater(world) + .address(RECIPIENT) + .build(); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + @Test + void shouldHaltOnStackOverflow() { + final ToyWorld world = new ToyWorld(); + world.createAccount(RECIPIENT, 0, Wei.of(1L)); + final MessageFrame frame = + new TestMessageFrameBuilderV2().worldUpdater(world).address(RECIPIENT).build(); + frame.setTopV2(MessageFrame.DEFAULT_MAX_STACK_SIZE); + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShlOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShlOperationV2Test.java index b1c73279340..5d345bfb61c 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShlOperationV2Test.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShlOperationV2Test.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.evm.v2.operation; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2.getV2StackItem; import org.hyperledger.besu.evm.UInt256; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -109,16 +110,10 @@ void shiftOperation(final String number, final String shift, final String expect .build(); operation.execute(frame, null); UInt256 expected = UInt256.fromBytesBE(Bytes32.fromHexString(expectedResult).toArrayUnsafe()); - assertThat(getStackItem(frame, 0)).isEqualTo(expected); + assertThat(getV2StackItem(frame, 0)).isEqualTo(expected); assertThat(frame.stackTopV2()).isEqualTo(1); } - private static UInt256 getStackItem(final MessageFrame frame, final int offset) { - final long[] s = frame.stackDataV2(); - final int idx = (frame.stackTopV2() - 1 - offset) << 2; - return new UInt256(s[idx], s[idx + 1], s[idx + 2], s[idx + 3]); - } - @Test void stackTopUpdated() { final MessageFrame frame = @@ -135,7 +130,7 @@ void stackTopUpdated() { .build(); operation.execute(frame, null); assertThat(frame.stackTopV2()).isEqualTo(4); - assertThat(getStackItem(frame, 0)) + assertThat(getV2StackItem(frame, 0)) .isEqualTo(UInt256.fromBytesBE(Bytes32.fromHexString("0x00").toArrayUnsafe())); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShrOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShrOperationV2Test.java index 9df93d003b1..9e7c7be0ae2 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShrOperationV2Test.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/ShrOperationV2Test.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.evm.v2.operation; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2.getV2StackItem; import org.hyperledger.besu.evm.UInt256; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -121,16 +122,10 @@ void shiftOperation(final String number, final String shift, final String expect .build(); operation.execute(frame, null); UInt256 expected = UInt256.fromBytesBE(Bytes32.fromHexString(expectedResult).toArrayUnsafe()); - assertThat(getStackItem(frame, 0)).isEqualTo(expected); + assertThat(getV2StackItem(frame, 0)).isEqualTo(expected); assertThat(frame.stackTopV2()).isEqualTo(1); } - private static UInt256 getStackItem(final MessageFrame frame, final int offset) { - final long[] s = frame.stackDataV2(); - final int idx = (frame.stackTopV2() - 1 - offset) << 2; - return new UInt256(s[idx], s[idx + 1], s[idx + 2], s[idx + 3]); - } - @Test void stackTopUpdated() { final MessageFrame frame = @@ -147,7 +142,7 @@ void stackTopUpdated() { .build(); operation.execute(frame, null); assertThat(frame.stackTopV2()).isEqualTo(4); - assertThat(getStackItem(frame, 0)) + assertThat(getV2StackItem(frame, 0)) .isEqualTo(UInt256.fromBytesBE(Bytes32.fromHexString("0x00").toArrayUnsafe())); } }