diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java index 527d67446e5..c4932ce059b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java @@ -37,6 +37,7 @@ import org.hyperledger.besu.evm.account.EvmAccount; import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.code.CodeV0; +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.processor.AbstractMessageProcessor; @@ -400,8 +401,13 @@ public TransactionProcessingResult processTransaction( messageFrameStack.addFirst(initialFrame); - while (!messageFrameStack.isEmpty()) { - process(messageFrameStack.peekFirst(), operationTracer); + if (initialFrame.getCode().isValid()) { + while (!messageFrameStack.isEmpty()) { + process(messageFrameStack.peekFirst(), operationTracer); + } + } else { + initialFrame.setState(MessageFrame.State.EXCEPTIONAL_HALT); + initialFrame.setExceptionalHaltReason(Optional.of(ExceptionalHaltReason.INVALID_CODE)); } if (initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java index 7fe35cde51a..b8bf244dc1d 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java @@ -221,10 +221,10 @@ public void run() { Log4j2ConfiguratorUtil.setAllLevels("", repeat == 0 ? Level.INFO : Level.OFF); int remainingIters = this.repeat; Log4j2ConfiguratorUtil.setLevel( - "org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder", Level.OFF); + "org.hyperledger.besu.ethereum.mainnet.AbstractProtocolScheduleBuilder", Level.OFF); final ProtocolSpec protocolSpec = component.getProtocolSpec().apply(0); Log4j2ConfiguratorUtil.setLevel( - "org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder", null); + "org.hyperledger.besu.ethereum.mainnet.AbstractProtocolScheduleBuilder", null); final Transaction tx = new Transaction( 0, diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeFactory.java b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeFactory.java index 1c0ed827df3..4b1bb733492 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeFactory.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeFactory.java @@ -71,7 +71,7 @@ public static Code createCode( final String stackValidationError = CodeV1.validateStack(layout); if (stackValidationError != null) { - return new CodeInvalid(codeHash, bytes, "EOF Code Invalid : " + codeValidationError); + return new CodeInvalid(codeHash, bytes, "EOF Code Invalid : " + stackValidationError); } return new CodeV1(codeHash, layout); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1.java b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1.java index 85e919b09d2..f8dabc9c3a8 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1.java @@ -549,7 +549,7 @@ public class CodeV1 implements Code { {3, 1, 1}, // 0xf0 - CREATE {7, 1, 1}, // 0xf1 - CALL {0, 1, 1}, // 0xf2 - CALLCODE - {2, 0, 1}, // 0xf3 - RETURN + {2, 0, -1}, // 0xf3 - RETURN {6, 1, 1}, // 0xf4 - DELEGATECALL {4, 1, 1}, // 0xf5 - CREATE2 {0, 0, 0}, // 0xf6 diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java b/evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java index 6747ef4806a..1645ee1b051 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java @@ -99,6 +99,12 @@ public static EOFLayout parseEOF(final Bytes container) { if (codeSectionCount < 0) { return invalidLayout(container, version, "Invalid Code section count"); } + if (codeSectionCount > 1024) { + return invalidLayout( + container, + version, + "Too many code sections - 0x" + Integer.toHexString(codeSectionCount)); + } int[] codeSectionSizes = new int[codeSectionCount]; for (int i = 0; i < codeSectionCount; i++) { int size = readUnsignedShort(inputStream); @@ -142,6 +148,24 @@ public static EOFLayout parseEOF(final Bytes container) { if (inputStream.read(code, 0, codeSectionSize) != codeSectionSize) { return invalidLayout(container, version, "Incomplete code section " + i); } + if (typeData[i][0] > 0x7f) { + return invalidLayout( + container, + version, + "Type data input stack too large - 0x" + Integer.toHexString(typeData[i][0])); + } + if (typeData[i][1] > 0x7f) { + return invalidLayout( + container, + version, + "Type data output stack too large - 0x" + Integer.toHexString(typeData[i][1])); + } + if (typeData[i][2] > 0x3ff) { + return invalidLayout( + container, + version, + "Type data max stack too large - 0x" + Integer.toHexString(typeData[i][2])); + } codeSections[i] = new CodeSection(Bytes.wrap(code), typeData[i][0], typeData[i][1], typeData[i][2]); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/ExceptionalHaltReason.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/ExceptionalHaltReason.java index d0f5ac6031a..b25325c3efe 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/ExceptionalHaltReason.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/ExceptionalHaltReason.java @@ -32,8 +32,11 @@ public interface ExceptionalHaltReason { ExceptionalHaltReason INVALID_CODE = DefaultExceptionalHaltReason.INVALID_CODE; ExceptionalHaltReason PRECOMPILE_ERROR = DefaultExceptionalHaltReason.PRECOMPILE_ERROR; ExceptionalHaltReason CODE_SECTION_MISSING = DefaultExceptionalHaltReason.CODE_SECTION_MISSING; - ExceptionalHaltReason MISMATCHED_CODE_SECTION_OUTPUTS = - DefaultExceptionalHaltReason.MISMATCHED_CODE_SECTION_OUTPUTS; + ExceptionalHaltReason INCORRECT_CODE_SECTION_RETURN_OUTPUTS = + DefaultExceptionalHaltReason.INCORRECT_CODE_SECTION_RETURN_OUTPUTS; + ExceptionalHaltReason TOO_FEW_INPUTS_FOR_CODE_SECTION = + DefaultExceptionalHaltReason.TOO_FEW_INPUTS_FOR_CODE_SECTION; + ExceptionalHaltReason JUMPF_STACK_MISMATCH = DefaultExceptionalHaltReason.JUMPF_STACK_MISMATCH; String name(); @@ -53,8 +56,12 @@ enum DefaultExceptionalHaltReason implements ExceptionalHaltReason { INVALID_CODE("Code is invalid"), PRECOMPILE_ERROR("Precompile error"), CODE_SECTION_MISSING("No code section at requested index"), - MISMATCHED_CODE_SECTION_OUTPUTS("Jumped into a code section with unequal declared outputs"), - INSUFFICIENT_CODE_SECTION_RETURN_DATA("The stack for a return "); + INSUFFICIENT_CODE_SECTION_RETURN_DATA("The stack for a return "), + INCORRECT_CODE_SECTION_RETURN_OUTPUTS( + "The return of a code section does not have the correct number of outputs"), + TOO_FEW_INPUTS_FOR_CODE_SECTION("Not enough stack items for a function call"), + JUMPF_STACK_MISMATCH( + "The stack height for a JUMPF does not match the requirements of the target section"); final String description; 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 9f7f649af73..7e430ee5d1a 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 @@ -286,6 +286,7 @@ private MessageFrame( this.memory = new Memory(); this.stack = new OperandStack(maxStackSize); this.returnStack = new ReturnStack(); + returnStack.push(new ReturnStack.ReturnStackItem(0, 0, 0)); this.output = Bytes.EMPTY; this.returnData = Bytes.EMPTY; this.logs = new ArrayList<>(); @@ -347,6 +348,15 @@ public void setPC(final int pc) { this.pc = pc; } + /** + * Set the code section index. + * + * @param section the code section index + */ + public void setSection(final int section) { + this.section = section; + } + /** * Return the current code section. Always zero for legacy code. * @@ -362,6 +372,8 @@ public ExceptionalHaltReason callFunction(final int calledSection) { return ExceptionalHaltReason.CODE_SECTION_MISSING; } else if (stack.size() + info.getMaxStackHeight() > maxStackSize) { return ExceptionalHaltReason.TOO_MANY_STACK_ITEMS; + } else if (stack.size() < info.getInputs()) { + return ExceptionalHaltReason.TOO_FEW_INPUTS_FOR_CODE_SECTION; } else { returnStack.push( new ReturnStack.ReturnStackItem(section, pc + 2, stack.size() - info.getInputs())); @@ -372,12 +384,11 @@ public ExceptionalHaltReason callFunction(final int calledSection) { } public ExceptionalHaltReason jumpFunction(final int section) { - CodeSection thisInfo = code.getCodeSection(this.section); CodeSection info = code.getCodeSection(section); if (info == null) { return ExceptionalHaltReason.CODE_SECTION_MISSING; - } else if (thisInfo.getOutputs() != info.getOutputs()) { - return ExceptionalHaltReason.MISMATCHED_CODE_SECTION_OUTPUTS; + } else if (stackSize() != peekReturnStack().getStackHeight() + info.getInputs()) { + return ExceptionalHaltReason.JUMPF_STACK_MISMATCH; } else { pc = -1; // will be +1ed at end of operations loop this.section = section; @@ -385,12 +396,20 @@ public ExceptionalHaltReason jumpFunction(final int section) { } } - public void returnFunction() { + public ExceptionalHaltReason returnFunction() { CodeSection thisInfo = code.getCodeSection(this.section); var returnInfo = returnStack.pop(); - stack.preserveTop(returnInfo.getStackHeight(), thisInfo.getOutputs()); - this.pc = returnInfo.getPC(); - this.section = returnInfo.getCodeSectionIndex(); + if ((returnInfo.getStackHeight() + thisInfo.getOutputs()) != stack.size()) { + return ExceptionalHaltReason.INCORRECT_CODE_SECTION_RETURN_OUTPUTS; + } else if (returnStack.isEmpty()) { + setState(MessageFrame.State.CODE_SUCCESS); + setOutputData(Bytes.EMPTY); + return null; + } else { + this.pc = returnInfo.getPC(); + this.section = returnInfo.getCodeSectionIndex(); + return null; + } } /** Deducts the remaining gas. */ @@ -540,6 +559,33 @@ public int stackSize() { return stack.size(); } + /** + * Return the current return stack size. + * + * @return The current return stack size + */ + public int returnStackSize() { + return returnStack.size(); + } + + /** + * The top item of the return stack + * + * @return The top item of the return stack, or null if the stack is empty + */ + public ReturnStack.ReturnStackItem peekReturnStack() { + return returnStack.peek(); + } + + /** + * Pushes a new return stack item onto the return stack + * + * @param returnStackItem item to be pushed + */ + public void pushReturnStackItem(final ReturnStack.ReturnStackItem returnStackItem) { + returnStack.push(returnStackItem); + } + /** * Returns whether the message frame is static or not. * diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/FixedStack.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/FixedStack.java index f69ecef503b..cd119e3ce15 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/FixedStack.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/FixedStack.java @@ -82,6 +82,14 @@ public T pop() { return removed; } + public T peek() { + if (top < 0) { + return null; + } else { + return entries[top]; + } + } + /** * Pops the specified number of operands from the stack. * diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/ReturnStack.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/ReturnStack.java index b94eec73b3d..13aa2f7e841 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/ReturnStack.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/ReturnStack.java @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.evm.internal; +import java.util.Objects; + public class ReturnStack extends FixedStack { // Java17 convert to record @@ -40,6 +42,33 @@ public int getPC() { public int getStackHeight() { return stackHeight; } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ReturnStackItem that = (ReturnStackItem) o; + return codeSectionIndex == that.codeSectionIndex + && pc == that.pc + && stackHeight == that.stackHeight; + } + + @Override + public int hashCode() { + return Objects.hash(codeSectionIndex, pc, stackHeight); + } + + @Override + public String toString() { + return "ReturnStackItem{" + + "codeSectionIndex=" + + codeSectionIndex + + ", pc=" + + pc + + ", stackHeight=" + + stackHeight + + '}'; + } } /** diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java index 23495a3d305..58ce20cb98a 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java @@ -185,34 +185,38 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { ? CodeV0.EMPTY_CODE : evm.getCode(contract.getCodeHash(), contract.getCode()); - final MessageFrame childFrame = - MessageFrame.builder() - .type(MessageFrame.Type.MESSAGE_CALL) - .messageFrameStack(frame.getMessageFrameStack()) - .worldUpdater(frame.getWorldUpdater().updater()) - .initialGas(gasAvailableForChildCall(frame)) - .address(address(frame)) - .originator(frame.getOriginatorAddress()) - .contract(to) - .gasPrice(frame.getGasPrice()) - .inputData(inputData) - .sender(sender(frame)) - .value(value(frame)) - .apparentValue(apparentValue(frame)) - .code(code) - .blockValues(frame.getBlockValues()) - .depth(frame.getMessageStackDepth() + 1) - .isStatic(isStatic(frame)) - .completer(child -> complete(frame, child)) - .miningBeneficiary(frame.getMiningBeneficiary()) - .blockHashLookup(frame.getBlockHashLookup()) - .maxStackSize(frame.getMaxStackSize()) - .build(); - frame.incrementRemainingGas(cost); - - frame.getMessageFrameStack().addFirst(childFrame); - frame.setState(MessageFrame.State.CODE_SUSPENDED); - return new OperationResult(cost, null, 0); + if (code.isValid()) { + final MessageFrame childFrame = + MessageFrame.builder() + .type(MessageFrame.Type.MESSAGE_CALL) + .messageFrameStack(frame.getMessageFrameStack()) + .worldUpdater(frame.getWorldUpdater().updater()) + .initialGas(gasAvailableForChildCall(frame)) + .address(address(frame)) + .originator(frame.getOriginatorAddress()) + .contract(to) + .gasPrice(frame.getGasPrice()) + .inputData(inputData) + .sender(sender(frame)) + .value(value(frame)) + .apparentValue(apparentValue(frame)) + .code(code) + .blockValues(frame.getBlockValues()) + .depth(frame.getMessageStackDepth() + 1) + .isStatic(isStatic(frame)) + .completer(child -> complete(frame, child)) + .miningBeneficiary(frame.getMiningBeneficiary()) + .blockHashLookup(frame.getBlockHashLookup()) + .maxStackSize(frame.getMaxStackSize()) + .build(); + frame.incrementRemainingGas(cost); + + frame.getMessageFrameStack().addFirst(childFrame); + frame.setState(MessageFrame.State.CODE_SUSPENDED); + return new OperationResult(cost, null, 0); + } else { + return new OperationResult(cost, ExceptionalHaltReason.INVALID_CODE, 0); + } } protected abstract long cost(final MessageFrame frame); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpVectorOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpVectorOperation.java index 707443ce87f..40db7f91184 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpVectorOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpVectorOperation.java @@ -43,7 +43,7 @@ protected OperationResult executeFixedCostOperation(final MessageFrame frame, fi + 2 * vectorSize + ((offsetCase >= vectorSize) ? 0 - : readBigEndianI16(frame.getPC() + 1 + offsetCase * 2, code.toArrayUnsafe())) + : readBigEndianI16(frame.getPC() + 2 + offsetCase * 2, code.toArrayUnsafe())) + 1); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/RetFOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/RetFOperation.java index ece3e68e07e..739f3684adb 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/RetFOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/RetFOperation.java @@ -29,7 +29,11 @@ public RetFOperation(final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - frame.returnFunction(); - return retfSuccess; + var exception = frame.returnFunction(); + if (exception == null) { + return retfSuccess; + } else { + return new OperationResult(retfSuccess.gasCost, exception); + } } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java b/evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java index 3e68bd6a3a4..1b270074228 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java @@ -149,11 +149,57 @@ public static Collection containersWithFormatErrors() { "Incomplete data section", 1 }, + { + "EF0001 0200010001 030001 00 FE DA", + "type section missing", + "Expected kind 1 but read kind 2", + 1 + }, + { + "EF0001 010004 030001 00 00000000 DA", + "code section missing", + "Expected kind 2 but read kind 3", + 1 + }, + { + "EF0001 010004 0200010001 00 00000000 FE", + "data section missing", + "Expected kind 3 but read kind 0", + 1 + }, + { + "EF0001 030001 00 DA", + "type and code section missing", + "Expected kind 1 but read kind 3", + 1 + }, + { + "EF0001 0200010001 00 FE", + "type and data section missing", + "Expected kind 1 but read kind 2", + 1 + }, + { + "EF0001 010004 00 00000000", + "code and data sections missing", + "Expected kind 2 but read kind 0", + 1 + }, + {"EF0001 00", "all sections missing", "Expected kind 1 but read kind 0", 1}, + { + "EF0001 010004 020401" + + " 0001".repeat(1025) + + " 030000 00" + + " 00000000".repeat(1025) + + " FE".repeat(1025), + "no data section, 1025 code sections", + "Too many code sections - 0x401", + 1 + }, }); } public static Collection correctContainers() { - return Arrays.asList( new Object[][] { { @@ -187,47 +233,62 @@ public static Collection correctContainers() { 1 }, { - "EF0001 0200010001 030001 00 FE DA", - "type section missing", - "Expected kind 1 but read kind 2", + "EF0001 010004 020400" + + " 0001".repeat(1024) + + " 030000 00" + + " 00000000".repeat(1024) + + " FE".repeat(1024), + "no data section, 1024 code sections", + null, 1 }, + }); + } + + public static Collection typeSectionTests() { + return Arrays.asList( + new Object[][] { { - "EF0001 010004 030001 00 00000000 DA", - "code section missing", - "Expected kind 2 but read kind 3", + "EF0001 010008 02000200020002 030000 00 0100000000000000", + "Incorrect section zero type input", + "Code section does not have zero inputs and outputs", 1 }, { - "EF0001 010004 0200010001 00 00000000 FE", - "data section missing", - "Expected kind 3 but read kind 0", + "EF0001 010008 02000200020002 030000 00 0001000000000000", + "Incorrect section zero type output", + "Code section does not have zero inputs and outputs", 1 }, { - "EF0001 030001 00 DA", - "type and code section missing", - "Expected kind 1 but read kind 3", + "EF0001 010010 0200040001000200020002 030000 00 00000000 80000000 00010000 02030000 FE 5000 3000 8000", + "inputs too large", + "Type data input stack too large - 0x80", 1 }, { - "EF0001 0200010001 00 FE", - "type and data section missing", - "Expected kind 1 but read kind 2", + "EF0001 010010 0200040001000200020002 030000 00 00000000 01000000 00800000 02030000 FE 5000 3000 8000", + "outputs too large", + "Type data output stack too large - 0x80", 1 }, { - "EF0001 010004 00 00000000", - "code and data sections missing", - "Expected kind 2 but read kind 0", + "EF0001 010010 0200040001000200020002 030000 00 00000400 01000000 00010000 02030400 FE 5000 3000 8000", + "stack too large", + "Type data max stack too large - 0x400", 1 }, - {"EF0001 00", "all sections missing", "Expected kind 1 but read kind 0", 1}, + { + "EF0001 010010 0200040001000200020002 030000 00 00000000 01000000 00010000 02030000 FE 5000 3000 8000", + "non-void input and output types", + null, + 1 + } }); } @ParameterizedTest(name = "{1}") - @MethodSource({"correctContainers", "containersWithFormatErrors"}) + @MethodSource({"correctContainers", "containersWithFormatErrors", "typeSectionTests"}) void test( final String containerString, final String description, diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/CallFOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/CallFOperationTest.java new file mode 100644 index 00000000000..c93fd3b227a --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/CallFOperationTest.java @@ -0,0 +1,156 @@ +/* + * Copyright contributors to Hyperledger Besu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operations; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.code.CodeSection; +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.ReturnStack; +import org.hyperledger.besu.evm.operation.CallFOperation; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; + +class CallFOperationTest { + + @Test + void callFHappyPath() { + final GasCalculator gasCalculator = mock(GasCalculator.class); + final Code mockCode = mock(Code.class); + final Bytes code = Bytes.fromHexString("00" + "b0" + "0001" + "00"); + when(mockCode.getCodeBytes(0)).thenReturn(code); + + final CodeSection codeSection = new CodeSection(Bytes.EMPTY, 1, 2, 3); + when(mockCode.getCodeSection(1)).thenReturn(codeSection); + + MessageFrame messageFrame = + new TestMessageFrameBuilder() + .code(mockCode) + .pc(1) + .initialGas(10L) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .build(); + + CallFOperation callF = new CallFOperation(gasCalculator); + Operation.OperationResult callfResult = callF.execute(messageFrame, null); + + assertThat(callfResult.getHaltReason()).isNull(); + assertThat(callfResult.getPcIncrement()).isEqualTo(1); + assertThat(messageFrame.getSection()).isEqualTo(1); + assertThat(messageFrame.getPC()).isEqualTo(-1); + assertThat(messageFrame.returnStackSize()).isEqualTo(2); + assertThat(messageFrame.peekReturnStack()).isEqualTo(new ReturnStack.ReturnStackItem(0, 3, 1)); + } + + @Test + void callFMissingCodeSection() { + final GasCalculator gasCalculator = mock(GasCalculator.class); + final Code mockCode = mock(Code.class); + final Bytes code = Bytes.fromHexString("00" + "b0" + "03ff" + "00"); + when(mockCode.getCodeBytes(0)).thenReturn(code); + + final CodeSection codeSection = new CodeSection(Bytes.EMPTY, 1, 2, 3); + when(mockCode.getCodeSection(1)).thenReturn(codeSection); + + MessageFrame messageFrame = + new TestMessageFrameBuilder() + .code(mockCode) + .pc(1) + .initialGas(10L) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .build(); + + CallFOperation callF = new CallFOperation(gasCalculator); + Operation.OperationResult callfResult = callF.execute(messageFrame, null); + + assertThat(callfResult.getHaltReason()).isEqualTo(ExceptionalHaltReason.CODE_SECTION_MISSING); + assertThat(callfResult.getPcIncrement()).isEqualTo(1); + assertThat(messageFrame.getSection()).isZero(); + assertThat(messageFrame.getPC()).isEqualTo(1); + assertThat(messageFrame.returnStackSize()).isEqualTo(1); + assertThat(messageFrame.peekReturnStack()).isEqualTo(new ReturnStack.ReturnStackItem(0, 0, 0)); + } + + @Test + void callFTooMuchStack() { + final GasCalculator gasCalculator = mock(GasCalculator.class); + final Code mockCode = mock(Code.class); + final Bytes code = Bytes.fromHexString("00" + "b0" + "0001" + "00"); + when(mockCode.getCodeBytes(0)).thenReturn(code); + + final CodeSection codeSection = new CodeSection(Bytes.EMPTY, 1, 2, 1023); + when(mockCode.getCodeSection(1)).thenReturn(codeSection); + + MessageFrame messageFrame = + new TestMessageFrameBuilder() + .code(mockCode) + .pc(1) + .initialGas(10L) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .build(); + + CallFOperation callF = new CallFOperation(gasCalculator); + Operation.OperationResult callfResult = callF.execute(messageFrame, null); + + assertThat(callfResult.getHaltReason()).isEqualTo(ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); + assertThat(callfResult.getPcIncrement()).isEqualTo(1); + assertThat(messageFrame.getSection()).isZero(); + assertThat(messageFrame.getPC()).isEqualTo(1); + assertThat(messageFrame.returnStackSize()).isEqualTo(1); + assertThat(messageFrame.peekReturnStack()).isEqualTo(new ReturnStack.ReturnStackItem(0, 0, 0)); + } + + @Test + void callFTooFewStack() { + final GasCalculator gasCalculator = mock(GasCalculator.class); + final Code mockCode = mock(Code.class); + final Bytes code = Bytes.fromHexString("00" + "b0" + "0001" + "00"); + when(mockCode.getCodeBytes(0)).thenReturn(code); + + final CodeSection codeSection = new CodeSection(Bytes.EMPTY, 5, 2, 5); + when(mockCode.getCodeSection(1)).thenReturn(codeSection); + + MessageFrame messageFrame = + new TestMessageFrameBuilder() + .code(mockCode) + .pc(1) + .initialGas(10L) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .build(); + + CallFOperation callF = new CallFOperation(gasCalculator); + Operation.OperationResult callfResult = callF.execute(messageFrame, null); + + assertThat(callfResult.getHaltReason()) + .isEqualTo(ExceptionalHaltReason.TOO_FEW_INPUTS_FOR_CODE_SECTION); + assertThat(callfResult.getPcIncrement()).isEqualTo(1); + assertThat(messageFrame.getSection()).isZero(); + assertThat(messageFrame.getPC()).isEqualTo(1); + assertThat(messageFrame.returnStackSize()).isEqualTo(1); + assertThat(messageFrame.peekReturnStack()).isEqualTo(new ReturnStack.ReturnStackItem(0, 0, 0)); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/JumpFOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/JumpFOperationTest.java new file mode 100644 index 00000000000..65b35fb0931 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/JumpFOperationTest.java @@ -0,0 +1,132 @@ +/* + * Copyright contributors to Hyperledger Besu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operations; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.code.CodeSection; +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.ReturnStack; +import org.hyperledger.besu.evm.operation.JumpFOperation; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; + +class JumpFOperationTest { + + @Test + void jumpFHappyPath() { + final GasCalculator gasCalculator = mock(GasCalculator.class); + final Code mockCode = mock(Code.class); + final Bytes code = Bytes.fromHexString("00" + "b2" + "0001" + "00"); + when(mockCode.getCodeBytes(2)).thenReturn(code); + when(mockCode.getCodeBytes(1)).thenReturn(code); + + final CodeSection codeSection = new CodeSection(Bytes.EMPTY, 1, 2, 3); + when(mockCode.getCodeSection(1)).thenReturn(codeSection); + when(mockCode.getCodeSection(2)).thenReturn(codeSection); + + MessageFrame messageFrame = + new TestMessageFrameBuilder() + .code(mockCode) + .pc(1) + .section(2) + .initialGas(10L) + .pushStackItem(Bytes.EMPTY) + .build(); + + JumpFOperation jumpF = new JumpFOperation(gasCalculator); + Operation.OperationResult jumpFResult = jumpF.execute(messageFrame, null); + + assertThat(jumpFResult.getHaltReason()).isNull(); + assertThat(jumpFResult.getPcIncrement()).isEqualTo(1); + assertThat(messageFrame.getSection()).isEqualTo(1); + assertThat(messageFrame.getPC()).isEqualTo(-1); + assertThat(messageFrame.returnStackSize()).isEqualTo(1); + assertThat(messageFrame.peekReturnStack()).isEqualTo(new ReturnStack.ReturnStackItem(0, 0, 0)); + } + + @Test + void jumpFMissingCodeSection() { + final GasCalculator gasCalculator = mock(GasCalculator.class); + final Code mockCode = mock(Code.class); + final Bytes code = Bytes.fromHexString("00" + "b2" + "03ff" + "00"); + when(mockCode.getCodeBytes(0)).thenReturn(code); + + final CodeSection codeSection = new CodeSection(Bytes.EMPTY, 1, 2, 3); + when(mockCode.getCodeSection(1)).thenReturn(codeSection); + + MessageFrame messageFrame = + new TestMessageFrameBuilder() + .code(mockCode) + .pc(1) + .initialGas(10L) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .build(); + + JumpFOperation jumpF = new JumpFOperation(gasCalculator); + Operation.OperationResult jumpFResult = jumpF.execute(messageFrame, null); + + assertThat(jumpFResult.getHaltReason()).isEqualTo(ExceptionalHaltReason.CODE_SECTION_MISSING); + assertThat(jumpFResult.getPcIncrement()).isEqualTo(1); + assertThat(messageFrame.getSection()).isZero(); + assertThat(messageFrame.getPC()).isEqualTo(1); + assertThat(messageFrame.returnStackSize()).isEqualTo(1); + assertThat(messageFrame.peekReturnStack()).isEqualTo(new ReturnStack.ReturnStackItem(0, 0, 0)); + } + + @Test + void jumpFTooMuchStack() { + final GasCalculator gasCalculator = mock(GasCalculator.class); + final Code mockCode = mock(Code.class); + final Bytes code = Bytes.fromHexString("00" + "b2" + "0001" + "00"); + when(mockCode.getCodeBytes(0)).thenReturn(code); + when(mockCode.getCodeBytes(2)).thenReturn(code); + when(mockCode.getCodeBytes(1)).thenReturn(code); + + final CodeSection codeSection1 = new CodeSection(Bytes.EMPTY, 1, 2, 3); + when(mockCode.getCodeSection(1)).thenReturn(codeSection1); + final CodeSection codeSection2 = new CodeSection(Bytes.EMPTY, 2, 2, 3); + when(mockCode.getCodeSection(2)).thenReturn(codeSection2); + + MessageFrame messageFrame = + new TestMessageFrameBuilder() + .code(mockCode) + .pc(1) + .section(2) + .initialGas(10L) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .build(); + + JumpFOperation jumpF = new JumpFOperation(gasCalculator); + Operation.OperationResult jumpFResult = jumpF.execute(messageFrame, null); + + assertThat(jumpFResult.getHaltReason()).isEqualTo(ExceptionalHaltReason.JUMPF_STACK_MISMATCH); + assertThat(jumpFResult.getPcIncrement()).isEqualTo(1); + assertThat(messageFrame.getSection()).isEqualTo(2); + assertThat(messageFrame.getPC()).isEqualTo(1); + assertThat(messageFrame.returnStackSize()).isEqualTo(1); + assertThat(messageFrame.peekReturnStack()).isEqualTo(new ReturnStack.ReturnStackItem(0, 0, 0)); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/RelativeJumpOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/RelativeJumpOperationTest.java index 6ad5d5f1623..e95ee6bda1b 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/RelativeJumpOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/RelativeJumpOperationTest.java @@ -15,6 +15,7 @@ */ package org.hyperledger.besu.evm.operations; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -28,7 +29,6 @@ import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; import org.apache.tuweni.bytes.Bytes; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -54,7 +54,7 @@ void rjumpOperation(final int jumpLength) { RelativeJumpOperation rjump = new RelativeJumpOperation(gasCalculator); Operation.OperationResult rjumpResult = rjump.execute(messageFrame, null); - Assertions.assertThat(rjumpResult.getPcIncrement()) + assertThat(rjumpResult.getPcIncrement()) .isEqualTo(code.size() - rjumpOperationIndex + jumpLength); } @@ -77,7 +77,29 @@ void rjumpiOperation() { RelativeJumpIfOperation rjumpi = new RelativeJumpIfOperation(gasCalculator); Operation.OperationResult rjumpResult = rjumpi.execute(messageFrame, null); - Assertions.assertThat(rjumpResult.getPcIncrement()).isEqualTo(2 + 1); + assertThat(rjumpResult.getPcIncrement()).isEqualTo(2 + 1); + } + + @Test + void rjumpiHitOperation() { + final GasCalculator gasCalculator = mock(GasCalculator.class); + final Code mockCode = mock(Code.class); + final int rjumpOperationIndex = 3; + final Bytes code = Bytes.fromHexString("00".repeat(rjumpOperationIndex) + "5dfffc00"); + + MessageFrame messageFrame = + new TestMessageFrameBuilder() + .code(mockCode) + .pc(rjumpOperationIndex) + .initialGas(5L) + .pushStackItem(Bytes.ofUnsignedInt(1)) + .build(); + when(mockCode.getCodeBytes(messageFrame.getSection())).thenReturn(code); + + RelativeJumpIfOperation rjumpi = new RelativeJumpIfOperation(gasCalculator); + Operation.OperationResult rjumpResult = rjumpi.execute(messageFrame, null); + + assertThat(rjumpResult.getPcIncrement()).isEqualTo(-1); } @Test @@ -104,6 +126,30 @@ void rjumpvOperation() { RelativeJumpVectorOperation rjumpv = new RelativeJumpVectorOperation(gasCalculator); Operation.OperationResult rjumpResult = rjumpv.execute(messageFrame, null); - Assertions.assertThat(rjumpResult.getPcIncrement()).isEqualTo(1 + 2 * jumpVectorSize + 1); + assertThat(rjumpResult.getPcIncrement()).isEqualTo(1 + 2 * jumpVectorSize + 1); + } + + @Test + void rjumpvHitOperation() { + final GasCalculator gasCalculator = mock(GasCalculator.class); + final Code mockCode = mock(Code.class); + final int rjumpOperationIndex = 3; + final int jumpVectorSize = 2; + final Bytes code = + Bytes.fromHexString("00".repeat(rjumpOperationIndex) + "5e" + "02" + "1234" + "5678"); + + MessageFrame messageFrame = + new TestMessageFrameBuilder() + .code(mockCode) + .pc(rjumpOperationIndex) + .initialGas(5L) + .pushStackItem(Bytes.of(jumpVectorSize - 1)) + .build(); + when(mockCode.getCodeBytes(messageFrame.getSection())).thenReturn(code); + + RelativeJumpVectorOperation rjumpv = new RelativeJumpVectorOperation(gasCalculator); + Operation.OperationResult rjumpResult = rjumpv.execute(messageFrame, null); + + assertThat(rjumpResult.getPcIncrement()).isEqualTo(2 + 2 * jumpVectorSize + 0x5678); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/RetFOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/RetFOperationTest.java new file mode 100644 index 00000000000..98774d5ce43 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/RetFOperationTest.java @@ -0,0 +1,131 @@ +/* + * Copyright contributors to Hyperledger Besu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operations; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.code.CodeSection; +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.ReturnStack; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.operation.RetFOperation; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; + +class RetFOperationTest { + + @Test + void retFHappyPath() { + final GasCalculator gasCalculator = mock(GasCalculator.class); + final Code mockCode = mock(Code.class); + final Bytes code = Bytes.fromHexString("00" + "b1" + "00"); + when(mockCode.getCodeBytes(2)).thenReturn(code); + when(mockCode.getCodeBytes(1)).thenReturn(code); + + final CodeSection codeSection = new CodeSection(Bytes.EMPTY, 1, 2, 3); + when(mockCode.getCodeSection(1)).thenReturn(codeSection); + + MessageFrame messageFrame = + new TestMessageFrameBuilder() + .code(mockCode) + .pc(1) + .section(1) + .initialGas(10L) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .build(); + messageFrame.pushReturnStackItem(new ReturnStack.ReturnStackItem(2, 3, 1)); + + RetFOperation retF = new RetFOperation(gasCalculator); + Operation.OperationResult retFResult = retF.execute(messageFrame, null); + + assertThat(retFResult.getHaltReason()).isNull(); + assertThat(retFResult.getPcIncrement()).isEqualTo(1); + assertThat(messageFrame.getSection()).isEqualTo(2); + assertThat(messageFrame.getPC()).isEqualTo(3); + assertThat(messageFrame.returnStackSize()).isEqualTo(1); + } + + @Test + void retFFinalReturn() { + final GasCalculator gasCalculator = mock(GasCalculator.class); + final Code mockCode = mock(Code.class); + final Bytes code = Bytes.fromHexString("00" + "b1" + "00"); + when(mockCode.getCodeBytes(2)).thenReturn(code); + when(mockCode.getCodeBytes(1)).thenReturn(code); + + final CodeSection codeSection = new CodeSection(Bytes.EMPTY, 1, 2, 3); + when(mockCode.getCodeSection(1)).thenReturn(codeSection); + + MessageFrame messageFrame = + new TestMessageFrameBuilder() + .code(mockCode) + .pc(1) + .section(1) + .initialGas(10L) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .build(); + + RetFOperation retF = new RetFOperation(gasCalculator); + Operation.OperationResult retFResult = retF.execute(messageFrame, null); + + assertThat(retFResult.getHaltReason()).isNull(); + assertThat(retFResult.getPcIncrement()).isEqualTo(1); + assertThat(messageFrame.getState()).isEqualTo(MessageFrame.State.CODE_SUCCESS); + assertThat(messageFrame.getOutputData()).isEqualTo(Bytes.EMPTY); + } + + @Test + void retFIncorrectOutput() { + final GasCalculator gasCalculator = mock(GasCalculator.class); + final Code mockCode = mock(Code.class); + final Bytes code = Bytes.fromHexString("00" + "b1" + "00"); + when(mockCode.getCodeBytes(0)).thenReturn(code); + + final CodeSection codeSection = new CodeSection(Bytes.EMPTY, 1, 2, 3); + when(mockCode.getCodeSection(1)).thenReturn(codeSection); + + MessageFrame messageFrame = + new TestMessageFrameBuilder() + .code(mockCode) + .pc(1) + .section(1) + .initialGas(10L) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .build(); + + RetFOperation retF = new RetFOperation(gasCalculator); + Operation.OperationResult retFResult = retF.execute(messageFrame, null); + + assertThat(retFResult.getHaltReason()) + .isEqualTo(ExceptionalHaltReason.INCORRECT_CODE_SECTION_RETURN_OUTPUTS); + assertThat(retFResult.getPcIncrement()).isEqualTo(1); + assertThat(messageFrame.getSection()).isEqualTo(1); + assertThat(messageFrame.getPC()).isEqualTo(1); + assertThat(messageFrame.returnStackSize()).isZero(); + assertThat(messageFrame.peekReturnStack()).isNull(); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestCodeExecutor.java b/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestCodeExecutor.java index 6d72d1b7285..9400fbc65b5 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestCodeExecutor.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestCodeExecutor.java @@ -53,7 +53,7 @@ public MessageFrame executeCode( final MessageCallProcessor messageCallProcessor = new MessageCallProcessor(evm, new PrecompileContractRegistry()); - final Bytes codeBytes = Bytes.fromHexString(codeHexString); + final Bytes codeBytes = Bytes.fromHexString(codeHexString.replaceAll("\\s", "")); final Code code = evm.getCode(Hash.hash(codeBytes), codeBytes); final MessageFrame initialFrame = diff --git a/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestMessageFrameBuilder.java b/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestMessageFrameBuilder.java index a6bf4082617..4d0217dc1b3 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestMessageFrameBuilder.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestMessageFrameBuilder.java @@ -53,6 +53,7 @@ public class TestMessageFrameBuilder { private Bytes inputData = Bytes.EMPTY; private Code code = CodeV0.EMPTY_CODE; private int pc = 0; + private int section = 0; private final List stackItems = new ArrayList<>(); private int depth = 0; private Optional> blockHashLookup = Optional.empty(); @@ -117,6 +118,11 @@ public TestMessageFrameBuilder pc(final int pc) { return this; } + public TestMessageFrameBuilder section(final int section) { + this.section = section; + return this; + } + public TestMessageFrameBuilder blockValues(final BlockValues blockValues) { this.blockValues = Optional.of(blockValues); return this; @@ -162,6 +168,7 @@ public MessageFrame build() { .maxStackSize(maxStackSize) .build(); frame.setPC(pc); + frame.setSection(section); stackItems.forEach(frame::pushStackItem); return frame; }