diff --git a/CHANGELOG.md b/CHANGELOG.md index c3efddef70..cba041d31d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - Improve the selection of the most profitable built block [#7174](https://github.com/hyperledger/besu/pull/7174) - Support for eth_maxPriorityFeePerGas [#5658](https://github.com/hyperledger/besu/issues/5658) - Enable continuous profiling with default setting [#7006](https://github.com/hyperledger/besu/pull/7006) +- A full and up to date implementation of EOF for Prague [#7169](https://github.com/hyperledger/besu/pull/7169) ### Bug fixes - Make `eth_gasPrice` aware of the base fee market [#7102](https://github.com/hyperledger/besu/pull/7102) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index 535dcc6936..b14efb2e80 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -1504,7 +1504,8 @@ public class BesuCommand implements DefaultCommandValues, Runnable { } if (genesisConfigOptionsSupplier.get().getCancunTime().isPresent() - || genesisConfigOptionsSupplier.get().getPragueTime().isPresent()) { + || genesisConfigOptionsSupplier.get().getPragueTime().isPresent() + || genesisConfigOptionsSupplier.get().getPragueEOFTime().isPresent()) { if (kzgTrustedSetupFile != null) { KZGPointEvalPrecompiledContract.init(kzgTrustedSetupFile); } else { diff --git a/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java index 1645688f09..8e47af3b75 100644 --- a/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java @@ -249,6 +249,13 @@ public interface GenesisConfigOptions { */ OptionalLong getPragueTime(); + /** + * Gets Prague EOF time. + * + * @return the prague time + */ + OptionalLong getPragueEOFTime(); + /** * Gets future eips time. * diff --git a/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java index 6118fc5080..d5635d9ae3 100644 --- a/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java @@ -298,6 +298,11 @@ public class JsonGenesisConfigOptions implements GenesisConfigOptions { return getOptionalLong("praguetime"); } + @Override + public OptionalLong getPragueEOFTime() { + return getOptionalLong("pragueeoftime"); + } + @Override public OptionalLong getFutureEipsTime() { return getOptionalLong("futureeipstime"); @@ -457,6 +462,7 @@ public class JsonGenesisConfigOptions implements GenesisConfigOptions { getShanghaiTime().ifPresent(l -> builder.put("shanghaiTime", l)); getCancunTime().ifPresent(l -> builder.put("cancunTime", l)); getPragueTime().ifPresent(l -> builder.put("pragueTime", l)); + getPragueEOFTime().ifPresent(l -> builder.put("pragueEOFTime", l)); getTerminalBlockNumber().ifPresent(l -> builder.put("terminalBlockNumber", l)); getTerminalBlockHash().ifPresent(h -> builder.put("terminalBlockHash", h.toHexString())); getFutureEipsTime().ifPresent(l -> builder.put("futureEipsTime", l)); @@ -605,6 +611,7 @@ public class JsonGenesisConfigOptions implements GenesisConfigOptions { getShanghaiTime(), getCancunTime(), getPragueTime(), + getPragueEOFTime(), getFutureEipsTime(), getExperimentalEipsTime()); // when adding forks add an entry to ${REPO_ROOT}/config/src/test/resources/all_forks.json diff --git a/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java index e814adb4ae..32800f58a1 100644 --- a/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java @@ -49,6 +49,7 @@ public class StubGenesisConfigOptions implements GenesisConfigOptions, Cloneable private OptionalLong shanghaiTime = OptionalLong.empty(); private OptionalLong cancunTime = OptionalLong.empty(); private OptionalLong pragueTime = OptionalLong.empty(); + private OptionalLong pragueEOFTime = OptionalLong.empty(); private OptionalLong futureEipsTime = OptionalLong.empty(); private OptionalLong experimentalEipsTime = OptionalLong.empty(); private OptionalLong terminalBlockNumber = OptionalLong.empty(); @@ -242,6 +243,11 @@ public class StubGenesisConfigOptions implements GenesisConfigOptions, Cloneable return pragueTime; } + @Override + public OptionalLong getPragueEOFTime() { + return pragueEOFTime; + } + @Override public OptionalLong getFutureEipsTime() { return futureEipsTime; @@ -635,6 +641,18 @@ public class StubGenesisConfigOptions implements GenesisConfigOptions, Cloneable return this; } + /** + * PragueEOF time. + * + * @param timestamp the timestamp + * @return the stub genesis config options + */ + public StubGenesisConfigOptions pragueEOFTime(final long timestamp) { + pragueTime = OptionalLong.of(timestamp); + pragueEOFTime = pragueTime; + return this; + } + /** * Future EIPs Time block. * diff --git a/config/src/test/java/org/hyperledger/besu/config/GenesisConfigOptionsTest.java b/config/src/test/java/org/hyperledger/besu/config/GenesisConfigOptionsTest.java index bb4a8f94a9..0e8138e1bc 100644 --- a/config/src/test/java/org/hyperledger/besu/config/GenesisConfigOptionsTest.java +++ b/config/src/test/java/org/hyperledger/besu/config/GenesisConfigOptionsTest.java @@ -199,6 +199,13 @@ class GenesisConfigOptionsTest { assertThat(config.getPragueTime()).hasValue(1670470143); } + @Test + void shouldGetPragueEOFTime() { + final GenesisConfigOptions config = + fromConfigOptions(singletonMap("pragueEOFTime", 1670470143)); + assertThat(config.getPragueEOFTime()).hasValue(1670470143); + } + @Test void shouldGetFutureEipsTime() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("futureEipsTime", 1337)); @@ -232,6 +239,7 @@ class GenesisConfigOptionsTest { assertThat(config.getShanghaiTime()).isEmpty(); assertThat(config.getCancunTime()).isEmpty(); assertThat(config.getPragueTime()).isEmpty(); + assertThat(config.getPragueEOFTime()).isEmpty(); assertThat(config.getFutureEipsTime()).isEmpty(); assertThat(config.getExperimentalEipsTime()).isEmpty(); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java index d42f2d39a3..e975a01f8f 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java @@ -312,6 +312,14 @@ public final class GenesisState { if (pragueTimestamp.isPresent()) { return genesis.getTimestamp() >= pragueTimestamp.getAsLong(); } + return isPragueEOFAtGenesis(genesis); + } + + private static boolean isPragueEOFAtGenesis(final GenesisConfigFile genesis) { + final OptionalLong pragueEOFTimestamp = genesis.getConfigOptions().getPragueEOFTime(); + if (pragueEOFTimestamp.isPresent()) { + return genesis.getTimestamp() >= pragueEOFTimestamp.getAsLong(); + } return isFutureEipsTimeAtGenesis(genesis); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecFactory.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecFactory.java index 06bc45084a..095a85ef53 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecFactory.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecFactory.java @@ -189,6 +189,17 @@ public class MainnetProtocolSpecFactory { miningParameters); } + public ProtocolSpecBuilder pragueEOFDefinition(final GenesisConfigOptions genesisConfigOptions) { + return MainnetProtocolSpecs.pragueEOFDefinition( + chainId, + contractSizeLimit, + evmStackSize, + isRevertReasonEnabled, + genesisConfigOptions, + evmConfiguration, + miningParameters); + } + /** * The "future" fork consists of EIPs that have been approved for Ethereum Mainnet but not * scheduled for a fork. This is also known as "Eligible For Inclusion" (EFI) or "Considered for diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java index 5c1d399264..16fee28bb5 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java @@ -58,6 +58,7 @@ import org.hyperledger.besu.evm.gascalculator.HomesteadGasCalculator; import org.hyperledger.besu.evm.gascalculator.IstanbulGasCalculator; import org.hyperledger.besu.evm.gascalculator.LondonGasCalculator; import org.hyperledger.besu.evm.gascalculator.PetersburgGasCalculator; +import org.hyperledger.besu.evm.gascalculator.PragueEOFGasCalculator; import org.hyperledger.besu.evm.gascalculator.PragueGasCalculator; import org.hyperledger.besu.evm.gascalculator.ShanghaiGasCalculator; import org.hyperledger.besu.evm.gascalculator.SpuriousDragonGasCalculator; @@ -735,9 +736,6 @@ public abstract class MainnetProtocolSpecs { final GenesisConfigOptions genesisConfigOptions, final EvmConfiguration evmConfiguration, final MiningParameters miningParameters) { - final int contractSizeLimit = - configContractSizeLimit.orElse(SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT); - final int stackSizeLimit = configStackSizeLimit.orElse(MessageFrame.DEFAULT_MAX_STACK_SIZE); final Address depositContractAddress = genesisConfigOptions.getDepositContractAddress().orElse(DEFAULT_DEPOSIT_CONTRACT_ADDRESS); @@ -750,47 +748,64 @@ public abstract class MainnetProtocolSpecs { genesisConfigOptions, evmConfiguration, miningParameters) - // EVM changes to support EOF EIPs (3670, 4200, 4750, 5450) + // EIP-3074 AUTH and AUTCALL gas .gasCalculator(PragueGasCalculator::new) + // EIP-3074 AUTH and AUTCALL .evmBuilder( (gasCalculator, jdCacheConfig) -> MainnetEVMs.prague( gasCalculator, chainId.orElse(BigInteger.ZERO), evmConfiguration)) - // change contract call creator to accept EOF code + + // EIP-2537 BLS12-381 precompiles + .precompileContractRegistryBuilder(MainnetPrecompiledContractRegistries::prague) + + // EIP-7002 Withdrawls / EIP-6610 Deposits / EIP-7685 Requests + .requestsValidator(pragueRequestsValidator(depositContractAddress)) + // EIP-7002 Withdrawls / EIP-6610 Deposits / EIP-7685 Requests + .requestProcessorCoordinator(pragueRequestsProcessors(depositContractAddress)) + + // EIP-2935 Blockhash processor + .blockHashProcessor(new PragueBlockHashProcessor()) + .name("Prague"); + } + + static ProtocolSpecBuilder pragueEOFDefinition( + final Optional chainId, + final OptionalInt configContractSizeLimit, + final OptionalInt configStackSizeLimit, + final boolean enableRevertReason, + final GenesisConfigOptions genesisConfigOptions, + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters) { + final int contractSizeLimit = + configContractSizeLimit.orElse(SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT); + + return pragueDefinition( + chainId, + configContractSizeLimit, + configStackSizeLimit, + enableRevertReason, + genesisConfigOptions, + evmConfiguration, + miningParameters) + // EIP-7692 EOF v1 Gas calculator + .gasCalculator(PragueEOFGasCalculator::new) + // EIP-7692 EOF v1 EVM and opcodes + .evmBuilder( + (gasCalculator, jdCacheConfig) -> + MainnetEVMs.pragueEOF( + gasCalculator, chainId.orElse(BigInteger.ZERO), evmConfiguration)) + // EIP-7698 EOF v1 creation transaction .contractCreationProcessorBuilder( (gasCalculator, evm) -> new ContractCreationProcessor( gasCalculator, evm, true, - List.of( - MaxCodeSizeRule.of(contractSizeLimit), EOFValidationCodeRule.of(1, false)), + List.of(MaxCodeSizeRule.of(contractSizeLimit), EOFValidationCodeRule.of(1)), 1, SPURIOUS_DRAGON_FORCE_DELETE_WHEN_EMPTY_ADDRESSES)) - // warm blockahsh contract - .transactionProcessorBuilder( - (gasCalculator, - feeMarket, - transactionValidator, - contractCreationProcessor, - messageCallProcessor) -> - new MainnetTransactionProcessor( - gasCalculator, - transactionValidator, - contractCreationProcessor, - messageCallProcessor, - true, - true, - stackSizeLimit, - feeMarket, - CoinbaseFeePriceCalculator.eip1559())) - - // use prague precompiled contracts - .precompileContractRegistryBuilder(MainnetPrecompiledContractRegistries::prague) - .requestsValidator(pragueRequestsValidator(depositContractAddress)) - .requestProcessorCoordinator(pragueRequestsProcessors(depositContractAddress)) - .blockHashProcessor(new PragueBlockHashProcessor()) - .name("Prague"); + .name("PragueEOF"); } static ProtocolSpecBuilder futureEipsDefinition( @@ -803,7 +818,7 @@ public abstract class MainnetProtocolSpecs { final MiningParameters miningParameters) { final int contractSizeLimit = configContractSizeLimit.orElse(SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT); - return pragueDefinition( + return pragueEOFDefinition( chainId, configContractSizeLimit, configStackSizeLimit, @@ -823,8 +838,7 @@ public abstract class MainnetProtocolSpecs { gasCalculator, evm, true, - List.of( - MaxCodeSizeRule.of(contractSizeLimit), EOFValidationCodeRule.of(1, false)), + List.of(MaxCodeSizeRule.of(contractSizeLimit), EOFValidationCodeRule.of(1)), 1, SPURIOUS_DRAGON_FORCE_DELETE_WHEN_EMPTY_ADDRESSES)) // use future configured precompiled contracts 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 51d300cbc4..d982265f24 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 @@ -32,8 +32,10 @@ import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; import org.hyperledger.besu.ethereum.trie.MerkleTrieException; +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.code.CodeInvalid; import org.hyperledger.besu.evm.code.CodeV0; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -382,13 +384,14 @@ public class MainnetTransactionProcessor { Address.contractAddress(senderAddress, sender.getNonce() - 1L); final Bytes initCodeBytes = transaction.getPayload(); + Code code = contractCreationProcessor.getCodeFromEVMForCreation(initCodeBytes); initialFrame = commonMessageFrameBuilder .type(MessageFrame.Type.CONTRACT_CREATION) .address(contractAddress) .contract(contractAddress) - .inputData(Bytes.EMPTY) - .code(contractCreationProcessor.getCodeFromEVMUncached(initCodeBytes)) + .inputData(initCodeBytes.slice(code.getSize())) + .code(code) .build(); } else { @SuppressWarnings("OptionalGetWithoutIsPresent") // isContractCall tests isPresent @@ -415,12 +418,17 @@ public class MainnetTransactionProcessor { } else { initialFrame.setState(MessageFrame.State.EXCEPTIONAL_HALT); initialFrame.setExceptionalHaltReason(Optional.of(ExceptionalHaltReason.INVALID_CODE)); + validationResult = + ValidationResult.invalid( + TransactionInvalidReason.EOF_CODE_INVALID, + ((CodeInvalid) initialFrame.getCode()).getInvalidReason()); } if (initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { worldUpdater.commit(); } else { - if (initialFrame.getExceptionalHaltReason().isPresent()) { + if (initialFrame.getExceptionalHaltReason().isPresent() + && initialFrame.getCode().isValid()) { validationResult = ValidationResult.invalid( TransactionInvalidReason.EXECUTION_HALTED, diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilder.java index 2059192c53..78198922ea 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilder.java @@ -252,6 +252,7 @@ public class ProtocolScheduleBuilder { lastForkBlock = validateForkOrder("Shanghai", config.getShanghaiTime(), lastForkBlock); lastForkBlock = validateForkOrder("Cancun", config.getCancunTime(), lastForkBlock); lastForkBlock = validateForkOrder("Prague", config.getPragueTime(), lastForkBlock); + lastForkBlock = validateForkOrder("PragueEOF", config.getPragueEOFTime(), lastForkBlock); lastForkBlock = validateForkOrder("FutureEips", config.getFutureEipsTime(), lastForkBlock); lastForkBlock = validateForkOrder("ExperimentalEips", config.getExperimentalEipsTime(), lastForkBlock); @@ -331,6 +332,7 @@ public class ProtocolScheduleBuilder { timestampMilestone(config.getShanghaiTime(), specFactory.shanghaiDefinition(config)), timestampMilestone(config.getCancunTime(), specFactory.cancunDefinition(config)), timestampMilestone(config.getPragueTime(), specFactory.pragueDefinition(config)), + timestampMilestone(config.getPragueEOFTime(), specFactory.pragueEOFDefinition(config)), timestampMilestone(config.getFutureEipsTime(), specFactory.futureEipsDefinition(config)), timestampMilestone( config.getExperimentalEipsTime(), specFactory.experimentalEipsDefinition(config)), diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionProcessor.java index d6f615310b..ad7de59aee 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionProcessor.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.mainnet.TransactionValidatorFactory; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.code.CodeV0; @@ -138,13 +139,14 @@ public class PrivateTransactionProcessor { privacyGroupId); final Bytes initCodeBytes = transaction.getPayload(); + Code code = contractCreationProcessor.getCodeFromEVMForCreation(initCodeBytes); initialFrame = commonMessageFrameBuilder .type(MessageFrame.Type.CONTRACT_CREATION) .address(privateContractAddress) .contract(privateContractAddress) - .inputData(Bytes.EMPTY) - .code(contractCreationProcessor.getCodeFromEVMUncached(initCodeBytes)) + .inputData(initCodeBytes.slice(code.getSize())) + .code(code) .build(); } else { final Address to = transaction.getTo().get(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java index 8659b9d837..a6c2cb69b2 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java @@ -49,6 +49,7 @@ public enum TransactionInvalidReason { INVALID_BLOBS, PLUGIN_TX_POOL_VALIDATOR, EXECUTION_HALTED, + EOF_CODE_INVALID, // Private Transaction Invalid Reasons PRIVATE_TRANSACTION_INVALID, PRIVATE_TRANSACTION_FAILED, diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/CodeValidateSubCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/CodeValidateSubCommand.java index 210efe014c..beb51cd4a6 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/CodeValidateSubCommand.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/CodeValidateSubCommand.java @@ -17,8 +17,9 @@ package org.hyperledger.besu.evmtool; import static java.nio.charset.StandardCharsets.UTF_8; import static org.hyperledger.besu.evmtool.CodeValidateSubCommand.COMMAND_NAME; +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.code.CodeFactory; -import org.hyperledger.besu.evm.code.CodeInvalid; +import org.hyperledger.besu.evm.code.CodeV1Validation; import org.hyperledger.besu.evm.code.EOFLayout; import org.hyperledger.besu.util.LogConfigurator; @@ -39,7 +40,7 @@ import picocli.CommandLine; @CommandLine.Command( name = COMMAND_NAME, - description = "Execute an Ethereum State Test.", + description = "Validates EVM code for fuzzing", mixinStandardHelpOptions = true, versionProvider = VersionProvider.class) public class CodeValidateSubCommand implements Runnable { @@ -109,24 +110,26 @@ public class CodeValidateSubCommand implements Runnable { } catch (RuntimeException re) { return "err: hex string -" + re + "\n"; } - if (codeBytes.size() == 0) { + if (codeBytes.isEmpty()) { return ""; } - var layout = EOFLayout.parseEOF(codeBytes); + EOFLayout layout = EOFLayout.parseEOF(codeBytes); if (!layout.isValid()) { - return "err: layout - " + layout.getInvalidReason() + "\n"; + return "err: layout - " + layout.invalidReason() + "\n"; } - var code = CodeFactory.createCode(codeBytes, 1, true); - if (!code.isValid()) { - return "err: " + ((CodeInvalid) code).getInvalidReason() + "\n"; + String error = CodeV1Validation.validate(layout); + if (error != null) { + return "err: " + error + "\n"; } + Code code = CodeFactory.createCode(codeBytes, 1); + return "OK " + IntStream.range(0, code.getCodeSectionCount()) .mapToObj(code::getCodeSection) - .map(cs -> layout.getContainer().slice(cs.getEntryPoint(), cs.getLength())) + .map(cs -> layout.container().slice(cs.getEntryPoint(), cs.getLength())) .map(Bytes::toUnprefixedHexString) .collect(Collectors.joining(",")) + "\n"; diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EOFTestSubCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EOFTestSubCommand.java new file mode 100644 index 0000000000..cb2fbbfeb6 --- /dev/null +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EOFTestSubCommand.java @@ -0,0 +1,226 @@ +/* + * 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.evmtool; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hyperledger.besu.ethereum.referencetests.EOFTestCaseSpec.TestResult.failed; +import static org.hyperledger.besu.ethereum.referencetests.EOFTestCaseSpec.TestResult.passed; +import static org.hyperledger.besu.evmtool.EOFTestSubCommand.COMMAND_NAME; + +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.ethereum.referencetests.EOFTestCaseSpec; +import org.hyperledger.besu.ethereum.referencetests.EOFTestCaseSpec.TestResult; +import org.hyperledger.besu.evm.EvmSpecVersion; +import org.hyperledger.besu.evm.code.CodeFactory; +import org.hyperledger.besu.evm.code.CodeInvalid; +import org.hyperledger.besu.evm.code.CodeV1; +import org.hyperledger.besu.evm.code.CodeV1Validation; +import org.hyperledger.besu.evm.code.EOFLayout; +import org.hyperledger.besu.util.LogConfigurator; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.tuweni.bytes.Bytes; +import picocli.CommandLine; + +@CommandLine.Command( + name = COMMAND_NAME, + description = "Runs EOF validation reference tests", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class) +public class EOFTestSubCommand implements Runnable { + public static final String COMMAND_NAME = "eof-test"; + @CommandLine.ParentCommand private final EvmToolCommand parentCommand; + + // picocli does it magically + @CommandLine.Parameters private final List eofTestFiles = new ArrayList<>(); + + @CommandLine.Option( + names = {"--fork-name"}, + description = "Limit execution to one fork.") + private String forkName = null; + + @CommandLine.Option( + names = {"--test-name"}, + description = "Limit execution to one test.") + private String testVectorName = null; + + public EOFTestSubCommand() { + this(null); + } + + public EOFTestSubCommand(final EvmToolCommand parentCommand) { + this.parentCommand = parentCommand; + } + + @Override + public void run() { + LogConfigurator.setLevel("", "OFF"); + // presume ethereum mainnet for reference and EOF tests + SignatureAlgorithmFactory.setDefaultInstance(); + final ObjectMapper eofTestMapper = JsonUtils.createObjectMapper(); + + final JavaType javaType = + eofTestMapper + .getTypeFactory() + .constructParametricType(Map.class, String.class, EOFTestCaseSpec.class); + try { + if (eofTestFiles.isEmpty()) { + // if no EOF tests were specified use standard input to get filenames + final BufferedReader in = + new BufferedReader(new InputStreamReader(parentCommand.in, UTF_8)); + while (true) { + final String fileName = in.readLine(); + if (fileName == null) { + // reached end of file. Stop the loop. + break; + } + final File file = new File(fileName); + if (file.isFile()) { + final Map eofTests = eofTestMapper.readValue(file, javaType); + executeEOFTest(file.toString(), eofTests); + } else { + parentCommand.out.println("File not found: " + fileName); + } + } + } else { + for (final Path eofTestFile : eofTestFiles) { + final Map eofTests; + if ("stdin".equals(eofTestFile.toString())) { + eofTests = eofTestMapper.readValue(parentCommand.in, javaType); + } else { + eofTests = eofTestMapper.readValue(eofTestFile.toFile(), javaType); + } + executeEOFTest(eofTestFile.toString(), eofTests); + } + } + } catch (final JsonProcessingException jpe) { + parentCommand.out.println("File content error: " + jpe); + } catch (final IOException e) { + System.err.println("Unable to read EOF test file"); + e.printStackTrace(System.err); + } + } + + record TestExecutionResult( + String fileName, + String group, + String name, + String fork, + boolean pass, + String expectedError, + String actualError) {} + + private void executeEOFTest(final String fileName, final Map eofTests) { + List results = new ArrayList<>(); + + for (var testGroup : eofTests.entrySet()) { + String groupName = testGroup.getKey(); + for (var testVector : testGroup.getValue().getVector().entrySet()) { + String testName = testVector.getKey(); + if (testVectorName != null && !testVectorName.equals(testName)) { + continue; + } + String code = testVector.getValue().code(); + for (var testResult : testVector.getValue().results().entrySet()) { + String expectedForkName = testResult.getKey(); + if (forkName != null && !forkName.equals(expectedForkName)) { + continue; + } + TestResult expectedResult = testResult.getValue(); + EvmSpecVersion evmVersion = EvmSpecVersion.fromName(expectedForkName); + if (evmVersion == null) { + results.add( + new TestExecutionResult( + fileName, + groupName, + testName, + expectedForkName, + false, + "Valid fork name", + "Unknown fork: " + expectedForkName)); + + continue; + } + TestResult actualResult; + if (evmVersion.ordinal() < EvmSpecVersion.PRAGUE_EOF.ordinal()) { + actualResult = failed("EOF_InvalidCode"); + } else { + actualResult = considerCode(code); + } + results.add( + new TestExecutionResult( + fileName, + groupName, + testName, + expectedForkName, + actualResult.result() == expectedResult.result(), + expectedResult.exception(), + actualResult.exception())); + } + } + } + for (TestExecutionResult result : results) { + try { + parentCommand.out.println(JsonUtils.createObjectMapper().writeValueAsString(result)); + } catch (JsonProcessingException e) { + e.printStackTrace(parentCommand.out); + throw new RuntimeException(e); + } + } + } + + public TestResult considerCode(final String hexCode) { + Bytes codeBytes; + try { + codeBytes = + Bytes.fromHexString( + hexCode.replaceAll("(^|\n)#[^\n]*($|\n)", "").replaceAll("[^0-9A-Za-z]", "")); + } catch (RuntimeException re) { + return failed(re.getMessage()); + } + if (codeBytes.isEmpty()) { + return passed(); + } + + var layout = EOFLayout.parseEOF(codeBytes); + if (!layout.isValid()) { + return failed("layout - " + layout.invalidReason()); + } + + var code = CodeFactory.createCode(codeBytes, 1); + if (!code.isValid()) { + return failed("validate " + ((CodeInvalid) code).getInvalidReason()); + } + if (code instanceof CodeV1 codeV1) { + var result = CodeV1Validation.validate(codeV1.getEofLayout()); + if (result != null) { + return (failed("deep validate error: " + result)); + } + } + + return passed(); + } +} 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 9e322c61d0..b6e093aa20 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 @@ -87,6 +87,8 @@ import picocli.CommandLine.Option; BenchmarkSubCommand.class, B11rSubCommand.class, CodeValidateSubCommand.class, + EOFTestSubCommand.class, + PrettyPrintSubCommand.class, StateTestSubCommand.class, T8nSubCommand.class, T8nServerSubCommand.class @@ -140,6 +142,11 @@ public class EvmToolCommand implements Runnable { description = "Receiving address for this invocation.") private final Address receiver = Address.ZERO; + @Option( + names = {"--create"}, + description = "Run call should be a create instead of a call operation.") + private final Boolean createTransaction = false; + @Option( names = {"--contract"}, paramLabel = "
", @@ -340,7 +347,7 @@ public class EvmToolCommand implements Runnable { .nonce(0) .gasPrice(Wei.ZERO) .gasLimit(Long.MAX_VALUE) - .to(receiver) + .to(createTransaction ? null : receiver) .value(Wei.ZERO) .payload(callData) .sender(sender) @@ -361,10 +368,10 @@ public class EvmToolCommand implements Runnable { } final EVM evm = protocolSpec.getEvm(); - if (codeBytes.isEmpty()) { + if (codeBytes.isEmpty() && !createTransaction) { codeBytes = component.getWorldState().get(receiver).getCode(); } - Code code = evm.getCode(Hash.hash(codeBytes), codeBytes); + Code code = evm.getCodeForCreation(codeBytes); if (!code.isValid()) { out.println(((CodeInvalid) code).getInvalidReason()); return; @@ -381,7 +388,9 @@ public class EvmToolCommand implements Runnable { WorldUpdater updater = component.getWorldUpdater(); updater.getOrCreate(sender); - updater.getOrCreate(receiver); + if (!createTransaction) { + updater.getOrCreate(receiver); + } var contractAccount = updater.getOrCreate(contract); contractAccount.setCode(codeBytes); @@ -412,18 +421,23 @@ public class EvmToolCommand implements Runnable { .baseFee(component.getBlockchain().getChainHeadHeader().getBaseFee().orElse(null)) .buildBlockHeader(); + Address contractAddress = + createTransaction ? Address.contractAddress(receiver, 0) : receiver; MessageFrame initialMessageFrame = MessageFrame.builder() - .type(MessageFrame.Type.MESSAGE_CALL) + .type( + createTransaction + ? MessageFrame.Type.CONTRACT_CREATION + : MessageFrame.Type.MESSAGE_CALL) .worldUpdater(updater.updater()) .initialGas(txGas) - .contract(Address.ZERO) - .address(receiver) + .contract(contractAddress) + .address(contractAddress) .originator(sender) .sender(sender) .gasPrice(gasPriceGWei) .blobGasPrice(blobGasPrice) - .inputData(callData) + .inputData(createTransaction ? codeBytes.slice(code.getSize()) : callData) .value(ethValue) .apparentValue(ethValue) .code(code) diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MainnetGenesisFileModule.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MainnetGenesisFileModule.java index af7ed1fc8c..761c81811a 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MainnetGenesisFileModule.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MainnetGenesisFileModule.java @@ -116,6 +116,9 @@ class MainnetGenesisFileModule extends GenesisFileModule { Map.entry( "prague", createSchedule(new StubGenesisConfigOptions().pragueTime(0).baseFeePerGas(0x0a))), + Map.entry( + "pragueeof", + createSchedule(new StubGenesisConfigOptions().pragueEOFTime(0).baseFeePerGas(0x0a))), Map.entry( "futureeips", createSchedule(new StubGenesisConfigOptions().futureEipsTime(0).baseFeePerGas(0x0a))), diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/PrettyPrintSubCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/PrettyPrintSubCommand.java new file mode 100644 index 0000000000..4e03f1aa69 --- /dev/null +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/PrettyPrintSubCommand.java @@ -0,0 +1,81 @@ +/* + * 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.evmtool; + +import static org.hyperledger.besu.evmtool.PrettyPrintSubCommand.COMMAND_NAME; + +import org.hyperledger.besu.evm.code.CodeV1Validation; +import org.hyperledger.besu.evm.code.EOFLayout; +import org.hyperledger.besu.util.LogConfigurator; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; +import picocli.CommandLine; + +@CommandLine.Command( + name = COMMAND_NAME, + description = "Pretty Prints EOF Code", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class) +public class PrettyPrintSubCommand implements Runnable { + public static final String COMMAND_NAME = "pretty-print"; + @CommandLine.ParentCommand private final EvmToolCommand parentCommand; + + @CommandLine.Option( + names = {"-f", "--force"}, + description = "Always print well formated code, even if there is an error", + paramLabel = "") + private final Boolean force = false; + + // picocli does it magically + @CommandLine.Parameters private final List codeList = new ArrayList<>(); + + public PrettyPrintSubCommand() { + this(null); + } + + public PrettyPrintSubCommand(final EvmToolCommand parentCommand) { + this.parentCommand = parentCommand; + } + + @Override + public void run() { + LogConfigurator.setLevel("", "OFF"); + + for (var hexCode : codeList) { + Bytes container = Bytes.fromHexString(hexCode); + if (container.get(0) != ((byte) 0xef) && container.get(1) != 0) { + parentCommand.out.println( + "Pretty printing of legacy EVM is not supported. Patches welcome!"); + + } else { + EOFLayout layout = EOFLayout.parseEOF(container); + if (layout.isValid()) { + String validation = CodeV1Validation.validate(layout); + if (validation == null || force) { + layout.prettyPrint(parentCommand.out); + } + if (validation != null) { + parentCommand.out.println("EOF code is invalid - " + validation); + } + } else { + parentCommand.out.println("EOF layout is invalid - " + layout.invalidReason()); + } + } + } + } +} diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/StateTestSubCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/StateTestSubCommand.java index 3e1b0270a0..60a09b26e8 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/StateTestSubCommand.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/StateTestSubCommand.java @@ -308,7 +308,9 @@ public class StateTestSubCommand implements Runnable { "validationError", "Exception '" + spec.getExpectException() + "' was expected but did not occur"); } - + if (!result.getValidationResult().isValid()) { + summaryLine.put("error", result.getValidationResult().getErrorMessage()); + } if (parentCommand.showJsonAlloc) { EvmToolCommand.dumpWorldState(worldState, parentCommand.out); } diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/BenchmarkExecutor.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/BenchmarkExecutor.java index 8c68295993..50a6df67d4 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/BenchmarkExecutor.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/BenchmarkExecutor.java @@ -31,6 +31,7 @@ import org.hyperledger.besu.evm.gascalculator.HomesteadGasCalculator; import org.hyperledger.besu.evm.gascalculator.IstanbulGasCalculator; import org.hyperledger.besu.evm.gascalculator.LondonGasCalculator; import org.hyperledger.besu.evm.gascalculator.PetersburgGasCalculator; +import org.hyperledger.besu.evm.gascalculator.PragueEOFGasCalculator; import org.hyperledger.besu.evm.gascalculator.PragueGasCalculator; import org.hyperledger.besu.evm.gascalculator.ShanghaiGasCalculator; import org.hyperledger.besu.evm.precompile.PrecompiledContract; @@ -131,6 +132,8 @@ public abstract class BenchmarkExecutor { return switch (EvmSpecVersion.valueOf(fork.toUpperCase(Locale.ROOT))) { case HOMESTEAD -> new HomesteadGasCalculator(); case FRONTIER -> new FrontierGasCalculator(); + case TANGERINE_WHISTLE -> null; + case SPURIOUS_DRAGON -> null; case BYZANTIUM -> new ByzantiumGasCalculator(); case CONSTANTINOPLE -> new ConstantinopleGasCalculator(); case PETERSBURG -> new PetersburgGasCalculator(); @@ -139,7 +142,9 @@ public abstract class BenchmarkExecutor { case LONDON, PARIS -> new LondonGasCalculator(); case SHANGHAI -> new ShanghaiGasCalculator(); case CANCUN -> new CancunGasCalculator(); - default -> new PragueGasCalculator(); + case PRAGUE -> new PragueGasCalculator(); + case PRAGUE_EOF, OSAKA, AMSTERDAM, BOGOTA, POLIS, BANGKOK, FUTURE_EIPS, EXPERIMENTAL_EIPS -> + new PragueEOFGasCalculator(); }; } diff --git a/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java b/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java index f98256324a..87af915f66 100644 --- a/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java +++ b/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java @@ -24,24 +24,24 @@ import java.io.PrintStream; import org.junit.jupiter.api.Test; import picocli.CommandLine; -public class CodeValidationSubCommandTest { +class CodeValidationSubCommandTest { - static final String CODE_STOP_ONLY = "0xef0001 010004 020001-0001 030000 00 00000000 00"; - static final String CODE_RETF_ONLY = "0xef0001 010004 020001-0001 030000 00 00000000 e4"; - static final String CODE_BAD_MAGIC = "0xefffff 010004 020001-0001 030000 00 00000000 e4"; + static final String CODE_STOP_ONLY = "0xef0001 010004 020001-0001 040000 00 00800000 00"; + static final String CODE_RETURN_ONLY = "0xef0001 010004 020001-0003 040000 00 00800002 5f5ff3"; + static final String CODE_BAD_MAGIC = "0xefffff 010004 020001-0001 040000 00 00800000 e4"; static final String CODE_INTERIOR_COMMENTS = """ - 0xef0001 010008 020002-000c-0002 030000 00 + 0xef0001 010008 020002-0009-0002 040000 00 # 7 inputs 1 output, - 00000007-07010007 - 59-59-59-59-59-59-59-e30001-50-e4 + 00800004-04010004 + 59-59-59-59-e30001-50-00 # No immediate data - f1-e4"""; + f8-e4"""; static final String CODE_MULTIPLE = - CODE_STOP_ONLY + "\n" + CODE_BAD_MAGIC + "\n" + CODE_RETF_ONLY + "\n"; + CODE_STOP_ONLY + "\n" + CODE_BAD_MAGIC + "\n" + CODE_RETURN_ONLY + "\n"; @Test - public void testSingleValidViaInput() { + void testSingleValidViaInput() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(CODE_STOP_ONLY.getBytes(UTF_8)); final CodeValidateSubCommand codeValidateSubCommand = @@ -51,7 +51,7 @@ public class CodeValidationSubCommandTest { } @Test - public void testSingleInvalidViaInput() { + void testSingleInvalidViaInput() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(CODE_BAD_MAGIC.getBytes(UTF_8)); final CodeValidateSubCommand codeValidateSubCommand = @@ -61,7 +61,7 @@ public class CodeValidationSubCommandTest { } @Test - public void testMultipleViaInput() { + void testMultipleViaInput() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(CODE_MULTIPLE.getBytes(UTF_8)); final CodeValidateSubCommand codeValidateSubCommand = @@ -72,12 +72,12 @@ public class CodeValidationSubCommandTest { """ OK 00 err: layout - EOF header byte 1 incorrect - OK e4 + OK 5f5ff3 """); } @Test - public void testSingleValidViaCli() { + void testSingleValidViaCli() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]); final CodeValidateSubCommand codeValidateSubCommand = @@ -89,7 +89,7 @@ public class CodeValidationSubCommandTest { } @Test - public void testSingleInvalidViaCli() { + void testSingleInvalidViaCli() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]); final CodeValidateSubCommand codeValidateSubCommand = @@ -101,37 +101,37 @@ public class CodeValidationSubCommandTest { } @Test - public void testMultipleViaCli() { + void testMultipleViaCli() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]); final CodeValidateSubCommand codeValidateSubCommand = new CodeValidateSubCommand(bais, new PrintStream(baos)); final CommandLine cmd = new CommandLine(codeValidateSubCommand); - cmd.parseArgs(CODE_STOP_ONLY, CODE_BAD_MAGIC, CODE_RETF_ONLY); + cmd.parseArgs(CODE_STOP_ONLY, CODE_BAD_MAGIC, CODE_RETURN_ONLY); codeValidateSubCommand.run(); assertThat(baos.toString(UTF_8)) .contains( """ OK 00 err: layout - EOF header byte 1 incorrect - OK e4 + OK 5f5ff3 """); } @Test - public void testCliEclipsesInput() { + void testCliEclipsesInput() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(CODE_STOP_ONLY.getBytes(UTF_8)); final CodeValidateSubCommand codeValidateSubCommand = new CodeValidateSubCommand(bais, new PrintStream(baos)); final CommandLine cmd = new CommandLine(codeValidateSubCommand); - cmd.parseArgs(CODE_RETF_ONLY); + cmd.parseArgs(CODE_RETURN_ONLY); codeValidateSubCommand.run(); - assertThat(baos.toString(UTF_8)).contains("OK e4\n"); + assertThat(baos.toString(UTF_8)).contains("OK 5f5ff3\n"); } @Test - public void testInteriorCommentsSkipped() { + void testInteriorCommentsSkipped() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]); final CodeValidateSubCommand codeValidateSubCommand = @@ -139,11 +139,11 @@ public class CodeValidationSubCommandTest { final CommandLine cmd = new CommandLine(codeValidateSubCommand); cmd.parseArgs(CODE_INTERIOR_COMMENTS); codeValidateSubCommand.run(); - assertThat(baos.toString(UTF_8)).contains("OK 59595959595959e3000150e4,f1e4\n"); + assertThat(baos.toString(UTF_8)).contains("OK 59595959e300015000,f8e4\n"); } @Test - public void testBlankLinesAndCommentsSkipped() { + void testBlankLinesAndCommentsSkipped() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(("# comment\n\n#blank line\n\n" + CODE_MULTIPLE).getBytes(UTF_8)); @@ -155,7 +155,7 @@ public class CodeValidationSubCommandTest { """ OK 00 err: layout - EOF header byte 1 incorrect - OK e4 + OK 5f5ff3 """); } } diff --git a/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/EvmToolSpecTests.java b/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/EvmToolSpecTests.java index 504d1f0467..1892a472b0 100644 --- a/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/EvmToolSpecTests.java +++ b/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/EvmToolSpecTests.java @@ -57,6 +57,10 @@ public class EvmToolSpecTests { return findSpecFiles(new String[] {"b11r"}); } + public static Object[][] prettyPrintTests() { + return findSpecFiles(new String[] {"pretty-print"}); + } + public static Object[][] stateTestTests() { return findSpecFiles(new String[] {"state-test"}); } @@ -110,7 +114,7 @@ public class EvmToolSpecTests { } @ParameterizedTest(name = "{0}") - @MethodSource({"b11rTests", "stateTestTests", "t8nTests", "traceTests"}) + @MethodSource({"b11rTests", "prettyPrintTests", "stateTestTests", "t8nTests", "traceTests"}) void testBySpec( final String file, final JsonNode cliNode, diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/rjumpv-max.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/rjumpv-max.json new file mode 100644 index 0000000000..988a8e3157 --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/rjumpv-max.json @@ -0,0 +1,8 @@ +{ + "cli": [ + "pretty-print", + "0xef0001010004020001090b04000000008000025f35e2ff0007000e0015001c0023002a00310038003f0046004d0054005b0062006900700077007e0085008c0093009a00a100a800af00b600bd00c400cb00d200d900e000e700ee00f500fc0103010a01110118011f0126012d0134013b0142014901500157015e0165016c0173017a01810188018f0196019d01a401ab01b201b901c001c701ce01d501dc01e301ea01f101f801ff0206020d0214021b0222022902300237023e0245024c0253025a02610268026f0276027d0284028b0292029902a002a702ae02b502bc02c302ca02d102d802df02e602ed02f402fb0302030903100317031e0325032c0333033a03410348034f0356035d0364036b0372037903800387038e0395039c03a303aa03b103b803bf03c603cd03d403db03e203e903f003f703fe0405040c0413041a04210428042f0436043d0444044b0452045904600467046e0475047c0483048a04910498049f04a604ad04b404bb04c204c904d004d704de04e504ec04f304fa05010508050f0516051d0524052b0532053905400547054e0555055c0563056a05710578057f0586058d0594059b05a205a905b005b705be05c505cc05d305da05e105e805ef05f605fd0604060b0612061906200627062e0635063c0643064a06510658065f0666066d0674067b0682068906900697069e06a506ac06b306ba06c106c806cf06d606dd06e406eb06f206f9070061ffff600255006110006002550061100160025500611002600255006110036002550061100460025500611005600255006110066002550061100760025500611008600255006110096002550061100a6002550061100b6002550061100c6002550061100d6002550061100e6002550061100f600255006110106002550061101160025500611012600255006110136002550061101460025500611015600255006110166002550061101760025500611018600255006110196002550061101a6002550061101b6002550061101c6002550061101d6002550061101e6002550061101f600255006110206002550061102160025500611022600255006110236002550061102460025500611025600255006110266002550061102760025500611028600255006110296002550061102a6002550061102b6002550061102c6002550061102d6002550061102e6002550061102f600255006110306002550061103160025500611032600255006110336002550061103460025500611035600255006110366002550061103760025500611038600255006110396002550061103a6002550061103b6002550061103c6002550061103d6002550061103e6002550061103f600255006110406002550061104160025500611042600255006110436002550061104460025500611045600255006110466002550061104760025500611048600255006110496002550061104a6002550061104b6002550061104c6002550061104d6002550061104e6002550061104f600255006110506002550061105160025500611052600255006110536002550061105460025500611055600255006110566002550061105760025500611058600255006110596002550061105a6002550061105b6002550061105c6002550061105d6002550061105e6002550061105f600255006110606002550061106160025500611062600255006110636002550061106460025500611065600255006110666002550061106760025500611068600255006110696002550061106a6002550061106b6002550061106c6002550061106d6002550061106e6002550061106f600255006110706002550061107160025500611072600255006110736002550061107460025500611075600255006110766002550061107760025500611078600255006110796002550061107a6002550061107b6002550061107c6002550061107d6002550061107e6002550061107f600255006110806002550061108160025500611082600255006110836002550061108460025500611085600255006110866002550061108760025500611088600255006110896002550061108a6002550061108b6002550061108c6002550061108d6002550061108e6002550061108f600255006110906002550061109160025500611092600255006110936002550061109460025500611095600255006110966002550061109760025500611098600255006110996002550061109a6002550061109b6002550061109c6002550061109d6002550061109e6002550061109f600255006110a0600255006110a1600255006110a2600255006110a3600255006110a4600255006110a5600255006110a6600255006110a7600255006110a8600255006110a9600255006110aa600255006110ab600255006110ac600255006110ad600255006110ae600255006110af600255006110b0600255006110b1600255006110b2600255006110b3600255006110b4600255006110b5600255006110b6600255006110b7600255006110b8600255006110b9600255006110ba600255006110bb600255006110bc600255006110bd600255006110be600255006110bf600255006110c0600255006110c1600255006110c2600255006110c3600255006110c4600255006110c5600255006110c6600255006110c7600255006110c8600255006110c9600255006110ca600255006110cb600255006110cc600255006110cd600255006110ce600255006110cf600255006110d0600255006110d1600255006110d2600255006110d3600255006110d4600255006110d5600255006110d6600255006110d7600255006110d8600255006110d9600255006110da600255006110db600255006110dc600255006110dd600255006110de600255006110df600255006110e0600255006110e1600255006110e2600255006110e3600255006110e4600255006110e5600255006110e6600255006110e7600255006110e8600255006110e9600255006110ea600255006110eb600255006110ec600255006110ed600255006110ee600255006110ef600255006110f0600255006110f1600255006110f2600255006110f3600255006110f4600255006110f5600255006110f6600255006110f7600255006110f8600255006110f9600255006110fa600255006110fb600255006110fc600255006110fd600255006110fe600255006110ff60025500" + ], + "stdin": "", + "stdout": "0x # EOF\nef0001 # Magic and Version ( 1 )\n010004 # Types length ( 4 )\n020001 # Total code sections ( 1 )\n 090b # Code section 0 , 2315 bytes\n040000 # Data section length( 0 )\n 00 # Terminator (end of header)\n # Code section 0 types\n 00 # 0 inputs \n 80 # 0 outputs (Non-returning function)\n 0002 # max stack: 2\n # Code section 0 - in=0 out=non-returning height=2\n 5f # [0] PUSH0\n 35 # [1] CALLDATALOAD\ne2ff0007000e0015001c0023002a00310038003f0046004d0054005b0062006900700077007e0085008c0093009a00a100a800af00b600bd00c400cb00d200d900e000e700ee00f500fc0103010a01110118011f0126012d0134013b0142014901500157015e0165016c0173017a01810188018f0196019d01a401ab01b201b901c001c701ce01d501dc01e301ea01f101f801ff0206020d0214021b0222022902300237023e0245024c0253025a02610268026f0276027d0284028b0292029902a002a702ae02b502bc02c302ca02d102d802df02e602ed02f402fb0302030903100317031e0325032c0333033a03410348034f0356035d0364036b0372037903800387038e0395039c03a303aa03b103b803bf03c603cd03d403db03e203e903f003f703fe0405040c0413041a04210428042f0436043d0444044b0452045904600467046e0475047c0483048a04910498049f04a604ad04b404bb04c204c904d004d704de04e504ec04f304fa05010508050f0516051d0524052b0532053905400547054e0555055c0563056a05710578057f0586058d0594059b05a205a905b005b705be05c505cc05d305da05e105e805ef05f605fd0604060b0612061906200627062e0635063c0643064a06510658065f0666066d0674067b0682068906900697069e06a506ac06b306ba06c106c806cf06d606dd06e406eb06f206f90700 # [2] RJUMPV(7,14,21,28,35,42,49,56,63,70,77,84,91,98,105,112,119,126,133,140,147,154,161,168,175,182,189,196,203,210,217,224,231,238,245,252,259,266,273,280,287,294,301,308,315,322,329,336,343,350,357,364,371,378,385,392,399,406,413,420,427,434,441,448,455,462,469,476,483,490,497,504,511,518,525,532,539,546,553,560,567,574,581,588,595,602,609,616,623,630,637,644,651,658,665,672,679,686,693,700,707,714,721,728,735,742,749,756,763,770,777,784,791,798,805,812,819,826,833,840,847,854,861,868,875,882,889,896,903,910,917,924,931,938,945,952,959,966,973,980,987,994,1001,1008,1015,1022,1029,1036,1043,1050,1057,1064,1071,1078,1085,1092,1099,1106,1113,1120,1127,1134,1141,1148,1155,1162,1169,1176,1183,1190,1197,1204,1211,1218,1225,1232,1239,1246,1253,1260,1267,1274,1281,1288,1295,1302,1309,1316,1323,1330,1337,1344,1351,1358,1365,1372,1379,1386,1393,1400,1407,1414,1421,1428,1435,1442,1449,1456,1463,1470,1477,1484,1491,1498,1505,1512,1519,1526,1533,1540,1547,1554,1561,1568,1575,1582,1589,1596,1603,1610,1617,1624,1631,1638,1645,1652,1659,1666,1673,1680,1687,1694,1701,1708,1715,1722,1729,1736,1743,1750,1757,1764,1771,1778,1785,1792)\n61ffff # [516] PUSH2(0xffff)\n 6002 # [519] PUSH1(2)\n 55 # [521] SSTORE\n 00 # [522] STOP\n611000 # [523] PUSH2(0x1000)\n 6002 # [526] PUSH1(2)\n 55 # [528] SSTORE\n 00 # [529] STOP\n611001 # [530] PUSH2(0x1001)\n 6002 # [533] PUSH1(2)\n 55 # [535] SSTORE\n 00 # [536] STOP\n611002 # [537] PUSH2(0x1002)\n 6002 # [540] PUSH1(2)\n 55 # [542] SSTORE\n 00 # [543] STOP\n611003 # [544] PUSH2(0x1003)\n 6002 # [547] PUSH1(2)\n 55 # [549] SSTORE\n 00 # [550] STOP\n611004 # [551] PUSH2(0x1004)\n 6002 # [554] PUSH1(2)\n 55 # [556] SSTORE\n 00 # [557] STOP\n611005 # [558] PUSH2(0x1005)\n 6002 # [561] PUSH1(2)\n 55 # [563] SSTORE\n 00 # [564] STOP\n611006 # [565] PUSH2(0x1006)\n 6002 # [568] PUSH1(2)\n 55 # [570] SSTORE\n 00 # [571] STOP\n611007 # [572] PUSH2(0x1007)\n 6002 # [575] PUSH1(2)\n 55 # [577] SSTORE\n 00 # [578] STOP\n611008 # [579] PUSH2(0x1008)\n 6002 # [582] PUSH1(2)\n 55 # [584] SSTORE\n 00 # [585] STOP\n611009 # [586] PUSH2(0x1009)\n 6002 # [589] PUSH1(2)\n 55 # [591] SSTORE\n 00 # [592] STOP\n61100a # [593] PUSH2(0x100a)\n 6002 # [596] PUSH1(2)\n 55 # [598] SSTORE\n 00 # [599] STOP\n61100b # [600] PUSH2(0x100b)\n 6002 # [603] PUSH1(2)\n 55 # [605] SSTORE\n 00 # [606] STOP\n61100c # [607] PUSH2(0x100c)\n 6002 # [610] PUSH1(2)\n 55 # [612] SSTORE\n 00 # [613] STOP\n61100d # [614] PUSH2(0x100d)\n 6002 # [617] PUSH1(2)\n 55 # [619] SSTORE\n 00 # [620] STOP\n61100e # [621] PUSH2(0x100e)\n 6002 # [624] PUSH1(2)\n 55 # [626] SSTORE\n 00 # [627] STOP\n61100f # [628] PUSH2(0x100f)\n 6002 # [631] PUSH1(2)\n 55 # [633] SSTORE\n 00 # [634] STOP\n611010 # [635] PUSH2(0x1010)\n 6002 # [638] PUSH1(2)\n 55 # [640] SSTORE\n 00 # [641] STOP\n611011 # [642] PUSH2(0x1011)\n 6002 # [645] PUSH1(2)\n 55 # [647] SSTORE\n 00 # [648] STOP\n611012 # [649] PUSH2(0x1012)\n 6002 # [652] PUSH1(2)\n 55 # [654] SSTORE\n 00 # [655] STOP\n611013 # [656] PUSH2(0x1013)\n 6002 # [659] PUSH1(2)\n 55 # [661] SSTORE\n 00 # [662] STOP\n611014 # [663] PUSH2(0x1014)\n 6002 # [666] PUSH1(2)\n 55 # [668] SSTORE\n 00 # [669] STOP\n611015 # [670] PUSH2(0x1015)\n 6002 # [673] PUSH1(2)\n 55 # [675] SSTORE\n 00 # [676] STOP\n611016 # [677] PUSH2(0x1016)\n 6002 # [680] PUSH1(2)\n 55 # [682] SSTORE\n 00 # [683] STOP\n611017 # [684] PUSH2(0x1017)\n 6002 # [687] PUSH1(2)\n 55 # [689] SSTORE\n 00 # [690] STOP\n611018 # [691] PUSH2(0x1018)\n 6002 # [694] PUSH1(2)\n 55 # [696] SSTORE\n 00 # [697] STOP\n611019 # [698] PUSH2(0x1019)\n 6002 # [701] PUSH1(2)\n 55 # [703] SSTORE\n 00 # [704] STOP\n61101a # [705] PUSH2(0x101a)\n 6002 # [708] PUSH1(2)\n 55 # [710] SSTORE\n 00 # [711] STOP\n61101b # [712] PUSH2(0x101b)\n 6002 # [715] PUSH1(2)\n 55 # [717] SSTORE\n 00 # [718] STOP\n61101c # [719] PUSH2(0x101c)\n 6002 # [722] PUSH1(2)\n 55 # [724] SSTORE\n 00 # [725] STOP\n61101d # [726] PUSH2(0x101d)\n 6002 # [729] PUSH1(2)\n 55 # [731] SSTORE\n 00 # [732] STOP\n61101e # [733] PUSH2(0x101e)\n 6002 # [736] PUSH1(2)\n 55 # [738] SSTORE\n 00 # [739] STOP\n61101f # [740] PUSH2(0x101f)\n 6002 # [743] PUSH1(2)\n 55 # [745] SSTORE\n 00 # [746] STOP\n611020 # [747] PUSH2(0x1020)\n 6002 # [750] PUSH1(2)\n 55 # [752] SSTORE\n 00 # [753] STOP\n611021 # [754] PUSH2(0x1021)\n 6002 # [757] PUSH1(2)\n 55 # [759] SSTORE\n 00 # [760] STOP\n611022 # [761] PUSH2(0x1022)\n 6002 # [764] PUSH1(2)\n 55 # [766] SSTORE\n 00 # [767] STOP\n611023 # [768] PUSH2(0x1023)\n 6002 # [771] PUSH1(2)\n 55 # [773] SSTORE\n 00 # [774] STOP\n611024 # [775] PUSH2(0x1024)\n 6002 # [778] PUSH1(2)\n 55 # [780] SSTORE\n 00 # [781] STOP\n611025 # [782] PUSH2(0x1025)\n 6002 # [785] PUSH1(2)\n 55 # [787] SSTORE\n 00 # [788] STOP\n611026 # [789] PUSH2(0x1026)\n 6002 # [792] PUSH1(2)\n 55 # [794] SSTORE\n 00 # [795] STOP\n611027 # [796] PUSH2(0x1027)\n 6002 # [799] PUSH1(2)\n 55 # [801] SSTORE\n 00 # [802] STOP\n611028 # [803] PUSH2(0x1028)\n 6002 # [806] PUSH1(2)\n 55 # [808] SSTORE\n 00 # [809] STOP\n611029 # [810] PUSH2(0x1029)\n 6002 # [813] PUSH1(2)\n 55 # [815] SSTORE\n 00 # [816] STOP\n61102a # [817] PUSH2(0x102a)\n 6002 # [820] PUSH1(2)\n 55 # [822] SSTORE\n 00 # [823] STOP\n61102b # [824] PUSH2(0x102b)\n 6002 # [827] PUSH1(2)\n 55 # [829] SSTORE\n 00 # [830] STOP\n61102c # [831] PUSH2(0x102c)\n 6002 # [834] PUSH1(2)\n 55 # [836] SSTORE\n 00 # [837] STOP\n61102d # [838] PUSH2(0x102d)\n 6002 # [841] PUSH1(2)\n 55 # [843] SSTORE\n 00 # [844] STOP\n61102e # [845] PUSH2(0x102e)\n 6002 # [848] PUSH1(2)\n 55 # [850] SSTORE\n 00 # [851] STOP\n61102f # [852] PUSH2(0x102f)\n 6002 # [855] PUSH1(2)\n 55 # [857] SSTORE\n 00 # [858] STOP\n611030 # [859] PUSH2(0x1030)\n 6002 # [862] PUSH1(2)\n 55 # [864] SSTORE\n 00 # [865] STOP\n611031 # [866] PUSH2(0x1031)\n 6002 # [869] PUSH1(2)\n 55 # [871] SSTORE\n 00 # [872] STOP\n611032 # [873] PUSH2(0x1032)\n 6002 # [876] PUSH1(2)\n 55 # [878] SSTORE\n 00 # [879] STOP\n611033 # [880] PUSH2(0x1033)\n 6002 # [883] PUSH1(2)\n 55 # [885] SSTORE\n 00 # [886] STOP\n611034 # [887] PUSH2(0x1034)\n 6002 # [890] PUSH1(2)\n 55 # [892] SSTORE\n 00 # [893] STOP\n611035 # [894] PUSH2(0x1035)\n 6002 # [897] PUSH1(2)\n 55 # [899] SSTORE\n 00 # [900] STOP\n611036 # [901] PUSH2(0x1036)\n 6002 # [904] PUSH1(2)\n 55 # [906] SSTORE\n 00 # [907] STOP\n611037 # [908] PUSH2(0x1037)\n 6002 # [911] PUSH1(2)\n 55 # [913] SSTORE\n 00 # [914] STOP\n611038 # [915] PUSH2(0x1038)\n 6002 # [918] PUSH1(2)\n 55 # [920] SSTORE\n 00 # [921] STOP\n611039 # [922] PUSH2(0x1039)\n 6002 # [925] PUSH1(2)\n 55 # [927] SSTORE\n 00 # [928] STOP\n61103a # [929] PUSH2(0x103a)\n 6002 # [932] PUSH1(2)\n 55 # [934] SSTORE\n 00 # [935] STOP\n61103b # [936] PUSH2(0x103b)\n 6002 # [939] PUSH1(2)\n 55 # [941] SSTORE\n 00 # [942] STOP\n61103c # [943] PUSH2(0x103c)\n 6002 # [946] PUSH1(2)\n 55 # [948] SSTORE\n 00 # [949] STOP\n61103d # [950] PUSH2(0x103d)\n 6002 # [953] PUSH1(2)\n 55 # [955] SSTORE\n 00 # [956] STOP\n61103e # [957] PUSH2(0x103e)\n 6002 # [960] PUSH1(2)\n 55 # [962] SSTORE\n 00 # [963] STOP\n61103f # [964] PUSH2(0x103f)\n 6002 # [967] PUSH1(2)\n 55 # [969] SSTORE\n 00 # [970] STOP\n611040 # [971] PUSH2(0x1040)\n 6002 # [974] PUSH1(2)\n 55 # [976] SSTORE\n 00 # [977] STOP\n611041 # [978] PUSH2(0x1041)\n 6002 # [981] PUSH1(2)\n 55 # [983] SSTORE\n 00 # [984] STOP\n611042 # [985] PUSH2(0x1042)\n 6002 # [988] PUSH1(2)\n 55 # [990] SSTORE\n 00 # [991] STOP\n611043 # [992] PUSH2(0x1043)\n 6002 # [995] PUSH1(2)\n 55 # [997] SSTORE\n 00 # [998] STOP\n611044 # [999] PUSH2(0x1044)\n 6002 # [1002] PUSH1(2)\n 55 # [1004] SSTORE\n 00 # [1005] STOP\n611045 # [1006] PUSH2(0x1045)\n 6002 # [1009] PUSH1(2)\n 55 # [1011] SSTORE\n 00 # [1012] STOP\n611046 # [1013] PUSH2(0x1046)\n 6002 # [1016] PUSH1(2)\n 55 # [1018] SSTORE\n 00 # [1019] STOP\n611047 # [1020] PUSH2(0x1047)\n 6002 # [1023] PUSH1(2)\n 55 # [1025] SSTORE\n 00 # [1026] STOP\n611048 # [1027] PUSH2(0x1048)\n 6002 # [1030] PUSH1(2)\n 55 # [1032] SSTORE\n 00 # [1033] STOP\n611049 # [1034] PUSH2(0x1049)\n 6002 # [1037] PUSH1(2)\n 55 # [1039] SSTORE\n 00 # [1040] STOP\n61104a # [1041] PUSH2(0x104a)\n 6002 # [1044] PUSH1(2)\n 55 # [1046] SSTORE\n 00 # [1047] STOP\n61104b # [1048] PUSH2(0x104b)\n 6002 # [1051] PUSH1(2)\n 55 # [1053] SSTORE\n 00 # [1054] STOP\n61104c # [1055] PUSH2(0x104c)\n 6002 # [1058] PUSH1(2)\n 55 # [1060] SSTORE\n 00 # [1061] STOP\n61104d # [1062] PUSH2(0x104d)\n 6002 # [1065] PUSH1(2)\n 55 # [1067] SSTORE\n 00 # [1068] STOP\n61104e # [1069] PUSH2(0x104e)\n 6002 # [1072] PUSH1(2)\n 55 # [1074] SSTORE\n 00 # [1075] STOP\n61104f # [1076] PUSH2(0x104f)\n 6002 # [1079] PUSH1(2)\n 55 # [1081] SSTORE\n 00 # [1082] STOP\n611050 # [1083] PUSH2(0x1050)\n 6002 # [1086] PUSH1(2)\n 55 # [1088] SSTORE\n 00 # [1089] STOP\n611051 # [1090] PUSH2(0x1051)\n 6002 # [1093] PUSH1(2)\n 55 # [1095] SSTORE\n 00 # [1096] STOP\n611052 # [1097] PUSH2(0x1052)\n 6002 # [1100] PUSH1(2)\n 55 # [1102] SSTORE\n 00 # [1103] STOP\n611053 # [1104] PUSH2(0x1053)\n 6002 # [1107] PUSH1(2)\n 55 # [1109] SSTORE\n 00 # [1110] STOP\n611054 # [1111] PUSH2(0x1054)\n 6002 # [1114] PUSH1(2)\n 55 # [1116] SSTORE\n 00 # [1117] STOP\n611055 # [1118] PUSH2(0x1055)\n 6002 # [1121] PUSH1(2)\n 55 # [1123] SSTORE\n 00 # [1124] STOP\n611056 # [1125] PUSH2(0x1056)\n 6002 # [1128] PUSH1(2)\n 55 # [1130] SSTORE\n 00 # [1131] STOP\n611057 # [1132] PUSH2(0x1057)\n 6002 # [1135] PUSH1(2)\n 55 # [1137] SSTORE\n 00 # [1138] STOP\n611058 # [1139] PUSH2(0x1058)\n 6002 # [1142] PUSH1(2)\n 55 # [1144] SSTORE\n 00 # [1145] STOP\n611059 # [1146] PUSH2(0x1059)\n 6002 # [1149] PUSH1(2)\n 55 # [1151] SSTORE\n 00 # [1152] STOP\n61105a # [1153] PUSH2(0x105a)\n 6002 # [1156] PUSH1(2)\n 55 # [1158] SSTORE\n 00 # [1159] STOP\n61105b # [1160] PUSH2(0x105b)\n 6002 # [1163] PUSH1(2)\n 55 # [1165] SSTORE\n 00 # [1166] STOP\n61105c # [1167] PUSH2(0x105c)\n 6002 # [1170] PUSH1(2)\n 55 # [1172] SSTORE\n 00 # [1173] STOP\n61105d # [1174] PUSH2(0x105d)\n 6002 # [1177] PUSH1(2)\n 55 # [1179] SSTORE\n 00 # [1180] STOP\n61105e # [1181] PUSH2(0x105e)\n 6002 # [1184] PUSH1(2)\n 55 # [1186] SSTORE\n 00 # [1187] STOP\n61105f # [1188] PUSH2(0x105f)\n 6002 # [1191] PUSH1(2)\n 55 # [1193] SSTORE\n 00 # [1194] STOP\n611060 # [1195] PUSH2(0x1060)\n 6002 # [1198] PUSH1(2)\n 55 # [1200] SSTORE\n 00 # [1201] STOP\n611061 # [1202] PUSH2(0x1061)\n 6002 # [1205] PUSH1(2)\n 55 # [1207] SSTORE\n 00 # [1208] STOP\n611062 # [1209] PUSH2(0x1062)\n 6002 # [1212] PUSH1(2)\n 55 # [1214] SSTORE\n 00 # [1215] STOP\n611063 # [1216] PUSH2(0x1063)\n 6002 # [1219] PUSH1(2)\n 55 # [1221] SSTORE\n 00 # [1222] STOP\n611064 # [1223] PUSH2(0x1064)\n 6002 # [1226] PUSH1(2)\n 55 # [1228] SSTORE\n 00 # [1229] STOP\n611065 # [1230] PUSH2(0x1065)\n 6002 # [1233] PUSH1(2)\n 55 # [1235] SSTORE\n 00 # [1236] STOP\n611066 # [1237] PUSH2(0x1066)\n 6002 # [1240] PUSH1(2)\n 55 # [1242] SSTORE\n 00 # [1243] STOP\n611067 # [1244] PUSH2(0x1067)\n 6002 # [1247] PUSH1(2)\n 55 # [1249] SSTORE\n 00 # [1250] STOP\n611068 # [1251] PUSH2(0x1068)\n 6002 # [1254] PUSH1(2)\n 55 # [1256] SSTORE\n 00 # [1257] STOP\n611069 # [1258] PUSH2(0x1069)\n 6002 # [1261] PUSH1(2)\n 55 # [1263] SSTORE\n 00 # [1264] STOP\n61106a # [1265] PUSH2(0x106a)\n 6002 # [1268] PUSH1(2)\n 55 # [1270] SSTORE\n 00 # [1271] STOP\n61106b # [1272] PUSH2(0x106b)\n 6002 # [1275] PUSH1(2)\n 55 # [1277] SSTORE\n 00 # [1278] STOP\n61106c # [1279] PUSH2(0x106c)\n 6002 # [1282] PUSH1(2)\n 55 # [1284] SSTORE\n 00 # [1285] STOP\n61106d # [1286] PUSH2(0x106d)\n 6002 # [1289] PUSH1(2)\n 55 # [1291] SSTORE\n 00 # [1292] STOP\n61106e # [1293] PUSH2(0x106e)\n 6002 # [1296] PUSH1(2)\n 55 # [1298] SSTORE\n 00 # [1299] STOP\n61106f # [1300] PUSH2(0x106f)\n 6002 # [1303] PUSH1(2)\n 55 # [1305] SSTORE\n 00 # [1306] STOP\n611070 # [1307] PUSH2(0x1070)\n 6002 # [1310] PUSH1(2)\n 55 # [1312] SSTORE\n 00 # [1313] STOP\n611071 # [1314] PUSH2(0x1071)\n 6002 # [1317] PUSH1(2)\n 55 # [1319] SSTORE\n 00 # [1320] STOP\n611072 # [1321] PUSH2(0x1072)\n 6002 # [1324] PUSH1(2)\n 55 # [1326] SSTORE\n 00 # [1327] STOP\n611073 # [1328] PUSH2(0x1073)\n 6002 # [1331] PUSH1(2)\n 55 # [1333] SSTORE\n 00 # [1334] STOP\n611074 # [1335] PUSH2(0x1074)\n 6002 # [1338] PUSH1(2)\n 55 # [1340] SSTORE\n 00 # [1341] STOP\n611075 # [1342] PUSH2(0x1075)\n 6002 # [1345] PUSH1(2)\n 55 # [1347] SSTORE\n 00 # [1348] STOP\n611076 # [1349] PUSH2(0x1076)\n 6002 # [1352] PUSH1(2)\n 55 # [1354] SSTORE\n 00 # [1355] STOP\n611077 # [1356] PUSH2(0x1077)\n 6002 # [1359] PUSH1(2)\n 55 # [1361] SSTORE\n 00 # [1362] STOP\n611078 # [1363] PUSH2(0x1078)\n 6002 # [1366] PUSH1(2)\n 55 # [1368] SSTORE\n 00 # [1369] STOP\n611079 # [1370] PUSH2(0x1079)\n 6002 # [1373] PUSH1(2)\n 55 # [1375] SSTORE\n 00 # [1376] STOP\n61107a # [1377] PUSH2(0x107a)\n 6002 # [1380] PUSH1(2)\n 55 # [1382] SSTORE\n 00 # [1383] STOP\n61107b # [1384] PUSH2(0x107b)\n 6002 # [1387] PUSH1(2)\n 55 # [1389] SSTORE\n 00 # [1390] STOP\n61107c # [1391] PUSH2(0x107c)\n 6002 # [1394] PUSH1(2)\n 55 # [1396] SSTORE\n 00 # [1397] STOP\n61107d # [1398] PUSH2(0x107d)\n 6002 # [1401] PUSH1(2)\n 55 # [1403] SSTORE\n 00 # [1404] STOP\n61107e # [1405] PUSH2(0x107e)\n 6002 # [1408] PUSH1(2)\n 55 # [1410] SSTORE\n 00 # [1411] STOP\n61107f # [1412] PUSH2(0x107f)\n 6002 # [1415] PUSH1(2)\n 55 # [1417] SSTORE\n 00 # [1418] STOP\n611080 # [1419] PUSH2(0x1080)\n 6002 # [1422] PUSH1(2)\n 55 # [1424] SSTORE\n 00 # [1425] STOP\n611081 # [1426] PUSH2(0x1081)\n 6002 # [1429] PUSH1(2)\n 55 # [1431] SSTORE\n 00 # [1432] STOP\n611082 # [1433] PUSH2(0x1082)\n 6002 # [1436] PUSH1(2)\n 55 # [1438] SSTORE\n 00 # [1439] STOP\n611083 # [1440] PUSH2(0x1083)\n 6002 # [1443] PUSH1(2)\n 55 # [1445] SSTORE\n 00 # [1446] STOP\n611084 # [1447] PUSH2(0x1084)\n 6002 # [1450] PUSH1(2)\n 55 # [1452] SSTORE\n 00 # [1453] STOP\n611085 # [1454] PUSH2(0x1085)\n 6002 # [1457] PUSH1(2)\n 55 # [1459] SSTORE\n 00 # [1460] STOP\n611086 # [1461] PUSH2(0x1086)\n 6002 # [1464] PUSH1(2)\n 55 # [1466] SSTORE\n 00 # [1467] STOP\n611087 # [1468] PUSH2(0x1087)\n 6002 # [1471] PUSH1(2)\n 55 # [1473] SSTORE\n 00 # [1474] STOP\n611088 # [1475] PUSH2(0x1088)\n 6002 # [1478] PUSH1(2)\n 55 # [1480] SSTORE\n 00 # [1481] STOP\n611089 # [1482] PUSH2(0x1089)\n 6002 # [1485] PUSH1(2)\n 55 # [1487] SSTORE\n 00 # [1488] STOP\n61108a # [1489] PUSH2(0x108a)\n 6002 # [1492] PUSH1(2)\n 55 # [1494] SSTORE\n 00 # [1495] STOP\n61108b # [1496] PUSH2(0x108b)\n 6002 # [1499] PUSH1(2)\n 55 # [1501] SSTORE\n 00 # [1502] STOP\n61108c # [1503] PUSH2(0x108c)\n 6002 # [1506] PUSH1(2)\n 55 # [1508] SSTORE\n 00 # [1509] STOP\n61108d # [1510] PUSH2(0x108d)\n 6002 # [1513] PUSH1(2)\n 55 # [1515] SSTORE\n 00 # [1516] STOP\n61108e # [1517] PUSH2(0x108e)\n 6002 # [1520] PUSH1(2)\n 55 # [1522] SSTORE\n 00 # [1523] STOP\n61108f # [1524] PUSH2(0x108f)\n 6002 # [1527] PUSH1(2)\n 55 # [1529] SSTORE\n 00 # [1530] STOP\n611090 # [1531] PUSH2(0x1090)\n 6002 # [1534] PUSH1(2)\n 55 # [1536] SSTORE\n 00 # [1537] STOP\n611091 # [1538] PUSH2(0x1091)\n 6002 # [1541] PUSH1(2)\n 55 # [1543] SSTORE\n 00 # [1544] STOP\n611092 # [1545] PUSH2(0x1092)\n 6002 # [1548] PUSH1(2)\n 55 # [1550] SSTORE\n 00 # [1551] STOP\n611093 # [1552] PUSH2(0x1093)\n 6002 # [1555] PUSH1(2)\n 55 # [1557] SSTORE\n 00 # [1558] STOP\n611094 # [1559] PUSH2(0x1094)\n 6002 # [1562] PUSH1(2)\n 55 # [1564] SSTORE\n 00 # [1565] STOP\n611095 # [1566] PUSH2(0x1095)\n 6002 # [1569] PUSH1(2)\n 55 # [1571] SSTORE\n 00 # [1572] STOP\n611096 # [1573] PUSH2(0x1096)\n 6002 # [1576] PUSH1(2)\n 55 # [1578] SSTORE\n 00 # [1579] STOP\n611097 # [1580] PUSH2(0x1097)\n 6002 # [1583] PUSH1(2)\n 55 # [1585] SSTORE\n 00 # [1586] STOP\n611098 # [1587] PUSH2(0x1098)\n 6002 # [1590] PUSH1(2)\n 55 # [1592] SSTORE\n 00 # [1593] STOP\n611099 # [1594] PUSH2(0x1099)\n 6002 # [1597] PUSH1(2)\n 55 # [1599] SSTORE\n 00 # [1600] STOP\n61109a # [1601] PUSH2(0x109a)\n 6002 # [1604] PUSH1(2)\n 55 # [1606] SSTORE\n 00 # [1607] STOP\n61109b # [1608] PUSH2(0x109b)\n 6002 # [1611] PUSH1(2)\n 55 # [1613] SSTORE\n 00 # [1614] STOP\n61109c # [1615] PUSH2(0x109c)\n 6002 # [1618] PUSH1(2)\n 55 # [1620] SSTORE\n 00 # [1621] STOP\n61109d # [1622] PUSH2(0x109d)\n 6002 # [1625] PUSH1(2)\n 55 # [1627] SSTORE\n 00 # [1628] STOP\n61109e # [1629] PUSH2(0x109e)\n 6002 # [1632] PUSH1(2)\n 55 # [1634] SSTORE\n 00 # [1635] STOP\n61109f # [1636] PUSH2(0x109f)\n 6002 # [1639] PUSH1(2)\n 55 # [1641] SSTORE\n 00 # [1642] STOP\n6110a0 # [1643] PUSH2(0x10a0)\n 6002 # [1646] PUSH1(2)\n 55 # [1648] SSTORE\n 00 # [1649] STOP\n6110a1 # [1650] PUSH2(0x10a1)\n 6002 # [1653] PUSH1(2)\n 55 # [1655] SSTORE\n 00 # [1656] STOP\n6110a2 # [1657] PUSH2(0x10a2)\n 6002 # [1660] PUSH1(2)\n 55 # [1662] SSTORE\n 00 # [1663] STOP\n6110a3 # [1664] PUSH2(0x10a3)\n 6002 # [1667] PUSH1(2)\n 55 # [1669] SSTORE\n 00 # [1670] STOP\n6110a4 # [1671] PUSH2(0x10a4)\n 6002 # [1674] PUSH1(2)\n 55 # [1676] SSTORE\n 00 # [1677] STOP\n6110a5 # [1678] PUSH2(0x10a5)\n 6002 # [1681] PUSH1(2)\n 55 # [1683] SSTORE\n 00 # [1684] STOP\n6110a6 # [1685] PUSH2(0x10a6)\n 6002 # [1688] PUSH1(2)\n 55 # [1690] SSTORE\n 00 # [1691] STOP\n6110a7 # [1692] PUSH2(0x10a7)\n 6002 # [1695] PUSH1(2)\n 55 # [1697] SSTORE\n 00 # [1698] STOP\n6110a8 # [1699] PUSH2(0x10a8)\n 6002 # [1702] PUSH1(2)\n 55 # [1704] SSTORE\n 00 # [1705] STOP\n6110a9 # [1706] PUSH2(0x10a9)\n 6002 # [1709] PUSH1(2)\n 55 # [1711] SSTORE\n 00 # [1712] STOP\n6110aa # [1713] PUSH2(0x10aa)\n 6002 # [1716] PUSH1(2)\n 55 # [1718] SSTORE\n 00 # [1719] STOP\n6110ab # [1720] PUSH2(0x10ab)\n 6002 # [1723] PUSH1(2)\n 55 # [1725] SSTORE\n 00 # [1726] STOP\n6110ac # [1727] PUSH2(0x10ac)\n 6002 # [1730] PUSH1(2)\n 55 # [1732] SSTORE\n 00 # [1733] STOP\n6110ad # [1734] PUSH2(0x10ad)\n 6002 # [1737] PUSH1(2)\n 55 # [1739] SSTORE\n 00 # [1740] STOP\n6110ae # [1741] PUSH2(0x10ae)\n 6002 # [1744] PUSH1(2)\n 55 # [1746] SSTORE\n 00 # [1747] STOP\n6110af # [1748] PUSH2(0x10af)\n 6002 # [1751] PUSH1(2)\n 55 # [1753] SSTORE\n 00 # [1754] STOP\n6110b0 # [1755] PUSH2(0x10b0)\n 6002 # [1758] PUSH1(2)\n 55 # [1760] SSTORE\n 00 # [1761] STOP\n6110b1 # [1762] PUSH2(0x10b1)\n 6002 # [1765] PUSH1(2)\n 55 # [1767] SSTORE\n 00 # [1768] STOP\n6110b2 # [1769] PUSH2(0x10b2)\n 6002 # [1772] PUSH1(2)\n 55 # [1774] SSTORE\n 00 # [1775] STOP\n6110b3 # [1776] PUSH2(0x10b3)\n 6002 # [1779] PUSH1(2)\n 55 # [1781] SSTORE\n 00 # [1782] STOP\n6110b4 # [1783] PUSH2(0x10b4)\n 6002 # [1786] PUSH1(2)\n 55 # [1788] SSTORE\n 00 # [1789] STOP\n6110b5 # [1790] PUSH2(0x10b5)\n 6002 # [1793] PUSH1(2)\n 55 # [1795] SSTORE\n 00 # [1796] STOP\n6110b6 # [1797] PUSH2(0x10b6)\n 6002 # [1800] PUSH1(2)\n 55 # [1802] SSTORE\n 00 # [1803] STOP\n6110b7 # [1804] PUSH2(0x10b7)\n 6002 # [1807] PUSH1(2)\n 55 # [1809] SSTORE\n 00 # [1810] STOP\n6110b8 # [1811] PUSH2(0x10b8)\n 6002 # [1814] PUSH1(2)\n 55 # [1816] SSTORE\n 00 # [1817] STOP\n6110b9 # [1818] PUSH2(0x10b9)\n 6002 # [1821] PUSH1(2)\n 55 # [1823] SSTORE\n 00 # [1824] STOP\n6110ba # [1825] PUSH2(0x10ba)\n 6002 # [1828] PUSH1(2)\n 55 # [1830] SSTORE\n 00 # [1831] STOP\n6110bb # [1832] PUSH2(0x10bb)\n 6002 # [1835] PUSH1(2)\n 55 # [1837] SSTORE\n 00 # [1838] STOP\n6110bc # [1839] PUSH2(0x10bc)\n 6002 # [1842] PUSH1(2)\n 55 # [1844] SSTORE\n 00 # [1845] STOP\n6110bd # [1846] PUSH2(0x10bd)\n 6002 # [1849] PUSH1(2)\n 55 # [1851] SSTORE\n 00 # [1852] STOP\n6110be # [1853] PUSH2(0x10be)\n 6002 # [1856] PUSH1(2)\n 55 # [1858] SSTORE\n 00 # [1859] STOP\n6110bf # [1860] PUSH2(0x10bf)\n 6002 # [1863] PUSH1(2)\n 55 # [1865] SSTORE\n 00 # [1866] STOP\n6110c0 # [1867] PUSH2(0x10c0)\n 6002 # [1870] PUSH1(2)\n 55 # [1872] SSTORE\n 00 # [1873] STOP\n6110c1 # [1874] PUSH2(0x10c1)\n 6002 # [1877] PUSH1(2)\n 55 # [1879] SSTORE\n 00 # [1880] STOP\n6110c2 # [1881] PUSH2(0x10c2)\n 6002 # [1884] PUSH1(2)\n 55 # [1886] SSTORE\n 00 # [1887] STOP\n6110c3 # [1888] PUSH2(0x10c3)\n 6002 # [1891] PUSH1(2)\n 55 # [1893] SSTORE\n 00 # [1894] STOP\n6110c4 # [1895] PUSH2(0x10c4)\n 6002 # [1898] PUSH1(2)\n 55 # [1900] SSTORE\n 00 # [1901] STOP\n6110c5 # [1902] PUSH2(0x10c5)\n 6002 # [1905] PUSH1(2)\n 55 # [1907] SSTORE\n 00 # [1908] STOP\n6110c6 # [1909] PUSH2(0x10c6)\n 6002 # [1912] PUSH1(2)\n 55 # [1914] SSTORE\n 00 # [1915] STOP\n6110c7 # [1916] PUSH2(0x10c7)\n 6002 # [1919] PUSH1(2)\n 55 # [1921] SSTORE\n 00 # [1922] STOP\n6110c8 # [1923] PUSH2(0x10c8)\n 6002 # [1926] PUSH1(2)\n 55 # [1928] SSTORE\n 00 # [1929] STOP\n6110c9 # [1930] PUSH2(0x10c9)\n 6002 # [1933] PUSH1(2)\n 55 # [1935] SSTORE\n 00 # [1936] STOP\n6110ca # [1937] PUSH2(0x10ca)\n 6002 # [1940] PUSH1(2)\n 55 # [1942] SSTORE\n 00 # [1943] STOP\n6110cb # [1944] PUSH2(0x10cb)\n 6002 # [1947] PUSH1(2)\n 55 # [1949] SSTORE\n 00 # [1950] STOP\n6110cc # [1951] PUSH2(0x10cc)\n 6002 # [1954] PUSH1(2)\n 55 # [1956] SSTORE\n 00 # [1957] STOP\n6110cd # [1958] PUSH2(0x10cd)\n 6002 # [1961] PUSH1(2)\n 55 # [1963] SSTORE\n 00 # [1964] STOP\n6110ce # [1965] PUSH2(0x10ce)\n 6002 # [1968] PUSH1(2)\n 55 # [1970] SSTORE\n 00 # [1971] STOP\n6110cf # [1972] PUSH2(0x10cf)\n 6002 # [1975] PUSH1(2)\n 55 # [1977] SSTORE\n 00 # [1978] STOP\n6110d0 # [1979] PUSH2(0x10d0)\n 6002 # [1982] PUSH1(2)\n 55 # [1984] SSTORE\n 00 # [1985] STOP\n6110d1 # [1986] PUSH2(0x10d1)\n 6002 # [1989] PUSH1(2)\n 55 # [1991] SSTORE\n 00 # [1992] STOP\n6110d2 # [1993] PUSH2(0x10d2)\n 6002 # [1996] PUSH1(2)\n 55 # [1998] SSTORE\n 00 # [1999] STOP\n6110d3 # [2000] PUSH2(0x10d3)\n 6002 # [2003] PUSH1(2)\n 55 # [2005] SSTORE\n 00 # [2006] STOP\n6110d4 # [2007] PUSH2(0x10d4)\n 6002 # [2010] PUSH1(2)\n 55 # [2012] SSTORE\n 00 # [2013] STOP\n6110d5 # [2014] PUSH2(0x10d5)\n 6002 # [2017] PUSH1(2)\n 55 # [2019] SSTORE\n 00 # [2020] STOP\n6110d6 # [2021] PUSH2(0x10d6)\n 6002 # [2024] PUSH1(2)\n 55 # [2026] SSTORE\n 00 # [2027] STOP\n6110d7 # [2028] PUSH2(0x10d7)\n 6002 # [2031] PUSH1(2)\n 55 # [2033] SSTORE\n 00 # [2034] STOP\n6110d8 # [2035] PUSH2(0x10d8)\n 6002 # [2038] PUSH1(2)\n 55 # [2040] SSTORE\n 00 # [2041] STOP\n6110d9 # [2042] PUSH2(0x10d9)\n 6002 # [2045] PUSH1(2)\n 55 # [2047] SSTORE\n 00 # [2048] STOP\n6110da # [2049] PUSH2(0x10da)\n 6002 # [2052] PUSH1(2)\n 55 # [2054] SSTORE\n 00 # [2055] STOP\n6110db # [2056] PUSH2(0x10db)\n 6002 # [2059] PUSH1(2)\n 55 # [2061] SSTORE\n 00 # [2062] STOP\n6110dc # [2063] PUSH2(0x10dc)\n 6002 # [2066] PUSH1(2)\n 55 # [2068] SSTORE\n 00 # [2069] STOP\n6110dd # [2070] PUSH2(0x10dd)\n 6002 # [2073] PUSH1(2)\n 55 # [2075] SSTORE\n 00 # [2076] STOP\n6110de # [2077] PUSH2(0x10de)\n 6002 # [2080] PUSH1(2)\n 55 # [2082] SSTORE\n 00 # [2083] STOP\n6110df # [2084] PUSH2(0x10df)\n 6002 # [2087] PUSH1(2)\n 55 # [2089] SSTORE\n 00 # [2090] STOP\n6110e0 # [2091] PUSH2(0x10e0)\n 6002 # [2094] PUSH1(2)\n 55 # [2096] SSTORE\n 00 # [2097] STOP\n6110e1 # [2098] PUSH2(0x10e1)\n 6002 # [2101] PUSH1(2)\n 55 # [2103] SSTORE\n 00 # [2104] STOP\n6110e2 # [2105] PUSH2(0x10e2)\n 6002 # [2108] PUSH1(2)\n 55 # [2110] SSTORE\n 00 # [2111] STOP\n6110e3 # [2112] PUSH2(0x10e3)\n 6002 # [2115] PUSH1(2)\n 55 # [2117] SSTORE\n 00 # [2118] STOP\n6110e4 # [2119] PUSH2(0x10e4)\n 6002 # [2122] PUSH1(2)\n 55 # [2124] SSTORE\n 00 # [2125] STOP\n6110e5 # [2126] PUSH2(0x10e5)\n 6002 # [2129] PUSH1(2)\n 55 # [2131] SSTORE\n 00 # [2132] STOP\n6110e6 # [2133] PUSH2(0x10e6)\n 6002 # [2136] PUSH1(2)\n 55 # [2138] SSTORE\n 00 # [2139] STOP\n6110e7 # [2140] PUSH2(0x10e7)\n 6002 # [2143] PUSH1(2)\n 55 # [2145] SSTORE\n 00 # [2146] STOP\n6110e8 # [2147] PUSH2(0x10e8)\n 6002 # [2150] PUSH1(2)\n 55 # [2152] SSTORE\n 00 # [2153] STOP\n6110e9 # [2154] PUSH2(0x10e9)\n 6002 # [2157] PUSH1(2)\n 55 # [2159] SSTORE\n 00 # [2160] STOP\n6110ea # [2161] PUSH2(0x10ea)\n 6002 # [2164] PUSH1(2)\n 55 # [2166] SSTORE\n 00 # [2167] STOP\n6110eb # [2168] PUSH2(0x10eb)\n 6002 # [2171] PUSH1(2)\n 55 # [2173] SSTORE\n 00 # [2174] STOP\n6110ec # [2175] PUSH2(0x10ec)\n 6002 # [2178] PUSH1(2)\n 55 # [2180] SSTORE\n 00 # [2181] STOP\n6110ed # [2182] PUSH2(0x10ed)\n 6002 # [2185] PUSH1(2)\n 55 # [2187] SSTORE\n 00 # [2188] STOP\n6110ee # [2189] PUSH2(0x10ee)\n 6002 # [2192] PUSH1(2)\n 55 # [2194] SSTORE\n 00 # [2195] STOP\n6110ef # [2196] PUSH2(0x10ef)\n 6002 # [2199] PUSH1(2)\n 55 # [2201] SSTORE\n 00 # [2202] STOP\n6110f0 # [2203] PUSH2(0x10f0)\n 6002 # [2206] PUSH1(2)\n 55 # [2208] SSTORE\n 00 # [2209] STOP\n6110f1 # [2210] PUSH2(0x10f1)\n 6002 # [2213] PUSH1(2)\n 55 # [2215] SSTORE\n 00 # [2216] STOP\n6110f2 # [2217] PUSH2(0x10f2)\n 6002 # [2220] PUSH1(2)\n 55 # [2222] SSTORE\n 00 # [2223] STOP\n6110f3 # [2224] PUSH2(0x10f3)\n 6002 # [2227] PUSH1(2)\n 55 # [2229] SSTORE\n 00 # [2230] STOP\n6110f4 # [2231] PUSH2(0x10f4)\n 6002 # [2234] PUSH1(2)\n 55 # [2236] SSTORE\n 00 # [2237] STOP\n6110f5 # [2238] PUSH2(0x10f5)\n 6002 # [2241] PUSH1(2)\n 55 # [2243] SSTORE\n 00 # [2244] STOP\n6110f6 # [2245] PUSH2(0x10f6)\n 6002 # [2248] PUSH1(2)\n 55 # [2250] SSTORE\n 00 # [2251] STOP\n6110f7 # [2252] PUSH2(0x10f7)\n 6002 # [2255] PUSH1(2)\n 55 # [2257] SSTORE\n 00 # [2258] STOP\n6110f8 # [2259] PUSH2(0x10f8)\n 6002 # [2262] PUSH1(2)\n 55 # [2264] SSTORE\n 00 # [2265] STOP\n6110f9 # [2266] PUSH2(0x10f9)\n 6002 # [2269] PUSH1(2)\n 55 # [2271] SSTORE\n 00 # [2272] STOP\n6110fa # [2273] PUSH2(0x10fa)\n 6002 # [2276] PUSH1(2)\n 55 # [2278] SSTORE\n 00 # [2279] STOP\n6110fb # [2280] PUSH2(0x10fb)\n 6002 # [2283] PUSH1(2)\n 55 # [2285] SSTORE\n 00 # [2286] STOP\n6110fc # [2287] PUSH2(0x10fc)\n 6002 # [2290] PUSH1(2)\n 55 # [2292] SSTORE\n 00 # [2293] STOP\n6110fd # [2294] PUSH2(0x10fd)\n 6002 # [2297] PUSH1(2)\n 55 # [2299] SSTORE\n 00 # [2300] STOP\n6110fe # [2301] PUSH2(0x10fe)\n 6002 # [2304] PUSH1(2)\n 55 # [2306] SSTORE\n 00 # [2307] STOP\n6110ff # [2308] PUSH2(0x10ff)\n 6002 # [2311] PUSH1(2)\n 55 # [2313] SSTORE\n 00 # [2314] STOP\n # Data section (empty)\n" +} \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/rjumpv.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/rjumpv.json new file mode 100644 index 0000000000..08a7df4965 --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/rjumpv.json @@ -0,0 +1,8 @@ +{ + "cli": [ + "pretty-print", + "0xEF0001010004020001001304000000008000026000e20200030000fff65b5b00600160015500" + ], + "stdin": "", + "stdout": "0x # EOF\nef0001 # Magic and Version ( 1 )\n010004 # Types length ( 4 )\n020001 # Total code sections ( 1 )\n 0013 # Code section 0 , 19 bytes\n040000 # Data section length( 0 )\n 00 # Terminator (end of header)\n # Code section 0 types\n 00 # 0 inputs \n 80 # 0 outputs (Non-returning function)\n 0002 # max stack: 2\n # Code section 0 - in=0 out=non-returning height=2\n 6000 # [0] PUSH1(0)\ne20200030000fff6 # [2] RJUMPV(3,0,-10)\n 5b # [10] NOOP\n 5b # [11] NOOP\n 00 # [12] STOP\n 6001 # [13] PUSH1(1)\n 6001 # [15] PUSH1(1)\n 55 # [17] SSTORE\n 00 # [18] STOP\n # Data section (empty)\n" +} \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/subcontainers.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/subcontainers.json new file mode 100644 index 0000000000..e277ea73e2 --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/subcontainers.json @@ -0,0 +1,8 @@ +{ + "cli": [ + "pretty-print", + "0xef000101000402000100130300010043040000000080000436600060003736600060006000ec0060005500ef0001010004020001000b03000100200400000000800003366000600037366000ee00ef0001010004020001000d0400400000800002d10000600055d1002060015500" + ], + "stdin": "", + "stdout": "0x # EOF\nef0001 # Magic and Version ( 1 )\n010004 # Types length ( 4 )\n020001 # Total code sections ( 1 )\n 0013 # Code section 0 , 19 bytes\n030001 # Total subcontainers ( 1 )\n 0043 # Sub container 0, 67 byte\n040000 # Data section length( 0 )\n 00 # Terminator (end of header)\n # Code section 0 types\n 00 # 0 inputs \n 80 # 0 outputs (Non-returning function)\n 0004 # max stack: 4\n # Code section 0 - in=0 out=non-returning height=4\n 36 # [0] CALLDATASIZE\n 6000 # [1] PUSH1(0)\n 6000 # [3] PUSH1(0)\n 37 # [5] CALLDATACOPY\n 36 # [6] CALLDATASIZE\n 6000 # [7] PUSH1(0)\n 6000 # [9] PUSH1(0)\n 6000 # [11] PUSH1(0)\n ec00 # [13] EOFCREATE(0)\n 6000 # [15] PUSH1(0)\n 55 # [17] SSTORE\n 00 # [18] STOP\n # Subcontainer 0 starts here\n ef0001 # Magic and Version ( 1 )\n 010004 # Types length ( 4 )\n 020001 # Total code sections ( 1 )\n 000b # Code section 0 , 11 bytes\n 030001 # Total subcontainers ( 1 )\n 0020 # Sub container 0, 32 byte\n 040000 # Data section length( 0 ) \n 00 # Terminator (end of header)\n # Code section 0 types\n 00 # 0 inputs \n 80 # 0 outputs (Non-returning function)\n 0003 # max stack: 3\n # Code section 0 - in=0 out=non-returning height=3\n 36 # [0] CALLDATASIZE\n 6000 # [1] PUSH1(0)\n 6000 # [3] PUSH1(0)\n 37 # [5] CALLDATACOPY\n 36 # [6] CALLDATASIZE\n 6000 # [7] PUSH1(0)\n ee00 # [9] RETURNCONTRACT(0)\n # Subcontainer 0.0 starts here\n ef0001 # Magic and Version ( 1 )\n 010004 # Types length ( 4 )\n 020001 # Total code sections ( 1 )\n 000d # Code section 0 , 13 bytes\n 040040 # Data section length( 64 ) (actual size 0) \n 00 # Terminator (end of header)\n # Code section 0 types\n 00 # 0 inputs \n 80 # 0 outputs (Non-returning function)\n 0002 # max stack: 2\n # Code section 0 - in=0 out=non-returning height=2\n d10000 # [0] DATALOADN(0x0000)\n 6000 # [3] PUSH1(0)\n 55 # [5] SSTORE\n d10020 # [6] DATALOADN(0x0020)\n 6001 # [9] PUSH1(1)\n 55 # [11] SSTORE\n 00 # [12] STOP\n # Data section (empty)\n # Subcontainer 0.0 ends\n # Data section (empty)\n # Subcontainer 0 ends\n # Data section (empty)\n" +} \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/blockhash.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/blockhash.json index de44541641..ad87a8238f 100644 --- a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/blockhash.json +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/blockhash.json @@ -99,6 +99,6 @@ {"pc":81,"op":72,"gas":"0x79bc22","gasCost":"0x2","memSize":0,"stack":["0x0","0x1","0x1","0x2","0x2","0xffff","0x1f4","0x78859e5b97166c486532b1595a673e9f9073643f1b519c6f18511b9913","0x2","0x389","0x0","0x0","0x1","0x0","0x3e3d6d5ff042148d326c1898713a76759ca273","0x44852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d","0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b"],"depth":1,"refund":0,"opName":"BASEFEE"}, {"pc":82,"op":8,"gas":"0x79bc20","gasCost":"0x8","memSize":0,"stack":["0x0","0x1","0x1","0x2","0x2","0xffff","0x1f4","0x78859e5b97166c486532b1595a673e9f9073643f1b519c6f18511b9913","0x2","0x389","0x0","0x0","0x1","0x0","0x3e3d6d5ff042148d326c1898713a76759ca273","0x44852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d","0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b","0x10"],"depth":1,"refund":0,"opName":"ADDMOD"}, {"pc":83,"op":62,"gas":"0x79bc18","gasCost":"0x0","memSize":0,"stack":["0x0","0x1","0x1","0x2","0x2","0xffff","0x1f4","0x78859e5b97166c486532b1595a673e9f9073643f1b519c6f18511b9913","0x2","0x389","0x0","0x0","0x1","0x0","0x3e3d6d5ff042148d326c1898713a76759ca273","0xb94f5374fce5edbc8e2a8697c15331677e6ebf1b"],"depth":1,"refund":0,"opName":"RETURNDATACOPY","error":"Out of bounds"}, - {"output":"","gasUsed":"0x7a1200","test":"00000936-mixed-1","fork":"Shanghai","d":0,"g":0,"v":0,"postHash":"0xd14c10ed22a1cfb642e374be985ac581c39f3969bd59249e0405aca3beb47a47","postLogsHash":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","pass":false} + {"output":"","gasUsed":"0x7a1200","test":"00000936-mixed-1","fork":"Shanghai","d":0,"g":0,"v":0,"postHash":"0xd14c10ed22a1cfb642e374be985ac581c39f3969bd59249e0405aca3beb47a47","postLogsHash":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","pass":false,"error":"INVALID_RETURN_DATA_BUFFER_ACCESS"} ] } diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-eof.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-eof.json new file mode 100644 index 0000000000..4e8529872d --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-eof.json @@ -0,0 +1,86 @@ +{ + "cli": [ + "state-test", + "stdin", + "--trace", + "--trace.memory", + "--trace.stack", + "--trace.returndata", + "--notime" + ], + "stdin": { + "create-eof": { + "env": { + "currentCoinbase": "b94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentDifficulty": "0x20000", + "currentRandom": "0x0000000000000000000000000000000000000000000000000000000000020000", + "currentGasLimit": "0x26e1f476fe1e22", + "currentNumber": "0x2", + "currentTimestamp": "0x3e8", + "previousHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "currentBaseFee": "0x10" + }, + "pre": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "code": "0x", + "storage": {}, + "balance": "0xffffffffff", + "nonce": "0x0" + } + }, + "transaction": { + "gasPrice": "0x10", + "nonce": "0x0", + "to": null, + "data": [ + "ef00010100040200010009030001001404000000008000035f355f5fa15f5fee00ef00010100040200010001040000000080000000c0de471fe5" + ], + "gasLimit": [ + "0x7a1200" + ], + "value": [ + "0xdbbe" + ], + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + }, + "out": "0x", + "post": { + "Prague": [ + { + "hash": "0x1a8642a04dae90535f00f53d3a30284c4db051d508a653db89eb100ba9aecbf3", + "logs": "0xf48b954a6a6f4ce6b28e4950b7027413f4bdc8f459df6003b6e8d7a1567c8940", + "indexes": { + "data": 0, + "gas": 0, + "value": 0 + } + } + ], + "Cancun": [ + { + "hash": "0xaa80d89bc89f58da8de41d3894bd1a241896ff91f7a5964edaefb39e8e3a4a98", + "logs": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "indexes": { + "data": 0, + "gas": 0, + "value": 0 + } + } + ] + } + } + }, + "stdout": [ + {"pc":0,"section":0,"op":95,"gas":"0x794068","gasCost":"0x2","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":1,"section":0,"op":53,"gas":"0x794066","gasCost":"0x3","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"CALLDATALOAD"}, + {"pc":2,"section":0,"op":95,"gas":"0x794063","gasCost":"0x2","memSize":0,"stack":["0xc0de471fe5000000000000000000000000000000000000000000000000000000"],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":3,"section":0,"op":95,"gas":"0x794061","gasCost":"0x2","memSize":0,"stack":["0xc0de471fe5000000000000000000000000000000000000000000000000000000","0x0"],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":4,"section":0,"op":161,"gas":"0x79405f","gasCost":"0x2ee","memSize":0,"stack":["0xc0de471fe5000000000000000000000000000000000000000000000000000000","0x0","0x0"],"depth":1,"refund":0,"opName":"LOG1"}, + {"pc":5,"section":0,"op":95,"gas":"0x793d71","gasCost":"0x2","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":6,"section":0,"op":95,"gas":"0x793d6f","gasCost":"0x2","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":7,"section":0,"op":238,"immediate":"0x00","gas":"0x793d6d","gasCost":"0x0","memSize":0,"stack":["0x0","0x0"],"depth":1,"refund":0,"opName":"RETURNCONTRACT"}, + {"output":"","gasUsed":"0xe433","test":"create-eof","fork":"Prague","d":0,"g":0,"v":0,"postHash":"0x1a8642a04dae90535f00f53d3a30284c4db051d508a653db89eb100ba9aecbf3","postLogsHash":"0xf48b954a6a6f4ce6b28e4950b7027413f4bdc8f459df6003b6e8d7a1567c8940","pass":true}, + {"pc":0,"op":239,"gas":"0x794068","gasCost":"0x0","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"INVALID","error":"Bad instruction"}, + {"output":"","gasUsed":"0x7a1200","test":"create-eof","fork":"Cancun","d":0,"g":0,"v":0,"postHash":"0xaa80d89bc89f58da8de41d3894bd1a241896ff91f7a5964edaefb39e8e3a4a98","postLogsHash":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","pass":true,"error":"INVALID_OPERATION"} + ] +} diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-invalid-eof.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-invalid-eof.json new file mode 100644 index 0000000000..dc786d6136 --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-invalid-eof.json @@ -0,0 +1,78 @@ +{ + "cli": [ + "state-test", + "stdin", + "--trace", + "--trace.memory", + "--trace.stack", + "--trace.returndata", + "--notime" + ], + "stdin": { + "create-eof": { + "env": { + "currentCoinbase": "b94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentDifficulty": "0x20000", + "currentRandom": "0x0000000000000000000000000000000000000000000000000000000000020000", + "currentGasLimit": "0x26e1f476fe1e22", + "currentNumber": "0x2", + "currentTimestamp": "0x3e8", + "previousHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "currentBaseFee": "0x10" + }, + "pre": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "code": "0x", + "storage": {}, + "balance": "0xffffffffff", + "nonce": "0x0" + } + }, + "transaction": { + "gasPrice": "0x10", + "nonce": "0x0", + "to": null, + "data": [ + "ef00011100040200010009030001001404000000008000035f355f5fa15f5fee00ef00010100040200010001040000000080000000c0de471fe5" + ], + "gasLimit": [ + "0x7a1200" + ], + "value": [ + "0xdbbe" + ], + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + }, + "out": "0x", + "post": { + "Prague": [ + { + "hash": "0x1a8642a04dae90535f00f53d3a30284c4db051d508a653db89eb100ba9aecbf3", + "logs": "0xf48b954a6a6f4ce6b28e4950b7027413f4bdc8f459df6003b6e8d7a1567c8940", + "indexes": { + "data": 0, + "gas": 0, + "value": 0 + } + } + ], + "Cancun": [ + { + "hash": "0xaa80d89bc89f58da8de41d3894bd1a241896ff91f7a5964edaefb39e8e3a4a98", + "logs": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "indexes": { + "data": 0, + "gas": 0, + "value": 0 + } + } + ] + } + } + }, + "stdout": [ + {"output":"","gasUsed":"0xd198","test":"create-eof","fork":"Prague","d":0,"g":0,"v":0,"postHash":"0x2a9c58298ba5d4ec86ca682b9fcc9ff67c3fc44dbd39f85a2f9b74bfe4e5178e","postLogsHash":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","pass":false,"error":"Invalid EOF Layout: Expected kind 1 but read kind 17"}, + {"pc":0,"op":239,"gas":"0x794068","gasCost":"0x0","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"INVALID","error":"Bad instruction"}, + {"output":"","gasUsed":"0x7a1200","test":"create-eof","fork":"Cancun","d":0,"g":0,"v":0,"postHash":"0xaa80d89bc89f58da8de41d3894bd1a241896ff91f7a5964edaefb39e8e3a4a98","postLogsHash":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","pass":true,"error":"INVALID_OPERATION"} + ] +} diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/create-eof.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/create-eof.json new file mode 100644 index 0000000000..bba2c85103 --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/create-eof.json @@ -0,0 +1,25 @@ +{ + "cli": [ + "--notime", + "--json", + "--create", + "--code", + "ef00010100040200010009030001001404000000008000035f355f5fa15f5fee00ef00010100040200010001040000000080000000c0de471fe5", + "--coinbase", + "4444588443C3A91288C5002483449ABA1054192B", + "--fork", + "pragueeof" + ], + "stdin": "", + "stdout": [ + {"pc":0,"section":0,"op":95,"gas":"0x2540be400","gasCost":"0x2","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":1,"section":0,"op":53,"gas":"0x2540be3fe","gasCost":"0x3","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"CALLDATALOAD"}, + {"pc":2,"section":0,"op":95,"gas":"0x2540be3fb","gasCost":"0x2","memSize":0,"stack":["0xc0de471fe5000000000000000000000000000000000000000000000000000000"],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":3,"section":0,"op":95,"gas":"0x2540be3f9","gasCost":"0x2","memSize":0,"stack":["0xc0de471fe5000000000000000000000000000000000000000000000000000000","0x0"],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":4,"section":0,"op":161,"gas":"0x2540be3f7","gasCost":"0x2ee","memSize":0,"stack":["0xc0de471fe5000000000000000000000000000000000000000000000000000000","0x0","0x0"],"depth":1,"refund":0,"opName":"LOG1"}, + {"pc":5,"section":0,"op":95,"gas":"0x2540be109","gasCost":"0x2","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":6,"section":0,"op":95,"gas":"0x2540be107","gasCost":"0x2","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":7,"section":0,"op":238,"immediate":"0x00","gas":"0x2540be105","gasCost":"0x0","memSize":0,"stack":["0x0","0x0"],"depth":1,"refund":0,"opName":"RETURNCONTRACT"}, + {"gasUser":"0x129b","gasTotal":"0x129b","output":"0x"} + ] +} \ No newline at end of file diff --git a/ethereum/referencetests/build.gradle b/ethereum/referencetests/build.gradle index 3558b5b761..c023dcd699 100644 --- a/ethereum/referencetests/build.gradle +++ b/ethereum/referencetests/build.gradle @@ -129,6 +129,21 @@ def generalstateRegressionReferenceTests = tasks.register("generalstateRegressio ) } +def eofReferenceTests = tasks.register("eofReferenceTests") { + final referenceTestsPath = "src/reference-test/external-resources/EOFTests" + final generatedTestsPath = "$buildDir/generated/sources/reference-test/$name/java" + inputs.files fileTree(referenceTestsPath), + fileTree(generatedTestsPath) + outputs.files generatedTestsPath + generateTestFiles( + fileTree(referenceTestsPath), + file("src/reference-test/templates/EOFReferenceTest.java.template"), + "EOFTests", + "$generatedTestsPath/org/hyperledger/besu/ethereum/vm/eof", + "EOFReferenceTest" + ) +} + sourceSets { referenceTest { java { @@ -140,7 +155,8 @@ sourceSets { eipStateReferenceTests, executionSpecTests, generalstateReferenceTests, - generalstateRegressionReferenceTests + generalstateRegressionReferenceTests, + eofReferenceTests } resources { srcDirs 'src/reference-test/resources', @@ -247,24 +263,20 @@ def generateTestFiles(FileTree jsonPath, File templateFile, String pathstrip, St mkdir(destination) def referenceTestTemplate = templateFile.text - // This is how many json files to include in each test file - def fileSets = jsonPath.getFiles().collate(5) - - fileSets.eachWithIndex { fileSet, idx -> - def paths = [] - fileSet.each { testJsonFile -> - def parentFile = testJsonFile.getParentFile() - def parentPathFile = parentFile.getPath().substring(parentFile.getPath().indexOf(pathstrip)) - if (!testJsonFile.getName().toString().startsWith(".") && !excludedPath.contains(parentPathFile)) { - def pathFile = testJsonFile.getPath() - paths << pathFile.substring(pathFile.indexOf(pathstrip)) - } + def paths = [] + jsonPath.getFiles().forEach { testJsonFile -> + def parentFile = testJsonFile.getParentFile() + def parentPathFile = parentFile.getPath().substring(parentFile.getPath().indexOf(pathstrip)) + if (!testJsonFile.getName().toString().startsWith(".") && !excludedPath.contains(parentPathFile)) { + def pathFile = testJsonFile.getPath() + paths << pathFile.substring(pathFile.indexOf(pathstrip)) } + } + paths.collate(5).eachWithIndex { tests, idx -> def testFile = file(destination + "/" + namePrefix + "_" + idx + ".java") - - def allPaths = '"' + paths.join('", "') + '"' + def allPaths = '"' + tests.join('",\n "') + '"' def testFileContents = referenceTestTemplate .replaceAll("%%TESTS_FILE%%", allPaths) diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/EOFTestCaseSpec.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/EOFTestCaseSpec.java new file mode 100644 index 0000000000..e67ec2091f --- /dev/null +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/EOFTestCaseSpec.java @@ -0,0 +1,53 @@ +/* + * 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.ethereum.referencetests; + +import java.util.NavigableMap; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class EOFTestCaseSpec { + + public record TestVector( + @JsonProperty("code") String code, + @JsonProperty("results") NavigableMap results) {} + + public record TestResult( + @JsonProperty("exception") String exception, @JsonProperty("result") boolean result) { + public static TestResult TEST_RESULT_PASSED = new TestResult(null, true); + + public static TestResult failed(final String exception) { + return new TestResult(exception, false); + } + + public static TestResult passed() { + return TEST_RESULT_PASSED; + } + } + + NavigableMap vector; + + @JsonCreator + public EOFTestCaseSpec(@JsonProperty("vectors") final NavigableMap vector) { + this.vector = vector; + } + + public NavigableMap getVector() { + return vector; + } +} diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java index 5c650ecaae..2ebf5f7996 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java @@ -86,7 +86,7 @@ public class ReferenceTestProtocolSchedules { builder.put( "CancunToPragueAtTime15k", createSchedule(genesisStub.clone().cancunTime(0).pragueTime(15000))); - builder.put("Prague", createSchedule(genesisStub.clone().pragueTime(0))); + builder.put("Prague", createSchedule(genesisStub.clone().pragueEOFTime(0))); builder.put("Future_EIPs", createSchedule(genesisStub.clone().futureEipsTime(0))); builder.put("Experimental_EIPs", createSchedule(genesisStub.clone().experimentalEipsTime(0))); return new ReferenceTestProtocolSchedules(builder.build()); diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/StateTestVersionedTransaction.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/StateTestVersionedTransaction.java index 3334c926ee..9748d2aa03 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/StateTestVersionedTransaction.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/StateTestVersionedTransaction.java @@ -111,7 +111,7 @@ public class StateTestVersionedTransaction { this.maxFeePerGas = Optional.ofNullable(maxFeePerGas).map(Wei::fromHexString).orElse(null); this.maxPriorityFeePerGas = Optional.ofNullable(maxPriorityFeePerGas).map(Wei::fromHexString).orElse(null); - this.to = to.isEmpty() ? null : Address.fromHexString(to); + this.to = (to == null || to.isEmpty()) ? null : Address.fromHexString(to); SignatureAlgorithm signatureAlgorithm = SignatureAlgorithmFactory.getInstance(); this.keys = diff --git a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/eof/EOFReferenceTestTools.java b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/eof/EOFReferenceTestTools.java new file mode 100644 index 0000000000..cf7ce66b07 --- /dev/null +++ b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/eof/EOFReferenceTestTools.java @@ -0,0 +1,142 @@ +/* + * 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.ethereum.eof; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.ethereum.referencetests.EOFTestCaseSpec; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EvmSpecVersion; +import org.hyperledger.besu.evm.code.CodeFactory; +import org.hyperledger.besu.evm.code.CodeInvalid; +import org.hyperledger.besu.evm.code.CodeV1; +import org.hyperledger.besu.evm.code.CodeV1Validation; +import org.hyperledger.besu.evm.code.EOFLayout; +import org.hyperledger.besu.testutil.JsonTestParameters; + +public class EOFReferenceTestTools { + private static final List EIPS_TO_RUN; + + static { + final String eips = + System.getProperty("test.ethereum.eof.eips", "Prague,Osaka,Amsterdam,Bogota,Polis,Bangkok"); + EIPS_TO_RUN = Arrays.asList(eips.split(",")); + } + + private static final JsonTestParameters params = + JsonTestParameters.create(EOFTestCaseSpec.class, EOFTestCaseSpec.TestResult.class) + .generator( + (testName, fullPath, eofSpec, collector) -> { + final Path path = Path.of(fullPath).getParent().getFileName(); + final String prefix = path + "/" + testName + "-"; + for (final Map.Entry entry : + eofSpec.getVector().entrySet()) { + final String name = entry.getKey(); + final Bytes code = Bytes.fromHexString(entry.getValue().code()); + for (final var result : entry.getValue().results().entrySet()) { + final String eip = result.getKey(); + final boolean runTest = EIPS_TO_RUN.contains(eip); + collector.add( + prefix + eip + '[' + name + ']', + fullPath, + eip, + code, + result.getValue(), + runTest); + } + } + }); + + static { + if (EIPS_TO_RUN.isEmpty()) { + params.ignoreAll(); + } + + // TXCREATE still in tests, but has been removed + params.ignore("EOF1_undefined_opcodes_186"); + } + + private EOFReferenceTestTools() { + // utility class + } + + // + public static Collection generateTestParametersForConfig(final String[] filePath) { + return params.generate(filePath); + } + + public static void executeTest( + final String fork, final Bytes code, final EOFTestCaseSpec.TestResult expected) { + EvmSpecVersion evmVersion = EvmSpecVersion.fromName(fork); + assertThat(evmVersion).isNotNull(); + + // hardwire in the magic byte transaction checks + if (evmVersion.getMaxEofVersion() < 1) { + assertThat(expected.exception()).isEqualTo("EOF_InvalidCode"); + } else { + EOFLayout layout = EOFLayout.parseEOF(code); + + if (layout.isValid()) { + Code parsedCode = CodeFactory.createCode(code, evmVersion.getMaxEofVersion()); + assertThat(parsedCode.isValid()) + .withFailMessage( + () -> + EOFLayout.parseEOF(code).prettyPrint() + + "\nExpected exception :" + + expected.exception() + + " actual exception :" + + (parsedCode.isValid() + ? null + : ((CodeInvalid) parsedCode).getInvalidReason())) + .isEqualTo(expected.result()); + if (parsedCode instanceof CodeV1 codeV1) { + var deepValidate = CodeV1Validation.validate(codeV1.getEofLayout()); + assertThat(deepValidate) + .withFailMessage( + () -> + codeV1.prettyPrint() + + "\nExpected exception :" + + expected.exception() + + " actual exception :" + + (parsedCode.isValid() ? null : deepValidate)) + .isNull(); + } + + if (expected.result()) { + System.out.println(code); + System.out.println(layout.writeContainer(null)); + assertThat(code) + .withFailMessage("Container round trip failed") + .isEqualTo(layout.writeContainer(null)); + } + } else { + assertThat(layout.isValid()) + .withFailMessage( + () -> + "Expected exception - " + + expected.exception() + + " actual exception - " + + (layout.isValid() ? null : layout.invalidReason())) + .isEqualTo(expected.result()); + } + } + } +} diff --git a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/BlockchainReferenceTestTools.java b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/BlockchainReferenceTestTools.java index dc32da7188..5512923c61 100644 --- a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/BlockchainReferenceTestTools.java +++ b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/BlockchainReferenceTestTools.java @@ -53,9 +53,9 @@ public class BlockchainReferenceTestTools { final String networks = System.getProperty( "test.ethereum.blockchain.eips", - "FrontierToHomesteadAt5,HomesteadToEIP150At5,HomesteadToDaoAt5,EIP158ToByzantiumAt5," + "FrontierToHomesteadAt5,HomesteadToEIP150At5,HomesteadToDaoAt5,EIP158ToByzantiumAt5,CancunToPragueAtTime15k" + "Frontier,Homestead,EIP150,EIP158,Byzantium,Constantinople,ConstantinopleFix,Istanbul,Berlin," - + "London,Merge,Paris,Shanghai,Cancun,Prague,Osaka,Bogota,CancunToPragueAtTime15k"); + + "London,Merge,Paris,Shanghai,Cancun,Prague,Osaka,Amsterdam,Bogota,Polis,Bangkok"); NETWORKS_TO_RUN = Arrays.asList(networks.split(",")); } @@ -75,21 +75,22 @@ public class BlockchainReferenceTestTools { // Consumes a huge amount of memory params.ignore("static_Call1MB1024Calldepth_d1g0v0_\\w+"); - params.ignore("ShanghaiLove_.*"); + params.ignore("ShanghaiLove_"); // Absurd amount of gas, doesn't run in parallel params.ignore("randomStatetest94_\\w+"); // Don't do time-consuming tests - params.ignore("CALLBlake2f_MaxRounds.*"); - params.ignore("loopMul_*"); + params.ignore("CALLBlake2f_MaxRounds"); + params.ignore("loopMul_"); // Inconclusive fork choice rule, since in merge CL should be choosing forks and setting the // chain head. // Perfectly valid test pre-merge. - params.ignore("UncleFromSideChain_(Merge|Paris|Shanghai|Cancun|Prague|Osaka|Bogota)"); + params.ignore( + "UncleFromSideChain_(Merge|Paris|Shanghai|Cancun|Prague|Osaka|Amsterdam|Bogota|Polis|Bangkok)"); - // EOF tests are written against an older version of the spec + // EOF tests don't have Prague stuff like deopsits right now params.ignore("/stEOF/"); // None of the Prague tests have withdrawls and deposits handling diff --git a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java index 20cc4710dc..f948a5b24e 100644 --- a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java +++ b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java @@ -66,7 +66,7 @@ public class GeneralStateReferenceTestTools { System.getProperty( "test.ethereum.state.eips", "Frontier,Homestead,EIP150,EIP158,Byzantium,Constantinople,ConstantinopleFix,Istanbul,Berlin," - + "London,Merge,Paris,Shanghai,Cancun,Prague,Osaka,Bogota"); + + "London,Merge,Paris,Shanghai,Cancun,Prague,Osaka,Amsterdam,Bogota,Polis,Bangkok"); EIPS_TO_RUN = Arrays.asList(eips.split(",")); } diff --git a/ethereum/referencetests/src/reference-test/templates/BlockchainReferenceTest.java.template b/ethereum/referencetests/src/reference-test/templates/BlockchainReferenceTest.java.template index f8b99e8a73..387fb0cc18 100644 --- a/ethereum/referencetests/src/reference-test/templates/BlockchainReferenceTest.java.template +++ b/ethereum/referencetests/src/reference-test/templates/BlockchainReferenceTest.java.template @@ -16,20 +16,20 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue; /** The blockchain test operation testing framework entry point. */ public class %%TESTS_NAME%% { - private static final String[] TEST_CONFIG_FILE_DIR_PATH = new String[] {%%TESTS_FILE%%}; + private static final String[] TEST_CONFIG_FILE_DIR_PATH = + new String[] { + %%TESTS_FILE%% + }; public static Stream getTestParametersForConfig() { - return generateTestParametersForConfig(TEST_CONFIG_FILE_DIR_PATH).stream().map(params -> - Arguments.of(params[0], params[1], params[2]) - ); + return generateTestParametersForConfig(TEST_CONFIG_FILE_DIR_PATH).stream() + .map(params -> Arguments.of(params[0], params[1], params[2])); } @ParameterizedTest(name = "Name: {0}") @MethodSource("getTestParametersForConfig") public void execution( - final String name, - final BlockchainReferenceTestCaseSpec spec, - final boolean runTest) { + final String name, final BlockchainReferenceTestCaseSpec spec, final boolean runTest) { assumeTrue(runTest, "Test " + name + " was ignored"); executeTest(spec); } diff --git a/ethereum/referencetests/src/reference-test/templates/EOFReferenceTest.java.template b/ethereum/referencetests/src/reference-test/templates/EOFReferenceTest.java.template new file mode 100644 index 0000000000..5f5cafd676 --- /dev/null +++ b/ethereum/referencetests/src/reference-test/templates/EOFReferenceTest.java.template @@ -0,0 +1,42 @@ +package org.hyperledger.besu.ethereum.vm.eof; + +import static org.hyperledger.besu.ethereum.eof.EOFReferenceTestTools.executeTest; +import static org.hyperledger.besu.ethereum.eof.EOFReferenceTestTools.generateTestParametersForConfig; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import org.hyperledger.besu.ethereum.referencetests.EOFTestCaseSpec; + +import java.util.Arrays; +import java.util.stream.Stream; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +/** The general state test operation testing framework entry point. */ +public class %%TESTS_NAME%% { + + private static final String[] TEST_CONFIG_FILE_DIR_PATH = + new String[] { + %%TESTS_FILE%% + }; + + public static Stream getTestParametersForConfig() { + return generateTestParametersForConfig(TEST_CONFIG_FILE_DIR_PATH).stream().map(Arguments::of); + } + + @ParameterizedTest(name = "Name: {0}") + @MethodSource("getTestParametersForConfig") + public void execution( + final String name, + final String fork, + final Bytes code, + final EOFTestCaseSpec.TestResult results, + final boolean runTest) { + assumeTrue(runTest, "Test " + name + " was ignored"); + executeTest(fork, code, results); + } +} diff --git a/ethereum/referencetests/src/reference-test/templates/GeneralStateReferenceTest.java.template b/ethereum/referencetests/src/reference-test/templates/GeneralStateReferenceTest.java.template index 4b93b87733..3d5976aff2 100644 --- a/ethereum/referencetests/src/reference-test/templates/GeneralStateReferenceTest.java.template +++ b/ethereum/referencetests/src/reference-test/templates/GeneralStateReferenceTest.java.template @@ -17,20 +17,20 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue; /** The general state test operation testing framework entry point. */ public class %%TESTS_NAME%% { - private static final String[] TEST_CONFIG_FILE_DIR_PATH = new String[] {%%TESTS_FILE%%}; + private static final String[] TEST_CONFIG_FILE_DIR_PATH = + new String[] { + %%TESTS_FILE%% + }; public static Stream getTestParametersForConfig() { - return generateTestParametersForConfig(TEST_CONFIG_FILE_DIR_PATH).stream().map(params -> - Arguments.of(params[0], params[1], params[2]) - ); + return generateTestParametersForConfig(TEST_CONFIG_FILE_DIR_PATH).stream() + .map(params -> Arguments.of(params[0], params[1], params[2])); } @ParameterizedTest(name = "Name: {0}") @MethodSource("getTestParametersForConfig") public void execution( - final String name, - final GeneralStateTestCaseEipSpec spec, - final boolean runTest) { + final String name, final GeneralStateTestCaseEipSpec spec, final boolean runTest) { assumeTrue(runTest, "Test " + name + " was ignored"); executeTest(spec); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/Code.java b/evm/src/main/java/org/hyperledger/besu/evm/Code.java index 3ccb87c3a1..bcec287249 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/Code.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/Code.java @@ -17,6 +17,8 @@ package org.hyperledger.besu.evm; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.evm.code.CodeSection; +import java.util.Optional; + import org.apache.tuweni.bytes.Bytes; /** Represents EVM code associated with an account. */ @@ -30,6 +32,13 @@ public interface Code { */ int getSize(); + /** + * Size of the data in bytes. This is for the data only, + * + * @return size of code in bytes. + */ + int getDataSize(); + /** * Get the bytes for the entire container, for example what EXTCODECOPY would want. For V0 it is * the same as getCodeBytes, for V1 it is the entire container, not just the data section. @@ -82,4 +91,63 @@ public interface Code { * @return The version of hte ode. */ int getEofVersion(); + + /** + * Returns the count of subcontainers, or zero if there are none or if the code version does not + * support subcontainers. + * + * @return The subcontainer count or zero if not supported; + */ + int getSubcontainerCount(); + + /** + * Returns the subcontainer at the selected index. If the container doesn't exist or is invalid, + * an empty result is returned. Legacy code always returns empty. + * + * @param index the index in the container to return + * @param auxData any Auxiliary data to append to the subcontainer code. If fetching an initcode + * container, pass null. + * @return Either the subcontainer, or empty. + */ + Optional getSubContainer(final int index, final Bytes auxData); + + /** + * Loads data from the appropriate data section + * + * @param offset Where within the data section to start copying + * @param length how many bytes to copy + * @return A slice of the code containing the requested data + */ + Bytes getData(final int offset, final int length); + + /** + * Read a signed 16-bit big-endian integer + * + * @param startIndex the index to start reading the integer in the code + * @return a java int representing the 16-bit signed integer. + */ + int readBigEndianI16(final int startIndex); + + /** + * Read an unsigned 16 bit big-endian integer + * + * @param startIndex the index to start reading the integer in the code + * @return a java int representing the 16-bit unsigned integer. + */ + int readBigEndianU16(final int startIndex); + + /** + * Read an unsigned 8-bit integer + * + * @param startIndex the index to start reading the integer in the code + * @return a java int representing the 8-bit unsigned integer. + */ + int readU8(final int startIndex); + + /** + * A more readable representation of the hex bytes, including whitespace and comments after hashes + * + * @return The pretty printed code + */ + String prettyPrint(); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java index debdb5e9b1..03348d9ab7 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java @@ -365,6 +365,16 @@ public class EVM { * @return the code */ public Code getCodeUncached(final Bytes codeBytes) { - return CodeFactory.createCode(codeBytes, evmSpecVersion.getMaxEofVersion(), false); + return CodeFactory.createCode(codeBytes, evmSpecVersion.getMaxEofVersion()); + } + + /** + * Gets code for creation. Skips code cache and allows for extra data after EOF contracts. + * + * @param codeBytes the code bytes + * @return the code + */ + public Code getCodeForCreation(final Bytes codeBytes) { + return CodeFactory.createCode(codeBytes, evmSpecVersion.getMaxEofVersion(), false, true); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/EvmSpecVersion.java b/evm/src/main/java/org/hyperledger/besu/evm/EvmSpecVersion.java index 3743c82c48..e543f672df 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EvmSpecVersion.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EvmSpecVersion.java @@ -50,10 +50,18 @@ public enum EvmSpecVersion { CANCUN(0, true, "Cancun", "Finalized"), /** Prague evm spec version. */ PRAGUE(0, false, "Prague", "In Development"), + /** PragueEOF evm spec version. */ + PRAGUE_EOF(1, false, "PragueEOF", "Prague + EOF. In Development"), /** Osaka evm spec version. */ - OSAKA(0, false, "Osaka", "Placeholder"), + OSAKA(1, false, "Osaka", "Placeholder"), + /** Amstedam evm spec version. */ + AMSTERDAM(1, false, "Amsterdam", "Placeholder"), /** Bogota evm spec version. */ - BOGOTA(0, false, "Bogota", "Placeholder"), + BOGOTA(1, false, "Bogota", "Placeholder"), + /** Polis evm spec version. */ + POLIS(1, false, "Polis", "Placeholder"), + /** Bogota evm spec version. */ + BANGKOK(1, false, "Bangkok", "Placeholder"), /** Development fork for unscheduled EIPs */ FUTURE_EIPS(1, false, "Future_EIPs", "Development, for accepted and unscheduled EIPs"), /** Development fork for EIPs not accepted to Mainnet */ @@ -146,6 +154,10 @@ public enum EvmSpecVersion { * @return the EVM spec version for that fork, or null if no fork matched. */ public static EvmSpecVersion fromName(final String name) { + // TODO remove once PragueEOF settles + if ("prague".equalsIgnoreCase(name)) { + return EvmSpecVersion.PRAGUE_EOF; + } for (var version : EvmSpecVersion.values()) { if (version.name().equalsIgnoreCase(name)) { return version; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java b/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java index adc26009ea..11ce3546a8 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java @@ -24,6 +24,7 @@ import org.hyperledger.besu.evm.gascalculator.HomesteadGasCalculator; import org.hyperledger.besu.evm.gascalculator.IstanbulGasCalculator; import org.hyperledger.besu.evm.gascalculator.LondonGasCalculator; import org.hyperledger.besu.evm.gascalculator.PetersburgGasCalculator; +import org.hyperledger.besu.evm.gascalculator.PragueEOFGasCalculator; import org.hyperledger.besu.evm.gascalculator.PragueGasCalculator; import org.hyperledger.besu.evm.gascalculator.ShanghaiGasCalculator; import org.hyperledger.besu.evm.gascalculator.SpuriousDragonGasCalculator; @@ -53,15 +54,25 @@ import org.hyperledger.besu.evm.operation.CodeSizeOperation; import org.hyperledger.besu.evm.operation.CoinbaseOperation; import org.hyperledger.besu.evm.operation.Create2Operation; import org.hyperledger.besu.evm.operation.CreateOperation; +import org.hyperledger.besu.evm.operation.DataCopyOperation; +import org.hyperledger.besu.evm.operation.DataLoadNOperation; +import org.hyperledger.besu.evm.operation.DataLoadOperation; +import org.hyperledger.besu.evm.operation.DataSizeOperation; import org.hyperledger.besu.evm.operation.DelegateCallOperation; import org.hyperledger.besu.evm.operation.DifficultyOperation; import org.hyperledger.besu.evm.operation.DivOperation; +import org.hyperledger.besu.evm.operation.DupNOperation; import org.hyperledger.besu.evm.operation.DupOperation; +import org.hyperledger.besu.evm.operation.EOFCreateOperation; import org.hyperledger.besu.evm.operation.EqOperation; +import org.hyperledger.besu.evm.operation.ExchangeOperation; import org.hyperledger.besu.evm.operation.ExpOperation; +import org.hyperledger.besu.evm.operation.ExtCallOperation; import org.hyperledger.besu.evm.operation.ExtCodeCopyOperation; import org.hyperledger.besu.evm.operation.ExtCodeHashOperation; import org.hyperledger.besu.evm.operation.ExtCodeSizeOperation; +import org.hyperledger.besu.evm.operation.ExtDelegateCallOperation; +import org.hyperledger.besu.evm.operation.ExtStaticCallOperation; import org.hyperledger.besu.evm.operation.GasLimitOperation; import org.hyperledger.besu.evm.operation.GasOperation; import org.hyperledger.besu.evm.operation.GasPriceOperation; @@ -69,6 +80,7 @@ import org.hyperledger.besu.evm.operation.GtOperation; import org.hyperledger.besu.evm.operation.InvalidOperation; import org.hyperledger.besu.evm.operation.IsZeroOperation; import org.hyperledger.besu.evm.operation.JumpDestOperation; +import org.hyperledger.besu.evm.operation.JumpFOperation; import org.hyperledger.besu.evm.operation.JumpOperation; import org.hyperledger.besu.evm.operation.JumpiOperation; import org.hyperledger.besu.evm.operation.Keccak256Operation; @@ -96,7 +108,9 @@ import org.hyperledger.besu.evm.operation.RelativeJumpIfOperation; import org.hyperledger.besu.evm.operation.RelativeJumpOperation; import org.hyperledger.besu.evm.operation.RelativeJumpVectorOperation; import org.hyperledger.besu.evm.operation.RetFOperation; +import org.hyperledger.besu.evm.operation.ReturnContractOperation; import org.hyperledger.besu.evm.operation.ReturnDataCopyOperation; +import org.hyperledger.besu.evm.operation.ReturnDataLoadOperation; import org.hyperledger.besu.evm.operation.ReturnDataSizeOperation; import org.hyperledger.besu.evm.operation.ReturnOperation; import org.hyperledger.besu.evm.operation.RevertOperation; @@ -115,6 +129,7 @@ import org.hyperledger.besu.evm.operation.SignExtendOperation; import org.hyperledger.besu.evm.operation.StaticCallOperation; import org.hyperledger.besu.evm.operation.StopOperation; import org.hyperledger.besu.evm.operation.SubOperation; +import org.hyperledger.besu.evm.operation.SwapNOperation; import org.hyperledger.besu.evm.operation.SwapOperation; import org.hyperledger.besu.evm.operation.TLoadOperation; import org.hyperledger.besu.evm.operation.TStoreOperation; @@ -952,6 +967,107 @@ public class MainnetEVMs { // TODO add EOF operations here once PragueEOF is collapsed into Prague } + /** + * PragueEOF evm. + * + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM pragueEOF(final EvmConfiguration evmConfiguration) { + return pragueEOF(DEV_NET_CHAIN_ID, evmConfiguration); + } + + /** + * PragueEOF evm. + * + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM pragueEOF(final BigInteger chainId, final EvmConfiguration evmConfiguration) { + return pragueEOF(new PragueEOFGasCalculator(), chainId, evmConfiguration); + } + + /** + * PragueEOF evm. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM pragueEOF( + final GasCalculator gasCalculator, + final BigInteger chainId, + final EvmConfiguration evmConfiguration) { + return new EVM( + pragueEOFOperations(gasCalculator, chainId), + gasCalculator, + evmConfiguration, + EvmSpecVersion.PRAGUE_EOF); + } + + /** + * Operation registry for PragueEOF's operations. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @return the operation registry + */ + public static OperationRegistry pragueEOFOperations( + final GasCalculator gasCalculator, final BigInteger chainId) { + OperationRegistry operationRegistry = new OperationRegistry(); + registerPragueEOFOperations(operationRegistry, gasCalculator, chainId); + return operationRegistry; + } + + /** + * Register PragueEOF's operations. + * + * @param registry the registry + * @param gasCalculator the gas calculator + * @param chainID the chain id + */ + public static void registerPragueEOFOperations( + final OperationRegistry registry, + final GasCalculator gasCalculator, + final BigInteger chainID) { + registerPragueOperations(registry, gasCalculator, chainID); + + // EIP-663 Unlimited Swap and Dup + registry.put(new DupNOperation(gasCalculator)); + registry.put(new SwapNOperation(gasCalculator)); + registry.put(new ExchangeOperation(gasCalculator)); + + // EIP-4200 relative jump + registry.put(new RelativeJumpOperation(gasCalculator)); + registry.put(new RelativeJumpIfOperation(gasCalculator)); + registry.put(new RelativeJumpVectorOperation(gasCalculator)); + + // EIP-4750 EOF Code Sections + registry.put(new CallFOperation(gasCalculator)); + registry.put(new RetFOperation(gasCalculator)); + + // EIP-6209 JUMPF Instruction + registry.put(new JumpFOperation(gasCalculator)); + + // EIP-7069 Revamped EOF Call + registry.put(new ExtCallOperation(gasCalculator)); + registry.put(new ExtDelegateCallOperation(gasCalculator)); + registry.put(new ExtStaticCallOperation(gasCalculator)); + registry.put(new ReturnDataLoadOperation(gasCalculator)); + + // EIP-7480 EOF Data Section Access + registry.put(new DataLoadOperation(gasCalculator)); + registry.put(new DataLoadNOperation(gasCalculator)); + registry.put(new DataSizeOperation(gasCalculator)); + registry.put(new DataCopyOperation(gasCalculator)); + + // EIP-7620 EOF Create and Return Contract operation + registry.put(new EOFCreateOperation(gasCalculator)); + registry.put(new ReturnContractOperation(gasCalculator)); + } + /** * Osaka evm. * @@ -1017,7 +1133,75 @@ public class MainnetEVMs { final OperationRegistry registry, final GasCalculator gasCalculator, final BigInteger chainID) { - registerPragueOperations(registry, gasCalculator, chainID); + registerPragueEOFOperations(registry, gasCalculator, chainID); + } + + /** + * Amsterdam evm. + * + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM amsterdam(final EvmConfiguration evmConfiguration) { + return amsterdam(DEV_NET_CHAIN_ID, evmConfiguration); + } + + /** + * Amsterdam evm. + * + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM amsterdam(final BigInteger chainId, final EvmConfiguration evmConfiguration) { + return amsterdam(new PragueGasCalculator(), chainId, evmConfiguration); + } + + /** + * Amsterdam evm. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM amsterdam( + final GasCalculator gasCalculator, + final BigInteger chainId, + final EvmConfiguration evmConfiguration) { + return new EVM( + amsterdamOperations(gasCalculator, chainId), + gasCalculator, + evmConfiguration, + EvmSpecVersion.AMSTERDAM); + } + + /** + * Operation registry for amsterdam's operations. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @return the operation registry + */ + public static OperationRegistry amsterdamOperations( + final GasCalculator gasCalculator, final BigInteger chainId) { + OperationRegistry operationRegistry = new OperationRegistry(); + registerAmsterdamOperations(operationRegistry, gasCalculator, chainId); + return operationRegistry; + } + + /** + * Register amsterdam operations. + * + * @param registry the registry + * @param gasCalculator the gas calculator + * @param chainID the chain id + */ + public static void registerAmsterdamOperations( + final OperationRegistry registry, + final GasCalculator gasCalculator, + final BigInteger chainID) { + registerOsakaOperations(registry, gasCalculator, chainID); } /** @@ -1085,7 +1269,143 @@ public class MainnetEVMs { final OperationRegistry registry, final GasCalculator gasCalculator, final BigInteger chainID) { - registerOsakaOperations(registry, gasCalculator, chainID); + registerAmsterdamOperations(registry, gasCalculator, chainID); + } + + /** + * Polis evm. + * + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM polis(final EvmConfiguration evmConfiguration) { + return polis(DEV_NET_CHAIN_ID, evmConfiguration); + } + + /** + * Polis evm. + * + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM polis(final BigInteger chainId, final EvmConfiguration evmConfiguration) { + return polis(new PragueGasCalculator(), chainId, evmConfiguration); + } + + /** + * Polis evm. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM polis( + final GasCalculator gasCalculator, + final BigInteger chainId, + final EvmConfiguration evmConfiguration) { + return new EVM( + polisOperations(gasCalculator, chainId), + gasCalculator, + evmConfiguration, + EvmSpecVersion.POLIS); + } + + /** + * Operation registry for Polis's operations. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @return the operation registry + */ + public static OperationRegistry polisOperations( + final GasCalculator gasCalculator, final BigInteger chainId) { + OperationRegistry operationRegistry = new OperationRegistry(); + registerPolisOperations(operationRegistry, gasCalculator, chainId); + return operationRegistry; + } + + /** + * Register polis operations. + * + * @param registry the registry + * @param gasCalculator the gas calculator + * @param chainID the chain id + */ + public static void registerPolisOperations( + final OperationRegistry registry, + final GasCalculator gasCalculator, + final BigInteger chainID) { + registerBogotaOperations(registry, gasCalculator, chainID); + } + + /** + * Bangkok evm. + * + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM bangkok(final EvmConfiguration evmConfiguration) { + return bangkok(DEV_NET_CHAIN_ID, evmConfiguration); + } + + /** + * Bangkok evm. + * + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM bangkok(final BigInteger chainId, final EvmConfiguration evmConfiguration) { + return bangkok(new PragueGasCalculator(), chainId, evmConfiguration); + } + + /** + * Bangkok evm. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM bangkok( + final GasCalculator gasCalculator, + final BigInteger chainId, + final EvmConfiguration evmConfiguration) { + return new EVM( + bangkokOperations(gasCalculator, chainId), + gasCalculator, + evmConfiguration, + EvmSpecVersion.BANGKOK); + } + + /** + * Operation registry for bangkok's operations. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @return the operation registry + */ + public static OperationRegistry bangkokOperations( + final GasCalculator gasCalculator, final BigInteger chainId) { + OperationRegistry operationRegistry = new OperationRegistry(); + registerBangkokOperations(operationRegistry, gasCalculator, chainId); + return operationRegistry; + } + + /** + * Register bangkok operations. + * + * @param registry the registry + * @param gasCalculator the gas calculator + * @param chainID the chain id + */ + public static void registerBangkokOperations( + final OperationRegistry registry, + final GasCalculator gasCalculator, + final BigInteger chainID) { + registerPolisOperations(registry, gasCalculator, chainID); } /** @@ -1154,13 +1474,6 @@ public class MainnetEVMs { final GasCalculator gasCalculator, final BigInteger chainID) { registerBogotaOperations(registry, gasCalculator, chainID); - - // "big" EOF - registry.put(new RelativeJumpOperation(gasCalculator)); - registry.put(new RelativeJumpIfOperation(gasCalculator)); - registry.put(new RelativeJumpVectorOperation(gasCalculator)); - registry.put(new CallFOperation(gasCalculator)); - registry.put(new RetFOperation(gasCalculator)); } /** 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 a85754fb5b..f9a85a20b7 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 @@ -14,8 +14,13 @@ */ package org.hyperledger.besu.evm.code; +import static org.hyperledger.besu.evm.code.EOFLayout.EOFContainerMode.INITCODE; + import org.hyperledger.besu.evm.Code; +import javax.annotation.Nonnull; + +import com.google.errorprone.annotations.InlineMe; import org.apache.tuweni.bytes.Bytes; /** The Code factory. */ @@ -33,24 +38,57 @@ public final class CodeFactory { * * @param bytes the bytes * @param maxEofVersion the max eof version - * @param inCreateOperation the in create operation + * @return the code + */ + public static Code createCode(final Bytes bytes, final int maxEofVersion) { + return createCode(bytes, maxEofVersion, false, false); + } + + /** + * Create Code. + * + * @param bytes the bytes + * @param maxEofVersion the max eof version + * @param legacyCreation Allow some corner cases. `EF` and not `EF00` code + * @deprecated use the no boolean or two boolean variant + * @return the code + */ + @Deprecated(since = "24.4.1") + @InlineMe( + replacement = "CodeFactory.createCode(bytes, maxEofVersion, legacyCreation, false)", + imports = "org.hyperledger.besu.evm.code.CodeFactory") + public static Code createCode( + final Bytes bytes, final int maxEofVersion, final boolean legacyCreation) { + return createCode(bytes, maxEofVersion, legacyCreation, false); + } + + /** + * Create Code. + * + * @param bytes the bytes + * @param maxEofVersion the max eof version + * @param legacyCreation Allow some corner cases. `EF` and not `EF00` code + * @param createTransaction This is in a create transaction, allow dangling data * @return the code */ public static Code createCode( - final Bytes bytes, final int maxEofVersion, final boolean inCreateOperation) { + final Bytes bytes, + final int maxEofVersion, + final boolean legacyCreation, + final boolean createTransaction) { if (maxEofVersion == 0) { return new CodeV0(bytes); } else if (maxEofVersion == 1) { int codeSize = bytes.size(); if (codeSize > 0 && bytes.get(0) == EOF_LEAD_BYTE) { - if (codeSize == 1 && !inCreateOperation) { + if (codeSize == 1 && !legacyCreation) { return new CodeV0(bytes); } if (codeSize < 3) { return new CodeInvalid(bytes, "EOF Container too short"); } if (bytes.get(1) != 0) { - if (inCreateOperation) { + if (legacyCreation) { // because some 0xef code made it to mainnet, this is only an error at contract create return new CodeInvalid(bytes, "Incorrect second byte"); } else { @@ -62,22 +100,11 @@ public final class CodeFactory { return new CodeInvalid(bytes, "Unsupported EOF Version: " + version); } - final EOFLayout layout = EOFLayout.parseEOF(bytes); - if (!layout.isValid()) { - return new CodeInvalid(bytes, "Invalid EOF Layout: " + layout.getInvalidReason()); - } - - final String codeValidationError = CodeV1Validation.validateCode(layout); - if (codeValidationError != null) { - return new CodeInvalid(bytes, "EOF Code Invalid : " + codeValidationError); - } - - final String stackValidationError = CodeV1Validation.validateStack(layout); - if (stackValidationError != null) { - return new CodeInvalid(bytes, "EOF Code Invalid : " + stackValidationError); + final EOFLayout layout = EOFLayout.parseEOF(bytes, !createTransaction); + if (createTransaction) { + layout.containerMode().set(INITCODE); } - - return new CodeV1(layout); + return createCode(layout, createTransaction); } else { return new CodeV0(bytes); } @@ -85,4 +112,18 @@ public final class CodeFactory { return new CodeInvalid(bytes, "Unsupported max code version " + maxEofVersion); } } + + @Nonnull + static Code createCode(final EOFLayout layout, final boolean createTransaction) { + if (!layout.isValid()) { + return new CodeInvalid(layout.container(), "Invalid EOF Layout: " + layout.invalidReason()); + } + + final String validationError = CodeV1Validation.validate(layout); + if (validationError != null) { + return new CodeInvalid(layout.container(), "EOF Code Invalid : " + validationError); + } + + return new CodeV1(layout); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeInvalid.java b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeInvalid.java index 9b92613f4b..688be5a3cf 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeInvalid.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeInvalid.java @@ -16,7 +16,9 @@ package org.hyperledger.besu.evm.code; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.internal.Words; +import java.util.Optional; import java.util.function.Supplier; import com.google.common.base.Suppliers; @@ -59,6 +61,11 @@ public class CodeInvalid implements Code { return codeBytes.size(); } + @Override + public int getDataSize() { + return 0; + } + @Override public Bytes getBytes() { return codeBytes; @@ -91,6 +98,41 @@ public class CodeInvalid implements Code { @Override public int getEofVersion() { - return -1; + return Integer.MAX_VALUE; + } + + @Override + public int getSubcontainerCount() { + return 0; + } + + @Override + public Optional getSubContainer(final int index, final Bytes auxData) { + return Optional.empty(); + } + + @Override + public Bytes getData(final int offset, final int length) { + return Bytes.EMPTY; + } + + @Override + public int readBigEndianI16(final int index) { + return Words.readBigEndianI16(index, codeBytes.toArrayUnsafe()); + } + + @Override + public int readBigEndianU16(final int index) { + return Words.readBigEndianU16(index, codeBytes.toArrayUnsafe()); + } + + @Override + public int readU8(final int index) { + return codeBytes.toArrayUnsafe()[index] & 0xff; + } + + @Override + public String prettyPrint() { + return codeBytes.toHexString(); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeSection.java b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeSection.java index cf28a26e22..7a9982bf2d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeSection.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeSection.java @@ -36,6 +36,9 @@ public final class CodeSection { /** The byte offset from the beginning of the container that the section starts at */ final int entryPoint; + /** Is this a returing code section (i.e. contains RETF or JUMPF into a returning section)? */ + final boolean returning; + /** * Instantiates a new Code section. * @@ -53,7 +56,13 @@ public final class CodeSection { final int entryPoint) { this.length = length; this.inputs = inputs; - this.outputs = outputs; + if (outputs == 0x80) { + this.outputs = 0; + returning = false; + } else { + this.outputs = outputs; + returning = true; + } this.maxStackHeight = maxStackHeight; this.entryPoint = entryPoint; } @@ -85,6 +94,15 @@ public final class CodeSection { return outputs; } + /** + * Does this code seciton have a RETF return anywhere? + * + * @return returning + */ + public boolean isReturning() { + return returning; + } + /** * Gets max stack height. * diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV0.java b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV0.java index 5281d0aa61..31a49ba15a 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV0.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV0.java @@ -16,8 +16,10 @@ package org.hyperledger.besu.evm.code; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.internal.Words; import org.hyperledger.besu.evm.operation.JumpDestOperation; +import java.util.Optional; import java.util.function.Supplier; import com.google.common.base.MoreObjects; @@ -57,15 +59,14 @@ public class CodeV0 implements Code { * Returns true if the object is equal to this; otherwise false. * * @param other The object to compare this with. - * @return True if the object is equal to this; otherwise false. + * @return True if the object is equal to this, otherwise false. */ @Override public boolean equals(final Object other) { if (other == null) return false; if (other == this) return true; - if (!(other instanceof CodeV0)) return false; + if (!(other instanceof CodeV0 that)) return false; - final CodeV0 that = (CodeV0) other; return this.bytes.equals(that.bytes); } @@ -84,6 +85,11 @@ public class CodeV0 implements Code { return bytes.size(); } + @Override + public int getDataSize() { + return 0; + } + @Override public Bytes getBytes() { return bytes; @@ -137,6 +143,21 @@ public class CodeV0 implements Code { return 0; } + @Override + public int getSubcontainerCount() { + return 0; + } + + @Override + public Optional getSubContainer(final int index, final Bytes auxData) { + return Optional.empty(); + } + + @Override + public Bytes getData(final int offset, final int length) { + return Bytes.EMPTY; + } + /** * Calculate jump destination. * @@ -295,4 +316,24 @@ public class CodeV0 implements Code { } return bitmap; } + + @Override + public int readBigEndianI16(final int index) { + return Words.readBigEndianI16(index, bytes.toArrayUnsafe()); + } + + @Override + public int readBigEndianU16(final int index) { + return Words.readBigEndianU16(index, bytes.toArrayUnsafe()); + } + + @Override + public int readU8(final int index) { + return bytes.toArrayUnsafe()[index] & 0xff; + } + + @Override + public String prettyPrint() { + return bytes.toHexString(); + } } 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 948fdd6128..2bea400ed7 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 @@ -18,12 +18,17 @@ import static com.google.common.base.Preconditions.checkArgument; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.internal.Words; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.Objects; +import java.util.Optional; import java.util.function.Supplier; import com.google.common.base.Suppliers; import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.MutableBytes; /** The CodeV1. */ public class CodeV1 implements Code { @@ -34,16 +39,16 @@ public class CodeV1 implements Code { /** * Instantiates a new CodeV1. * - * @param layout the layout + * @param eofLayout the layout */ - CodeV1(final EOFLayout layout) { - this.eofLayout = layout; - this.codeHash = Suppliers.memoize(() -> Hash.hash(eofLayout.getContainer())); + CodeV1(final EOFLayout eofLayout) { + this.eofLayout = eofLayout; + this.codeHash = Suppliers.memoize(() -> Hash.hash(eofLayout.container())); } @Override public int getSize() { - return eofLayout.getContainer().size(); + return eofLayout.container().size(); } @Override @@ -60,7 +65,7 @@ public class CodeV1 implements Code { @Override public Bytes getBytes() { - return eofLayout.getContainer(); + return eofLayout.container(); } @Override @@ -80,7 +85,35 @@ public class CodeV1 implements Code { @Override public int getEofVersion() { - return eofLayout.getVersion(); + return eofLayout.version(); + } + + @Override + public int getSubcontainerCount() { + return eofLayout.getSubcontainerCount(); + } + + @Override + public Optional getSubContainer(final int index, final Bytes auxData) { + EOFLayout subcontainerLayout = eofLayout.getSubcontainer(index); + if (auxData != null && !auxData.isEmpty()) { + Bytes subcontainerWithAuxData = subcontainerLayout.writeContainer(auxData); + if (subcontainerWithAuxData == null) { + return Optional.empty(); + } + subcontainerLayout = EOFLayout.parseEOF(subcontainerWithAuxData); + } else { + // if no auxdata is added we must validate data is not truncated separately + if (subcontainerLayout.dataLength() != subcontainerLayout.data().size()) { + return Optional.empty(); + } + } + + Code subContainerCode = CodeFactory.createCode(subcontainerLayout, auxData == null); + + return subContainerCode.isValid() && subContainerCode.getEofVersion() > 0 + ? Optional.of(subContainerCode) + : Optional.empty(); } @Override @@ -95,4 +128,56 @@ public class CodeV1 implements Code { public int hashCode() { return Objects.hash(codeHash, eofLayout); } + + @Override + public Bytes getData(final int offset, final int length) { + Bytes data = eofLayout.data(); + int dataLen = data.size(); + if (offset > dataLen) { + return Bytes.EMPTY; + } else if ((offset + length) > dataLen) { + byte[] result = new byte[length]; + MutableBytes mbytes = MutableBytes.wrap(result); + data.slice(offset).copyTo(mbytes, 0); + return Bytes.wrap(result); + } else { + return data.slice(offset, length); + } + } + + @Override + public int getDataSize() { + return eofLayout.data().size(); + } + + @Override + public int readBigEndianI16(final int index) { + return Words.readBigEndianI16(index, eofLayout.container().toArrayUnsafe()); + } + + @Override + public int readBigEndianU16(final int index) { + return Words.readBigEndianU16(index, eofLayout.container().toArrayUnsafe()); + } + + @Override + public int readU8(final int index) { + return eofLayout.container().toArrayUnsafe()[index] & 0xff; + } + + @Override + public String prettyPrint() { + StringWriter sw = new StringWriter(); + eofLayout.prettyPrint(new PrintWriter(sw, true), "", ""); + return sw.toString(); + } + + /** + * The EOFLayout object for the code + * + * @return the EOFLayout object for the parsed code + */ + public EOFLayout getEofLayout() { + return eofLayout; + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1Validation.java b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1Validation.java index b6f6173d7e..b52f1dfebb 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1Validation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1Validation.java @@ -14,553 +14,80 @@ */ package org.hyperledger.besu.evm.code; +import static java.lang.Math.max; +import static java.lang.Math.min; +import static java.lang.String.format; +import static org.hyperledger.besu.evm.code.EOFLayout.EOFContainerMode.INITCODE; +import static org.hyperledger.besu.evm.code.EOFLayout.EOFContainerMode.RUNTIME; +import static org.hyperledger.besu.evm.code.OpcodeInfo.V1_OPCODES; import static org.hyperledger.besu.evm.internal.Words.readBigEndianI16; import static org.hyperledger.besu.evm.internal.Words.readBigEndianU16; +import org.hyperledger.besu.evm.code.EOFLayout.EOFContainerMode; import org.hyperledger.besu.evm.operation.CallFOperation; +import org.hyperledger.besu.evm.operation.DataLoadNOperation; +import org.hyperledger.besu.evm.operation.DupNOperation; +import org.hyperledger.besu.evm.operation.EOFCreateOperation; +import org.hyperledger.besu.evm.operation.ExchangeOperation; +import org.hyperledger.besu.evm.operation.InvalidOperation; +import org.hyperledger.besu.evm.operation.JumpFOperation; import org.hyperledger.besu.evm.operation.PushOperation; import org.hyperledger.besu.evm.operation.RelativeJumpIfOperation; import org.hyperledger.besu.evm.operation.RelativeJumpOperation; import org.hyperledger.besu.evm.operation.RelativeJumpVectorOperation; import org.hyperledger.besu.evm.operation.RetFOperation; +import org.hyperledger.besu.evm.operation.ReturnContractOperation; +import org.hyperledger.besu.evm.operation.ReturnOperation; +import org.hyperledger.besu.evm.operation.RevertOperation; +import org.hyperledger.besu.evm.operation.StopOperation; +import org.hyperledger.besu.evm.operation.SwapNOperation; +import java.util.ArrayDeque; import java.util.Arrays; import java.util.BitSet; +import java.util.List; +import java.util.Queue; +import javax.annotation.Nullable; import org.apache.tuweni.bytes.Bytes; /** Code V1 Validation */ public final class CodeV1Validation { + static final int MAX_STACK_HEIGHT = 1024; + private CodeV1Validation() { // to prevent instantiation } - static final byte INVALID = 0x01; - static final byte VALID = 0x02; - static final byte TERMINAL = 0x04; - static final byte VALID_AND_TERMINAL = VALID | TERMINAL; - static final byte[] OPCODE_ATTRIBUTES = { - VALID_AND_TERMINAL, // 0x00 STOP - VALID, // 0x01 - ADD - VALID, // 0x02 - MUL - VALID, // 0x03 - SUB - VALID, // 0x04 - DIV - VALID, // 0x05 - SDIV - VALID, // 0x06 - MOD - VALID, // 0x07 - SMOD - VALID, // 0x08 - ADDMOD - VALID, // 0x09 - MULMOD - VALID, // 0x0a - EXP - VALID, // 0x0b - SIGNEXTEND - INVALID, // 0x0c - INVALID, // 0x0d - INVALID, // 0x0e - INVALID, // 0x0f - VALID, // 0x10 - LT - VALID, // 0x11 - GT - VALID, // 0x12 - SLT - VALID, // 0x13 - SGT - VALID, // 0x14 - EQ - VALID, // 0x15 - ISZERO - VALID, // 0x16 - AND - VALID, // 0x17 - OR - VALID, // 0x18 - XOR - VALID, // 0x19 - NOT - VALID, // 0x1a - BYTE - VALID, // 0x1b - SHL - VALID, // 0x1c - SHR - VALID, // 0x1d - SAR - INVALID, // 0x1e - INVALID, // 0x1f - VALID, // 0x20 - SHA3 - INVALID, // 0x21 - INVALID, // 0x22 - INVALID, // 0x23 - INVALID, // 0x24 - INVALID, // 0x25 - INVALID, // 0x26 - INVALID, // 0x27 - INVALID, // 0x28 - INVALID, // 0x29 - INVALID, // 0x2a - INVALID, // 0x2b - INVALID, // 0x2c - INVALID, // 0x2d - INVALID, // 0x2e - INVALID, // 0x2f - VALID, // 0x30 - ADDRESS - VALID, // 0x31 - BALANCE - VALID, // 0x32 - ORIGIN - VALID, // 0x33 - CALLER - VALID, // 0x34 - CALLVALUE - VALID, // 0x35 - CALLDATALOAD - VALID, // 0x36 - CALLDATASIZE - VALID, // 0x37 - CALLDATACOPY - VALID, // 0x38 - CODESIZE - VALID, // 0x39 - CODECOPY - VALID, // 0x3a - GASPRICE - VALID, // 0x3b - EXTCODESIZE - VALID, // 0x3c - EXTCODECOPY - VALID, // 0x3d - RETURNDATASIZE - VALID, // 0x3e - RETURNDATACOPY - VALID, // 0x3f - EXTCODEHASH - VALID, // 0x40 - BLOCKHASH - VALID, // 0x41 - COINBASE - VALID, // 0x42 - TIMESTAMP - VALID, // 0x43 - NUMBER - VALID, // 0x44 - PREVRANDAO (née DIFFICULTY) - VALID, // 0x45 - GASLIMIT - VALID, // 0x46 - CHAINID - VALID, // 0x47 - SELFBALANCE - VALID, // 0x48 - BASEFEE - INVALID, // 0x49 - INVALID, // 0x4a - INVALID, // 0x4b - INVALID, // 0x4c - INVALID, // 0x4d - INVALID, // 0x4e - INVALID, // 0x4f - VALID, // 0x50 - POP - VALID, // 0x51 - MLOAD - VALID, // 0x52 - MSTORE - VALID, // 0x53 - MSTORE8 - VALID, // 0x54 - SLOAD - VALID, // 0x55 - SSTORE - INVALID, // 0x56 - JUMP - INVALID, // 0x57 - JUMPI - INVALID, // 0x58 - PC - VALID, // 0x59 - MSIZE - VALID, // 0x5a - GAS - VALID, // 0x5b - NOOOP (née JUMPDEST) - VALID, // 0X5c - TLOAD - VALID, // 0X5d - TSTORE - VALID, // 0X5e - MCOPY - VALID, // 0X5f - PUSH0 - VALID, // 0x60 - PUSH1 - VALID, // 0x61 - PUSH2 - VALID, // 0x62 - PUSH3 - VALID, // 0x63 - PUSH4 - VALID, // 0x64 - PUSH5 - VALID, // 0x65 - PUSH6 - VALID, // 0x66 - PUSH7 - VALID, // 0x67 - PUSH8 - VALID, // 0x68 - PUSH9 - VALID, // 0x69 - PUSH10 - VALID, // 0x6a - PUSH11 - VALID, // 0x6b - PUSH12 - VALID, // 0x6c - PUSH13 - VALID, // 0x6d - PUSH14 - VALID, // 0x6e - PUSH15 - VALID, // 0x6f - PUSH16 - VALID, // 0x70 - PUSH17 - VALID, // 0x71 - PUSH18 - VALID, // 0x72 - PUSH19 - VALID, // 0x73 - PUSH20 - VALID, // 0x74 - PUSH21 - VALID, // 0x75 - PUSH22 - VALID, // 0x76 - PUSH23 - VALID, // 0x77 - PUSH24 - VALID, // 0x78 - PUSH25 - VALID, // 0x79 - PUSH26 - VALID, // 0x7a - PUSH27 - VALID, // 0x7b - PUSH28 - VALID, // 0x7c - PUSH29 - VALID, // 0x7d - PUSH30 - VALID, // 0x7e - PUSH31 - VALID, // 0x7f - PUSH32 - VALID, // 0x80 - DUP1 - VALID, // 0x81 - DUP2 - VALID, // 0x82 - DUP3 - VALID, // 0x83 - DUP4 - VALID, // 0x84 - DUP5 - VALID, // 0x85 - DUP6 - VALID, // 0x86 - DUP7 - VALID, // 0x87 - DUP8 - VALID, // 0x88 - DUP9 - VALID, // 0x89 - DUP10 - VALID, // 0x8a - DUP11 - VALID, // 0x8b - DUP12 - VALID, // 0x8c - DUP13 - VALID, // 0x8d - DUP14 - VALID, // 0x8e - DUP15 - VALID, // 0x8f - DUP16 - VALID, // 0x90 - SWAP1 - VALID, // 0x91 - SWAP2 - VALID, // 0x92 - SWAP3 - VALID, // 0x93 - SWAP4 - VALID, // 0x94 - SWAP5 - VALID, // 0x95 - SWAP6 - VALID, // 0x96 - SWAP7 - VALID, // 0x97 - SWAP8 - VALID, // 0x98 - SWAP9 - VALID, // 0x99 - SWAP10 - VALID, // 0x9a - SWAP11 - VALID, // 0x9b - SWAP12 - VALID, // 0x9c - SWAP13 - VALID, // 0x9d - SWAP14 - VALID, // 0x9e - SWAP15 - VALID, // 0x9f - SWAP16 - VALID, // 0xa0 - LOG0 - VALID, // 0xa1 - LOG1 - VALID, // 0xa2 - LOG2 - VALID, // 0xa3 - LOG3 - VALID, // 0xa4 - LOG4 - INVALID, // 0xa5 - INVALID, // 0xa6 - INVALID, // 0xa7 - INVALID, // 0xa8 - INVALID, // 0xa9 - INVALID, // 0xaa - INVALID, // 0xab - INVALID, // 0xac - INVALID, // 0xad - INVALID, // 0xae - INVALID, // 0xaf - INVALID, // 0xb0 - INVALID, // 0xb1 - INVALID, // 0xb2 - INVALID, // 0xb3 - INVALID, // 0xb4 - INVALID, // 0xb5 - INVALID, // 0xb6 - INVALID, // 0xb7 - INVALID, // 0xb8 - INVALID, // 0xb9 - INVALID, // 0xba - INVALID, // 0xbb - INVALID, // 0xbc - INVALID, // 0xbd - INVALID, // 0xbe - INVALID, // 0xbf - INVALID, // 0xc0 - INVALID, // 0xc1 - INVALID, // 0xc2 - INVALID, // 0xc3 - INVALID, // 0xc4 - INVALID, // 0xc5 - INVALID, // 0xc6 - INVALID, // 0xc7 - INVALID, // 0xc8 - INVALID, // 0xc9 - INVALID, // 0xca - INVALID, // 0xcb - INVALID, // 0xcc - INVALID, // 0xcd - INVALID, // 0xce - INVALID, // 0xcf - INVALID, // 0xd0 - INVALID, // 0xd1 - INVALID, // 0xd2 - INVALID, // 0xd3 - INVALID, // 0xd4 - INVALID, // 0xd5 - INVALID, // 0xd6 - INVALID, // 0xd7 - INVALID, // 0xd8 - INVALID, // 0xd9 - INVALID, // 0xda - INVALID, // 0xdb - INVALID, // 0xdc - INVALID, // 0xdd - INVALID, // 0xde - INVALID, // 0xef - VALID_AND_TERMINAL, // 0xe0 - RJUMP - VALID, // 0xe1 - RJUMPI - VALID, // 0xe2 - RJUMPV - VALID, // 0xe3 - CALLF - VALID_AND_TERMINAL, // 0xe4 - RETF - INVALID, // 0xe5 - INVALID, // 0xe6 - INVALID, // 0xe7 - INVALID, // 0xe8 - INVALID, // 0xe9 - INVALID, // 0xea - INVALID, // 0xeb - INVALID, // 0xec - INVALID, // 0xed - INVALID, // 0xee - INVALID, // 0xef - VALID, // 0xf0 - CREATE - VALID, // 0xf1 - CALL - INVALID, // 0xf2 - CALLCODE - VALID_AND_TERMINAL, // 0xf3 - RETURN - VALID, // 0xf4 - DELEGATECALL - VALID, // 0xf5 - CREATE2 - INVALID, // 0xf6 - INVALID, // 0xf7 - INVALID, // 0xf8 - INVALID, // 0xf9 - VALID, // 0xfa - STATICCALL - INVALID, // 0xfb - INVALID, // 0xfc - VALID_AND_TERMINAL, // 0xfd - REVERT - VALID_AND_TERMINAL, // 0xfe - INVALID - INVALID, // 0xff - SELFDESTRUCT - }; - static final int MAX_STACK_HEIGHT = 1024; - // java17 move to record - // [0] - stack input consumed - // [1] - stack outputs added - // [2] - PC advance - static final byte[][] OPCODE_STACK_VALIDATION = { - {0, 0, -1}, // 0x00 - STOP - {2, 1, 1}, // 0x01 - ADD - {2, 1, 1}, // 0x02 - MUL - {2, 1, 1}, // 0x03 - SUB - {2, 1, 1}, // 0x04 - DIV - {2, 1, 1}, // 0x05 - SDIV - {2, 1, 1}, // 0x06 - MOD - {2, 1, 1}, // 0x07 - SMOD - {3, 1, 1}, // 0x08 - ADDMOD - {3, 1, 1}, // 0x09 - MULMOD - {2, 1, 1}, // 0x0a - EXP - {2, 1, 1}, // 0x0b - SIGNEXTEND - {0, 0, 0}, // 0x0c - {0, 0, 0}, // 0x0d - {0, 0, 0}, // 0x0e - {0, 0, 0}, // 0x0f - {2, 1, 1}, // 0x10 - LT - {2, 1, 1}, // 0x11 - GT - {2, 1, 1}, // 0x12 - SLT - {2, 1, 1}, // 0x13 - SGT - {2, 1, 1}, // 0x14 - EQ - {1, 1, 1}, // 0x15 - ISZERO - {2, 1, 1}, // 0x16 - AND - {2, 1, 1}, // 0x17 - OR - {2, 1, 1}, // 0x18 - XOR - {1, 1, 1}, // 0x19 - NOT - {2, 1, 1}, // 0x1a - BYTE - {2, 1, 1}, // 0x1b - SHL - {2, 1, 1}, // 0x1c - SHR - {2, 1, 1}, // 0x1d - SAR - {0, 0, 0}, // 0x1e - {0, 0, 0}, // 0x1f - {2, 1, 1}, // 0x20 - SHA3 - {0, 0, 0}, // 0x21 - {0, 0, 0}, // 0x22 - {0, 0, 0}, // 0x23 - {0, 0, 0}, // 0x24 - {0, 0, 0}, // 0x25 - {0, 0, 0}, // 0x26 - {0, 0, 0}, // 0x27 - {0, 0, 0}, // 0x28 - {0, 0, 0}, // 0x29 - {0, 0, 0}, // 0x2a - {0, 0, 0}, // 0x2b - {0, 0, 0}, // 0x2c - {0, 0, 0}, // 0x2d - {0, 0, 0}, // 0x2e - {0, 0, 0}, // 0x2f - {0, 1, 1}, // 0x30 - ADDRESS - {1, 1, 1}, // 0x31 - BALANCE - {0, 1, 1}, // 0x32 - ORIGIN - {0, 1, 1}, // 0x33 - CALLER - {0, 1, 1}, // 0x34 - CALLVALUE - {1, 1, 1}, // 0x35 - CALLDATALOAD - {0, 1, 1}, // 0x36 - CALLDATASIZE - {3, 0, 1}, // 0x37 - CALLDATACOPY - {0, 1, 1}, // 0x38 - CODESIZE - {3, 0, 1}, // 0x39 - CODECOPY - {0, 1, 1}, // 0x3a - GASPRICE - {1, 1, 1}, // 0x3b - EXTCODESIZE - {4, 0, 1}, // 0x3c - EXTCODECOPY - {0, 1, 1}, // 0x3d - RETURNDATASIZE - {3, 0, 1}, // 0x3e - RETURNDATACOPY - {1, 1, 1}, // 0x3f - EXTCODEHASH - {1, 1, 1}, // 0x40 - BLOCKHASH - {0, 1, 1}, // 0x41 - COINBASE - {0, 1, 1}, // 0x42 - TIMESTAMP - {0, 1, 1}, // 0x43 - NUMBER - {0, 1, 1}, // 0x44 - PREVRANDAO (née DIFFICULTY) - {0, 1, 1}, // 0x45 - GASLIMIT - {0, 1, 1}, // 0x46 - CHAINID - {0, 1, 1}, // 0x47 - SELFBALANCE - {0, 1, 1}, // 0x48 - BASEFEE - {0, 0, 0}, // 0x49 - {0, 0, 0}, // 0x4a - {0, 0, 0}, // 0x4b - {0, 0, 0}, // 0x4c - {0, 0, 0}, // 0x4d - {0, 0, 0}, // 0x4e - {0, 0, 0}, // 0x4f - {1, 0, 1}, // 0x50 - POP - {1, 1, 1}, // 0x51 - MLOAD - {2, 0, 1}, // 0x52 - MSTORE - {2, 0, 1}, // 0x53 - MSTORE8 - {1, 1, 1}, // 0x54 - SLOAD - {2, 0, 1}, // 0x55 - SSTORE - {0, 0, 0}, // 0x56 - JUMP - {0, 0, 0}, // 0x57 - JUMPI - {0, 0, 0}, // 0x58 - PC - {0, 1, 1}, // 0x59 - MSIZE - {0, 1, 1}, // 0x5a - GAS - {0, 0, 1}, // 0x5b - NOOP (née JUMPDEST) - {1, 1, 1}, // 0x5c - TLOAD - {2, 0, 1}, // 0x5d - TSTORE - {4, 0, 1}, // 0x5e - MCOPY - {0, 1, 1}, // 0x5f - PUSH0 - {0, 1, 2}, // 0x60 - PUSH1 - {0, 1, 3}, // 0x61 - PUSH2 - {0, 1, 4}, // 0x62 - PUSH3 - {0, 1, 5}, // 0x63 - PUSH4 - {0, 1, 6}, // 0x64 - PUSH5 - {0, 1, 7}, // 0x65 - PUSH6 - {0, 1, 8}, // 0x66 - PUSH7 - {0, 1, 9}, // 0x67 - PUSH8 - {0, 1, 10}, // 0x68 - PUSH9 - {0, 1, 11}, // 0x69 - PUSH10 - {0, 1, 12}, // 0x6a - PUSH11 - {0, 1, 13}, // 0x6b - PUSH12 - {0, 1, 14}, // 0x6c - PUSH13 - {0, 1, 15}, // 0x6d - PUSH14 - {0, 1, 16}, // 0x6e - PUSH15 - {0, 1, 17}, // 0x6f - PUSH16 - {0, 1, 18}, // 0x70 - PUSH17 - {0, 1, 19}, // 0x71 - PUSH18 - {0, 1, 20}, // 0x72 - PUSH19 - {0, 1, 21}, // 0x73 - PUSH20 - {0, 1, 22}, // 0x74 - PUSH21 - {0, 1, 23}, // 0x75 - PUSH22 - {0, 1, 24}, // 0x76 - PUSH23 - {0, 1, 25}, // 0x77 - PUSH24 - {0, 1, 26}, // 0x78 - PUSH25 - {0, 1, 27}, // 0x79 - PUSH26 - {0, 1, 28}, // 0x7a - PUSH27 - {0, 1, 29}, // 0x7b - PUSH28 - {0, 1, 30}, // 0x7c - PUSH29 - {0, 1, 31}, // 0x7d - PUSH30 - {0, 1, 32}, // 0x7e - PUSH31 - {0, 1, 33}, // 0x7f - PUSH32 - {1, 2, 1}, // 0x80 - DUP1 - {2, 3, 1}, // 0x81 - DUP2 - {3, 4, 1}, // 0x82 - DUP3 - {4, 5, 1}, // 0x83 - DUP4 - {5, 6, 1}, // 0x84 - DUP5 - {6, 7, 1}, // 0x85 - DUP6 - {7, 8, 1}, // 0x86 - DUP7 - {8, 9, 1}, // 0x87 - DUP8 - {9, 10, 1}, // 0x88 - DUP9 - {10, 11, 1}, // 0x89 - DUP10 - {11, 12, 1}, // 0x8a - DUP11 - {12, 13, 1}, // 0x8b - DUP12 - {13, 14, 1}, // 0x8c - DUP13 - {14, 15, 1}, // 0x8d - DUP14 - {15, 16, 1}, // 0x8e - DUP15 - {16, 17, 1}, // 0x8f - DUP16 - {2, 2, 1}, // 0x90 - SWAP1 - {3, 3, 1}, // 0x91 - SWAP2 - {4, 4, 1}, // 0x92 - SWAP3 - {5, 5, 1}, // 0x93 - SWAP4 - {6, 6, 1}, // 0x94 - SWAP5 - {7, 7, 1}, // 0x95 - SWAP6 - {8, 8, 1}, // 0x96 - SWAP7 - {9, 9, 1}, // 0x97 - SWAP8 - {10, 10, 1}, // 0x98 - SWAP9 - {11, 11, 1}, // 0x99 - SWAP10 - {12, 12, 1}, // 0x9a - SWAP11 - {13, 13, 1}, // 0x9b - SWAP12 - {14, 14, 1}, // 0x9c - SWAP13 - {15, 15, 1}, // 0x9d - SWAP14 - {16, 16, 1}, // 0x9e - SWAP15 - {17, 17, 1}, // 0x9f - SWAP16 - {2, 0, 1}, // 0xa0 - LOG0 - {3, 0, 1}, // 0xa1 - LOG1 - {4, 0, 1}, // 0xa2 - LOG2 - {5, 0, 1}, // 0xa3 - LOG3 - {6, 0, 1}, // 0xa4 - LOG4 - {0, 0, 0}, // 0xa5 - {0, 0, 0}, // 0xa6 - {0, 0, 0}, // 0xa7 - {0, 0, 0}, // 0xa8 - {0, 0, 0}, // 0xa9 - {0, 0, 0}, // 0xaa - {0, 0, 0}, // 0xab - {0, 0, 0}, // 0xac - {0, 0, 0}, // 0xad - {0, 0, 0}, // 0xae - {0, 0, 0}, // 0xaf - {0, 0, 0}, // 0xb0 - {0, 0, 0}, // 0xb1 - {0, 0, 0}, // 0xb2 - {0, 0, 0}, // 0xb3 - {0, 0, 0}, // 0xb4 - {0, 0, 0}, // 0xb5 - {0, 0, 0}, // 0xb6 - {0, 0, 0}, // 0xb7 - {0, 0, 0}, // 0xb8 - {0, 0, 0}, // 0xb9 - {0, 0, 0}, // 0xba - {0, 0, 0}, // 0xbb - {0, 0, 0}, // 0xbc - {0, 0, 0}, // 0xbd - {0, 0, 0}, // 0xbe - {0, 0, 0}, // 0xbf - {0, 0, 0}, // 0xc0 - {0, 0, 0}, // 0xc1 - {0, 0, 0}, // 0xc2 - {0, 0, 0}, // 0xc3 - {0, 0, 0}, // 0xc4 - {0, 0, 0}, // 0xc5 - {0, 0, 0}, // 0xc6 - {0, 0, 0}, // 0xc7 - {0, 0, 0}, // 0xc8 - {0, 0, 0}, // 0xc9 - {0, 0, 0}, // 0xca - {0, 0, 0}, // 0xcb - {0, 0, 0}, // 0xcc - {0, 0, 0}, // 0xcd - {0, 0, 0}, // 0xce - {0, 0, 0}, // 0xcf - {0, 0, 0}, // 0xd0 - {0, 0, 0}, // 0xd1 - {0, 0, 0}, // 0xd2 - {0, 0, 0}, // 0xd3 - {0, 0, 0}, // 0xd4 - {0, 0, 0}, // 0xd5 - {0, 0, 0}, // 0xd6 - {0, 0, 0}, // 0xd7 - {0, 0, 0}, // 0xd8 - {0, 0, 0}, // 0xd9 - {0, 0, 0}, // 0xda - {0, 0, 0}, // 0xdb - {0, 0, 0}, // 0xdc - {0, 0, 0}, // 0xdd - {0, 0, 0}, // 0xde - {0, 0, 0}, // 0xef - {0, 0, -3}, // 0xe0 - RJUMP - {1, 0, 3}, // 0xe1 - RJUMPI - {1, 0, 2}, // 0xe2 - RJUMPV - {0, 0, 3}, // 0xe3 - CALLF - {0, 0, -1}, // 0xe4 - RETF - {0, 0, 0}, // 0xe5 - JUMPF - {0, 0, 0}, // 0xe6 - {0, 0, 0}, // 0xe7 - {0, 0, 0}, // 0xe8 - {0, 0, 0}, // 0xe9 - {0, 0, 0}, // 0xea - {0, 0, 0}, // 0xeb - {0, 0, 0}, // 0xec - {0, 0, 0}, // 0xed - {0, 0, 0}, // 0xee - {0, 0, 0}, // 0xef - {3, 1, 1}, // 0xf0 - CREATE - {7, 1, 1}, // 0xf1 - CALL - {0, 0, 0}, // 0xf2 - CALLCODE - {2, 0, -1}, // 0xf3 - RETURN - {6, 1, 1}, // 0xf4 - DELEGATECALL - {4, 1, 1}, // 0xf5 - CREATE2 - {0, 0, 0}, // 0xf6 - {0, 0, 0}, // 0xf7 - {0, 0, 0}, // 0xf8 - {0, 0, 0}, // 0xf9 - {6, 1, 1}, // 0xfa - STATICCALL - {0, 0, 0}, // 0xfb - {0, 0, 0}, // 0xfc - {2, 0, -1}, // 0xfd - REVERT - {0, 0, -1}, // 0xfe - INVALID - {0, 0, 0}, // 0xff - SELFDESTRUCT - }; + /** + * Validates the code and stack for the EOF Layout, with optional deep consideration of the + * containers. + * + * @param layout The parsed EOFLayout of the code + * @return either null, indicating no error, or a String describing the validation error. + */ + public static String validate(final EOFLayout layout) { + Queue workList = new ArrayDeque<>(layout.getSubcontainerCount()); + workList.add(layout); + + while (!workList.isEmpty()) { + EOFLayout container = workList.poll(); + workList.addAll(List.of(container.subContainers())); + + final String codeValidationError = CodeV1Validation.validateCode(container); + if (codeValidationError != null) { + return codeValidationError; + } + + final String stackValidationError = CodeV1Validation.validateStack(container); + if (stackValidationError != null) { + return stackValidationError; + } + } + + return null; + } /** * Validate Code @@ -569,12 +96,13 @@ public final class CodeV1Validation { * @return validation code, null otherwise. */ public static String validateCode(final EOFLayout eofLayout) { - int sectionCount = eofLayout.getCodeSectionCount(); - for (int i = 0; i < sectionCount; i++) { - CodeSection cs = eofLayout.getCodeSection(i); + if (!eofLayout.isValid()) { + return "Invalid EOF container - " + eofLayout.invalidReason(); + } + for (CodeSection cs : eofLayout.codeSections()) { var validation = CodeV1Validation.validateCode( - eofLayout.getContainer().slice(cs.getEntryPoint(), cs.getLength()), sectionCount); + eofLayout.container().slice(cs.getEntryPoint(), cs.getLength()), cs, eofLayout); if (validation != null) { return validation; } @@ -588,71 +116,228 @@ public final class CodeV1Validation { * @param code the code section code * @return null if valid, otherwise a string containing an error reason. */ - static String validateCode(final Bytes code, final int sectionCount) { + static String validateCode( + final Bytes code, final CodeSection thisCodeSection, final EOFLayout eofLayout) { final int size = code.size(); final BitSet rjumpdests = new BitSet(size); final BitSet immediates = new BitSet(size); final byte[] rawCode = code.toArrayUnsafe(); - int attribute = INVALID; + OpcodeInfo opcodeInfo = V1_OPCODES[0xfe]; int pos = 0; + EOFContainerMode eofContainerMode = eofLayout.containerMode().get(); + boolean hasReturningOpcode = false; while (pos < size) { final int operationNum = rawCode[pos] & 0xff; - attribute = OPCODE_ATTRIBUTES[operationNum]; - if ((attribute & INVALID) == INVALID) { + opcodeInfo = V1_OPCODES[operationNum]; + if (!opcodeInfo.valid()) { // undefined instruction - return String.format("Invalid Instruction 0x%02x", operationNum); + return format("Invalid Instruction 0x%02x", operationNum); } pos += 1; int pcPostInstruction = pos; - if (operationNum > PushOperation.PUSH_BASE && operationNum <= PushOperation.PUSH_MAX) { - final int multiByteDataLen = operationNum - PushOperation.PUSH_BASE; - pcPostInstruction += multiByteDataLen; - } else if (operationNum == RelativeJumpOperation.OPCODE - || operationNum == RelativeJumpIfOperation.OPCODE) { - if (pos + 2 > size) { - return "Truncated relative jump offset"; - } - pcPostInstruction += 2; - final int offset = readBigEndianI16(pos, rawCode); - final int rjumpdest = pcPostInstruction + offset; - if (rjumpdest < 0 || rjumpdest >= size) { - return "Relative jump destination out of bounds"; - } - rjumpdests.set(rjumpdest); - } else if (operationNum == RelativeJumpVectorOperation.OPCODE) { - if (pos + 1 > size) { - return "Truncated jump table"; - } - final int jumpTableSize = RelativeJumpVectorOperation.getVectorSize(code, pos); - if (jumpTableSize == 0) { - return "Empty jump table"; - } - pcPostInstruction += 1 + 2 * jumpTableSize; - if (pcPostInstruction > size) { - return "Truncated jump table"; - } - for (int offsetPos = pos + 1; offsetPos < pcPostInstruction; offsetPos += 2) { - final int offset = readBigEndianI16(offsetPos, rawCode); + switch (operationNum) { + case StopOperation.OPCODE, ReturnOperation.OPCODE: + if (eofContainerMode == null) { + eofContainerMode = RUNTIME; + eofLayout.containerMode().set(RUNTIME); + } else if (!eofContainerMode.equals(RUNTIME)) { + return format( + "%s is only a valid opcode in containers used for runtime operations.", + opcodeInfo.name()); + } + break; + case PushOperation.PUSH_BASE, + PushOperation.PUSH_BASE + 1, + PushOperation.PUSH_BASE + 2, + PushOperation.PUSH_BASE + 3, + PushOperation.PUSH_BASE + 4, + PushOperation.PUSH_BASE + 5, + PushOperation.PUSH_BASE + 6, + PushOperation.PUSH_BASE + 7, + PushOperation.PUSH_BASE + 8, + PushOperation.PUSH_BASE + 9, + PushOperation.PUSH_BASE + 10, + PushOperation.PUSH_BASE + 11, + PushOperation.PUSH_BASE + 12, + PushOperation.PUSH_BASE + 13, + PushOperation.PUSH_BASE + 14, + PushOperation.PUSH_BASE + 15, + PushOperation.PUSH_BASE + 16, + PushOperation.PUSH_BASE + 17, + PushOperation.PUSH_BASE + 18, + PushOperation.PUSH_BASE + 19, + PushOperation.PUSH_BASE + 20, + PushOperation.PUSH_BASE + 21, + PushOperation.PUSH_BASE + 22, + PushOperation.PUSH_BASE + 23, + PushOperation.PUSH_BASE + 24, + PushOperation.PUSH_BASE + 25, + PushOperation.PUSH_BASE + 26, + PushOperation.PUSH_BASE + 27, + PushOperation.PUSH_BASE + 28, + PushOperation.PUSH_BASE + 29, + PushOperation.PUSH_BASE + 30, + PushOperation.PUSH_BASE + 31, + PushOperation.PUSH_BASE + 32: + final int multiByteDataLen = operationNum - PushOperation.PUSH_BASE; + pcPostInstruction += multiByteDataLen; + break; + case DataLoadNOperation.OPCODE: + if (pos + 2 > size) { + return "Truncated DataLoadN offset"; + } + pcPostInstruction += 2; + final int dataLoadOffset = readBigEndianU16(pos, rawCode); + // only verfy the last byte of the load is within the minimum data + if (dataLoadOffset > eofLayout.dataLength() - 32) { + return "DataLoadN loads data past minimum data length"; + } + break; + case RelativeJumpOperation.OPCODE, RelativeJumpIfOperation.OPCODE: + if (pos + 2 > size) { + return "Truncated relative jump offset"; + } + pcPostInstruction += 2; + final int offset = readBigEndianI16(pos, rawCode); final int rjumpdest = pcPostInstruction + offset; if (rjumpdest < 0 || rjumpdest >= size) { return "Relative jump destination out of bounds"; } rjumpdests.set(rjumpdest); - } - } else if (operationNum == CallFOperation.OPCODE) { - if (pos + 2 > size) { - return "Truncated CALLF"; - } - int section = readBigEndianU16(pos, rawCode); - if (section >= sectionCount) { - return "CALLF to non-existent section - " + Integer.toHexString(section); - } - pcPostInstruction += 2; + break; + case RelativeJumpVectorOperation.OPCODE: + pcPostInstruction += 1; + if (pcPostInstruction > size) { + return "Truncated jump table"; + } + int jumpBasis = pcPostInstruction; + final int jumpTableSize = RelativeJumpVectorOperation.getVectorSize(code, pos); + pcPostInstruction += 2 * jumpTableSize; + if (pcPostInstruction > size) { + return "Truncated jump table"; + } + for (int offsetPos = jumpBasis; offsetPos < pcPostInstruction; offsetPos += 2) { + final int rjumpvOffset = readBigEndianI16(offsetPos, rawCode); + final int rjumpvDest = pcPostInstruction + rjumpvOffset; + if (rjumpvDest < 0 || rjumpvDest >= size) { + return "Relative jump destination out of bounds"; + } + rjumpdests.set(rjumpvDest); + } + break; + case CallFOperation.OPCODE: + if (pos + 2 > size) { + return "Truncated CALLF"; + } + int section = readBigEndianU16(pos, rawCode); + if (section >= eofLayout.getCodeSectionCount()) { + return "CALLF to non-existent section - " + Integer.toHexString(section); + } + if (!eofLayout.getCodeSection(section).returning) { + return "CALLF to non-returning section - " + Integer.toHexString(section); + } + pcPostInstruction += 2; + break; + case RetFOperation.OPCODE: + hasReturningOpcode = true; + break; + case JumpFOperation.OPCODE: + if (pos + 2 > size) { + return "Truncated JUMPF"; + } + int targetSection = readBigEndianU16(pos, rawCode); + if (targetSection >= eofLayout.getCodeSectionCount()) { + return "JUMPF to non-existent section - " + Integer.toHexString(targetSection); + } + CodeSection targetCodeSection = eofLayout.getCodeSection(targetSection); + if (targetCodeSection.isReturning() + && thisCodeSection.getOutputs() < targetCodeSection.getOutputs()) { + return format( + "JUMPF targeting a returning code section %2x with more outputs %d than current section's outputs %d", + targetSection, targetCodeSection.getOutputs(), thisCodeSection.getOutputs()); + } + hasReturningOpcode |= eofLayout.getCodeSection(targetSection).isReturning(); + pcPostInstruction += 2; + break; + case EOFCreateOperation.OPCODE: + if (pos + 1 > size) { + return format( + "Dangling immediate for %s at pc=%d", + opcodeInfo.name(), pos - opcodeInfo.pcAdvance()); + } + int subcontainerNum = rawCode[pos] & 0xff; + if (subcontainerNum >= eofLayout.getSubcontainerCount()) { + return format( + "%s refers to non-existent subcontainer %d at pc=%d", + opcodeInfo.name(), subcontainerNum, pos - opcodeInfo.pcAdvance()); + } + EOFLayout subContainer = eofLayout.getSubcontainer(subcontainerNum); + var subcontainerMode = subContainer.containerMode().get(); + if (subcontainerMode == null) { + subContainer.containerMode().set(INITCODE); + } else if (subcontainerMode == RUNTIME) { + return format( + "subcontainer %d cannot be used both as initcode and runtime", subcontainerNum); + } + if (subContainer.dataLength() != subContainer.data().size()) { + return format( + "A subcontainer used for %s has a truncated data section, expected %d and is %d.", + V1_OPCODES[operationNum].name(), + subContainer.dataLength(), + subContainer.data().size()); + } + pcPostInstruction += 1; + break; + case ReturnContractOperation.OPCODE: + if (eofContainerMode == null) { + eofContainerMode = INITCODE; + eofLayout.containerMode().set(INITCODE); + } else if (!eofContainerMode.equals(INITCODE)) { + return format( + "%s is only a valid opcode in containers used for initcode", opcodeInfo.name()); + } + if (pos + 1 > size) { + return format( + "Dangling immediate for %s at pc=%d", + opcodeInfo.name(), pos - opcodeInfo.pcAdvance()); + } + int returnedContractNum = rawCode[pos] & 0xff; + if (returnedContractNum >= eofLayout.getSubcontainerCount()) { + return format( + "%s refers to non-existent subcontainer %d at pc=%d", + opcodeInfo.name(), returnedContractNum, pos - opcodeInfo.pcAdvance()); + } + EOFLayout returnedContract = eofLayout.getSubcontainer(returnedContractNum); + var returnedContractMode = returnedContract.containerMode().get(); + if (returnedContractMode == null) { + returnedContract.containerMode().set(RUNTIME); + } else if (returnedContractMode.equals(INITCODE)) { + return format( + "subcontainer %d cannot be used both as initcode and runtime", returnedContractNum); + } + pcPostInstruction += 1; + break; + default: + // a few opcodes have potentially dangling immediates + if (opcodeInfo.pcAdvance() > 1) { + pcPostInstruction += opcodeInfo.pcAdvance() - 1; + if (pcPostInstruction >= size) { + return format( + "Dangling immediate for %s at pc=%d", + opcodeInfo.name(), pos - opcodeInfo.pcAdvance()); + } + } + break; } immediates.set(pos, pcPostInstruction); pos = pcPostInstruction; } - if ((attribute & TERMINAL) != TERMINAL) { + if (thisCodeSection.isReturning() != hasReturningOpcode) { + return thisCodeSection.isReturning() + ? "No RETF or qualifying JUMPF" + : "Non-returing section has RETF or JUMPF into returning section"; + } + if (!opcodeInfo.terminal()) { return "No terminating instruction"; } if (rjumpdests.intersects(immediates)) { @@ -661,12 +346,20 @@ public final class CodeV1Validation { return null; } + @Nullable static String validateStack(final EOFLayout eofLayout) { - for (int i = 0; i < eofLayout.getCodeSectionCount(); i++) { - var validation = CodeV1Validation.validateStack(i, eofLayout); + WorkList workList = new WorkList(eofLayout.getCodeSectionCount()); + workList.put(0); + int sectionToValidatie = workList.take(); + while (sectionToValidatie >= 0) { + var validation = CodeV1Validation.validateStack(sectionToValidatie, eofLayout, workList); if (validation != null) { return validation; } + sectionToValidatie = workList.take(); + } + if (!workList.isComplete()) { + return format("Unreachable code section %d", workList.getFirstUnmarkedItem()); } return null; } @@ -679,122 +372,279 @@ public final class CodeV1Validation { * * @param codeSectionToValidate The index of code to validate in the code sections * @param eofLayout The EOF container to validate + * @param workList The list of code sections needing validation * @return null if valid, otherwise an error string providing the validation error. */ - public static String validateStack(final int codeSectionToValidate, final EOFLayout eofLayout) { + @Nullable + static String validateStack( + final int codeSectionToValidate, final EOFLayout eofLayout, final WorkList workList) { + if (!eofLayout.isValid()) { + return "EOF Layout invalid - " + eofLayout.invalidReason(); + } try { CodeSection toValidate = eofLayout.getCodeSection(codeSectionToValidate); byte[] code = - eofLayout.getContainer().slice(toValidate.entryPoint, toValidate.length).toArrayUnsafe(); + eofLayout.container().slice(toValidate.entryPoint, toValidate.length).toArrayUnsafe(); int codeLength = code.length; - int[] stackHeights = new int[codeLength]; - Arrays.fill(stackHeights, -1); - - int thisWork = 0; - int maxWork = 1; - int[][] workList = new int[codeLength][2]; + int[] stack_min = new int[codeLength]; + int[] stack_max = new int[codeLength]; + Arrays.fill(stack_min, 1025); + Arrays.fill(stack_max, -1); int initialStackHeight = toValidate.getInputs(); int maxStackHeight = initialStackHeight; - stackHeights[0] = initialStackHeight; - workList[0][1] = initialStackHeight; + stack_min[0] = initialStackHeight; + stack_max[0] = initialStackHeight; int unusedBytes = codeLength; - while (thisWork < maxWork) { - int currentPC = workList[thisWork][0]; - int currentStackHeight = workList[thisWork][1]; - if (thisWork > 0 && stackHeights[currentPC] >= 0) { - // we've been here, validate the jump is what is expected - if (stackHeights[currentPC] != currentStackHeight) { - return String.format( - "Jump into code stack height (%d) does not match previous value (%d)", - stackHeights[currentPC], currentStackHeight); - } else { - thisWork++; - continue; - } - } else { - stackHeights[currentPC] = currentStackHeight; - } + int currentPC = 0; + int currentMin = initialStackHeight; + int currentMax = initialStackHeight; - while (currentPC < codeLength) { - int thisOp = code[currentPC] & 0xff; + while (currentPC < codeLength) { + int thisOp = code[currentPC] & 0xff; - byte[] stackInfo = OPCODE_STACK_VALIDATION[thisOp]; - int stackInputs; - int stackOutputs; - int pcAdvance = stackInfo[2]; - if (thisOp == CallFOperation.OPCODE) { + OpcodeInfo opcodeInfo = V1_OPCODES[thisOp]; + int stackInputs; + int stackOutputs; + int sectionStackUsed; + int pcAdvance = opcodeInfo.pcAdvance(); + switch (thisOp) { + case CallFOperation.OPCODE: int section = readBigEndianU16(currentPC + 1, code); - stackInputs = eofLayout.getCodeSection(section).getInputs(); - stackOutputs = eofLayout.getCodeSection(section).getOutputs(); - } else { - stackInputs = stackInfo[0]; - stackOutputs = stackInfo[1]; - } + workList.put(section); + CodeSection codeSection = eofLayout.getCodeSection(section); + stackInputs = codeSection.getInputs(); + stackOutputs = codeSection.getOutputs(); + sectionStackUsed = codeSection.getMaxStackHeight(); + break; + case DupNOperation.OPCODE: + int depth = code[currentPC + 1] & 0xff; + stackInputs = depth + 1; + stackOutputs = depth + 2; + sectionStackUsed = 0; + break; + case SwapNOperation.OPCODE: + int swapDepth = 2 + (code[currentPC + 1] & 0xff); + stackInputs = swapDepth; + stackOutputs = swapDepth; + sectionStackUsed = 0; + break; + case ExchangeOperation.OPCODE: + int imm = code[currentPC + 1] & 0xff; + int exchangeDepth = (imm >> 4) + (imm & 0xf) + 3; + stackInputs = exchangeDepth; + stackOutputs = exchangeDepth; + sectionStackUsed = 0; + break; + default: + stackInputs = opcodeInfo.inputs(); + stackOutputs = opcodeInfo.outputs(); + sectionStackUsed = 0; + } - if (stackInputs > currentStackHeight) { - return String.format( - "Operation 0x%02X requires stack of %d but only has %d items", - thisOp, stackInputs, currentStackHeight); - } + int nextPC; + if (!opcodeInfo.valid()) { + return format("Invalid Instruction 0x%02x", thisOp); + } + nextPC = currentPC + pcAdvance; - currentStackHeight = currentStackHeight - stackInputs + stackOutputs; - if (currentStackHeight > MAX_STACK_HEIGHT) { - return "Stack height exceeds 1024"; - } + if (nextPC > codeLength) { + return format( + "Dangling immediate argument for opcode 0x%x at PC %d in code section %d.", + thisOp, currentPC - pcAdvance, codeSectionToValidate); + } + if (stack_max[currentPC] < 0) { + return format( + "Code that was not forward referenced in section 0x%x pc %d", + codeSectionToValidate, currentPC); + } - maxStackHeight = Math.max(maxStackHeight, currentStackHeight); + if (stackInputs > currentMin) { + return format( + "Operation 0x%02X requires stack of %d but may only have %d items", + thisOp, stackInputs, currentMin); + } + + int stackDelta = stackOutputs - stackInputs; + currentMax = currentMax + stackDelta; + currentMin = currentMin + stackDelta; + if (currentMax + sectionStackUsed - stackOutputs > MAX_STACK_HEIGHT) { + return "Stack height exceeds 1024"; + } - if (thisOp == RelativeJumpOperation.OPCODE || thisOp == RelativeJumpIfOperation.OPCODE) { - // no `& 0xff` on high byte because this is one case we want sign extension - int rvalue = readBigEndianI16(currentPC + 1, code); - workList[maxWork] = new int[] {currentPC + rvalue + 3, currentStackHeight}; - maxWork++; - } else if (thisOp == RelativeJumpVectorOperation.OPCODE) { + unusedBytes -= pcAdvance; + maxStackHeight = max(maxStackHeight, currentMax); + + switch (thisOp) { + case RelativeJumpOperation.OPCODE: + int jValue = readBigEndianI16(currentPC + 1, code); + int targetPC = nextPC + jValue; + if (targetPC > currentPC) { + stack_min[targetPC] = min(stack_min[targetPC], currentMin); + stack_max[targetPC] = max(stack_max[targetPC], currentMax); + } else { + if (stack_min[targetPC] != currentMin) { + return format( + "Stack minimum violation on backwards jump from %d to %d, %d != %d", + currentPC, targetPC, stack_min[currentPC], currentMax); + } + if (stack_max[targetPC] != currentMax) { + return format( + "Stack maximum violation on backwards jump from %d to %d, %d != %d", + currentPC, targetPC, stack_max[currentPC], currentMax); + } + } + + // terminal op, reset currentMin and currentMax to forward set values + if (nextPC < codeLength) { + currentMax = stack_max[nextPC]; + currentMin = stack_min[nextPC]; + } + break; + case RelativeJumpIfOperation.OPCODE: + stack_max[nextPC] = max(stack_max[nextPC], currentMax); + stack_min[nextPC] = min(stack_min[nextPC], currentMin); + int jiValue = readBigEndianI16(currentPC + 1, code); + int targetPCi = nextPC + jiValue; + if (targetPCi > currentPC) { + stack_min[targetPCi] = min(stack_min[targetPCi], currentMin); + stack_max[targetPCi] = max(stack_max[targetPCi], currentMax); + } else { + if (stack_min[targetPCi] != currentMin) { + return format( + "Stack minimum violation on backwards jump from %d to %d, %d != %d", + currentPC, targetPCi, stack_min[currentPC], currentMin); + } + if (stack_max[targetPCi] != currentMax) { + return format( + "Stack maximum violation on backwards jump from %d to %d, %d != %d", + currentPC, targetPCi, stack_max[currentPC], currentMax); + } + } + break; + case RelativeJumpVectorOperation.OPCODE: int immediateDataSize = (code[currentPC + 1] & 0xff) * 2; - unusedBytes -= immediateDataSize; - int tableEnd = immediateDataSize + currentPC + 2; + unusedBytes -= immediateDataSize + 2; + int tableEnd = immediateDataSize + currentPC + 4; + nextPC = tableEnd; + stack_max[nextPC] = max(stack_max[nextPC], currentMax); + stack_min[nextPC] = min(stack_min[nextPC], currentMin); for (int i = currentPC + 2; i < tableEnd; i += 2) { - int rvalue = readBigEndianI16(i, code); - workList[maxWork] = new int[] {tableEnd + rvalue, currentStackHeight}; - maxWork++; + int vValue = readBigEndianI16(i, code); + int targetPCv = tableEnd + vValue; + if (targetPCv > currentPC) { + stack_min[targetPCv] = min(stack_min[targetPCv], currentMin); + stack_max[targetPCv] = max(stack_max[targetPCv], currentMax); + } else { + if (stack_min[targetPCv] != currentMin) { + return format( + "Stack minimum violation on backwards jump from %d to %d, %d != %d", + currentPC, targetPCv, stack_min[currentPC], currentMin); + } + if (stack_max[targetPCv] != currentMax) { + return format( + "Stack maximum violation on backwards jump from %d to %d, %d != %d", + currentPC, targetPCv, stack_max[currentPC], currentMax); + } + } } - currentPC = tableEnd - 2; - } else if (thisOp == RetFOperation.OPCODE) { + break; + case RetFOperation.OPCODE: int returnStackItems = toValidate.getOutputs(); - if (currentStackHeight != returnStackItems) { - return String.format( - "Section return (RETF) calculated height 0x%x does not match configured height 0x%x", - currentStackHeight, returnStackItems); + if (currentMin != currentMax) { + return format( + "RETF in section %d has a stack range (%d/%d)and must have only one stack value", + codeSectionToValidate, currentMin, currentMax); + } + if (stack_min[currentPC] != returnStackItems + || stack_min[currentPC] != stack_max[currentPC]) { + return format( + "RETF in section %d calculated height %d does not match configured return stack %d, min height %d, and max height %d", + codeSectionToValidate, + currentMin, + returnStackItems, + stack_min[currentPC], + stack_max[currentPC]); + } + // terminal op, reset currentMin and currentMax to forward set values + if (nextPC < codeLength) { + currentMax = stack_max[nextPC]; + currentMin = stack_min[nextPC]; + } + break; + case JumpFOperation.OPCODE: + int jumpFTargetSectionNum = readBigEndianI16(currentPC + 1, code); + workList.put(jumpFTargetSectionNum); + CodeSection targetCs = eofLayout.getCodeSection(jumpFTargetSectionNum); + if (currentMax + targetCs.getMaxStackHeight() - targetCs.getInputs() + > MAX_STACK_HEIGHT) { + return format( + "JUMPF at section %d pc %d would exceed maximum stack with %d items", + codeSectionToValidate, + currentPC, + currentMax + targetCs.getMaxStackHeight() - targetCs.getInputs()); + } + if (targetCs.isReturning()) { + if (currentMin != currentMax) { + return format( + "JUMPF at section %d pc %d has a variable stack height %d/%d", + codeSectionToValidate, currentPC, currentMin, currentMax); + } + if (currentMax != toValidate.outputs + targetCs.inputs - targetCs.outputs) { + return format( + "JUMPF at section %d pc %d has incompatible stack height for returning section %d (%d != %d + %d - %d)", + codeSectionToValidate, + currentPC, + jumpFTargetSectionNum, + currentMax, + toValidate.outputs, + targetCs.inputs, + targetCs.outputs); + } + } else { + if (currentMin < targetCs.getInputs()) { + return format( + "JUMPF at section %d pc %d has insufficient minimum stack height for non returning section %d (%d != %d)", + codeSectionToValidate, + currentPC, + jumpFTargetSectionNum, + currentMin, + targetCs.inputs); + } + } + // fall through for terminal op handling + case StopOperation.OPCODE, + ReturnContractOperation.OPCODE, + ReturnOperation.OPCODE, + RevertOperation.OPCODE, + InvalidOperation.OPCODE: + // terminal op, reset currentMin and currentMax to forward set values + if (nextPC < codeLength) { + currentMax = stack_max[nextPC]; + currentMin = stack_min[nextPC]; + } + break; + default: + // Ordinary operations, update stack for next operation + if (nextPC < codeLength) { + currentMax = max(stack_max[nextPC], currentMax); + stack_max[nextPC] = currentMax; + currentMin = min(stack_min[nextPC], currentMin); + stack_min[nextPC] = min(stack_min[nextPC], currentMin); } - } - if (pcAdvance < 0) { - unusedBytes += pcAdvance; break; - } else if (pcAdvance == 0) { - return String.format("Invalid Instruction 0x%02x", thisOp); - } - - currentPC += pcAdvance; - if (currentPC >= stackHeights.length) { - return String.format( - "Dangling immediate argument for opcode 0x%x at PC %d in code section %d.", - currentStackHeight, codeLength - pcAdvance, codeSectionToValidate); - } - stackHeights[currentPC] = currentStackHeight; - unusedBytes -= pcAdvance; } - - thisWork++; + currentPC = nextPC; } + if (maxStackHeight != toValidate.maxStackHeight) { - return String.format( + return format( "Calculated max stack height (%d) does not match reported stack height (%d)", maxStackHeight, toValidate.maxStackHeight); } if (unusedBytes != 0) { - return String.format("Dead code detected in section %d", codeSectionToValidate); + return format("Dead code detected in section %d", codeSectionToValidate); } return null; 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 8e5853120c..95ad7f95dd 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 @@ -14,45 +14,97 @@ */ package org.hyperledger.besu.evm.code; +import static org.hyperledger.besu.evm.code.OpcodeInfo.V1_OPCODES; + +import org.hyperledger.besu.evm.operation.ExchangeOperation; +import org.hyperledger.besu.evm.operation.RelativeJumpIfOperation; +import org.hyperledger.besu.evm.operation.RelativeJumpOperation; +import org.hyperledger.besu.evm.operation.RelativeJumpVectorOperation; + import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.Nullable; import org.apache.tuweni.bytes.Bytes; -/** The EOF layout. */ -public class EOFLayout { +/** + * The EOF layout. + * + * @param container The literal EOF bytes fo the whole container + * @param version The parsed version id. zero if unparseable. + * @param codeSections The parsed Code sections. Null if invalid. + * @param subContainers The parsed subcontainers. Null if invalid. + * @param dataLength The length of the data as reported by the container. For subcontainers this may + * be larger than the data in the data field. Zero if invalid. + * @param data The data hard coded in the container. Empty if invalid. + * @param invalidReason If the raw container is invalid, the reason it is invalid. Null if valid. + * @param containerMode The mode of the container (runtime or initcode, if known) + */ +public record EOFLayout( + Bytes container, + int version, + CodeSection[] codeSections, + EOFLayout[] subContainers, + int dataLength, + Bytes data, + String invalidReason, + AtomicReference containerMode) { + + enum EOFContainerMode { + UNKNOWN, + INITCODE, + RUNTIME + } + + /** The EOF prefix byte as a (signed) java byte. */ + public static final byte EOF_PREFIX_BYTE = (byte) 0xEF; - /** The Section Terminator. */ + /** header terminator */ static final int SECTION_TERMINATOR = 0x00; - /** The Section types. */ + /** type data (stack heights, inputs/outputs) */ static final int SECTION_TYPES = 0x01; - /** The Section code. */ + /** code */ static final int SECTION_CODE = 0x02; - /** The Section data. */ - static final int SECTION_DATA = 0x03; + /** sub-EOF subContainers for create */ + static final int SECTION_CONTAINER = 0x03; + + /** data */ + static final int SECTION_DATA = 0x04; /** The Max supported section. */ static final int MAX_SUPPORTED_VERSION = 1; - private final Bytes container; - private final int version; - private final CodeSection[] codeSections; - private final String invalidReason; - - private EOFLayout(final Bytes container, final int version, final CodeSection[] codeSections) { - this.container = container; - this.version = version; - this.codeSections = codeSections; - this.invalidReason = null; + private EOFLayout( + final Bytes container, + final int version, + final CodeSection[] codeSections, + final EOFLayout[] containers, + final int dataSize, + final Bytes data) { + this( + container, + version, + codeSections, + containers, + dataSize, + data, + null, + new AtomicReference<>(null)); } private EOFLayout(final Bytes container, final int version, final String invalidReason) { - this.container = container; - this.version = version; - this.codeSections = null; - this.invalidReason = invalidReason; + this( + container, version, null, null, 0, Bytes.EMPTY, invalidReason, new AtomicReference<>(null)); } private static EOFLayout invalidLayout( @@ -71,6 +123,13 @@ public class EOFLayout { return null; } + private static int peekKind(final ByteArrayInputStream inputStream) { + inputStream.mark(1); + int kind = inputStream.read(); + inputStream.reset(); + return kind; + } + /** * Parse EOF. * @@ -78,6 +137,18 @@ public class EOFLayout { * @return the eof layout */ public static EOFLayout parseEOF(final Bytes container) { + return parseEOF(container, true); + } + + /** + * Parse EOF. + * + * @param container the container + * @param strictSize Require the container to fill all bytes, a validation error will result if + * strict and excess data is in the container + * @return the eof layout + */ + public static EOFLayout parseEOF(final Bytes container, final boolean strictSize) { final ByteArrayInputStream inputStream = new ByteArrayInputStream(container.toArrayUnsafe()); if (inputStream.available() < 3) { @@ -100,7 +171,7 @@ public class EOFLayout { return invalidLayout(container, version, error); } int typesLength = readUnsignedShort(inputStream); - if (typesLength <= 0) { + if (typesLength <= 0 || typesLength % 4 != 0) { return invalidLayout(container, version, "Invalid Types section size"); } @@ -136,6 +207,37 @@ public class EOFLayout { codeSectionSizes[i] = size; } + int containerSectionCount; + int[] containerSectionSizes; + if (peekKind(inputStream) == SECTION_CONTAINER) { + error = readKind(inputStream, SECTION_CONTAINER); + if (error != null) { + return invalidLayout(container, version, error); + } + containerSectionCount = readUnsignedShort(inputStream); + if (containerSectionCount <= 0) { + return invalidLayout(container, version, "Invalid container section count"); + } + if (containerSectionCount > 256) { + return invalidLayout( + container, + version, + "Too many container sections - 0x" + Integer.toHexString(containerSectionCount)); + } + containerSectionSizes = new int[containerSectionCount]; + for (int i = 0; i < containerSectionCount; i++) { + int size = readUnsignedShort(inputStream); + if (size <= 0) { + return invalidLayout( + container, version, "Invalid container section size for section " + i); + } + containerSectionSizes[i] = size; + } + } else { + containerSectionCount = 0; + containerSectionSizes = new int[0]; + } + error = readKind(inputStream, SECTION_DATA); if (error != null) { return invalidLayout(container, version, error); @@ -172,6 +274,12 @@ public class EOFLayout { + 3 // data section header + 1 // padding + (codeSectionCount * 4); // type data + if (containerSectionCount > 0) { + pos += + 3 // subcontainer header + + (containerSectionCount * 2); // subcontainer sizes + } + for (int i = 0; i < codeSectionCount; i++) { int codeSectionSize = codeSectionSizes[i]; if (inputStream.skip(codeSectionSize) != codeSectionSize) { @@ -197,17 +305,52 @@ public class EOFLayout { } codeSections[i] = new CodeSection(codeSectionSize, typeData[i][0], typeData[i][1], typeData[i][2], pos); + if (i == 0 && typeData[0][1] != 0x80) { + return invalidLayout( + container, + version, + "Code section at zero expected non-returning flag, but had return stack of " + + typeData[0][1]); + } pos += codeSectionSize; } - if (inputStream.skip(dataSize) != dataSize) { - return invalidLayout(container, version, "Incomplete data section"); + EOFLayout[] subContainers = new EOFLayout[containerSectionCount]; + for (int i = 0; i < containerSectionCount; i++) { + int subcontianerSize = containerSectionSizes[i]; + if (subcontianerSize != inputStream.skip(subcontianerSize)) { + return invalidLayout(container, version, "incomplete subcontainer"); + } + Bytes subcontainer = container.slice(pos, subcontianerSize); + pos += subcontianerSize; + EOFLayout subLayout = EOFLayout.parseEOF(subcontainer); + if (!subLayout.isValid()) { + String invalidSubReason = subLayout.invalidReason; + return invalidLayout( + container, + version, + invalidSubReason.contains("invalid subcontainer") + ? invalidSubReason + : "invalid subcontainer - " + invalidSubReason); + } + subContainers[i] = subLayout; } + + long loadedDataCount = inputStream.skip(dataSize); + Bytes data = container.slice(pos, (int) loadedDataCount); + + Bytes completeContainer; if (inputStream.read() != -1) { - return invalidLayout(container, version, "Dangling data after end of all sections"); + if (strictSize) { + return invalidLayout(container, version, "Dangling data after end of all sections"); + } else { + completeContainer = container.slice(0, pos + dataSize); + } + } else { + completeContainer = container; } - return new EOFLayout(container, version, codeSections); + return new EOFLayout(completeContainer, version, codeSections, subContainers, dataSize, data); } /** @@ -224,24 +367,6 @@ public class EOFLayout { } } - /** - * Gets container. - * - * @return the container - */ - public Bytes getContainer() { - return container; - } - - /** - * Gets version. - * - * @return the version - */ - public int getVersion() { - return version; - } - /** * Get code section count. * @@ -262,12 +387,22 @@ public class EOFLayout { } /** - * Gets invalid reason. + * Get sub container section count. + * + * @return the sub container count + */ + public int getSubcontainerCount() { + return subContainers == null ? 0 : subContainers.length; + } + + /** + * Get code sections. * - * @return the invalid reason + * @param i the index + * @return the Code section */ - public String getInvalidReason() { - return invalidReason; + public EOFLayout getSubcontainer(final int i) { + return subContainers[i]; } /** @@ -278,4 +413,313 @@ public class EOFLayout { public boolean isValid() { return invalidReason == null; } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (!(o instanceof EOFLayout eofLayout)) return false; + return version == eofLayout.version + && container.equals(eofLayout.container) + && Arrays.equals(codeSections, eofLayout.codeSections) + && Arrays.equals(subContainers, eofLayout.subContainers) + && Objects.equals(invalidReason, eofLayout.invalidReason); + } + + @Override + public int hashCode() { + int result = Objects.hash(container, version, invalidReason); + result = 31 * result + Arrays.hashCode(codeSections); + result = 31 * result + Arrays.hashCode(subContainers); + return result; + } + + @Override + public String toString() { + return "EOFLayout{" + + "container=" + + container + + ", version=" + + version + + ", codeSections=" + + (codeSections == null ? "null" : Arrays.asList(codeSections).toString()) + + ", containers=" + + (subContainers == null ? "null" : Arrays.asList(subContainers).toString()) + + ", invalidReason='" + + invalidReason + + '\'' + + '}'; + } + + /** + * Re-writes the container with optional auxiliary data. + * + * @param auxData the auxiliary data + * @return Null if there was an error (validation or otherwise) , or the bytes of the re-written + * container. + */ + @Nullable + public Bytes writeContainer(@Nullable final Bytes auxData) { + // do not write invalid containers + if (invalidReason != null) { + return null; + } + + try { + ByteArrayOutputStream baos = + new ByteArrayOutputStream(container.size() + dataLength - data.size()); + DataOutputStream out = new DataOutputStream(baos); + + // EOF header + out.writeByte(EOF_PREFIX_BYTE); + out.writeByte(0); + out.writeByte(version); + + // Types header + out.writeByte(SECTION_TYPES); + out.writeShort(codeSections.length * 4); + + // Code header + out.writeByte(SECTION_CODE); + out.writeShort(codeSections.length); + for (CodeSection cs : codeSections) { + out.writeShort(cs.length); + } + + // Subcontainers header + if (subContainers != null && subContainers.length > 0) { + out.writeByte(SECTION_CONTAINER); + out.writeShort(subContainers.length); + for (EOFLayout container : subContainers) { + out.writeShort(container.container.size()); + } + } + + // Data header + out.writeByte(SECTION_DATA); + if (auxData == null) { + out.writeShort(dataLength); + } else { + int newSize = data.size() + auxData.size(); + if (newSize < dataLength) { + // aux data must cover claimed data lengths. + return null; + } + out.writeShort(newSize); + } + + // header end + out.writeByte(0); + + // Types information + for (CodeSection cs : codeSections) { + out.writeByte(cs.inputs); + if (cs.returning) { + out.writeByte(cs.outputs); + } else { + out.writeByte(0x80); + } + out.writeShort(cs.maxStackHeight); + } + + // Code sections + for (CodeSection cs : codeSections) { + out.write(container.slice(cs.entryPoint, cs.length).toArray()); + } + + // Subcontainers + if (subContainers != null) { + for (EOFLayout container : subContainers) { + out.write(container.container.toArrayUnsafe()); + } + } + + // data + out.write(data.toArrayUnsafe()); + if (auxData != null) { + out.write(auxData.toArrayUnsafe()); + } + + return Bytes.wrap(baos.toByteArray()); + } catch (IOException ioe) { + // ByteArrayOutputStream should never throw, so something has gone very wrong. Wrap as + // runtime + // and re-throw. + throw new RuntimeException(ioe); + } + } + + /** + * A more readable representation of the hex bytes, including whitespace and comments after hashes + * + * @return The pretty printed code + */ + public String prettyPrint() { + StringWriter sw = new StringWriter(); + prettyPrint(new PrintWriter(sw, true), "", ""); + return sw.toString(); + } + + /** + * A more readable representation of the hex bytes, including whitespace and comments after hashes + * + * @param out the print writer to pretty print to + */ + public void prettyPrint(final PrintWriter out) { + out.println("0x # EOF"); + prettyPrint(out, "", ""); + } + + /** + * A more readable representation of the hex bytes, including whitespace and comments after hashes + * + * @param out the print writer to pretty print to + * @param prefix The prefix to prepend to all output lines (useful for nested subconntainers) + * @param subcontainerPrefix The prefix to add to subcontainer names. + */ + public void prettyPrint( + final PrintWriter out, final String prefix, final String subcontainerPrefix) { + + if (!isValid()) { + out.print(prefix); + out.println("# Invalid EOF"); + out.print(prefix); + out.println("# " + invalidReason); + out.println(container); + } + + out.print(prefix); + out.printf("ef00%02x # Magic and Version ( %1$d )%n", version); + out.print(prefix); + out.printf("01%04x # Types length ( %1$d )%n", codeSections.length * 4); + out.print(prefix); + out.printf("02%04x # Total code sections ( %1$d )%n", codeSections.length); + for (int i = 0; i < codeSections.length; i++) { + out.print(prefix); + out.printf(" %04x # Code section %d , %1$d bytes%n", getCodeSection(i).getLength(), i); + } + if (subContainers.length > 0) { + out.print(prefix); + out.printf("03%04x # Total subcontainers ( %1$d )%n", subContainers.length); + for (int i = 0; i < subContainers.length; i++) { + out.print(prefix); + out.printf(" %04x # Sub container %d, %1$d byte%n", subContainers[i].container.size(), i); + } + } + out.print(prefix); + out.printf("04%04x # Data section length( %1$d )", dataLength); + if (dataLength != data.size()) { + out.printf(" (actual size %d)", data.size()); + } + out.print(prefix); + out.printf("%n"); + out.print(prefix); + out.printf(" 00 # Terminator (end of header)%n"); + for (int i = 0; i < codeSections.length; i++) { + CodeSection cs = getCodeSection(i); + out.print(prefix); + out.printf(" # Code section %d types%n", i); + out.print(prefix); + out.printf(" %02x # %1$d inputs %n", cs.getInputs()); + out.print(prefix); + out.printf( + " %02x # %d outputs %s%n", + cs.isReturning() ? cs.getOutputs() : 0x80, + cs.getOutputs(), + cs.isReturning() ? "" : " (Non-returning function)"); + out.print(prefix); + out.printf(" %04x # max stack: %1$d%n", cs.getMaxStackHeight()); + } + for (int i = 0; i < codeSections.length; i++) { + CodeSection cs = getCodeSection(i); + out.print(prefix); + out.printf( + " # Code section %d - in=%d out=%s height=%d%n", + i, cs.inputs, cs.isReturning() ? cs.outputs : "non-returning", cs.maxStackHeight); + byte[] byteCode = container.slice(cs.getEntryPoint(), cs.getLength()).toArray(); + int pc = 0; + while (pc < byteCode.length) { + out.print(prefix); + OpcodeInfo ci = V1_OPCODES[byteCode[pc] & 0xff]; + + if (ci.opcode() == RelativeJumpVectorOperation.OPCODE) { + int tableSize = byteCode[pc + 1] & 0xff; + out.printf("%02x%02x", byteCode[pc], byteCode[pc + 1]); + for (int j = 0; j <= tableSize; j++) { + out.printf("%02x%02x", byteCode[pc + j * 2 + 2], byteCode[pc + j * 2 + 3]); + } + out.printf(" # [%d] %s(", pc, ci.name()); + for (int j = 0; j <= tableSize; j++) { + if (j != 0) { + out.print(','); + } + int b0 = byteCode[pc + j * 2 + 2]; // we want the sign extension, so no `& 0xff` + int b1 = byteCode[pc + j * 2 + 3] & 0xff; + out.print(b0 << 8 | b1); + } + pc += tableSize * 2 + 4; + out.print(")\n"); + } else if (ci.opcode() == RelativeJumpOperation.OPCODE + || ci.opcode() == RelativeJumpIfOperation.OPCODE) { + int b0 = byteCode[pc + 1] & 0xff; + int b1 = byteCode[pc + 2] & 0xff; + short delta = (short) (b0 << 8 | b1); + out.printf("%02x%02x%02x # [%d] %s(%d)", byteCode[pc], b0, b1, pc, ci.name(), delta); + pc += 3; + out.printf("%n"); + } else if (ci.opcode() == ExchangeOperation.OPCODE) { + int imm = byteCode[pc + 1] & 0xff; + out.printf( + " %02x%02x # [%d] %s(%d, %d)", + byteCode[pc], imm, pc, ci.name(), imm >> 4, imm & 0x0F); + pc += 2; + out.printf("%n"); + } else { + int advance = ci.pcAdvance(); + if (advance == 1) { + out.print(" "); + } else if (advance == 2) { + out.print(" "); + } + out.printf("%02x", byteCode[pc]); + for (int j = 1; j < advance; j++) { + out.printf("%02x", byteCode[pc + j]); + } + out.printf(" # [%d] %s", pc, ci.name()); + if (advance == 2) { + out.printf("(%d)", byteCode[pc + 1] & 0xff); + } else if (advance > 2) { + out.print("(0x"); + for (int j = 1; j < advance; j++) { + out.printf("%02x", byteCode[pc + j]); + } + out.print(")"); + } + out.printf("%n"); + pc += advance; + } + } + } + + for (int i = 0; i < subContainers.length; i++) { + var subContainer = subContainers[i]; + out.print(prefix); + out.printf(" # Subcontainer %s%d starts here%n", subcontainerPrefix, i); + + subContainer.prettyPrint(out, prefix + " ", subcontainerPrefix + i + "."); + out.print(prefix); + out.printf(" # Subcontainer %s%d ends%n", subcontainerPrefix, i); + } + + out.print(prefix); + if (data.isEmpty()) { + out.print(" # Data section (empty)\n"); + } else { + out.printf(" # Data section length ( %1$d )", dataLength); + if (dataLength != data.size()) { + out.printf(" actual length ( %d )", data.size()); + } + out.printf("%n%s %s%n", prefix, data.toUnprefixedHexString()); + } + out.flush(); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/OpcodeInfo.java b/evm/src/main/java/org/hyperledger/besu/evm/code/OpcodeInfo.java new file mode 100644 index 0000000000..27289f9120 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/OpcodeInfo.java @@ -0,0 +1,336 @@ +/* + * 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.code; + +import com.google.common.base.Preconditions; + +/** + * Information about opcodes. Currently merges Legacy and EOFv1 + * + * @param name formal name of the opcode, such as STOP + * @param opcode the number of the opcode + * @param valid Is this a valid opcode (from an EOFV1 perspective) + * @param terminal Is this opcode terminal? (i.e. can it end a code section) + * @param inputs How many stack inputs are required/consumed? + * @param outputs How many stack items will be output? + * @param stackDelta What is the net difference in stack height from this operation + * @param pcAdvance How far should the PC advance (0 for terminal only, 1 for most, 2+ for opcodes + * with immediates) + */ +public record OpcodeInfo( + String name, + int opcode, + boolean valid, + boolean terminal, + int inputs, + int outputs, + int stackDelta, + int pcAdvance) { + static OpcodeInfo unallocatedOpcode(final int opcode) { + return new OpcodeInfo("-", opcode, false, false, 0, 0, 0, 1); + } + + static OpcodeInfo invalidOpcode(final String name, final int opcode) { + return new OpcodeInfo(name, opcode, false, false, 0, 0, 0, 1); + } + + static OpcodeInfo terminalOpcode( + final String name, + final int opcode, + final int inputs, + final int outputs, + final int pcAdvance) { + return new OpcodeInfo(name, opcode, true, true, inputs, outputs, outputs - inputs, pcAdvance); + } + + static OpcodeInfo validOpcode( + final String name, + final int opcode, + final int inputs, + final int outputs, + final int pcAdvance) { + return new OpcodeInfo(name, opcode, true, false, inputs, outputs, outputs - inputs, pcAdvance); + } + + /** + * Gets the opcode info for a specific opcode + * + * @param i opcode + * @return the OpcodeInfo object describing that opcode + */ + public static OpcodeInfo getOpcode(final int i) { + Preconditions.checkArgument(i >= 0 && i <= 255); + return V1_OPCODES[i]; + } + + static final OpcodeInfo[] V1_OPCODES = { + OpcodeInfo.terminalOpcode("STOP", 0x00, 0, 0, 1), + OpcodeInfo.validOpcode("ADD", 0x01, 2, 1, 1), + OpcodeInfo.validOpcode("MUL", 0x02, 2, 1, 1), + OpcodeInfo.validOpcode("SUB", 0x03, 2, 1, 1), + OpcodeInfo.validOpcode("DIV", 0x04, 2, 1, 1), + OpcodeInfo.validOpcode("SDIV", 0x05, 2, 1, 1), + OpcodeInfo.validOpcode("MOD", 0x06, 2, 1, 1), + OpcodeInfo.validOpcode("SMOD", 0x07, 2, 1, 1), + OpcodeInfo.validOpcode("ADDMOD", 0x08, 3, 1, 1), + OpcodeInfo.validOpcode("MULMOD", 0x09, 3, 1, 1), + OpcodeInfo.validOpcode("EXP", 0x0a, 2, 1, 1), + OpcodeInfo.validOpcode("SIGNEXTEND", 0x0b, 2, 1, 1), + OpcodeInfo.unallocatedOpcode(0x0c), + OpcodeInfo.unallocatedOpcode(0x0d), + OpcodeInfo.unallocatedOpcode(0x0e), + OpcodeInfo.unallocatedOpcode(0x0f), + OpcodeInfo.validOpcode("LT", 0x10, 2, 1, 1), + OpcodeInfo.validOpcode("GT", 0x11, 2, 1, 1), + OpcodeInfo.validOpcode("SLT", 0x12, 2, 1, 1), + OpcodeInfo.validOpcode("SGT", 0x13, 2, 1, 1), + OpcodeInfo.validOpcode("EQ", 0x14, 2, 1, 1), + OpcodeInfo.validOpcode("ISZERO", 0x15, 1, 1, 1), + OpcodeInfo.validOpcode("AND", 0x16, 2, 1, 1), + OpcodeInfo.validOpcode("OR", 0x17, 2, 1, 1), + OpcodeInfo.validOpcode("XOR", 0x18, 2, 1, 1), + OpcodeInfo.validOpcode("NOT", 0x19, 1, 1, 1), + OpcodeInfo.validOpcode("BYTE", 0x1a, 2, 1, 1), + OpcodeInfo.validOpcode("SHL", 0x1b, 2, 1, 1), + OpcodeInfo.validOpcode("SHR", 0x1c, 2, 1, 1), + OpcodeInfo.validOpcode("SAR", 0x1d, 2, 1, 1), + OpcodeInfo.unallocatedOpcode(0x1e), + OpcodeInfo.unallocatedOpcode(0x1f), + OpcodeInfo.validOpcode("SHA3", 0x20, 2, 1, 1), + OpcodeInfo.unallocatedOpcode(0x21), + OpcodeInfo.unallocatedOpcode(0x22), + OpcodeInfo.unallocatedOpcode(0x23), + OpcodeInfo.unallocatedOpcode(0x24), + OpcodeInfo.unallocatedOpcode(0x25), + OpcodeInfo.unallocatedOpcode(0x26), + OpcodeInfo.unallocatedOpcode(0x27), + OpcodeInfo.unallocatedOpcode(0x28), + OpcodeInfo.unallocatedOpcode(0x29), + OpcodeInfo.unallocatedOpcode(0x2a), + OpcodeInfo.unallocatedOpcode(0x2b), + OpcodeInfo.unallocatedOpcode(0x2c), + OpcodeInfo.unallocatedOpcode(0x2d), + OpcodeInfo.unallocatedOpcode(0x2e), + OpcodeInfo.unallocatedOpcode(0x2f), + OpcodeInfo.validOpcode("ADDRESS", 0x30, 0, 1, 1), + OpcodeInfo.validOpcode("BALANCE", 0x31, 1, 1, 1), + OpcodeInfo.validOpcode("ORIGIN", 0x32, 0, 1, 1), + OpcodeInfo.validOpcode("CALLER", 0x33, 0, 1, 1), + OpcodeInfo.validOpcode("CALLVALUE", 0x34, 0, 1, 1), + OpcodeInfo.validOpcode("CALLDATALOAD", 0x35, 1, 1, 1), + OpcodeInfo.validOpcode("CALLDATASIZE", 0x36, 0, 1, 1), + OpcodeInfo.validOpcode("CALLDATACOPY", 0x37, 3, 0, 1), + OpcodeInfo.invalidOpcode("CODESIZE", 0x38), + OpcodeInfo.invalidOpcode("CODECOPY", 0x39), + OpcodeInfo.validOpcode("GASPRICE", 0x3a, 0, 1, 1), + OpcodeInfo.invalidOpcode("EXTCODESIZE", 0x3b), + OpcodeInfo.invalidOpcode("EXTCODECOPY", 0x3c), + OpcodeInfo.validOpcode("RETURNDATASIZE", 0x3d, 0, 1, 1), + OpcodeInfo.validOpcode("RETURNDATACOPY", 0x3e, 3, 0, 1), + OpcodeInfo.invalidOpcode("EXTCODEHASH", 0x3f), + OpcodeInfo.validOpcode("BLOCKHASH", 0x40, 1, 1, 1), + OpcodeInfo.validOpcode("COINBASE", 0x41, 0, 1, 1), + OpcodeInfo.validOpcode("TIMESTAMP", 0x42, 0, 1, 1), + OpcodeInfo.validOpcode("NUMBER", 0x43, 0, 1, 1), + OpcodeInfo.validOpcode("PREVRANDAO", 0x44, 0, 1, 1), // was DIFFICULTY + OpcodeInfo.validOpcode("GASLIMIT", 0x45, 0, 1, 1), + OpcodeInfo.validOpcode("CHAINID", 0x46, 0, 1, 1), + OpcodeInfo.validOpcode("SELFBALANCE", 0x47, 0, 1, 1), + OpcodeInfo.validOpcode("BASEFEE", 0x48, 0, 1, 1), + OpcodeInfo.validOpcode("BLOBAHASH", 0x49, 1, 1, 1), + OpcodeInfo.validOpcode("BLOBBASEFEE", 0x4a, 0, 1, 1), + OpcodeInfo.unallocatedOpcode(0x4b), + OpcodeInfo.unallocatedOpcode(0x4c), + OpcodeInfo.unallocatedOpcode(0x4d), + OpcodeInfo.unallocatedOpcode(0x4e), + OpcodeInfo.unallocatedOpcode(0x4f), + OpcodeInfo.validOpcode("POP", 0x50, 1, 0, 1), + OpcodeInfo.validOpcode("MLOAD", 0x51, 1, 1, 1), + OpcodeInfo.validOpcode("MSTORE", 0x52, 2, 0, 1), + OpcodeInfo.validOpcode("MSTORE8", 0x53, 2, 0, 1), + OpcodeInfo.validOpcode("SLOAD", 0x54, 1, 1, 1), + OpcodeInfo.validOpcode("SSTORE", 0x55, 2, 0, 1), + OpcodeInfo.invalidOpcode("JUMP", 0x56), + OpcodeInfo.invalidOpcode("JUMPI", 0x57), + OpcodeInfo.invalidOpcode("PC", 0x58), + OpcodeInfo.validOpcode("MSIZE", 0x59, 0, 1, 1), + OpcodeInfo.invalidOpcode("GAS", 0x5a), + OpcodeInfo.validOpcode("NOOP", 0x5b, 0, 0, 1), // was JUMPDEST + OpcodeInfo.validOpcode("TLOAD", 0x5c, 1, 1, 1), + OpcodeInfo.validOpcode("TSTORE", 0x5d, 2, 0, 1), + OpcodeInfo.validOpcode("MCOPY", 0x5e, 3, 0, 1), + OpcodeInfo.validOpcode("PUSH0", 0x5f, 0, 1, 1), + OpcodeInfo.validOpcode("PUSH1", 0x60, 0, 1, 2), + OpcodeInfo.validOpcode("PUSH2", 0x61, 0, 1, 3), + OpcodeInfo.validOpcode("PUSH3", 0x62, 0, 1, 4), + OpcodeInfo.validOpcode("PUSH4", 0x63, 0, 1, 5), + OpcodeInfo.validOpcode("PUSH5", 0x64, 0, 1, 6), + OpcodeInfo.validOpcode("PUSH6", 0x65, 0, 1, 7), + OpcodeInfo.validOpcode("PUSH7", 0x66, 0, 1, 8), + OpcodeInfo.validOpcode("PUSH8", 0x67, 0, 1, 9), + OpcodeInfo.validOpcode("PUSH9", 0x68, 0, 1, 10), + OpcodeInfo.validOpcode("PUSH10", 0x69, 0, 1, 11), + OpcodeInfo.validOpcode("PUSH11", 0x6a, 0, 1, 12), + OpcodeInfo.validOpcode("PUSH12", 0x6b, 0, 1, 13), + OpcodeInfo.validOpcode("PUSH13", 0x6c, 0, 1, 14), + OpcodeInfo.validOpcode("PUSH14", 0x6d, 0, 1, 15), + OpcodeInfo.validOpcode("PUSH15", 0x6e, 0, 1, 16), + OpcodeInfo.validOpcode("PUSH16", 0x6f, 0, 1, 17), + OpcodeInfo.validOpcode("PUSH17", 0x70, 0, 1, 18), + OpcodeInfo.validOpcode("PUSH18", 0x71, 0, 1, 19), + OpcodeInfo.validOpcode("PUSH19", 0x72, 0, 1, 20), + OpcodeInfo.validOpcode("PUSH20", 0x73, 0, 1, 21), + OpcodeInfo.validOpcode("PUSH21", 0x74, 0, 1, 22), + OpcodeInfo.validOpcode("PUSH22", 0x75, 0, 1, 23), + OpcodeInfo.validOpcode("PUSH23", 0x76, 0, 1, 24), + OpcodeInfo.validOpcode("PUSH24", 0x77, 0, 1, 25), + OpcodeInfo.validOpcode("PUSH25", 0x78, 0, 1, 26), + OpcodeInfo.validOpcode("PUSH26", 0x79, 0, 1, 27), + OpcodeInfo.validOpcode("PUSH27", 0x7a, 0, 1, 28), + OpcodeInfo.validOpcode("PUSH28", 0x7b, 0, 1, 29), + OpcodeInfo.validOpcode("PUSH29", 0x7c, 0, 1, 30), + OpcodeInfo.validOpcode("PUSH30", 0x7d, 0, 1, 31), + OpcodeInfo.validOpcode("PUSH31", 0x7e, 0, 1, 32), + OpcodeInfo.validOpcode("PUSH32", 0x7f, 0, 1, 33), + OpcodeInfo.validOpcode("DUP1", 0x80, 1, 2, 1), + OpcodeInfo.validOpcode("DUP2", 0x81, 2, 3, 1), + OpcodeInfo.validOpcode("DUP3", 0x82, 3, 4, 1), + OpcodeInfo.validOpcode("DUP4", 0x83, 4, 5, 1), + OpcodeInfo.validOpcode("DUP5", 0x84, 5, 6, 1), + OpcodeInfo.validOpcode("DUP6", 0x85, 6, 7, 1), + OpcodeInfo.validOpcode("DUP7", 0x86, 7, 8, 1), + OpcodeInfo.validOpcode("DUP8", 0x87, 8, 9, 1), + OpcodeInfo.validOpcode("DUP9", 0x88, 9, 10, 1), + OpcodeInfo.validOpcode("DUP10", 0x89, 10, 11, 1), + OpcodeInfo.validOpcode("DUP11", 0x8a, 11, 12, 1), + OpcodeInfo.validOpcode("DUP12", 0x8b, 12, 13, 1), + OpcodeInfo.validOpcode("DUP13", 0x8c, 13, 14, 1), + OpcodeInfo.validOpcode("DUP14", 0x8d, 14, 15, 1), + OpcodeInfo.validOpcode("DUP15", 0x8e, 15, 16, 1), + OpcodeInfo.validOpcode("DUP16", 0x8f, 16, 17, 1), + OpcodeInfo.validOpcode("SWAP1", 0x90, 2, 2, 1), + OpcodeInfo.validOpcode("SWAP2", 0x91, 3, 3, 1), + OpcodeInfo.validOpcode("SWAP3", 0x92, 4, 4, 1), + OpcodeInfo.validOpcode("SWAP4", 0x93, 5, 5, 1), + OpcodeInfo.validOpcode("SWAP5", 0x94, 6, 6, 1), + OpcodeInfo.validOpcode("SWAP6", 0x95, 7, 7, 1), + OpcodeInfo.validOpcode("SWAP7", 0x96, 8, 8, 1), + OpcodeInfo.validOpcode("SWAP8", 0x97, 9, 9, 1), + OpcodeInfo.validOpcode("SWAP9", 0x98, 10, 10, 1), + OpcodeInfo.validOpcode("SWAP10", 0x99, 11, 11, 1), + OpcodeInfo.validOpcode("SWAP11", 0x9a, 12, 12, 1), + OpcodeInfo.validOpcode("SWAP12", 0x9b, 13, 13, 1), + OpcodeInfo.validOpcode("SWAP13", 0x9c, 14, 14, 1), + OpcodeInfo.validOpcode("SWAP14", 0x9d, 15, 15, 1), + OpcodeInfo.validOpcode("SWAP15", 0x9e, 16, 16, 1), + OpcodeInfo.validOpcode("SWAP16", 0x9f, 17, 17, 1), + OpcodeInfo.validOpcode("LOG0", 0xa0, 2, 0, 1), + OpcodeInfo.validOpcode("LOG1", 0xa1, 3, 0, 1), + OpcodeInfo.validOpcode("LOG2", 0xa2, 4, 0, 1), + OpcodeInfo.validOpcode("LOG3", 0xa3, 5, 0, 1), + OpcodeInfo.validOpcode("LOG4", 0xa4, 6, 0, 1), + OpcodeInfo.unallocatedOpcode(0xa5), + OpcodeInfo.unallocatedOpcode(0xa6), + OpcodeInfo.unallocatedOpcode(0xa7), + OpcodeInfo.unallocatedOpcode(0xa8), + OpcodeInfo.unallocatedOpcode(0xa9), + OpcodeInfo.unallocatedOpcode(0xaa), + OpcodeInfo.unallocatedOpcode(0xab), + OpcodeInfo.unallocatedOpcode(0xac), + OpcodeInfo.unallocatedOpcode(0xad), + OpcodeInfo.unallocatedOpcode(0xae), + OpcodeInfo.unallocatedOpcode(0xaf), + OpcodeInfo.unallocatedOpcode(0xb0), + OpcodeInfo.unallocatedOpcode(0xb1), + OpcodeInfo.unallocatedOpcode(0xb2), + OpcodeInfo.unallocatedOpcode(0xb3), + OpcodeInfo.unallocatedOpcode(0xb4), + OpcodeInfo.unallocatedOpcode(0xb5), + OpcodeInfo.unallocatedOpcode(0xb6), + OpcodeInfo.unallocatedOpcode(0xb7), + OpcodeInfo.unallocatedOpcode(0xb8), + OpcodeInfo.unallocatedOpcode(0xb9), + OpcodeInfo.unallocatedOpcode(0xba), + OpcodeInfo.unallocatedOpcode(0xbb), + OpcodeInfo.unallocatedOpcode(0xbc), + OpcodeInfo.unallocatedOpcode(0xbd), + OpcodeInfo.unallocatedOpcode(0xbe), + OpcodeInfo.unallocatedOpcode(0xbf), + OpcodeInfo.unallocatedOpcode(0xc0), + OpcodeInfo.unallocatedOpcode(0xc1), + OpcodeInfo.unallocatedOpcode(0xc2), + OpcodeInfo.unallocatedOpcode(0xc3), + OpcodeInfo.unallocatedOpcode(0xc4), + OpcodeInfo.unallocatedOpcode(0xc5), + OpcodeInfo.unallocatedOpcode(0xc6), + OpcodeInfo.unallocatedOpcode(0xc7), + OpcodeInfo.unallocatedOpcode(0xc8), + OpcodeInfo.unallocatedOpcode(0xc9), + OpcodeInfo.unallocatedOpcode(0xca), + OpcodeInfo.unallocatedOpcode(0xcb), + OpcodeInfo.unallocatedOpcode(0xcc), + OpcodeInfo.unallocatedOpcode(0xcd), + OpcodeInfo.unallocatedOpcode(0xce), + OpcodeInfo.unallocatedOpcode(0xcf), + OpcodeInfo.validOpcode("DATALOAD", 0xd0, 1, 1, 1), + OpcodeInfo.validOpcode("DATALOADN", 0xd1, 0, 1, 3), + OpcodeInfo.validOpcode("DATASIZE", 0xd2, 0, 1, 1), + OpcodeInfo.validOpcode("DATACOPY", 0xd3, 3, 0, 1), + OpcodeInfo.unallocatedOpcode(0xd4), + OpcodeInfo.unallocatedOpcode(0xd5), + OpcodeInfo.unallocatedOpcode(0xd6), + OpcodeInfo.unallocatedOpcode(0xd7), + OpcodeInfo.unallocatedOpcode(0xd8), + OpcodeInfo.unallocatedOpcode(0xd9), + OpcodeInfo.unallocatedOpcode(0xda), + OpcodeInfo.unallocatedOpcode(0xdb), + OpcodeInfo.unallocatedOpcode(0xdc), + OpcodeInfo.unallocatedOpcode(0xdd), + OpcodeInfo.unallocatedOpcode(0xde), + OpcodeInfo.unallocatedOpcode(0xdf), + OpcodeInfo.terminalOpcode("RJUMP", 0xe0, 0, 0, 3), + OpcodeInfo.validOpcode("RJUMPI", 0xe1, 1, 0, 3), + OpcodeInfo.validOpcode("RJUMPV", 0xe2, 1, 0, 2), + OpcodeInfo.validOpcode("CALLF", 0xe3, 0, 0, 3), + OpcodeInfo.terminalOpcode("RETF", 0xe4, 0, 0, 1), + OpcodeInfo.terminalOpcode("JUMPF", 0xe5, 0, 0, 3), + OpcodeInfo.validOpcode("DUPN", 0xe6, 0, 1, 2), + OpcodeInfo.validOpcode("SWAPN", 0xe7, 0, 0, 2), + OpcodeInfo.validOpcode("EXCHANGE", 0xe8, 0, 0, 2), + OpcodeInfo.unallocatedOpcode(0xe9), + OpcodeInfo.unallocatedOpcode(0xea), + OpcodeInfo.unallocatedOpcode(0xeb), + OpcodeInfo.validOpcode("EOFCREATE", 0xec, 4, 1, 2), + OpcodeInfo.unallocatedOpcode(0xed), + OpcodeInfo.terminalOpcode("RETURNCONTRACT", 0xee, 2, 1, 2), + OpcodeInfo.unallocatedOpcode(0xef), + OpcodeInfo.invalidOpcode("CREATE", 0xf0), + OpcodeInfo.invalidOpcode("CALL", 0xf1), + OpcodeInfo.invalidOpcode("CALLCODE", 0xf2), + OpcodeInfo.terminalOpcode("RETURN", 0xf3, 2, 0, 1), + OpcodeInfo.invalidOpcode("DELEGATECALL", 0xf4), + OpcodeInfo.invalidOpcode("CREATE2", 0xf5), + OpcodeInfo.unallocatedOpcode(0xf6), + OpcodeInfo.validOpcode("RETURNDATALOAD", 0xf7, 1, 1, 1), + OpcodeInfo.validOpcode("EXTCALL", 0xf8, 4, 1, 1), + OpcodeInfo.validOpcode("EXTDELEGATECALL", 0xf9, 3, 1, 1), + OpcodeInfo.invalidOpcode("STATICCALL", 0xfa), + OpcodeInfo.validOpcode("EXTSTATICCALL", 0xfb, 3, 1, 1), + OpcodeInfo.unallocatedOpcode(0xfc), + OpcodeInfo.terminalOpcode("REVERT", 0xfd, 2, 0, 1), + OpcodeInfo.terminalOpcode("INVALID", 0xfe, 0, 0, 1), + OpcodeInfo.invalidOpcode("SELFDESTRUCT", 0xff), + }; +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/WorkList.java b/evm/src/main/java/org/hyperledger/besu/evm/code/WorkList.java new file mode 100644 index 0000000000..0e30b35df8 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/WorkList.java @@ -0,0 +1,95 @@ +/* + * 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.code; + +/** + * A work list, allowing a DAG to be evaluated while detecting disconnected sections. + * + *

When an item is marked if it has not been marked it is added to the work list. `take()` + * returns the fist item that has not yet been returned from a take, or `-1` if no items are + * available. Items are added by calling `put(int)`, which is idempotent. Items can be put several + * times but will only be taken once. + * + *

`isComplete()` checks if all items have been taken. `getFirstUnmarkedItem()` is used when + * reporting errors to identify an unconnected item. + */ +class WorkList { + boolean[] marked; + int[] items; + int nextIndex; + int listEnd; + + /** + * Create a work list of the appropriate size. The list is empty. + * + * @param size number of possible items + */ + WorkList(final int size) { + marked = new boolean[size]; + items = new int[size]; + nextIndex = 0; + listEnd = -1; + } + + /** + * Take the next item, if available + * + * @return the item number, or -1 if no items are available. + */ + int take() { + if (nextIndex > listEnd) { + return -1; + } + int result = items[nextIndex]; + nextIndex++; + return result; + } + + /** + * Have all items been taken? + * + * @return true if all items were marked and then taken + */ + boolean isComplete() { + return nextIndex >= items.length; + } + + /** + * Put an item in the work list. This is idempotent, an item will only be added on the first call. + * + * @param item the item to add to the list. + */ + void put(final int item) { + if (!marked[item]) { + listEnd++; + items[listEnd] = item; + marked[item] = true; + } + } + + /** + * Walks the taken list and returns the first unmarked item + * + * @return the first unmarked item, or -1 if all items are marked. + */ + int getFirstUnmarkedItem() { + for (int i = 0; i < marked.length; i++) { + if (!marked[i]) { + return i; + } + } + return -1; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/CachedInvalidCodeRule.java b/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/CachedInvalidCodeRule.java index 39431d785a..e0a7e49f41 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/CachedInvalidCodeRule.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/CachedInvalidCodeRule.java @@ -41,7 +41,7 @@ public class CachedInvalidCodeRule implements ContractValidationRule { @Override public Optional validate( final Bytes contractCode, final MessageFrame frame) { - final Code code = CodeFactory.createCode(contractCode, maxEofVersion, false); + final Code code = CodeFactory.createCode(contractCode, maxEofVersion); if (!code.isValid()) { return Optional.of(ExceptionalHaltReason.INVALID_CODE); } else { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/EOFValidationCodeRule.java b/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/EOFValidationCodeRule.java index 19adcc3bf2..a052bcfa83 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/EOFValidationCodeRule.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/EOFValidationCodeRule.java @@ -35,11 +35,9 @@ public class EOFValidationCodeRule implements ContractValidationRule { private static final Logger LOG = LoggerFactory.getLogger(EOFValidationCodeRule.class); final int maxEofVersion; - final boolean inCreateTransaction; - private EOFValidationCodeRule(final int maxEofVersion, final boolean inCreateTransaction) { + private EOFValidationCodeRule(final int maxEofVersion) { this.maxEofVersion = maxEofVersion; - this.inCreateTransaction = inCreateTransaction; } /** @@ -53,13 +51,13 @@ public class EOFValidationCodeRule implements ContractValidationRule { @Override public Optional validate( final Bytes contractCode, final MessageFrame frame) { - Code code = CodeFactory.createCode(contractCode, maxEofVersion, inCreateTransaction); + Code code = CodeFactory.createCode(contractCode, maxEofVersion); if (!code.isValid()) { LOG.trace("EOF Validation Error: {}", ((CodeInvalid) code).getInvalidReason()); return Optional.of(ExceptionalHaltReason.INVALID_CODE); } - if (frame.getCode().getEofVersion() > code.getEofVersion()) { + if (frame.getCode().getEofVersion() != code.getEofVersion()) { LOG.trace( "Cannot deploy older eof versions: initcode version - {} runtime code version - {}", frame.getCode().getEofVersion(), @@ -74,11 +72,9 @@ public class EOFValidationCodeRule implements ContractValidationRule { * Create EOF validation. * * @param maxEofVersion Maximum EOF version to validate - * @param inCreateTransaction Is this inside a create transaction? * @return The EOF validation contract validation rule. */ - public static ContractValidationRule of( - final int maxEofVersion, final boolean inCreateTransaction) { - return new EOFValidationCodeRule(maxEofVersion, inCreateTransaction); + public static ContractValidationRule of(final int maxEofVersion) { + return new EOFValidationCodeRule(maxEofVersion); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/fluent/EVMExecutor.java b/evm/src/main/java/org/hyperledger/besu/evm/fluent/EVMExecutor.java index 087038d52f..64e9653ab7 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/fluent/EVMExecutor.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/fluent/EVMExecutor.java @@ -161,8 +161,12 @@ public class EVMExecutor { case SHANGHAI -> shanghai(chainId, evmConfiguration); case CANCUN -> cancun(chainId, evmConfiguration); case PRAGUE -> prague(chainId, evmConfiguration); + case PRAGUE_EOF -> pragueEOF(chainId, evmConfiguration); case OSAKA -> osaka(chainId, evmConfiguration); + case AMSTERDAM -> amsterdam(chainId, evmConfiguration); case BOGOTA -> bogota(chainId, evmConfiguration); + case POLIS -> polis(chainId, evmConfiguration); + case BANGKOK -> bangkok(chainId, evmConfiguration); case FUTURE_EIPS -> futureEips(chainId, evmConfiguration); case EXPERIMENTAL_EIPS -> experimentalEips(chainId, evmConfiguration); }; @@ -503,6 +507,21 @@ public class EVMExecutor { return executor; } + /** + * Instantiate PragueEOF evm executor. + * + * @param chainId the chain ID + * @param evmConfiguration the evm configuration + * @return the evm executor + */ + public static EVMExecutor pragueEOF( + final BigInteger chainId, final EvmConfiguration evmConfiguration) { + final EVMExecutor executor = new EVMExecutor(MainnetEVMs.pragueEOF(chainId, evmConfiguration)); + executor.precompileContractRegistry = + MainnetPrecompiledContracts.prague(executor.evm.getGasCalculator()); + return executor; + } + /** * Instantiate Osaka evm executor. * @@ -518,6 +537,21 @@ public class EVMExecutor { return executor; } + /** + * Instantiate Amsterdam evm executor. + * + * @param chainId the chain ID + * @param evmConfiguration the evm configuration + * @return the evm executor + */ + public static EVMExecutor amsterdam( + final BigInteger chainId, final EvmConfiguration evmConfiguration) { + final EVMExecutor executor = new EVMExecutor(MainnetEVMs.amsterdam(chainId, evmConfiguration)); + executor.precompileContractRegistry = + MainnetPrecompiledContracts.prague(executor.evm.getGasCalculator()); + return executor; + } + /** * Instantiate Bogota evm executor. * @@ -533,6 +567,36 @@ public class EVMExecutor { return executor; } + /** + * Instantiate Polis evm executor. + * + * @param chainId the chain ID + * @param evmConfiguration the evm configuration + * @return the evm executor + */ + public static EVMExecutor polis( + final BigInteger chainId, final EvmConfiguration evmConfiguration) { + final EVMExecutor executor = new EVMExecutor(MainnetEVMs.polis(chainId, evmConfiguration)); + executor.precompileContractRegistry = + MainnetPrecompiledContracts.prague(executor.evm.getGasCalculator()); + return executor; + } + + /** + * Instantiate Bangkok evm executor. + * + * @param chainId the chain ID + * @param evmConfiguration the evm configuration + * @return the evm executor + */ + public static EVMExecutor bangkok( + final BigInteger chainId, final EvmConfiguration evmConfiguration) { + final EVMExecutor executor = new EVMExecutor(MainnetEVMs.bangkok(chainId, evmConfiguration)); + executor.precompileContractRegistry = + MainnetPrecompiledContracts.prague(executor.evm.getGasCalculator()); + return executor; + } + /** * Instantiate Future EIPs evm executor. * @@ -540,6 +604,7 @@ public class EVMExecutor { * @return the evm executor * @deprecated Migrate to use {@link EVMExecutor#evm(EvmSpecVersion)}. */ + @SuppressWarnings("DeprecatedIsStillUsed") @InlineMe( replacement = "EVMExecutor.evm(EvmSpecVersion.FUTURE_EIPS, BigInteger.ONE, evmConfiguration)", imports = { @@ -672,11 +737,11 @@ public class EVMExecutor { final Deque messageFrameStack = initialMessageFrame.getMessageFrameStack(); while (!messageFrameStack.isEmpty()) { final MessageFrame messageFrame = messageFrameStack.peek(); - if (messageFrame.getType() == MessageFrame.Type.CONTRACT_CREATION) { - ccp.process(messageFrame, tracer); - } else if (messageFrame.getType() == MessageFrame.Type.MESSAGE_CALL) { - mcp.process(messageFrame, tracer); - } + (switch (messageFrame.getType()) { + case CONTRACT_CREATION -> ccp; + case MESSAGE_CALL -> mcp; + }) + .process(messageFrame, tracer); } if (commitWorldState) { worldUpdater.commit(); 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 c39df00b6b..af121ae92f 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 @@ -56,24 +56,16 @@ public interface ExceptionalHaltReason { /** The constant PRECOMPILE_ERROR. */ ExceptionalHaltReason PRECOMPILE_ERROR = DefaultExceptionalHaltReason.PRECOMPILE_ERROR; - /** The constant CODE_SECTION_MISSING. */ - ExceptionalHaltReason CODE_SECTION_MISSING = DefaultExceptionalHaltReason.CODE_SECTION_MISSING; - - /** The constant INCORRECT_CODE_SECTION_RETURN_OUTPUTS. */ - ExceptionalHaltReason INCORRECT_CODE_SECTION_RETURN_OUTPUTS = - DefaultExceptionalHaltReason.INCORRECT_CODE_SECTION_RETURN_OUTPUTS; - - /** The constant TOO_FEW_INPUTS_FOR_CODE_SECTION. */ - ExceptionalHaltReason TOO_FEW_INPUTS_FOR_CODE_SECTION = - DefaultExceptionalHaltReason.TOO_FEW_INPUTS_FOR_CODE_SECTION; - - /** The constant JUMPF_STACK_MISMATCH. */ - ExceptionalHaltReason JUMPF_STACK_MISMATCH = DefaultExceptionalHaltReason.JUMPF_STACK_MISMATCH; - /** The constant EOF_CREATE_VERSION_INCOMPATIBLE. */ ExceptionalHaltReason EOF_CREATE_VERSION_INCOMPATIBLE = DefaultExceptionalHaltReason.EOF_CREATE_VERSION_INCOMPATIBLE; + /** The constant NONEXISTENT_CONTAINER */ + ExceptionalHaltReason NONEXISTENT_CONTAINER = DefaultExceptionalHaltReason.NONEXISTENT_CONTAINER; + + /** The constant ADDRESS_OUT_OF_RANGE */ + ExceptionalHaltReason ADDRESS_OUT_OF_RANGE = DefaultExceptionalHaltReason.ADDRESS_OUT_OF_RANGE; + /** * Name string. * @@ -114,21 +106,15 @@ public interface ExceptionalHaltReason { INVALID_CODE("Code is invalid"), /** The Precompile error. */ PRECOMPILE_ERROR("Precompile error"), - /** The Code section missing. */ - CODE_SECTION_MISSING("No code section at requested index"), /** The Insufficient code section return data. */ INSUFFICIENT_CODE_SECTION_RETURN_DATA("The stack for a return "), - /** The Incorrect code section return outputs. */ - INCORRECT_CODE_SECTION_RETURN_OUTPUTS( - "The return of a code section does not have the correct number of outputs"), - /** The Too few inputs for code section. */ - TOO_FEW_INPUTS_FOR_CODE_SECTION("Not enough stack items for a function call"), - /** The Jumpf stack mismatch. */ - JUMPF_STACK_MISMATCH( - "The stack height for a JUMPF does not match the requirements of the target section"), /** The Eof version incompatible. */ EOF_CREATE_VERSION_INCOMPATIBLE( - "EOF Code is attempting to create EOF code of an earlier version"); + "EOF Code is attempting to create EOF code of an earlier version"), + /** Container referenced by EOFCREATE operation does not exist */ + NONEXISTENT_CONTAINER("Referenced subcontainer index does not exist (too large?)"), + /** A given address cannot be used by EOF */ + ADDRESS_OUT_OF_RANGE("Address has more than 20 bytes and is out of range"); /** The Description. */ final String description; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/Memory.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/Memory.java index fe2bba89cf..fbc96b034d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/Memory.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/Memory.java @@ -18,7 +18,6 @@ import org.hyperledger.besu.evm.internal.Words; import java.util.Arrays; -import com.google.common.annotations.VisibleForTesting; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.bytes.MutableBytes; @@ -55,9 +54,6 @@ public class Memory { } private static RuntimeException overflow(final String v) { - // TODO: we should probably have another specific exception so this properly end up as an - // exceptional halt condition with a clear message (message that can indicate that if anyone - // runs into this, he should contact us so we know it's a case we do need to handle). final String msg = "Memory index or length %s too large, cannot be larger than %d"; throw new IllegalStateException(String.format(msg, v, MAX_BYTES)); } @@ -180,7 +176,6 @@ public class Memory { * * @return The current number of active words stored in memory. */ - @VisibleForTesting public int getActiveWords() { return activeWords; } 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 d006c5de56..e01c915510 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 @@ -25,7 +25,6 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.VersionedHash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.Code; -import org.hyperledger.besu.evm.code.CodeSection; import org.hyperledger.besu.evm.internal.MemoryEntry; import org.hyperledger.besu.evm.internal.OperandStack; import org.hyperledger.besu.evm.internal.ReturnStack; @@ -216,6 +215,7 @@ public class MessageFrame { private final Supplier returnStack; private Bytes output = Bytes.EMPTY; private Bytes returnData = Bytes.EMPTY; + private Code createdCode = null; private final boolean isStatic; // Transaction state fields. @@ -277,13 +277,7 @@ public class MessageFrame { this.worldUpdater = worldUpdater; this.gasRemaining = initialGas; this.stack = new OperandStack(txValues.maxStackSize()); - this.returnStack = - Suppliers.memoize( - () -> { - var rStack = new ReturnStack(); - rStack.push(new ReturnStack.ReturnStackItem(0, 0, 0)); - return rStack; - }); + this.returnStack = Suppliers.memoize(ReturnStack::new); this.pc = code.isValid() ? code.getCodeSection(0).getEntryPoint() : 0; this.recipient = recipient; this.contract = contract; @@ -336,71 +330,6 @@ public class MessageFrame { return section; } - /** - * Call function and return exceptional halt reason. - * - * @param calledSection the called section - * @return the exceptional halt reason - */ - public ExceptionalHaltReason callFunction(final int calledSection) { - CodeSection info = code.getCodeSection(calledSection); - if (info == null) { - return ExceptionalHaltReason.CODE_SECTION_MISSING; - } else if (stack.size() + info.getMaxStackHeight() > txValues.maxStackSize()) { - return ExceptionalHaltReason.TOO_MANY_STACK_ITEMS; - } else if (stack.size() < info.getInputs()) { - return ExceptionalHaltReason.TOO_FEW_INPUTS_FOR_CODE_SECTION; - } else { - returnStack - .get() - .push(new ReturnStack.ReturnStackItem(section, pc + 2, stack.size() - info.getInputs())); - pc = info.getEntryPoint() - 1; // will be +1ed at end of operations loop - this.section = calledSection; - return null; - } - } - - /** - * Execute the mechanics of the JUMPF operation. - * - * @param section the section - * @return the exceptional halt reason, if the jump failed - */ - public ExceptionalHaltReason jumpFunction(final int section) { - CodeSection info = code.getCodeSection(section); - if (info == null) { - return ExceptionalHaltReason.CODE_SECTION_MISSING; - } 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; - return null; - } - } - - /** - * Return function exceptional halt reason. - * - * @return the exceptional halt reason - */ - public ExceptionalHaltReason returnFunction() { - CodeSection thisInfo = code.getCodeSection(this.section); - var rStack = returnStack.get(); - var returnInfo = rStack.pop(); - if ((returnInfo.getStackHeight() + thisInfo.getOutputs()) != stack.size()) { - return ExceptionalHaltReason.INCORRECT_CODE_SECTION_RETURN_OUTPUTS; - } else if (rStack.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. */ public void clearGasRemaining() { this.gasRemaining = 0L; @@ -462,6 +391,24 @@ public class MessageFrame { this.output = output; } + /** + * Sets the created code from CREATE* operations + * + * @param createdCode the code that was created + */ + public void setCreatedCode(final Code createdCode) { + this.createdCode = createdCode; + } + + /** + * gets the created code from CREATE* operations + * + * @return the code that was created + */ + public Code getCreatedCode() { + return createdCode; + } + /** Clears the output data buffer. */ public void clearOutputData() { setOutputData(Bytes.EMPTY); @@ -1027,18 +974,6 @@ public class MessageFrame { return txValues.warmedUpStorage().put(address, slot, Boolean.TRUE) != null; } - /** - * Returns whether an address' slot is warmed up. Is deliberately publicly exposed for access from - * trace - * - * @param address the address context - * @param slot the slot to query - * @return whether the address/slot couple is warmed up - */ - public boolean isStorageWarm(final Address address, final Bytes32 slot) { - return this.txValues.warmedUpStorage().contains(address, slot); - } - /** * Return the world state. * @@ -1206,6 +1141,15 @@ public class MessageFrame { return txValues.messageFrameStack(); } + /** + * The return stack used for EOF code sections. + * + * @return the return stack + */ + public ReturnStack getReturnStack() { + return returnStack.get(); + } + /** * Sets exceptional halt reason. * diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/FrontierGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/FrontierGasCalculator.java index 0808841c8e..0376903c64 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/FrontierGasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/FrontierGasCalculator.java @@ -76,7 +76,8 @@ public class FrontierGasCalculator implements GasCalculator { private static final long NEW_ACCOUNT_GAS_COST = 25_000L; - private static final long CREATE_OPERATION_GAS_COST = 32_000L; + /** Yellow paper constant for the cost of creating a new contract on-chain */ + protected static final long CREATE_OPERATION_GAS_COST = 32_000L; private static final long COPY_WORD_GAS_COST = 3L; @@ -122,7 +123,9 @@ public class FrontierGasCalculator implements GasCalculator { private static final long SELF_DESTRUCT_REFUND_AMOUNT = 24_000L; /** Default constructor. */ - public FrontierGasCalculator() {} + public FrontierGasCalculator() { + // Default Constructor, for JavaDoc lint + } @Override public long transactionIntrinsicGasCost(final Bytes payload, final boolean isContractCreate) { @@ -214,21 +217,13 @@ public class FrontierGasCalculator implements GasCalculator { return CALL_OPERATION_BASE_GAS_COST; } - /** - * Returns the gas cost to transfer funds in a call operation. - * - * @return the gas cost to transfer funds in a call operation - */ - long callValueTransferGasCost() { + @Override + public long callValueTransferGasCost() { return CALL_VALUE_TRANSFER_GAS_COST; } - /** - * Returns the gas cost to create a new account. - * - * @return the gas cost to create a new account - */ - long newAccountGasCost() { + @Override + public long newAccountGasCost() { return NEW_ACCOUNT_GAS_COST; } @@ -309,6 +304,16 @@ public class FrontierGasCalculator implements GasCalculator { } } + @Override + public long getMinRetainedGas() { + return 0; + } + + @Override + public long getMinCalleeGas() { + return 0; + } + /** * Returns the amount of gas the CREATE operation will consume. * diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java index 06f24c534e..2e1728b234 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java @@ -144,6 +144,20 @@ public interface GasCalculator { */ long callOperationBaseGasCost(); + /** + * Returns the gas cost to transfer funds in a call operation. + * + * @return the gas cost to transfer funds in a call operation + */ + long callValueTransferGasCost(); + + /** + * Returns the gas cost to create a new account. + * + * @return the gas cost to create a new account + */ + long newAccountGasCost(); + /** * Returns the gas cost for one of the various CALL operations. * @@ -227,6 +241,20 @@ public interface GasCalculator { */ long gasAvailableForChildCall(MessageFrame frame, long stipend, boolean transfersValue); + /** + * For EXT*CALL, the minimum amount of gas the parent must retain. First described in EIP-7069 + * + * @return MIN_RETAINED_GAS + */ + long getMinRetainedGas(); + + /** + * For EXT*CALL, the minimum amount of gas that a child must receive. First described in EIP-7069 + * + * @return MIN_CALLEE_GAS + */ + long getMinCalleeGas(); + /** * Returns the amount of gas the CREATE operation will consume. * diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueEOFGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueEOFGasCalculator.java new file mode 100644 index 0000000000..5fa2fe8725 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueEOFGasCalculator.java @@ -0,0 +1,57 @@ +/* + * 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.gascalculator; + +import static org.hyperledger.besu.datatypes.Address.BLS12_MAP_FP2_TO_G2; + +/** + * Gas Calculator for Prague + * + *

Placeholder for new gas schedule items. If Prague finalzies without changes this can be + * removed + * + *

    + *
  • TBD + *
+ */ +public class PragueEOFGasCalculator extends PragueGasCalculator { + + static final long MIN_RETAINED_GAS = 5_000; + static final long MIN_CALLEE_GAS = 2300; + + /** Instantiates a new Prague Gas Calculator. */ + public PragueEOFGasCalculator() { + this(BLS12_MAP_FP2_TO_G2.toArrayUnsafe()[19]); + } + + /** + * Instantiates a new Prague Gas Calculator + * + * @param maxPrecompile the max precompile + */ + protected PragueEOFGasCalculator(final int maxPrecompile) { + super(maxPrecompile); + } + + @Override + public long getMinRetainedGas() { + return MIN_RETAINED_GAS; + } + + @Override + public long getMinCalleeGas() { + return MIN_CALLEE_GAS; + } +} 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 9c752fa1a6..5601ca381a 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,91 +14,16 @@ */ package org.hyperledger.besu.evm.internal; -import java.util.Objects; - /** The type Return stack. */ public class ReturnStack extends FlexStack { - /** The type Return stack item. */ - // Java17 convert to record - public static final class ReturnStackItem { - - /** The Code section index. */ - final int codeSectionIndex; - - /** The Pc. */ - final int pc; - - /** The Stack height. */ - final int stackHeight; - - /** - * Instantiates a new Return stack item. - * - * @param codeSectionIndex the code section index - * @param pc the pc - * @param stackHeight the stack height - */ - public ReturnStackItem(final int codeSectionIndex, final int pc, final int stackHeight) { - this.codeSectionIndex = codeSectionIndex; - this.pc = pc; - this.stackHeight = stackHeight; - } - - /** - * Gets code section index. - * - * @return the code section index - */ - public int getCodeSectionIndex() { - return codeSectionIndex; - } - - /** - * Gets pc. - * - * @return the pc - */ - public int getPC() { - return pc; - } - - /** - * Gets stack height. - * - * @return the stack height - */ - 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 - + '}'; - } - } + /** + * The type Return stack item. + * + * @param codeSectionIndex the code section index + * @param pc the pc + */ + public record ReturnStackItem(int codeSectionIndex, int pc) {} /** * Max return stack size specified in 0 || frame.getDepth() >= 1024) { frame.expandMemory(inputDataOffset(frame), inputDataLength(frame)); frame.expandMemory(outputDataOffset(frame), outputDataLength(frame)); + // For the following, we either increment the gas or return zero so weo don't get double + // charged. If we return zero then the traces don't have the right per-opcode cost. frame.incrementRemainingGas(gasAvailableForChildCall(frame) + cost); frame.popStackItems(getStackItemsConsumed()); - frame.pushStackItem(FAILURE_STACK_ITEM); + frame.pushStackItem(LEGACY_FAILURE_STACK_ITEM); return new OperationResult(cost, null); } @@ -197,29 +212,30 @@ public abstract class AbstractCallOperation extends AbstractOperation { ? CodeV0.EMPTY_CODE : evm.getCode(contract.getCodeHash(), contract.getCode()); - if (code.isValid()) { - // frame addition is automatically handled by parent messageFrameStack - MessageFrame.builder() - .parentMessageFrame(frame) - .type(MessageFrame.Type.MESSAGE_CALL) - .initialGas(gasAvailableForChildCall(frame)) - .address(address(frame)) - .contract(to) - .inputData(inputData) - .sender(sender(frame)) - .value(value(frame)) - .apparentValue(apparentValue(frame)) - .code(code) - .isStatic(isStatic(frame)) - .completer(child -> complete(frame, child)) - .build(); - frame.incrementRemainingGas(cost); - - frame.setState(MessageFrame.State.CODE_SUSPENDED); - return new OperationResult(cost, null, 0); - } else { + // invalid code results in a quick exit + if (!code.isValid()) { return new OperationResult(cost, ExceptionalHaltReason.INVALID_CODE, 0); } + + MessageFrame.builder() + .parentMessageFrame(frame) + .type(MessageFrame.Type.MESSAGE_CALL) + .initialGas(gasAvailableForChildCall(frame)) + .address(address(frame)) + .contract(to) + .inputData(inputData) + .sender(sender(frame)) + .value(value(frame)) + .apparentValue(apparentValue(frame)) + .code(code) + .isStatic(isStatic(frame)) + .completer(child -> complete(frame, child)) + .build(); + // see note in stack depth check about incrementing cost + frame.incrementRemainingGas(cost); + + frame.setState(MessageFrame.State.CODE_SUSPENDED); + return new OperationResult(cost, null, 0); } /** @@ -281,7 +297,7 @@ public abstract class AbstractCallOperation extends AbstractOperation { if (outputSize > outputData.size()) { frame.expandMemory(outputOffset, outputSize); frame.writeMemory(outputOffset, outputData.size(), outputData, true); - } else { + } else if (outputSize > 0) { frame.writeMemory(outputOffset, outputSize, outputData, true); } @@ -294,13 +310,20 @@ public abstract class AbstractCallOperation extends AbstractOperation { frame.incrementRemainingGas(gasRemaining); frame.popStackItems(getStackItemsConsumed()); - if (childFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { - frame.pushStackItem(SUCCESS_STACK_ITEM); - } else { - frame.pushStackItem(FAILURE_STACK_ITEM); - } + Bytes resultItem; + + resultItem = getCallResultStackItem(childFrame); + frame.pushStackItem(resultItem); final int currentPC = frame.getPC(); frame.setPC(currentPC + 1); } + + Bytes getCallResultStackItem(final MessageFrame childFrame) { + if (childFrame.getState() == State.COMPLETED_SUCCESS) { + return LEGACY_SUCCESS_STACK_ITEM; + } else { + return LEGACY_FAILURE_STACK_ITEM; + } + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java index ba345ed861..a484f28ceb 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.evm.operation; import static org.hyperledger.besu.evm.internal.Words.clampedToLong; +import static org.hyperledger.besu.evm.operation.AbstractCallOperation.LEGACY_FAILURE_STACK_ITEM; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; @@ -31,6 +32,7 @@ import org.hyperledger.besu.evm.internal.Words; import java.util.Optional; import java.util.function.Supplier; +import com.google.common.base.Suppliers; import org.apache.tuweni.bytes.Bytes; /** The Abstract create operation. */ @@ -40,8 +42,15 @@ public abstract class AbstractCreateOperation extends AbstractOperation { protected static final OperationResult UNDERFLOW_RESPONSE = new OperationResult(0L, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + /** The constant UNDERFLOW_RESPONSE. */ + protected static final OperationResult INVALID_OPERATION = + new OperationResult(0L, ExceptionalHaltReason.INVALID_OPERATION); + /** The maximum init code size */ - protected int maxInitcodeSize; + protected final int maxInitcodeSize; + + /** The EOF Version this create operation requires initcode to be in */ + protected final int eofVersion; /** * Instantiates a new Abstract create operation. @@ -52,6 +61,7 @@ public abstract class AbstractCreateOperation extends AbstractOperation { * @param stackItemsProduced the stack items produced * @param gasCalculator the gas calculator * @param maxInitcodeSize Maximum init code size + * @param eofVersion the EOF version this create operation is valid in */ protected AbstractCreateOperation( final int opcode, @@ -59,19 +69,25 @@ public abstract class AbstractCreateOperation extends AbstractOperation { final int stackItemsConsumed, final int stackItemsProduced, final GasCalculator gasCalculator, - final int maxInitcodeSize) { + final int maxInitcodeSize, + final int eofVersion) { super(opcode, name, stackItemsConsumed, stackItemsProduced, gasCalculator); this.maxInitcodeSize = maxInitcodeSize; + this.eofVersion = eofVersion; } @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { + if (frame.getCode().getEofVersion() != eofVersion) { + return INVALID_OPERATION; + } + // manual check because some reads won't come until the "complete" step. if (frame.stackSize() < getStackItemsConsumed()) { return UNDERFLOW_RESPONSE; } - Supplier codeSupplier = () -> getInitCode(frame, evm); + Supplier codeSupplier = Suppliers.memoize(() -> getInitCode(frame, evm)); final long cost = cost(frame, codeSupplier); if (frame.isStatic()) { @@ -85,36 +101,41 @@ public abstract class AbstractCreateOperation extends AbstractOperation { final MutableAccount account = frame.getWorldUpdater().getAccount(address); frame.clearReturnData(); - final long inputOffset = clampedToLong(frame.getStackItem(1)); - final long inputSize = clampedToLong(frame.getStackItem(2)); - if (inputSize > maxInitcodeSize) { - frame.popStackItems(getStackItemsConsumed()); - return new OperationResult(cost, ExceptionalHaltReason.CODE_TOO_LARGE); - } + + Code code = codeSupplier.get(); if (value.compareTo(account.getBalance()) > 0 || frame.getDepth() >= 1024 || account.getNonce() == -1 - || codeSupplier.get() == null) { + || code == null + || code.getEofVersion() != frame.getCode().getEofVersion()) { fail(frame); } else { account.incrementNonce(); - final Bytes inputData = frame.readMemory(inputOffset, inputSize); - // Never cache CREATEx initcode. The amount of reuse is very low, and caching mostly - // addresses disk loading delay, and we already have the code. - Code code = evm.getCodeUncached(inputData); + if (code.getSize() > maxInitcodeSize) { + frame.popStackItems(getStackItemsConsumed()); + return new OperationResult(cost, ExceptionalHaltReason.CODE_TOO_LARGE); + } + if (!code.isValid()) { + fail(frame); + } else { - if (code.isValid() && frame.getCode().getEofVersion() <= code.getEofVersion()) { frame.decrementRemainingGas(cost); spawnChildMessage(frame, code, evm); frame.incrementRemainingGas(cost); - } else { - fail(frame); } } + return new OperationResult(cost, null, getPcIncrement()); + } - return new OperationResult(cost, null); + /** + * How many bytes does this operation occupy? + * + * @return The number of bytes the operation and immediate arguments occupy + */ + protected int getPcIncrement() { + return 1; } /** @@ -149,13 +170,14 @@ public abstract class AbstractCreateOperation extends AbstractOperation { final long inputSize = clampedToLong(frame.getStackItem(2)); frame.readMutableMemory(inputOffset, inputSize); frame.popStackItems(getStackItemsConsumed()); - frame.pushStackItem(FAILURE_STACK_ITEM); + frame.pushStackItem(LEGACY_FAILURE_STACK_ITEM); } private void spawnChildMessage(final MessageFrame parent, final Code code, final EVM evm) { final Wei value = Wei.wrap(parent.getStackItem(0)); final Address contractAddress = targetContractAddress(parent, code); + final Bytes inputData = getInputData(parent); final long childGasStipend = gasCalculator().gasAvailableForChildCreate(parent.getRemainingGas()); @@ -168,7 +190,7 @@ public abstract class AbstractCreateOperation extends AbstractOperation { .initialGas(childGasStipend) .address(contractAddress) .contract(contractAddress) - .inputData(Bytes.EMPTY) + .inputData(inputData) .sender(parent.getRecipientAddress()) .value(value) .apparentValue(value) @@ -179,11 +201,24 @@ public abstract class AbstractCreateOperation extends AbstractOperation { parent.setState(MessageFrame.State.CODE_SUSPENDED); } + /** + * Get the input data to be appended to the EOF factory contract. For CREATE and CREATE2 this is + * always empty + * + * @param frame the message frame the operation was called in + * @return the input data as raw bytes, or `Bytes.EMPTY` if there is no aux data + */ + protected Bytes getInputData(final MessageFrame frame) { + return Bytes.EMPTY; + } + private void complete(final MessageFrame frame, final MessageFrame childFrame, final EVM evm) { frame.setState(MessageFrame.State.CODE_EXECUTING); Code outputCode = - CodeFactory.createCode(childFrame.getOutputData(), evm.getMaxEOFVersion(), true); + (childFrame.getCreatedCode() != null) + ? childFrame.getCreatedCode() + : CodeFactory.createCode(childFrame.getOutputData(), evm.getMaxEOFVersion()); frame.popStackItems(getStackItemsConsumed()); if (outputCode.isValid()) { @@ -198,18 +233,18 @@ public abstract class AbstractCreateOperation extends AbstractOperation { onSuccess(frame, createdAddress); } else { frame.setReturnData(childFrame.getOutputData()); - frame.pushStackItem(FAILURE_STACK_ITEM); + frame.pushStackItem(LEGACY_FAILURE_STACK_ITEM); onFailure(frame, childFrame.getExceptionalHaltReason()); } } else { frame.getWorldUpdater().deleteAccount(childFrame.getRecipientAddress()); frame.setReturnData(childFrame.getOutputData()); - frame.pushStackItem(FAILURE_STACK_ITEM); + frame.pushStackItem(LEGACY_FAILURE_STACK_ITEM); onInvalid(frame, (CodeInvalid) outputCode); } final int currentPC = frame.getPC(); - frame.setPC(currentPC + 1); + frame.setPC(currentPC + getPcIncrement()); } /** diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java new file mode 100644 index 0000000000..2c0535b197 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java @@ -0,0 +1,201 @@ +/* + * 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.operation; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.account.Account; +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.internal.Words; + +import javax.annotation.Nonnull; + +import org.apache.tuweni.bytes.Bytes; + +/** + * A skeleton class for implementing call operations. + * + *

A call operation creates a child message call from the current message context, allows it to + * execute, and then updates the current message context based on its execution. + */ +public abstract class AbstractExtCallOperation extends AbstractCallOperation { + + static final int STACK_TO = 0; + + /** EXT*CALL response indicating success */ + public static final Bytes EOF1_SUCCESS_STACK_ITEM = Bytes.EMPTY; + + /** EXT*CALL response indicating a "soft failure" */ + public static final Bytes EOF1_EXCEPTION_STACK_ITEM = BYTES_ONE; + + /** EXT*CALL response indicating a hard failure, such as a REVERT was called */ + public static final Bytes EOF1_FAILURE_STACK_ITEM = Bytes.of(2); + + /** + * Instantiates a new Abstract call 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 + */ + AbstractExtCallOperation( + final int opcode, + final String name, + final int stackItemsConsumed, + final int stackItemsProduced, + final GasCalculator gasCalculator) { + super(opcode, name, stackItemsConsumed, stackItemsProduced, gasCalculator); + } + + @Override + protected Address to(final MessageFrame frame) { + return Words.toAddress(frame.getStackItem(STACK_TO)); + } + + @Override + protected long gas(final MessageFrame frame) { + return Long.MAX_VALUE; + } + + @Override + protected long outputDataOffset(final MessageFrame frame) { + return 0; + } + + @Override + protected long outputDataLength(final MessageFrame frame) { + return 0; + } + + @Override + public long gasAvailableForChildCall(final MessageFrame frame) { + throw new UnsupportedOperationException("EXTCALL does not use gasAvailableForChildCall"); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + final Bytes toBytes = frame.getStackItem(STACK_TO).trimLeadingZeros(); + final Wei value = value(frame); + final boolean zeroValue = value.isZero(); + long inputOffset = inputDataOffset(frame); + long inputLength = inputDataLength(frame); + + if (!zeroValue && isStatic(frame)) { + return new OperationResult( + gasCalculator().callValueTransferGasCost(), ExceptionalHaltReason.ILLEGAL_STATE_CHANGE); + } + if (toBytes.size() > Address.SIZE) { + return new OperationResult( + gasCalculator().memoryExpansionGasCost(frame, inputOffset, inputLength) + + (zeroValue ? 0 : gasCalculator().callValueTransferGasCost()) + + gasCalculator().getColdAccountAccessCost(), + ExceptionalHaltReason.ADDRESS_OUT_OF_RANGE); + } + Address to = Words.toAddress(toBytes); + final Account contract = frame.getWorldUpdater().get(to); + boolean accountCreation = contract == null && !zeroValue; + long cost = + gasCalculator().memoryExpansionGasCost(frame, inputOffset, inputLength) + + (zeroValue ? 0 : gasCalculator().callValueTransferGasCost()) + + (frame.warmUpAddress(to) + ? gasCalculator().getWarmStorageReadCost() + : gasCalculator().getColdAccountAccessCost()) + + (accountCreation ? gasCalculator().newAccountGasCost() : 0); + long currentGas = frame.getRemainingGas() - cost; + if (currentGas < 0) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + final Code code = + contract == null + ? CodeV0.EMPTY_CODE + : evm.getCode(contract.getCodeHash(), contract.getCode()); + + // invalid code results in a quick exit + if (!code.isValid()) { + return new OperationResult(cost, ExceptionalHaltReason.INVALID_CODE, 0); + } + + // last exceptional failure, prepare for call or soft failures + frame.clearReturnData(); + + // delegate calls to prior EOF versions are prohibited + if (isDelegate() && frame.getCode().getEofVersion() != code.getEofVersion()) { + return softFailure(frame, cost); + } + + long retainedGas = Math.max(currentGas / 64, gasCalculator().getMinRetainedGas()); + long childGas = currentGas - retainedGas; + + final Account account = frame.getWorldUpdater().get(frame.getRecipientAddress()); + final Wei balance = (zeroValue || account == null) ? Wei.ZERO : account.getBalance(); + + // There myst be a minimum gas for a call to have access to. + if (childGas < gasCalculator().getMinRetainedGas()) { + return softFailure(frame, cost); + } + // transferring value you don't have is not a halting exception, just a failure + if (!zeroValue && (value.compareTo(balance) > 0)) { + return softFailure(frame, cost); + } + // stack too deep, for large gas systems. + if (frame.getDepth() >= 1024) { + return softFailure(frame, cost); + } + + // all checks passed, do the call + final Bytes inputData = frame.readMutableMemory(inputOffset, inputLength); + + MessageFrame.builder() + .parentMessageFrame(frame) + .type(MessageFrame.Type.MESSAGE_CALL) + .initialGas(childGas) + .address(address(frame)) + .contract(to) + .inputData(inputData) + .sender(sender(frame)) + .value(value(frame)) + .apparentValue(apparentValue(frame)) + .code(code) + .isStatic(isStatic(frame)) + .completer(child -> complete(frame, child)) + .build(); + + frame.setState(MessageFrame.State.CODE_SUSPENDED); + return new OperationResult(cost + childGas, null, 0); + } + + private @Nonnull OperationResult softFailure(final MessageFrame frame, final long cost) { + frame.popStackItems(getStackItemsConsumed()); + frame.pushStackItem(EOF1_EXCEPTION_STACK_ITEM); + return new OperationResult(cost, null); + } + + @Override + Bytes getCallResultStackItem(final MessageFrame childFrame) { + return switch (childFrame.getState()) { + case COMPLETED_SUCCESS -> EOF1_SUCCESS_STACK_ITEM; + case EXCEPTIONAL_HALT -> EOF1_EXCEPTION_STACK_ITEM; + default -> EOF1_FAILURE_STACK_ITEM; + }; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java index bd2c0c1762..82b20b268b 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java @@ -25,8 +25,6 @@ import org.apache.tuweni.bytes.Bytes; public abstract class AbstractOperation implements Operation { static final Bytes BYTES_ONE = Bytes.of(1); - static final Bytes SUCCESS_STACK_ITEM = BYTES_ONE; - static final Bytes FAILURE_STACK_ITEM = Bytes.EMPTY; private final int opcode; private final String name; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AddModOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AddModOperation.java index d8e6d4aef8..4e21e36bc2 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AddModOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AddModOperation.java @@ -56,7 +56,7 @@ public class AddModOperation extends AbstractFixedCostOperation { final Bytes value2 = frame.popStackItem(); if (value2.isZero()) { - frame.pushStackItem(FAILURE_STACK_ITEM); + frame.pushStackItem(Bytes.EMPTY); } else { BigInteger b0 = new BigInteger(1, value0.toArrayUnsafe()); BigInteger b1 = new BigInteger(1, value1.toArrayUnsafe()); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallFOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallFOperation.java index a56099a031..c699063415 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallFOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallFOperation.java @@ -14,11 +14,12 @@ */ package org.hyperledger.besu.evm.operation; -import static org.hyperledger.besu.evm.internal.Words.readBigEndianU16; - +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.code.CodeSection; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.internal.ReturnStack; /** The Call F operation. */ public class CallFOperation extends AbstractOperation { @@ -40,26 +41,18 @@ public class CallFOperation extends AbstractOperation { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - final byte[] code = frame.getCode().getBytes().toArrayUnsafe(); - return staticOperation(frame, code, frame.getPC()); - } - - /** - * Performs Call F operation. - * - * @param frame the frame - * @param code the code - * @param pc the pc - * @return the successful operation result - */ - public static OperationResult staticOperation( - final MessageFrame frame, final byte[] code, final int pc) { - int section = readBigEndianU16(pc + 1, code); - var exception = frame.callFunction(section); - if (exception == null) { - return callfSuccess; - } else { - return new OperationResult(callfSuccess.gasCost, exception); + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; } + + int pc = frame.getPC(); + int section = code.readBigEndianU16(pc + 1); + CodeSection info = code.getCodeSection(section); + frame.getReturnStack().push(new ReturnStack.ReturnStackItem(frame.getSection(), pc + 2)); + frame.setPC(info.getEntryPoint() - 1); // will be +1ed at end of operations loop + frame.setSection(section); + + return callfSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java index bdf7172f36..a044cd138f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java @@ -42,7 +42,7 @@ public class Create2Operation extends AbstractCreateOperation { * @param maxInitcodeSize Maximum init code size */ public Create2Operation(final GasCalculator gasCalculator, final int maxInitcodeSize) { - super(0xF5, "CREATE2", 4, 1, gasCalculator, maxInitcodeSize); + super(0xF5, "CREATE2", 4, 1, gasCalculator, maxInitcodeSize, 0); } @Override diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java index c0332f378c..fc26002453 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java @@ -39,7 +39,7 @@ public class CreateOperation extends AbstractCreateOperation { * @param maxInitcodeSize Maximum init code size */ public CreateOperation(final GasCalculator gasCalculator, final int maxInitcodeSize) { - super(0xF0, "CREATE", 3, 1, gasCalculator, maxInitcodeSize); + super(0xF0, "CREATE", 3, 1, gasCalculator, maxInitcodeSize, 0); } @Override diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DataCopyOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DataCopyOperation.java new file mode 100644 index 0000000000..7813358eac --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DataCopyOperation.java @@ -0,0 +1,67 @@ +/* + * 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.operation; + +import static org.hyperledger.besu.evm.internal.Words.clampedToInt; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import org.apache.tuweni.bytes.Bytes; + +/** The Data load operation. */ +public class DataCopyOperation extends AbstractOperation { + + /** + * Instantiates a new Data Load operation. + * + * @param gasCalculator the gas calculator + */ + public DataCopyOperation(final GasCalculator gasCalculator) { + super(0xd3, "DATACOPY", 3, 1, gasCalculator); + } + + /** + * Cost of data Copy operation. + * + * @param frame the frame + * @param memOffset the mem offset + * @param length the length + * @return the long + */ + protected long cost(final MessageFrame frame, final long memOffset, final long length) { + return gasCalculator().getVeryLowTierGasCost() + + gasCalculator().extCodeCopyOperationGasCost(frame, memOffset, length); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + final int memOffset = clampedToInt(frame.popStackItem()); + final int sourceOffset = clampedToInt(frame.popStackItem()); + final int length = clampedToInt(frame.popStackItem()); + final long cost = cost(frame, memOffset, length); + + final Bytes data = code.getData(sourceOffset, length); + frame.writeMemory(memOffset, length, data); + + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DataLoadNOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DataLoadNOperation.java new file mode 100644 index 0000000000..7431ed5ac7 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DataLoadNOperation.java @@ -0,0 +1,54 @@ +/* + * 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.operation; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import org.apache.tuweni.bytes.Bytes; + +/** The Data load operation. */ +public class DataLoadNOperation extends AbstractFixedCostOperation { + + /** The constant OPCODE. */ + public static final int OPCODE = 0xd1; + + /** + * Instantiates a new Data Load operation. + * + * @param gasCalculator the gas calculator + */ + public DataLoadNOperation(final GasCalculator gasCalculator) { + super(OPCODE, "DATALOADN", 0, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + + int pc = frame.getPC(); + int index = code.readBigEndianU16(pc + 1); + final Bytes data = code.getData(index, 32); + frame.pushStackItem(data); + frame.setPC(pc + 2); + + return successResponse; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DataLoadOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DataLoadOperation.java new file mode 100644 index 0000000000..29db444fac --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DataLoadOperation.java @@ -0,0 +1,52 @@ +/* + * 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.operation; + +import static org.hyperledger.besu.evm.internal.Words.clampedToInt; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import org.apache.tuweni.bytes.Bytes; + +/** The Data load operation. */ +public class DataLoadOperation extends AbstractFixedCostOperation { + + /** + * Instantiates a new Data Load operation. + * + * @param gasCalculator the gas calculator + */ + public DataLoadOperation(final GasCalculator gasCalculator) { + super(0xd0, "DATALOAD", 1, 1, gasCalculator, 4); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + final int sourceOffset = clampedToInt(frame.popStackItem()); + + final Bytes data = code.getData(sourceOffset, 32); + frame.pushStackItem(data); + + return successResponse; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DataSizeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DataSizeOperation.java new file mode 100644 index 0000000000..13cfab0df2 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DataSizeOperation.java @@ -0,0 +1,47 @@ +/* + * 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.operation; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import org.apache.tuweni.bytes.Bytes; + +/** The Data load operation. */ +public class DataSizeOperation extends AbstractFixedCostOperation { + + /** + * Instantiates a new Data Load operation. + * + * @param gasCalculator the gas calculator + */ + public DataSizeOperation(final GasCalculator gasCalculator) { + super(0xd2, "DATASIZE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { + final Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + final int size = code.getDataSize(); + frame.pushStackItem(Bytes.ofUnsignedInt(size)); + + return successResponse; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DelegateCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DelegateCallOperation.java index 0205eb8753..e7fb3f7c62 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/DelegateCallOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DelegateCallOperation.java @@ -83,4 +83,9 @@ public class DelegateCallOperation extends AbstractCallOperation { public long gasAvailableForChildCall(final MessageFrame frame) { return gasCalculator().gasAvailableForChildCall(frame, gas(frame), false); } + + @Override + protected boolean isDelegate() { + return true; + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DupNOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DupNOperation.java new file mode 100644 index 0000000000..61108e2b61 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DupNOperation.java @@ -0,0 +1,55 @@ +/* + * 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.operation; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +/** The Dup operation. */ +public class DupNOperation extends AbstractFixedCostOperation { + + /** DUPN Opcode 0xe6 */ + public static final int OPCODE = 0xe6; + + /** The Dup success operation result. */ + static final OperationResult dupSuccess = new OperationResult(3, null); + + /** + * Instantiates a new Dup operation. + * + * @param gasCalculator the gas calculator + */ + public DupNOperation(final GasCalculator gasCalculator) { + super(OPCODE, "DUPN", 0, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + int pc = frame.getPC(); + + int depth = code.readU8(pc + 1); + frame.pushStackItem(frame.getStackItem(depth)); + frame.setPC(pc + 1); + + return dupSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/EOFCreateOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/EOFCreateOperation.java new file mode 100644 index 0000000000..cb6b2d4e3c --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/EOFCreateOperation.java @@ -0,0 +1,91 @@ +/* + * 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.operation; + +import static org.hyperledger.besu.crypto.Hash.keccak256; +import static org.hyperledger.besu.evm.internal.Words.clampedAdd; +import static org.hyperledger.besu.evm.internal.Words.clampedToInt; +import static org.hyperledger.besu.evm.internal.Words.clampedToLong; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import java.util.function.Supplier; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +/** The Create2 operation. */ +public class EOFCreateOperation extends AbstractCreateOperation { + + /** Opcode 0xEC for operation EOFCREATE */ + public static final int OPCODE = 0xec; + + private static final Bytes PREFIX = Bytes.fromHexString("0xFF"); + + /** + * Instantiates a new EOFCreate operation. + * + * @param gasCalculator the gas calculator + */ + public EOFCreateOperation(final GasCalculator gasCalculator) { + super(OPCODE, "EOFCREATE", 4, 1, gasCalculator, Integer.MAX_VALUE, 1); + } + + @Override + public long cost(final MessageFrame frame, final Supplier codeSupplier) { + final int inputOffset = clampedToInt(frame.getStackItem(2)); + final int inputSize = clampedToInt(frame.getStackItem(3)); + return clampedAdd( + gasCalculator().memoryExpansionGasCost(frame, inputOffset, inputSize), + clampedAdd( + gasCalculator().txCreateCost(), + gasCalculator().createKeccakCost(codeSupplier.get().getSize()))); + } + + @Override + public Address targetContractAddress(final MessageFrame frame, final Code initcode) { + final Address sender = frame.getRecipientAddress(); + final Bytes32 salt = Bytes32.leftPad(frame.getStackItem(1)); + final Bytes32 hash = keccak256(Bytes.concatenate(PREFIX, sender, salt, initcode.getCodeHash())); + final Address address = Address.extract(hash); + frame.warmUpAddress(address); + return address; + } + + @Override + protected Code getInitCode(final MessageFrame frame, final EVM evm) { + final Code code = frame.getCode(); + int startIndex = frame.getPC() + 1; + final int initContainerIndex = code.readU8(startIndex); + + return code.getSubContainer(initContainerIndex, null).orElse(null); + } + + @Override + protected Bytes getInputData(final MessageFrame frame) { + final long inputOffset = clampedToLong(frame.getStackItem(2)); + final long inputSize = clampedToLong(frame.getStackItem(3)); + return frame.readMemory(inputOffset, inputSize); + } + + @Override + protected int getPcIncrement() { + return 2; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExchangeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExchangeOperation.java new file mode 100644 index 0000000000..cc6f8c9351 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExchangeOperation.java @@ -0,0 +1,60 @@ +/* + * 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.operation; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import org.apache.tuweni.bytes.Bytes; + +/** The Exchange operation. */ +public class ExchangeOperation extends AbstractFixedCostOperation { + + /** EXCHANGE Opcode 0xe8 */ + public static final int OPCODE = 0xe8; + + /** The Exchange operation success result. */ + static final OperationResult exchangeSuccess = new OperationResult(3, null); + + /** + * Instantiates a new Exchange operation. + * + * @param gasCalculator the gas calculator + */ + public ExchangeOperation(final GasCalculator gasCalculator) { + super(OPCODE, "EXCHANGE", 0, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + int pc = frame.getPC(); + int imm = code.readU8(pc + 1); + int n = (imm >> 4) + 1; + int m = (imm & 0x0F) + 1 + n; + + final Bytes tmp = frame.getStackItem(n); + frame.setStackItem(n, frame.getStackItem(m)); + frame.setStackItem(m, tmp); + frame.setPC(pc + 1); + + return exchangeSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCallOperation.java new file mode 100644 index 0000000000..bf986a572e --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCallOperation.java @@ -0,0 +1,69 @@ +/* + * 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.operation; + +import static org.hyperledger.besu.evm.internal.Words.clampedToLong; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +/** The Call operation. */ +public class ExtCallOperation extends AbstractExtCallOperation { + + static final int STACK_VALUE = 1; + static final int STACK_INPUT_OFFSET = 2; + static final int STACK_INPUT_LENGTH = 3; + + /** + * Instantiates a new Call operation. + * + * @param gasCalculator the gas calculator + */ + public ExtCallOperation(final GasCalculator gasCalculator) { + super(0xF8, "EXTCALL", 4, 1, gasCalculator); + } + + @Override + protected Wei value(final MessageFrame frame) { + return Wei.wrap(frame.getStackItem(STACK_VALUE)); + } + + @Override + protected Wei apparentValue(final MessageFrame frame) { + return value(frame); + } + + @Override + protected long inputDataOffset(final MessageFrame frame) { + return clampedToLong(frame.getStackItem(STACK_INPUT_OFFSET)); + } + + @Override + protected long inputDataLength(final MessageFrame frame) { + return clampedToLong(frame.getStackItem(STACK_INPUT_LENGTH)); + } + + @Override + protected Address address(final MessageFrame frame) { + return to(frame); + } + + @Override + protected Address sender(final MessageFrame frame) { + return frame.getRecipientAddress(); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java index 7801b6d7d3..37a92ffc6e 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java @@ -20,6 +20,7 @@ import static org.hyperledger.besu.evm.internal.Words.clampedToLong; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.code.EOFLayout; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -30,6 +31,9 @@ import org.apache.tuweni.bytes.Bytes; /** The Ext code copy operation. */ public class ExtCodeCopyOperation extends AbstractOperation { + /** This is the "code" legacy contracts see when copying code from an EOF contract. */ + public static final Bytes EOF_REPLACEMENT_CODE = Bytes.fromHexString("0xef00"); + /** * Instantiates a new Ext code copy operation. * @@ -78,7 +82,12 @@ public class ExtCodeCopyOperation extends AbstractOperation { final Account account = frame.getWorldUpdater().get(address); final Bytes code = account != null ? account.getCode() : Bytes.EMPTY; - frame.writeMemory(memOffset, sourceOffset, numBytes, code); + if (code.size() >= 2 && code.get(0) == EOFLayout.EOF_PREFIX_BYTE && code.get(1) == 0) { + frame.writeMemory(memOffset, sourceOffset, numBytes, EOF_REPLACEMENT_CODE); + } else { + frame.writeMemory(memOffset, sourceOffset, numBytes, code); + } + return new OperationResult(cost, null); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java index ba6cd22d9d..953ddfb04d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java @@ -15,8 +15,10 @@ package org.hyperledger.besu.evm.operation; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.code.EOFLayout; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -29,6 +31,9 @@ import org.apache.tuweni.bytes.Bytes; /** The Ext code hash operation. */ public class ExtCodeHashOperation extends AbstractOperation { + // // 0x9dbf3648db8210552e9c4f75c6a1c3057c0ca432043bd648be15fe7be05646f5 + static final Hash EOF_REPLACEMENT_HASH = Hash.hash(ExtCodeCopyOperation.EOF_REPLACEMENT_CODE); + /** * Instantiates a new Ext code hash operation. * @@ -65,7 +70,12 @@ public class ExtCodeHashOperation extends AbstractOperation { if (account == null || account.isEmpty()) { frame.pushStackItem(Bytes.EMPTY); } else { - frame.pushStackItem(account.getCodeHash()); + final Bytes code = account.getCode(); + if (code.size() >= 2 && code.get(0) == EOFLayout.EOF_PREFIX_BYTE && code.get(1) == 0) { + frame.pushStackItem(EOF_REPLACEMENT_HASH); + } else { + frame.pushStackItem(account.getCodeHash()); + } } return new OperationResult(cost, null); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java index 9d891f7d37..95e5acc6ff 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java @@ -17,6 +17,7 @@ package org.hyperledger.besu.evm.operation; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.code.EOFLayout; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -29,6 +30,8 @@ import org.apache.tuweni.bytes.Bytes; /** The Ext code size operation. */ public class ExtCodeSizeOperation extends AbstractOperation { + static final Bytes EOF_SIZE = Bytes.of(2); + /** * Instantiates a new Ext code size operation. * @@ -62,8 +65,18 @@ public class ExtCodeSizeOperation extends AbstractOperation { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); } else { final Account account = frame.getWorldUpdater().get(address); - frame.pushStackItem( - account == null ? Bytes.EMPTY : Words.intBytes(account.getCode().size())); + Bytes codeSize; + if (account == null) { + codeSize = Bytes.EMPTY; + } else { + final Bytes code = account.getCode(); + if (code.size() >= 2 && code.get(0) == EOFLayout.EOF_PREFIX_BYTE && code.get(1) == 0) { + codeSize = EOF_SIZE; + } else { + codeSize = Words.intBytes(code.size()); + } + } + frame.pushStackItem(codeSize); return new OperationResult(cost, null); } } catch (final UnderflowException ufe) { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtDelegateCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtDelegateCallOperation.java new file mode 100644 index 0000000000..bddb337c73 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtDelegateCallOperation.java @@ -0,0 +1,73 @@ +/* + * 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.operation; + +import static org.hyperledger.besu.evm.internal.Words.clampedToLong; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +/** The Delegate call operation. */ +public class ExtDelegateCallOperation extends AbstractExtCallOperation { + + static final int STACK_INPUT_OFFSET = 1; + static final int STACK_INPUT_LENGTH = 2; + + /** + * Instantiates a new Delegate call operation. + * + * @param gasCalculator the gas calculator + */ + public ExtDelegateCallOperation(final GasCalculator gasCalculator) { + super(0xF9, "EXTDELEGATECALL", 3, 1, gasCalculator); + } + + @Override + protected Wei value(final MessageFrame frame) { + return Wei.ZERO; + } + + @Override + protected Wei apparentValue(final MessageFrame frame) { + return frame.getApparentValue(); + } + + @Override + protected long inputDataOffset(final MessageFrame frame) { + return clampedToLong(frame.getStackItem(STACK_INPUT_OFFSET)); + } + + @Override + protected long inputDataLength(final MessageFrame frame) { + return clampedToLong(frame.getStackItem(STACK_INPUT_LENGTH)); + } + + @Override + protected Address address(final MessageFrame frame) { + return frame.getRecipientAddress(); + } + + @Override + protected Address sender(final MessageFrame frame) { + return frame.getSenderAddress(); + } + + @Override + protected boolean isDelegate() { + return true; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtStaticCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtStaticCallOperation.java new file mode 100644 index 0000000000..3a202f46e7 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtStaticCallOperation.java @@ -0,0 +1,73 @@ +/* + * 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.operation; + +import static org.hyperledger.besu.evm.internal.Words.clampedToLong; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +/** The Static call operation. */ +public class ExtStaticCallOperation extends AbstractExtCallOperation { + + static final int STACK_INPUT_OFFSET = 1; + static final int STACK_INPUT_LENGTH = 2; + + /** + * Instantiates a new Static call operation. + * + * @param gasCalculator the gas calculator + */ + public ExtStaticCallOperation(final GasCalculator gasCalculator) { + super(0xFB, "EXTSTATICCALL", 3, 1, gasCalculator); + } + + @Override + protected Wei value(final MessageFrame frame) { + return Wei.ZERO; + } + + @Override + protected Wei apparentValue(final MessageFrame frame) { + return value(frame); + } + + @Override + protected long inputDataOffset(final MessageFrame frame) { + return clampedToLong(frame.getStackItem(STACK_INPUT_OFFSET)); + } + + @Override + protected long inputDataLength(final MessageFrame frame) { + return clampedToLong(frame.getStackItem(STACK_INPUT_LENGTH)); + } + + @Override + protected Address address(final MessageFrame frame) { + return to(frame); + } + + @Override + protected Address sender(final MessageFrame frame) { + return frame.getRecipientAddress(); + } + + @Override + protected boolean isStatic(final MessageFrame frame) { + return true; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/JumpFOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/JumpFOperation.java index 86c6f90660..c76caa9667 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/JumpFOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/JumpFOperation.java @@ -14,8 +14,7 @@ */ package org.hyperledger.besu.evm.operation; -import static org.hyperledger.besu.evm.internal.Words.readBigEndianU16; - +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -27,7 +26,7 @@ public class JumpFOperation extends AbstractOperation { public static final int OPCODE = 0xe5; /** The Jump F success operation result. */ - static final OperationResult jumpfSuccess = new OperationResult(3, null); + static final OperationResult jumpfSuccess = new OperationResult(5, null); /** * Instantiates a new Jump F operation. @@ -40,26 +39,15 @@ public class JumpFOperation extends AbstractOperation { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - final byte[] code = frame.getCode().getBytes().toArrayUnsafe(); - return staticOperation(frame, code, frame.getPC()); - } - - /** - * Performs Jump F operation. - * - * @param frame the frame - * @param code the code - * @param pc the pc - * @return the successful operation result - */ - public static OperationResult staticOperation( - final MessageFrame frame, final byte[] code, final int pc) { - int section = readBigEndianU16(pc + 1, code); - var exception = frame.jumpFunction(section); - if (exception == null) { - return jumpfSuccess; - } else { - return new OperationResult(jumpfSuccess.gasCost, exception); + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; } + int pc = frame.getPC(); + int section = code.readBigEndianU16(pc + 1); + var info = code.getCodeSection(section); + frame.setPC(info.getEntryPoint() - 1); // will be +1ed at end of operations loop + frame.setSection(section); + return jumpfSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpIfOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpIfOperation.java index 839b8612fe..cf345fc930 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpIfOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpIfOperation.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.evm.operation; +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -21,7 +22,7 @@ import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.apache.tuweni.bytes.Bytes; /** The type Relative jump If operation. */ -public class RelativeJumpIfOperation extends RelativeJumpOperation { +public class RelativeJumpIfOperation extends AbstractFixedCostOperation { /** The constant OPCODE. */ public static final int OPCODE = 0xe1; @@ -37,11 +38,16 @@ public class RelativeJumpIfOperation extends RelativeJumpOperation { @Override protected OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } final Bytes condition = frame.popStackItem(); - // If condition is zero (false), no jump is will be performed. Therefore, skip the rest. if (!condition.isZero()) { - return super.executeFixedCostOperation(frame, evm); + final int pcPostInstruction = frame.getPC() + 1; + return new OperationResult(gasCost, null, 2 + code.readBigEndianI16(pcPostInstruction) + 1); + } else { + return new OperationResult(gasCost, null, 2 + 1); } - return new OperationResult(gasCost, null, 2 + 1); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpOperation.java index e96d747ff1..eb63d2409f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpOperation.java @@ -14,12 +14,10 @@ */ package org.hyperledger.besu.evm.operation; +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.Words; - -import org.apache.tuweni.bytes.Bytes; /** The type Relative jump operation. */ public class RelativeJumpOperation extends AbstractFixedCostOperation { @@ -58,9 +56,11 @@ public class RelativeJumpOperation extends AbstractFixedCostOperation { @Override protected OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { - final Bytes code = frame.getCode().getBytes(); + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } final int pcPostInstruction = frame.getPC() + 1; - return new OperationResult( - gasCost, null, 2 + Words.readBigEndianI16(pcPostInstruction, code.toArrayUnsafe()) + 1); + return new OperationResult(gasCost, null, 2 + code.readBigEndianI16(pcPostInstruction) + 1); } } 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 7304ffa8bb..22d4d4e53e 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 @@ -14,8 +14,7 @@ */ package org.hyperledger.besu.evm.operation; -import static org.hyperledger.besu.evm.internal.Words.readBigEndianI16; - +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -39,36 +38,42 @@ public class RelativeJumpVectorOperation extends AbstractFixedCostOperation { @Override protected OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { - final Bytes code = frame.getCode().getBytes(); + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } int offsetCase; try { - offsetCase = frame.popStackItem().toInt(); + offsetCase = frame.popStackItem().trimLeadingZeros().toInt(); if (offsetCase < 0) { offsetCase = Integer.MAX_VALUE; } } catch (ArithmeticException | IllegalArgumentException ae) { offsetCase = Integer.MAX_VALUE; } - final int vectorSize = getVectorSize(code, frame.getPC() + 1); + final int vectorSize = getVectorSize(code.getBytes(), frame.getPC() + 1); + int jumpDelta = + (offsetCase < vectorSize) + ? code.readBigEndianI16( + frame.getPC() + 2 + offsetCase * 2) // lookup delta if offset is in vector + : 0; // if offsetCase is outside the vector the jump delta is zero / next opcode. return new OperationResult( gasCost, null, - 1 - + 2 * vectorSize - + ((offsetCase >= vectorSize) - ? 0 - : readBigEndianI16(frame.getPC() + 2 + offsetCase * 2, code.toArrayUnsafe())) - + 1); + 2 // Opcode + length immediate + + 2 * vectorSize // vector size + + jumpDelta); } /** - * Gets vector size. + * Gets vector size. Vector size is one greater than length immediate, because (a) zero length + * tables are useless and (b) it allows for 256 byte tables * * @param code the code * @param offsetCountByteIndex the offset count byte index * @return the vector size */ public static int getVectorSize(final Bytes code, final int offsetCountByteIndex) { - return code.get(offsetCountByteIndex) & 0xff; + return (code.toArrayUnsafe()[offsetCountByteIndex] & 0xff) + 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 1e5155fb81..4efa71eaeb 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 @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.evm.operation; +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -38,11 +39,15 @@ public class RetFOperation extends AbstractOperation { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - var exception = frame.returnFunction(); - if (exception == null) { - return retfSuccess; - } else { - return new OperationResult(retfSuccess.gasCost, exception); + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; } + var rStack = frame.getReturnStack(); + var returnInfo = rStack.pop(); + frame.setPC(returnInfo.pc()); + frame.setSection(returnInfo.codeSectionIndex()); + + return retfSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnContractOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnContractOperation.java new file mode 100644 index 0000000000..c8031f4c4d --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnContractOperation.java @@ -0,0 +1,76 @@ +/* + * 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.operation; + +import static org.hyperledger.besu.evm.internal.Words.clampedToLong; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; + +/** The Return operation. */ +public class ReturnContractOperation extends AbstractOperation { + + /** Opcode of RETURNCONTRACT operation */ + public static final int OPCODE = 0xEE; + + /** + * Instantiates a new Return operation. + * + * @param gasCalculator the gas calculator + */ + public ReturnContractOperation(final GasCalculator gasCalculator) { + super(OPCODE, "RETURNCONTRACT", 2, 0, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + + int pc = frame.getPC(); + int index = code.readU8(pc + 1); + + final long from = clampedToLong(frame.popStackItem()); + final long length = clampedToLong(frame.popStackItem()); + + final long cost = gasCalculator().memoryExpansionGasCost(frame, from, length); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + if (index >= code.getSubcontainerCount()) { + return new OperationResult(cost, ExceptionalHaltReason.NONEXISTENT_CONTAINER); + } + + Bytes auxData = frame.readMemory(from, length); + Optional newCode = code.getSubContainer(index, auxData); + if (newCode.isEmpty()) { + return new OperationResult(cost, ExceptionalHaltReason.NONEXISTENT_CONTAINER); + } + + frame.setCreatedCode(newCode.get()); + frame.setState(MessageFrame.State.CODE_SUCCESS); + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataCopyOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataCopyOperation.java index c7c69149db..e6c91b0ee4 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataCopyOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataCopyOperation.java @@ -51,13 +51,15 @@ public class ReturnDataCopyOperation extends AbstractOperation { final Bytes returnData = frame.getReturnData(); final int returnDataLength = returnData.size(); - try { - final long end = Math.addExact(sourceOffset, numBytes); - if (end > returnDataLength) { - return INVALID_RETURN_DATA_BUFFER_ACCESS; + if (frame.getCode().getEofVersion() < 1) { + try { + final long end = Math.addExact(sourceOffset, numBytes); + if (end > returnDataLength) { + return INVALID_RETURN_DATA_BUFFER_ACCESS; + } + } catch (final ArithmeticException ae) { + return OUT_OF_BOUNDS; } - } catch (final ArithmeticException ae) { - return OUT_OF_BOUNDS; } final long cost = gasCalculator().dataCopyOperationGasCost(frame, memOffset, numBytes); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataLoadOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataLoadOperation.java new file mode 100644 index 0000000000..7d8a5e4209 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataLoadOperation.java @@ -0,0 +1,56 @@ +/* + * 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.operation; + +import static org.hyperledger.besu.evm.internal.Words.clampedToInt; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +/** The Return data copy operation. */ +public class ReturnDataLoadOperation extends AbstractOperation { + + /** + * Instantiates a new Return data copy operation. + * + * @param gasCalculator the gas calculator + */ + public ReturnDataLoadOperation(final GasCalculator gasCalculator) { + super(0xf7, "RETURNDATALOAD", 3, 0, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + final int offset = clampedToInt(frame.popStackItem()); + Bytes returnData = frame.getReturnData(); + int retunDataSize = returnData.size(); + + Bytes value; + if (offset > retunDataSize) { + value = Bytes.EMPTY; + } else if (offset + 32 >= returnData.size()) { + value = Bytes32.rightPad(returnData.slice(offset)); + } else { + value = returnData.slice(offset, 32); + } + + frame.pushStackItem(value); + return new OperationResult(3L, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/StopOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/StopOperation.java index 7bbdb00682..c3026be5d4 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/StopOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/StopOperation.java @@ -23,6 +23,9 @@ import org.apache.tuweni.bytes.Bytes; /** The Stop operation. */ public class StopOperation extends AbstractFixedCostOperation { + /** Opcode of STOP operation */ + public static final int OPCODE = 0x00; + /** The Stop operation success result. */ static final OperationResult stopSuccess = new OperationResult(0, null); @@ -32,7 +35,7 @@ public class StopOperation extends AbstractFixedCostOperation { * @param gasCalculator the gas calculator */ public StopOperation(final GasCalculator gasCalculator) { - super(0x00, "STOP", 0, 0, gasCalculator, gasCalculator.getZeroTierGasCost()); + super(OPCODE, "STOP", 0, 0, gasCalculator, gasCalculator.getZeroTierGasCost()); } @Override diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SwapNOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SwapNOperation.java new file mode 100644 index 0000000000..badd14b34f --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SwapNOperation.java @@ -0,0 +1,59 @@ +/* + * 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.operation; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import org.apache.tuweni.bytes.Bytes; + +/** The SwapN operation. */ +public class SwapNOperation extends AbstractFixedCostOperation { + + /** SWAPN Opcode 0xe7 */ + public static final int OPCODE = 0xe7; + + /** The Swap operation success result. */ + static final OperationResult swapSuccess = new OperationResult(3, null); + + /** + * Instantiates a new SwapN operation. + * + * @param gasCalculator the gas calculator + */ + public SwapNOperation(final GasCalculator gasCalculator) { + super(OPCODE, "SWAPN", 0, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + int pc = frame.getPC(); + int index = code.readU8(pc + 1); + + final Bytes tmp = frame.getStackItem(0); + frame.setStackItem(0, frame.getStackItem(index + 1)); + frame.setStackItem(index + 1, tmp); + frame.setPC(pc + 1); + + return swapSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/TLoadOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/TLoadOperation.java index 2511492a1a..684d18f987 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/TLoadOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/TLoadOperation.java @@ -18,7 +18,6 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.OverflowException; import org.hyperledger.besu.evm.internal.UnderflowException; import org.apache.tuweni.bytes.Bytes32; @@ -50,8 +49,6 @@ public class TLoadOperation extends AbstractOperation { } } catch (final UnderflowException ufe) { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); - } catch (final OverflowException ofe) { - return new OperationResult(cost, ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); } } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java b/evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java index 7116229c48..5e66832634 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java @@ -243,12 +243,12 @@ public abstract class AbstractMessageProcessor { } /** - * Gets code from evm, skipping the code cache + * Gets code from evm, with handling for EOF code plus calldata * * @param codeBytes the code bytes * @return the code from evm */ - public Code getCodeFromEVMUncached(final Bytes codeBytes) { - return evm.getCodeUncached(codeBytes); + public Code getCodeFromEVMForCreation(final Bytes codeBytes) { + return evm.getCodeForCreation(codeBytes); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/processor/ContractCreationProcessor.java b/evm/src/main/java/org/hyperledger/besu/evm/processor/ContractCreationProcessor.java index 744f7147fd..0e47db90ac 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/processor/ContractCreationProcessor.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/processor/ContractCreationProcessor.java @@ -137,7 +137,8 @@ public class ContractCreationProcessor extends AbstractMessageProcessor { @Override public void codeSuccess(final MessageFrame frame, final OperationTracer operationTracer) { - final Bytes contractCode = frame.getOutputData(); + final Bytes contractCode = + frame.getCreatedCode() == null ? frame.getOutputData() : frame.getCreatedCode().getBytes(); final long depositFee = gasCalculator.codeDepositGasCost(contractCode.size()); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/tracing/StandardJsonTracer.java b/evm/src/main/java/org/hyperledger/besu/evm/tracing/StandardJsonTracer.java index 86abd3ba06..4ea5bc1b7f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/tracing/StandardJsonTracer.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/tracing/StandardJsonTracer.java @@ -16,6 +16,7 @@ package org.hyperledger.besu.evm.tracing; import static com.google.common.base.Strings.padStart; +import org.hyperledger.besu.evm.code.OpcodeInfo; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.operation.AbstractCallOperation; @@ -48,6 +49,7 @@ public class StandardJsonTracer implements OperationTracer { private Bytes memory; private int memorySize; private int depth; + private int subdepth; private String storageString; /** @@ -135,6 +137,7 @@ public class StandardJsonTracer implements OperationTracer { memory = null; } depth = messageFrame.getMessageStackSize(); + subdepth = messageFrame.returnStackSize(); StringBuilder sb = new StringBuilder(); if (showStorage) { @@ -181,10 +184,22 @@ public class StandardJsonTracer implements OperationTracer { final StringBuilder sb = new StringBuilder(1024); sb.append("{"); sb.append("\"pc\":").append(pc).append(","); - if (section > 0) { + boolean eofContract = messageFrame.getCode().getEofVersion() > 0; + if (eofContract) { sb.append("\"section\":").append(section).append(","); } sb.append("\"op\":").append(opcode).append(","); + OpcodeInfo opInfo = OpcodeInfo.getOpcode(opcode); + if (eofContract && opInfo.pcAdvance() > 1) { + var immediate = + messageFrame + .getCode() + .getBytes() + .slice( + pc + messageFrame.getCode().getCodeSection(0).getEntryPoint() + 1, + opInfo.pcAdvance() - 1); + sb.append("\"immediate\":\"").append(immediate.toHexString()).append("\","); + } sb.append("\"gas\":\"").append(gas).append("\","); sb.append("\"gasCost\":\"").append(shortNumber(thisGasCost)).append("\","); if (memory != null) { @@ -198,6 +213,9 @@ public class StandardJsonTracer implements OperationTracer { sb.append("\"returnData\":\"").append(returnData.toHexString()).append("\","); } sb.append("\"depth\":").append(depth).append(","); + if (subdepth > 1) { + sb.append("\"subdepth\":").append(subdepth).append(","); + } sb.append("\"refund\":").append(messageFrame.getGasRefund()).append(","); sb.append("\"opName\":\"").append(currentOp.getName()).append("\""); if (executeResult.getHaltReason() != null) { diff --git a/evm/src/test/java/org/hyperledger/besu/evm/EOFTestConstants.java b/evm/src/test/java/org/hyperledger/besu/evm/EOFTestConstants.java new file mode 100644 index 0000000000..f71adfee2a --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/EOFTestConstants.java @@ -0,0 +1,95 @@ +/* + * 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; + +import org.apache.tuweni.bytes.Bytes; + +public class EOFTestConstants { + + public static final Bytes INNER_CONTRACT = + bytesFromPrettyPrint( + """ + # EOF + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0009 # Code section 0 , 9 bytes + 030001 # Total subcontainers ( 1 ) + 0014 # Sub container 0, 20 byte + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0003 # max stack: 3 + # Code section 0 + 5f # [0] PUSH0 + 35 # [1] CALLDATALOAD + 5f # [2] PUSH0 + 5f # [3] PUSH0 + a1 # [4] LOG1 + 5f # [5] PUSH0 + 5f # [6] PUSH0 + ee00 # [7] RETURNCONTRACT(0) + # Subcontainer 0 starts here + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0001 # Code section 0 , 1 bytes + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs + 80 # 0 outputs (Non-returning function) + 0000 # max stack: 0 + # Code section 0 + 00 # [0] STOP + """); + + public static Bytes EOF_CREATE_CONTRACT = + bytesFromPrettyPrint( + String.format( + """ + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 000e # Code section 0 , 14 bytes + 030001 # Total subcontainers ( 1 ) + %04x # Subcontainer 0 size ? + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0004 # max stack: 4 + # Code section 0 + 61c0de # [0] PUSH2(0xc0de) + 5f # [3] PUSH0 + 52 # [4] MSTORE + 6002 # [5] PUSH1(2) + 601e # [7] PUSH1 30 + 5f # [9] PUSH0 + 5f # [10] PUSH0 + ec00 # [11] EOFCREATE(0) + 00 # [13] STOP + # Data section (empty) + %s # subcontainer + """, + INNER_CONTRACT.size(), INNER_CONTRACT.toUnprefixedHexString())); + + public static Bytes bytesFromPrettyPrint(final String prettyPrint) { + return Bytes.fromHexString(prettyPrint.replaceAll("#.*?\n", "").replaceAll("\\s", "")); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeFactoryTest.java b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeFactoryTest.java index adcf5b3585..d3335d077c 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeFactoryTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeFactoryTest.java @@ -25,12 +25,12 @@ class CodeFactoryTest { @Test void invalidCodeIncompleteMagic() { - invalidCode("0xEF"); + invalidCode("0xEF", true); } @Test void invalidCodeInvalidMagic() { - invalidCode("0xEFFF0101000302000400600000AABBCCDD"); + invalidCode("0xEFFF0101000302000400600000AABBCCDD", true); } @Test @@ -179,7 +179,12 @@ class CodeFactoryTest { } private static void invalidCode(final String str) { - Code code = CodeFactory.createCode(Bytes.fromHexString(str), 1, true); + Code code = CodeFactory.createCode(Bytes.fromHexString(str), 1); + assertThat(code.isValid()).isFalse(); + } + + private static void invalidCode(final String str, final boolean legacy) { + Code code = CodeFactory.createCode(Bytes.fromHexString(str), 1, legacy, false); assertThat(code.isValid()).isFalse(); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV0Test.java b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV0Test.java index 36416329d1..1bc984222b 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV0Test.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV0Test.java @@ -62,7 +62,7 @@ class CodeV0Test { void shouldReuseJumpDestMap() { final JumpOperation operation = new JumpOperation(gasCalculator); final Bytes jumpBytes = Bytes.fromHexString("0x6003565b00"); - final CodeV0 getsCached = (CodeV0) spy(CodeFactory.createCode(jumpBytes, 0, false)); + final CodeV0 getsCached = (CodeV0) spy(CodeFactory.createCode(jumpBytes, 0)); MessageFrame frame = createJumpFrame(getsCached); OperationResult result = operation.execute(frame, evm); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV1Test.java b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV1Test.java index 5f06bb4872..8eda50316f 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV1Test.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV1Test.java @@ -18,12 +18,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.evm.code.CodeV1Validation.validateCode; import static org.hyperledger.besu.evm.code.CodeV1Validation.validateStack; +import java.util.Arrays; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.IntStream; import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -39,10 +40,50 @@ class CodeV1Test { public static final String ZERO_HEX = "00"; public static final String NOOP_HEX = "5b"; + private static void assertValidation(final String error, final String code) { + assertValidation(error, code, false, 1, 5); + } + + private static void assertValidation( + final String error, + final String code, + final boolean returning, + final int... codeSectionSizes) { + Bytes codeBytes = Bytes.fromHexString(code); + for (int i : codeSectionSizes) { + CodeSection[] codeSections = new CodeSection[i]; + Arrays.fill(codeSections, new CodeSection(1, 0, returning ? 0 : 0x80, 1, 1)); + EOFLayout testLayout = + new EOFLayout( + codeBytes, + 1, + codeSections, + new EOFLayout[0], + 0, + Bytes.EMPTY, + error, + new AtomicReference<>()); + assertValidation(error, codeBytes, codeSections[0], testLayout); + } + } + + private static void assertValidation( + final String error, + final Bytes codeBytes, + final CodeSection thisCodeSection, + final EOFLayout eofLayout) { + final String validationError = validateCode(codeBytes, thisCodeSection, eofLayout); + if (error == null) { + assertThat(validationError).isNull(); + } else { + assertThat(validationError).startsWith(error); + } + } + @Test void validCode() { String codeHex = - "0xEF0001 01000C 020003 000b 0002 0008 030000 00 00000000 02010001 01000002 60016002e30001e30002e4 01e4 60005360106000f3"; + "0xEF0001 01000C 020003 000b 0002 0008 040000 00 00800000 02010001 01000002 60016002e30001e30002f3 01e4 60005360106000e4"; final EOFLayout layout = EOFLayout.parseEOF(Bytes.fromHexString(codeHex.replace(" ", ""))); String validationError = validateCode(layout); @@ -50,26 +91,36 @@ class CodeV1Test { assertThat(validationError).isNull(); } + @Test + void invalidCode() { + String codeHex = + "0xEF0001 01000C 020003 000b 0002 0008 040000 00 00000000 02010001 01000002 60016002e30001e30002f3 01e4 60005360106000e4"; + final EOFLayout layout = EOFLayout.parseEOF(Bytes.fromHexString(codeHex.replace(" ", ""))); + + String validationError = validateCode(layout); + + assertThat(validationError) + .isEqualTo( + "Invalid EOF container - Code section at zero expected non-returning flag, but had return stack of 0"); + } + @ParameterizedTest @ValueSource( - strings = {"3000", "5000", "e0000000", "6000e1000000", "6000e201000000", "fe00", "0000"}) + strings = {"3000", "5000", "e0000000", "6000e1000000", "6000e200000000", "fe00", "0000"}) void testValidOpcodes(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isNull(); + assertValidation(null, code); } @ParameterizedTest @ValueSource(strings = {"00", "3030f3", "3030fd", "fe"}) void testValidCodeTerminator(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isNull(); + assertValidation(null, code); } @ParameterizedTest @MethodSource("testPushValidImmediateArguments") void testPushValidImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isNull(); + assertValidation(null, code); } private static Stream testPushValidImmediateArguments() { @@ -82,8 +133,7 @@ class CodeV1Test { @ParameterizedTest @MethodSource("testRjumpValidImmediateArguments") void testRjumpValidImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isNull(); + assertValidation(null, code); } private static Stream testRjumpValidImmediateArguments() { @@ -103,8 +153,7 @@ class CodeV1Test { @ParameterizedTest @MethodSource("testRjumpiValidImmediateArguments") void testRjumpiValidImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isNull(); + assertValidation(null, code); } private static Stream testRjumpiValidImmediateArguments() { @@ -125,28 +174,26 @@ class CodeV1Test { @ParameterizedTest @MethodSource("rjumptableValidImmediateArguments") void testRjumptableValidImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isNull(); + assertValidation(null, code); } private static Stream rjumptableValidImmediateArguments() { return Stream.of( - "6001e201000000", - "6001e202000000010000", - "6001e203000000040100" + "5b".repeat(256) + ZERO_HEX, - "6001e2040000000401007fff" + "5b".repeat(32767) + ZERO_HEX, - "6001e201fffc0000", - "5b".repeat(248) + "6001e202fffaff0000", - "5b".repeat(32760) + "6001e202fffa800000", - "e201000000") + "6001e200000000", + "6001e201000000010000", + "6001e202000600080100" + "5b".repeat(256) + ZERO_HEX, + "6001e2030008000801007ffe" + "5b".repeat(32767) + ZERO_HEX, + "6001e200fffc0000", + "5b".repeat(252) + "6001e201fffaff0000", + "5b".repeat(32764) + "6001e201fffa800000", + "e200000000") .map(Arguments::arguments); } @ParameterizedTest @MethodSource("invalidCodeArguments") void testInvalidCode(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).startsWith("Invalid Instruction 0x"); + assertValidation("Invalid Instruction 0x", code); } private static Stream invalidCodeArguments() { @@ -154,13 +201,12 @@ class CodeV1Test { IntStream.rangeClosed(0x0c, 0x0f), IntStream.of(0x1e, 0x1f), IntStream.rangeClosed(0x21, 0x2f), - IntStream.rangeClosed(0x49, 0x4f), + IntStream.rangeClosed(0x4b, 0x4f), IntStream.rangeClosed(0xa5, 0xaf), - IntStream.rangeClosed(0xb0, 0xbf), - IntStream.rangeClosed(0xc0, 0xcf), - IntStream.rangeClosed(0xd0, 0xdf), - IntStream.rangeClosed(0xe5, 0xef), - IntStream.of(0xf6, 0xf7, 0xf8, 0xf9, 0xfb, 0xfc)) + IntStream.rangeClosed(0xb0, 0xcf), + IntStream.rangeClosed(0xd4, 0xdf), + IntStream.rangeClosed(0xe9, 0xeb), + IntStream.of(0xef, 0xf6, 0xfc)) .flatMapToInt(i -> i) .mapToObj(i -> String.format("%02x", i) + ZERO_HEX) .map(Arguments::arguments); @@ -169,8 +215,7 @@ class CodeV1Test { @ParameterizedTest @MethodSource("pushTruncatedImmediateArguments") void testPushTruncatedImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("No terminating instruction"); + assertValidation("No terminating instruction", code); } private static Stream pushTruncatedImmediateArguments() { @@ -184,15 +229,13 @@ class CodeV1Test { @ParameterizedTest @ValueSource(strings = {"e0", "e000"}) void testRjumpTruncatedImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("Truncated relative jump offset"); + assertValidation("Truncated relative jump offset", code); } @ParameterizedTest @ValueSource(strings = {"6001e1", "6001e100"}) void testRjumpiTruncatedImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("Truncated relative jump offset"); + assertValidation("Truncated relative jump offset", code); } @ParameterizedTest @@ -206,8 +249,7 @@ class CodeV1Test { "6001e2030000000100" }) void testRjumpvTruncatedImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("Truncated jump table"); + assertValidation("Truncated jump table", code); } @ParameterizedTest @@ -219,12 +261,11 @@ class CodeV1Test { "6001e10000", "6001e1000100", "6001e1fffa00", - "6001e201000100", - "6001e201fff900" + "6001e200000300", + "6001e200fff900" }) void testRjumpsOutOfBounds(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("Relative jump destination out of bounds"); + assertValidation("Relative jump destination out of bounds", code); } @ParameterizedTest @@ -240,44 +281,44 @@ class CodeV1Test { "6001e10001e0000000", "6001e10002e0000000", // RJUMPV into RJUMP immediate - "6001e2010001e0000000", - "6001e2010002e0000000", + "6001e2000001e0000000", + "6001e2000002e0000000", // RJUMP into RJUMPI immediate "e000036001e1000000", "e000046001e1000000", + // RJUMPI backwards into push + "6001e1fffc00", // RJUMPI into RJUMPI immediate "6001e1ffff00", "6001e1fffe00", "6001e100036001e1000000", "6001e100046001e1000000", // RJUMPV into RJUMPI immediate - "6001e20100036001e1000000", - "6001e20100046001e1000000", + "6001e20000036001e1000000", + "6001e20000046001e1000000", // RJUMP into RJUMPV immediate - "e00001e201000000", - "e00002e201000000", - "e00003e201000000", + "e00001e200000000", + "e00002e200000000", + "e00003e200000000", // RJUMPI into RJUMPV immediate - "6001e10001e201000000", - "6001e10002e201000000", - "6001e10003e201000000", + "6001e10001e200000000", + "6001e10002e200000000", + "6001e10003e200000000", // RJUMPV into RJUMPV immediate - "6001e201ffff00", - "6001e201fffe00", - "6001e201fffd00", - "6001e2010001e201000000", - "6001e2010002e201000000", - "6001e2010003e201000000", - "6001e2010001e2020000fff400", - "6001e2010002e2020000fff400", - "6001e2010003e2020000fff400", - "6001e2010004e2020000fff400", - "6001e2010005e2020000fff400" + "6001e200ffff00", + "6001e200fffe00", + "6001e200fffd00", + "6001e2000001e200000000", + "6001e2000002e200000000", + "6001e2000003e200000000", + "6001e2000001e2010000000000", + "6001e2000002e2010000000000", + "6001e2000003e2010000000000", + "6001e2000004e2010000000000", + "6001e2000005e2010000000000" }) void testRjumpsIntoImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError) - .isEqualTo("Relative jump destinations targets invalid immediate data"); + assertValidation("Relative jump destinations targets invalid immediate data", code); } private static Stream rjumpsIntoImmediateExtraArguments() { @@ -302,7 +343,7 @@ class CodeV1Test { ZERO_HEX.repeat(n) + // push data ZERO_HEX, // STOP - String.format("6001e20100%02x", offset) + String.format("6001e20000%02x", offset) + String.format("%02x", 0x60 + n - 1) + // PUSHn ZERO_HEX.repeat(n) @@ -314,63 +355,52 @@ class CodeV1Test { .map(Arguments::arguments); } - @ParameterizedTest - @ValueSource(strings = {"6001e20000"}) - void testRjumpvEmptyTable(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("Empty jump table"); - } - @ParameterizedTest @ValueSource(strings = {"e3", "e300"}) void testCallFTruncated(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("Truncated CALLF"); + assertValidation("Truncated CALLF", code); } @ParameterizedTest @ValueSource(strings = {"e5", "e500"}) - @Disabled("Out of Shahghai, will likely return in Cancun or Prague") void testJumpCallFTruncated(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("Truncated CALLF"); + assertValidation("Truncated JUMPF", code); } @ParameterizedTest @ValueSource(strings = {"e30004", "e303ff", "e3ffff"}) void testCallFWrongSection(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 3); - assertThat(validationError).startsWith("CALLF to non-existent section -"); + assertValidation("CALLF to non-existent section -", code, false, 3); } @ParameterizedTest @ValueSource(strings = {"e50004", "e503ff", "e5ffff"}) - @Disabled("Out of Shahghai, will likely return in Cancun or Prague") void testJumpFWrongSection(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 3); - assertThat(validationError).startsWith("CALLF to non-existent section -"); + assertValidation("JUMPF to non-existent section -", code, false, 3); } @ParameterizedTest - @ValueSource(strings = {"e3000100", "e3000200", "e3000000"}) + @ValueSource(strings = {"e3000100", "e3000200"}) void testCallFValid(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 3); - assertThat(validationError).isNull(); + var testContainer = + EOFLayout.parseEOF( + Bytes.fromHexString( + "ef000101000c0200030001000100010400000000800000000000000000000000e4e4")); + + assertValidation( + null, Bytes.fromHexString(code), testContainer.getCodeSection(0), testContainer); } @ParameterizedTest @ValueSource(strings = {"e50001", "e50002", "e50000"}) - @Disabled("Out of Shahghai, will likely return in Cancun or Prague") void testJumpFValid(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 3); - assertThat(validationError).isNull(); + assertValidation(null, code, false, 3); } @ParameterizedTest @MethodSource("immediateContainsOpcodeArguments") void testImmediateContainsOpcode(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isNull(); + assertValidation(null, code); } private static Stream immediateContainsOpcodeArguments() { @@ -394,7 +424,7 @@ class CodeV1Test { // 0x60 byte which could be interpreted as PUSH, but it's not because it's in RJUMPV // data // offset = -160 - "5b".repeat(160) + "e201ff6000") + "5b".repeat(160) + "e200ff6000") .map(Arguments::arguments); } @@ -439,14 +469,15 @@ class CodeV1Test { + String.format("01%04x", sectionCount * 4) + String.format("02%04x", sectionCount) + codeLengths - + "030000" + + "040000" + "00" + typesData + codeData; EOFLayout eofLayout = EOFLayout.parseEOF(Bytes.fromHexString(sb)); - assertThat(validateStack(sectionToTest, eofLayout)).isEqualTo(expectedError); + assertThat(validateStack(sectionToTest, eofLayout, new WorkList(sectionCount))) + .isEqualTo(expectedError); } /** @@ -457,85 +488,86 @@ class CodeV1Test { * @return parameterized test vectors */ static Stream stackEmpty() { - return Stream.of(Arguments.of("Empty", null, 0, List.of(List.of("00", 0, 0, 0)))); + return Stream.of(Arguments.of("Empty", null, 0, List.of(List.of("00", 0, 0x80, 0)))); } static Stream stackEmptyAtExit() { return Stream.of( // this depends on requiring stacks to be "clean" returns - Arguments.of("Stack Empty at Exit", null, 0, List.of(List.of("43 50 00", 0, 0, 1))), + Arguments.of("Stack Empty at Exit", null, 0, List.of(List.of("43 50 00", 0, 0x80, 1))), Arguments.of( "Stack empty with input", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("50 00", 1, 0, 1))), + List.of(List.of("00", 0, 0x80, 0), List.of("50 00", 1, 0x80, 1))), // this depends on requiring stacks to be "clean" returns Arguments.of( "Stack not empty at output", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("00", 1, 0, 1)))); + List.of(List.of("00", 0, 0x80, 0), List.of("00", 1, 0x80, 1)))); } static Stream stackImmediateBytes() { return Stream.of( Arguments.of( - "Immediate Bytes - simple push", null, 0, List.of(List.of("6001 50 00", 0, 0, 1)))); + "Immediate Bytes - simple push", null, 0, List.of(List.of("6001 50 00", 0, 0x80, 1)))); } static Stream stackUnderflow() { return Stream.of( Arguments.of( "Stack underflow", - "Operation 0x50 requires stack of 1 but only has 0 items", + "Operation 0x50 requires stack of 1 but may only have 0 items", 0, - List.of(List.of("50 00", 0, 0, 1)))); + List.of(List.of("50 00", 0, 0x80, 1)))); } static Stream stackRJumpForward() { return Stream.of( - Arguments.of("RJUMP 0", null, 0, List.of(List.of("e00000 00", 0, 0, 0))), + Arguments.of("RJUMP 0", null, 0, List.of(List.of("e00000 00", 0, 0x80, 0))), Arguments.of( "RJUMP 1 w/ dead code", - "Dead code detected in section 0", + "Code that was not forward referenced in section 0x0 pc 3", 0, - List.of(List.of("e00001 43 00", 0, 0, 0))), + List.of(List.of("e00001 43 00", 0, 0x80, 0))), Arguments.of( "RJUMP 2 w/ dead code", - "Dead code detected in section 0", + "Code that was not forward referenced in section 0x0 pc 3", 0, - List.of(List.of("e00002 43 50 00", 0, 0, 0))), + List.of(List.of("e00002 43 50 00", 0, 0x80, 0))), Arguments.of( "RJUMP 3 and -10", - null, + "Code that was not forward referenced in section 0x0 pc 3", 0, - List.of(List.of("e00003 01 50 00 6001 6001 e0fff6", 0, 0, 2)))); + List.of(List.of("e00003 01 50 00 6001 6001 e0fff6", 0, 0x80, 2)))); } static Stream stackRJumpBackward() { return Stream.of( - Arguments.of("RJUMP -3", null, 0, List.of(List.of("e0fffd", 0, 0, 0))), - Arguments.of("RJUMP -4", null, 0, List.of(List.of("5B e0fffc", 0, 0, 0))), + Arguments.of("RJUMP -3", null, 0, List.of(List.of("e0fffd", 0, 0x80, 0))), + Arguments.of("RJUMP -4", null, 0, List.of(List.of("5B e0fffc", 0, 0x80, 0))), Arguments.of( "RJUMP -4 unmatched stack", - "Jump into code stack height (0) does not match previous value (1)", + "Stack minimum violation on backwards jump from 1 to 0, 1 != 1", 0, - List.of(List.of("43 e0fffc", 0, 0, 0))), + List.of(List.of("43 e0fffc", 0, 0x80, 0))), Arguments.of( "RJUMP -4 unmatched stack", - "Jump into code stack height (1) does not match previous value (0)", + "Stack minimum violation on backwards jump from 2 to 1, 0 != 0", 0, - List.of(List.of("43 50 e0fffc 00", 0, 0, 0))), - Arguments.of("RJUMP -3 matched stack", null, 0, List.of(List.of("43 50 e0fffd", 0, 0, 1))), + List.of(List.of("43 50 e0fffc 00", 0, 0x80, 0))), Arguments.of( - "RJUMP -4 matched stack", null, 0, List.of(List.of("43 50 5B e0fffc", 0, 0, 1))), + "RJUMP -3 matched stack", null, 0, List.of(List.of("43 50 e0fffd", 0, 0x80, 1))), Arguments.of( - "RJUMP -5 matched stack", null, 0, List.of(List.of("43 50 43 e0fffb", 0, 0, 1))), + "RJUMP -4 matched stack", null, 0, List.of(List.of("43 50 5B e0fffc", 0, 0x80, 1))), + Arguments.of( + "RJUMP -5 matched stack", null, 0, List.of(List.of("43 50 43 e0fffb", 0, 0x80, 1))), Arguments.of( "RJUMP -4 unmatched stack", - "Jump into code stack height (0) does not match previous value (1)", + "Stack minimum violation on backwards jump from 3 to 2, 1 != 1", 0, - List.of(List.of("43 50 43 e0fffc 50 00", 0, 0, 0)))); + List.of(List.of("43 50 43 e0fffc 50 00", 0, 0x80, 0)))); } static Stream stackRJumpI() { @@ -544,61 +576,61 @@ class CodeV1Test { "RJUMPI Each branch ending with STOP", null, 0, - List.of(List.of("60ff 6001 e10002 50 00 50 00", 0, 0, 2))), + List.of(List.of("60ff 6001 e10002 50 00 50 00", 0, 0x80, 2))), Arguments.of( "RJUMPI One branch ending with RJUMP", null, 0, - List.of(List.of("60ff 6001 e10004 50 e00001 50 00", 0, 0, 2))), + List.of(List.of("60ff 6001 e10004 50 e00001 50 00", 0, 0x80, 2))), Arguments.of( "RJUMPI Fallthrough", null, 0, - List.of(List.of("60ff 6001 e10004 80 80 50 50 50 00", 0, 0, 3))), + List.of(List.of("60ff 6001 e10004 80 80 50 50 50 00", 0, 0x80, 3))), Arguments.of( - "RJUMPI Offset 0", null, 0, List.of(List.of("60ff 6001 e10000 50 00", 0, 0, 2))), + "RJUMPI Offset 0", null, 0, List.of(List.of("60ff 6001 e10000 50 00", 0, 0x80, 2))), Arguments.of( "Simple loop (RJUMPI offset = -5)", null, 0, - List.of(List.of("6001 60ff 81 02 80 e1fffa 50 50 00", 0, 0, 3))), + List.of(List.of("6001 60ff 81 02 80 e1fffa 50 50 00", 0, 0x80, 3))), Arguments.of( "RJUMPI One branch increasing max stack more stack than another", null, 0, - List.of(List.of("6001 e10007 30 30 30 50 50 50 00 30 50 00", 0, 0, 3))), + List.of(List.of("6001 e10007 30 30 30 50 50 50 00 30 50 00", 0, 0x80, 3))), Arguments.of( "RJUMPI One branch increasing max stack more stack than another II", null, 0, - List.of(List.of("6001 e10003 30 50 00 30 30 30 50 50 50 00", 0, 0, 3))), + List.of(List.of("6001 e10003 30 50 00 30 30 30 50 50 50 00", 0, 0x80, 3))), Arguments.of( "RJUMPI Missing stack argument", - "Operation 0xE1 requires stack of 1 but only has 0 items", + "Operation 0xE1 requires stack of 1 but may only have 0 items", 0, - List.of(List.of("e10000 00", 0, 0, 0))), + List.of(List.of("e10000 00", 0, 0x80, 0))), Arguments.of( "Stack underflow one branch", - "Operation 0x02 requires stack of 2 but only has 1 items", + "Operation 0x02 requires stack of 2 but may only have 1 items", 0, - List.of(List.of("60ff 6001 e10002 50 00 02 50 00", 0, 0, 0))), + List.of(List.of("60ff 6001 e10002 50 00 02 50 00", 0, 0x80, 0))), Arguments.of( "Stack underflow another branch", - "Operation 0x02 requires stack of 2 but only has 1 items", + "Operation 0x02 requires stack of 2 but may only have 1 items", 0, - List.of(List.of("60ff 6001 e10002 02 00 19 50 00", 0, 0, 0))), + List.of(List.of("60ff 6001 e10002 02 00 19 50 00", 0, 0x80, 0))), // this depends on requiring stacks to be "clean" returns Arguments.of( "RJUMPI Stack not empty in the end of one branch", null, 0, - List.of(List.of("60ff 6001 e10002 50 00 19 00", 0, 0, 2))), + List.of(List.of("60ff 6001 e10002 50 00 19 00", 0, 0x80, 2))), // this depends on requiring stacks to be "clean" returns Arguments.of( "RJUMPI Stack not empty in the end of one branch II", null, 0, - List.of(List.of("60ff 6001 e10002 19 00 50 00", 0, 0, 2)))); + List.of(List.of("60ff 6001 e10002 19 00 50 00", 0, 0x80, 2)))); } static Stream stackCallF() { @@ -607,225 +639,231 @@ class CodeV1Test { "0 input 0 output", null, 0, - List.of(List.of("e30001 00", 0, 0, 0), List.of("e4", 0, 0, 0))), + List.of(List.of("e30001 00", 0, 0x80, 0), List.of("e4", 0, 0, 0))), Arguments.of( "0 inputs, 0 output 3 sections", null, 0, - List.of(List.of("e30002 00", 0, 0, 0), List.of("e4", 1, 1, 1), List.of("e4", 0, 0, 0))), + List.of( + List.of("e30002 00", 0, 0x80, 0), List.of("e4", 1, 1, 1), List.of("e4", 0, 0, 0))), Arguments.of( "more than 0 inputs", null, 0, - List.of(List.of("30 e30001 00", 0, 0, 1), List.of("00", 1, 0, 1))), + List.of(List.of("30 e30001 00", 0, 0x80, 1), List.of("00", 1, 0x80, 1))), Arguments.of( "forwarding an argument", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("e30002 00", 1, 0, 1), List.of("00", 1, 0, 1))), + List.of( + List.of("00", 0, 0x80, 0), + List.of("e30002 00", 1, 0x80, 1), + List.of("00", 1, 0x80, 1))), Arguments.of( "more than 1 inputs", null, 0, - List.of(List.of("30 80 e30001 00", 0, 0, 2), List.of("00", 2, 0, 2))), + List.of(List.of("30 80 e30001 00", 0, 0x80, 2), List.of("00", 2, 0x80, 2))), Arguments.of( "more than 0 outputs", null, 0, - List.of(List.of("e30001 50 00", 0, 0, 1), List.of("3000", 0, 1, 1))), + List.of(List.of("e30001 50 00", 0, 0x80, 1), List.of("30e4", 0, 1, 1))), Arguments.of( "more than 0 outputs 3 sections", null, 0, List.of( - List.of("e30002 50 00", 0, 0, 1), - List.of("00", 0, 0, 0), + List.of("e30002 50 00", 0, 0x80, 1), + List.of("00", 0, 0x80, 0), List.of("30305000", 0, 1, 2))), Arguments.of( "more than 1 outputs", null, 0, - List.of(List.of("e30001 50 50 00", 0, 0, 2), List.of("303000", 0, 2, 2))), + List.of(List.of("e30001 50 50 00", 0, 0x80, 2), List.of("3030e4", 0, 2, 2))), Arguments.of( "more than 0 inputs, more than 0 outputs", null, 0, List.of( - List.of("30 30 e30001 50 50 50 00", 0, 0, 3), - List.of("30 30 e30001 50 50 00", 2, 3, 5))), - Arguments.of("recursion", null, 0, List.of(List.of("e30000 00", 0, 0, 0))), + List.of("30 30 e30001 50 50 50 00", 0, 0x80, 3), + List.of("30 30 e30001 50 50 e4", 2, 3, 5))), + Arguments.of("recursion", null, 0, List.of(List.of("e30000 00", 0, 0x80, 0))), Arguments.of( "recursion 2 inputs", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("e30000 00", 2, 0, 2))), + List.of(List.of("00", 0, 0x80, 0), List.of("e30000 00", 2, 0x80, 2))), Arguments.of( "recursion 2 inputs 2 outputs", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("e30000 50 50 00", 2, 2, 2))), + List.of(List.of("00", 0, 0x80, 0), List.of("e30000 50 50 00", 2, 2, 2))), Arguments.of( "recursion 2 inputs 1 output", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("30 30 e30001 50 50 50 00", 2, 1, 4))), + List.of(List.of("00", 0, 0x80, 0), List.of("30 30 e30001 50 50 50 00", 2, 1, 4))), Arguments.of( "multiple CALLFs with different types", null, 1, List.of( - List.of("00", 0, 0, 0), - List.of("44 e30002 80 80 e30003 44 80 e30004 50 50 00", 0, 0, 3), - List.of("3030505000", 1, 1, 3), - List.of("50505000", 3, 0, 3), - List.of("00", 2, 2, 2))), + List.of("00", 0, 0x80, 0), + List.of("44 e30002 80 80 e30003 44 80 e30004 50 50 e4", 0, 0, 3), + List.of("30305050e4", 1, 1, 3), + List.of("505050e4", 3, 0, 3), + List.of("e4", 2, 2, 2))), Arguments.of( "underflow", - "Operation 0xE3 requires stack of 1 but only has 0 items", + "Operation 0xE3 requires stack of 1 but may only have 0 items", 0, - List.of(List.of("e30001 00", 0, 0, 0), List.of("00", 1, 0, 0))), + List.of(List.of("e30001 00", 0, 0x80, 0), List.of("e4", 1, 0, 0))), Arguments.of( "underflow 2", - "Operation 0xE3 requires stack of 2 but only has 1 items", + "Operation 0xE3 requires stack of 2 but may only have 1 items", 0, - List.of(List.of("30 e30001 00", 0, 0, 0), List.of("00", 2, 0, 2))), + List.of(List.of("30 e30001 00", 0, 0x80, 0), List.of("e4", 2, 0, 2))), Arguments.of( "underflow 3", - "Operation 0xE3 requires stack of 1 but only has 0 items", + "Operation 0xE3 requires stack of 1 but may only have 0 items", 1, - List.of(List.of("00", 0, 0, 0), List.of("50 e30001 00", 1, 0, 1))), + List.of(List.of("00", 0, 0x80, 0), List.of("50 e30001 e4", 1, 0, 1))), Arguments.of( "underflow 4", - "Operation 0xE3 requires stack of 3 but only has 2 items", + "Operation 0xE3 requires stack of 3 but may only have 2 items", 0, List.of( - List.of("44 e30001 80 e30002 00", 0, 0, 0), - List.of("00", 1, 1, 1), - List.of("00", 3, 0, 3)))); + List.of("44 e30001 80 e30002 00", 0, 0x80, 0), + List.of("e4", 1, 1, 1), + List.of("e4", 3, 0, 3)))); } static Stream stackRetF() { return Stream.of( Arguments.of( "0 outputs at section 0", - null, + "EOF Layout invalid - Code section at zero expected non-returning flag, but had return stack of 0", 0, - List.of(List.of("e4", 0, 0, 0), List.of("00", 0, 0, 0))), + List.of(List.of("e4", 0, 0, 0), List.of("e4", 0, 0, 0))), Arguments.of( "0 outputs at section 1", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("e4", 0, 0, 0))), + List.of(List.of("00", 0, 0x80, 0), List.of("e4", 0, 0, 0))), Arguments.of( "0 outputs at section 2", null, 2, - List.of(List.of("00", 0, 0, 0), List.of("00", 1, 1, 1), List.of("e4", 0, 0, 0))), + List.of(List.of("00", 0, 0x80, 0), List.of("e4", 1, 1, 1), List.of("e4", 0, 0, 0))), Arguments.of( "more than 0 outputs section 0", - null, + "EOF Layout invalid - Code section at zero expected non-returning flag, but had return stack of 0", 0, List.of(List.of("44 50 e4", 0, 0, 1), List.of("4400", 0, 1, 1))), Arguments.of( "more than 0 outputs section 0", - null, + "EOF Layout invalid - Code section at zero expected non-returning flag, but had return stack of 0", 1, List.of(List.of("00", 0, 0, 0), List.of("44 e4", 0, 1, 1))), Arguments.of( "more than 1 outputs section 1", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("44 80 e4", 0, 2, 2))), + List.of(List.of("00", 0, 0x80, 0), List.of("44 80 e4", 0, 2, 2))), Arguments.of( "Forwarding return values", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("e4", 1, 1, 1))), + List.of(List.of("00", 0, 0x80, 0), List.of("e4", 1, 1, 1))), Arguments.of( "Forwarding of return values 2", null, 1, List.of( - List.of("00", 0, 0, 0), List.of("e30002 e4", 0, 1, 1), List.of("3000", 0, 1, 1))), + List.of("00", 0, 0x80, 0), + List.of("e30002 e4", 0, 1, 1), + List.of("30e4", 0, 1, 1))), Arguments.of( "Multiple RETFs", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("e10003 44 80 e4 30 80 e4", 1, 2, 2))), + List.of(List.of("00", 0, 0x80, 0), List.of("e10003 44 80 e4 30 80 e4", 1, 2, 2))), Arguments.of( "underflow 1", - "Section return (RETF) calculated height 0x0 does not match configured height 0x1", + "RETF in section 1 calculated height 0 does not match configured return stack 1, min height 0, and max height 0", 1, - List.of(List.of("00", 0, 0, 0), List.of("e4", 0, 1, 0))), + List.of(List.of("00", 0, 0x80, 0), List.of("e4", 0, 1, 0))), Arguments.of( "underflow 2", - "Section return (RETF) calculated height 0x1 does not match configured height 0x2", + "RETF in section 1 calculated height 1 does not match configured return stack 2, min height 1, and max height 1", 1, - List.of(List.of("00", 0, 0, 0), List.of("44 e4", 0, 2, 1))), + List.of(List.of("00", 0, 0x80, 0), List.of("44 e4", 0, 2, 1))), Arguments.of( "underflow 3", - "Section return (RETF) calculated height 0x1 does not match configured height 0x2", + "RETF in section 1 calculated height 1 does not match configured return stack 2, min height 1, and max height 1", 1, - List.of(List.of("00", 0, 0, 0), List.of("e10003 44 80 e4 30 e4", 1, 2, 2)))); + List.of(List.of("00", 0, 0x80, 0), List.of("e10003 44 80 e4 30 e4", 1, 2, 2)))); } static Stream stackUnreachable() { return Stream.of( Arguments.of( "Max stack not changed by unreachable code", - "Dead code detected in section 0", + "Code that was not forward referenced in section 0x0 pc 3", 0, - List.of(List.of("30 50 00 30 30 30 50 50 50 00", 0, 0, 1))), + List.of(List.of("30 50 00 30 30 30 50 50 50 00", 0, 0x80, 1))), Arguments.of( "Max stack not changed by unreachable code RETf", - "Dead code detected in section 0", + "Code that was not forward referenced in section 0x0 pc 3", 0, - List.of(List.of("30 50 e4 30 30 30 50 50 50 00", 0, 0, 1))), + List.of(List.of("30 50 e4 30 30 30 50 50 50 00", 0, 0x80, 1))), Arguments.of( "Max stack not changed by unreachable code RJUMP", - "Dead code detected in section 0", + "Code that was not forward referenced in section 0x0 pc 5", 0, - List.of(List.of("30 50 e00006 30 30 30 50 50 50 00", 0, 0, 1))), + List.of(List.of("30 50 e00006 30 30 30 50 50 50 00", 0, 0x80, 1))), Arguments.of( "Stack underflow in unreachable code", - "Dead code detected in section 0", + "Code that was not forward referenced in section 0x0 pc 3", 0, - List.of(List.of("30 50 00 50 00", 0, 0, 1))), + List.of(List.of("30 50 00 50 00", 0, 0x80, 1))), Arguments.of( "Stack underflow in unreachable code RETF", - "Dead code detected in section 0", + "Code that was not forward referenced in section 0x0 pc 3", 0, - List.of(List.of("30 50 e4 50 00", 0, 0, 1))), + List.of(List.of("30 50 e4 50 00", 0, 0x80, 1))), Arguments.of( "Stack underflow in unreachable code RJUMP", - "Dead code detected in section 0", + "Code that was not forward referenced in section 0x0 pc 5", 0, - List.of(List.of("30 50 e00001 50 00", 0, 0, 1)))); + List.of(List.of("30 50 e00001 50 00", 0, 0x80, 1)))); } static Stream stackHeight() { return Stream.of( Arguments.of( "Stack height mismatch backwards", - "Jump into code stack height (0) does not match previous value (1)", + "Stack minimum violation on backwards jump from 1 to 0, 1 != 1", 0, - List.of(List.of("30 e0fffc00", 0, 0, 1))), + List.of(List.of("30 e0fffc00", 0, 0x80, 1))), Arguments.of( "Stack height mismatch forwards", - "Jump into code stack height (3) does not match previous value (0)", + "Calculated max stack height (5) does not match reported stack height (2)", 0, - List.of(List.of("30e10003303030303000", 0, 0, 2)))); + List.of(List.of("30e10003303030303000", 0, 0x80, 2)))); } static Stream invalidInstructions() { return IntStream.range(0, 256) - .filter(opcode -> CodeV1Validation.OPCODE_ATTRIBUTES[opcode] == CodeV1Validation.INVALID) + .filter(opcode -> !OpcodeInfo.V1_OPCODES[opcode].valid()) .mapToObj( opcode -> Arguments.of( String.format("Invalid opcode %02x", opcode), String.format("Invalid Instruction 0x%02x", opcode), 0, - List.of(List.of(String.format("0x%02x", opcode), 0, 0, 0)))); + List.of(List.of(String.format("0x%02x", opcode), 0, 0x80, 0)))); } } 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 d25d1bcdf8..94f1d9598e 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 @@ -72,109 +72,109 @@ public class EOFLayoutTest { "Invalid Code section size for section 1", 1 }, - {"EF0001 010004 0200010001 03", "No data section size", "Invalid Data section size", 1}, + {"EF0001 010004 0200010001 04", "No data section size", "Invalid Data section size", 1}, { - "EF0001 010004 0200010001 0300", + "EF0001 010004 0200010001 0400", "Short data section size", "Invalid Data section size", 1 }, - {"EF0001 010004 0200010001 030000", "No Terminator", "Improper section headers", 1}, - {"EF0001 010004 0200010002 030000 00", "No type section", "Incomplete type section", 1}, + {"EF0001 010004 0200010001 040000", "No Terminator", "Improper section headers", 1}, + {"EF0001 010004 0200010002 040000 00", "No type section", "Incomplete type section", 1}, { - "EF0001 010004 0200010002 030001 030001 00 DA DA", + "EF0001 010004 0200010002 040001 040001 00 DA DA", "Duplicate data sections", - "Expected kind 0 but read kind 3", + "Expected kind 0 but read kind 4", 1 }, { - "EF0001 010004 0200010002 030000 00 00", + "EF0001 010004 0200010002 040000 00 00", "Incomplete type section", "Incomplete type section", 1 }, { - "EF0001 010008 02000200020002 030000 00 00000000FE", + "EF0001 010008 02000200020002 040000 00 00000000FE", "Incomplete type section", "Incomplete type section", 1 }, { - "EF0001 010008 0200010001 030000 00 00000000 FE ", + "EF0001 010008 0200010001 040000 00 00000000 FE ", "Incorrect type section size", "Type section length incompatible with code section count - 0x1 * 4 != 0x8", 1 }, { - "EF0001 010008 02000200010001 030000 00 0100000000000000 FE FE", + "EF0001 010008 02000200010001 040000 00 0100000000000000 FE FE", "Incorrect section zero type input", "Code section does not have zero inputs and outputs", 1 }, { - "EF0001 010008 02000200010001 030000 00 0001000000000000 FE FE", + "EF0001 010008 02000200010001 040000 00 0001000000000000 FE FE", "Incorrect section zero type output", "Code section does not have zero inputs and outputs", 1 }, { - "EF0001 010004 0200010002 030000 00 00000000 ", + "EF0001 010004 0200010002 040000 00 00000000 ", "Incomplete code section", "Incomplete code section 0", 1 }, { - "EF0001 010004 0200010002 030000 00 00000000 FE", + "EF0001 010004 0200010002 040000 00 00000000 FE", "Incomplete code section", "Incomplete code section 0", 1 }, { - "EF0001 010008 02000200020002 030000 00 00000000 00000000 FEFE ", + "EF0001 010008 02000200020002 040000 00 00800000 00000000 FEFE ", "No code section multiple", "Incomplete code section 1", 1 }, { - "EF0001 010008 02000200020002 030000 00 00000000 00000000 FEFE FE", + "EF0001 010008 02000200020002 040000 00 00800000 00000000 FEFE FE", "Incomplete code section multiple", "Incomplete code section 1", 1 }, { - "EF0001 010004 0200010001 030003 00 00000000 FE DEADBEEF", + "EF0001 010004 0200010001 040003 00 00800000 FE DEADBEEF", "Incomplete data section", "Dangling data after end of all sections", 1 }, { - "EF0001 010004 0200010001 030003 00 00000000 FE BEEF", + "EF0001 010004 0200010001 040003 00 00800000 FE BEEF", "Incomplete data section", "Incomplete data section", 1 }, { - "EF0001 0200010001 030001 00 FE DA", + "EF0001 0200010001 040001 00 FE DA", "type section missing", "Expected kind 1 but read kind 2", 1 }, { - "EF0001 010004 030001 00 00000000 DA", + "EF0001 010004 040001 00 00000000 DA", "code section missing", - "Expected kind 2 but read kind 3", + "Expected kind 2 but read kind 4", 1 }, { "EF0001 010004 0200010001 00 00000000 FE", "data section missing", - "Expected kind 3 but read kind 0", + "Expected kind 4 but read kind 0", 1 }, { - "EF0001 030001 00 DA", + "EF0001 040001 00 DA", "type and code section missing", - "Expected kind 1 but read kind 3", + "Expected kind 1 but read kind 4", 1 }, { @@ -193,7 +193,7 @@ public class EOFLayoutTest { { "EF0001 011004 020401" + " 0001".repeat(1025) - + " 030000 00" + + " 040000 00" + " 00000000".repeat(1025) + " FE".repeat(1025), "no data section, 1025 code sections", @@ -216,31 +216,13 @@ public class EOFLayoutTest { return Arrays.asList( new Object[][] { { - "EF0001 010004 0200010001 030000 00 00000000 FE", - "no data section, one code section", + "0xef0001 010004 0200010010 040000 00 00800002 e00001 f3 6001 6000 53 6001 6000 e0fff3 ", + "1", null, 1 }, { - "EF0001 010004 0200010001 030001 00 00000000 FE DA", - "with data section, one code section", - null, - 1 - }, - { - "EF0001 010008 02000200010001 030000 00 00000000 00000000 FE FE", - "no data section, multiple code section", - null, - 1 - }, - { - "EF0001 010008 02000200010001 030001 00 00000000 00000000 FE FE DA", - "with data section, multiple code section", - null, - 1 - }, - { - "EF0001 010010 0200040001000200020002 030000 00 00000000 01000001 00010001 02030003 FE 5000 3000 8000", + "EF0001 010010 0200040001000200020002 040000 00 00800000 01000001 00010001 02030003 FE 5000 3000 8000", "non-void input and output types", null, 1 @@ -248,8 +230,8 @@ public class EOFLayoutTest { { "EF0001 011000 020400" + " 0001".repeat(1024) - + " 030000 00" - + " 00000000".repeat(1024) + + " 040000 00" + + " 00800000".repeat(1024) + " FE".repeat(1024), "no data section, 1024 code sections", null, @@ -262,37 +244,37 @@ public class EOFLayoutTest { return Arrays.asList( new Object[][] { { - "EF0001 010008 02000200020002 030000 00 0100000000000000", + "EF0001 010008 02000200020002 040000 00 0100000000000000", "Incorrect section zero type input", "Code section does not have zero inputs and outputs", 1 }, { - "EF0001 010008 02000200020002 030000 00 0001000000000000", + "EF0001 010008 02000200020002 040000 00 0001000000000000", "Incorrect section zero type output", "Code section does not have zero inputs and outputs", 1 }, { - "EF0001 010010 0200040001000200020002 030000 00 00000000 F0000000 00010000 02030000 FE 5000 3000 8000", + "EF0001 010010 0200040001000200020002 040000 00 00800000 F0000000 00010000 02030000 FE 5000 3000 8000", "inputs too large", "Type data input stack too large - 0xf0", 1 }, { - "EF0001 010010 0200040001000200020002 030000 00 00000000 01000000 00F00000 02030000 FE 5000 3000 8000", + "EF0001 010010 0200040001000200020002 040000 00 00800000 01000000 00F00000 02030000 FE 5000 3000 8000", "outputs too large", "Type data output stack too large - 0xf0", 1 }, { - "EF0001 010010 0200040001000200020002 030000 00 00000400 01000000 00010000 02030400 FE 5000 3000 8000", + "EF0001 010010 0200040001000200020002 040000 00 00000400 01000000 00010000 02030400 FE 5000 3000 8000", "stack too large", "Type data max stack too large - 0x400", 1 }, { - "EF0001 010010 0200040001000200020002 030000 00 00000000 01000001 00010001 02030003 FE 5000 3000 8000", + "EF0001 010010 0200040001000200020002 040000 00 00800000 01000001 00010001 02030003 FE 5000 3000 8000", "non-void input and output types", null, 1 @@ -300,20 +282,84 @@ public class EOFLayoutTest { }); } + public static Collection subContainers() { + return Arrays.asList( + new Object[][] { + { + "EF0001 010004 0200010001 0300010014 040000 00 00800000 FE EF000101000402000100010400000000800000FE", + "no data section, one code section, one subcontainer", + null, + 1 + }, + { + "EF00 01 010004 0200010001 0300010014 040000 00 00800000 00 (EF0001 010004 0200010001 040000 00 00800000 00)", + "evmone - one container", + null, + 1 + }, + { + "EF00 01 010004 0200010001 0300010014 040003 00 00800000 00 (EF0001 010004 0200010001 040000 00 00800000 00) 000000", + "evmone - one container two bytes", + null, + 1 + }, + { + "EF00 01 010004 0200010001 030003001400140014 040000 00 00800000 00 (EF0001 010004 0200010001 040000 00 00800000 00)(EF0001 010004 0200010001 040000 00 00800000 00)(EF0001 010004 0200010001 040000 00 00800000 00)", + "evmone - three subContainers", + null, + 1 + }, + { + "EF00 01 010004 0200010001 030003001400140014 040003 00 00800000 00 (EF0001 010004 0200010001 040000 00 00800000 00)(EF0001 010004 0200010001 040000 00 00800000 00)(EF0001 010004 0200010001 040000 00 00800000 00) ddeeff", + "evmone - three subContainers and data", + null, + 1 + }, + { + "EF00 01 01000C 020003000100010001 030003001400140014 040003 00 008000000000000000000000 00 00 00 (EF0001 010004 0200010001 040000 00 00800000 00)(EF0001 010004 0200010001 040000 00 00800000 00)(EF0001 010004 0200010001 040000 00 00800000 00) ddeeff", + "evmone - three subContainers three code and data", + null, + 1 + }, + { + "EF00 01 010004 0200010001 0300010100 040000 00 00800000 00 (EF0001 010004 02000100ED 040000 00 00800000 " + + "5d".repeat(237) + + ")", + "evmone - 256 byte container", + null, + 1 + }, + { + "EF00 01 010004 0200010001 030100" + + "0014".repeat(256) + + "040000 00 00800000 00 " + + "(EF0001 010004 0200010001 040000 00 00800000 00)".repeat(256), + "evmone - 256 subContainers", + null, + 1 + }, + }); + } + @ParameterizedTest(name = "{1}") - @MethodSource({"correctContainers", "containersWithFormatErrors", "typeSectionTests"}) + @MethodSource({ + // "correctContainers", + // "containersWithFormatErrors", + // "typeSectionTests", + "subContainers" + }) void test( final String containerString, final String description, final String failureReason, final int expectedVersion) { - final Bytes container = Bytes.fromHexString(containerString.replace(" ", "")); + final Bytes container = Bytes.fromHexString(containerString.replaceAll("[^a-fxA-F0-9]", "")); final EOFLayout layout = EOFLayout.parseEOF(container); - assertThat(layout.getVersion()).isEqualTo(expectedVersion); - assertThat(layout.getInvalidReason()).isEqualTo(failureReason); - assertThat(layout.getContainer()).isEqualTo(container); - if (layout.getInvalidReason() != null) { + assertThat(layout.version()).isEqualTo(expectedVersion); + assertThat(layout.invalidReason()).isEqualTo(failureReason); + assertThat(layout.container()).isEqualTo(container); + if (layout.invalidReason() != null) { assertThat(layout.isValid()).isFalse(); assertThat(layout.getCodeSectionCount()).isZero(); } else { diff --git a/evm/src/test/java/org/hyperledger/besu/evm/fluent/EVMExecutorTest.java b/evm/src/test/java/org/hyperledger/besu/evm/fluent/EVMExecutorTest.java index 97a2a4b836..871d99f768 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/fluent/EVMExecutorTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/fluent/EVMExecutorTest.java @@ -128,7 +128,7 @@ class EVMExecutorTest { assertThat(cancunEVM.getChainId()).contains(defaultChainId); EVMExecutor pragueEVM = - EVMExecutor.prague(defaultChainId.toBigInteger(), EvmConfiguration.DEFAULT); + EVMExecutor.pragueEOF(defaultChainId.toBigInteger(), EvmConfiguration.DEFAULT); assertThat(pragueEVM.getChainId()).contains(defaultChainId); EVMExecutor futureEipsVM = EVMExecutor.futureEips(EvmConfiguration.DEFAULT); @@ -141,7 +141,7 @@ class EVMExecutorTest { EVMExecutor.evm(EvmSpecVersion.SHANGHAI) .worldUpdater(createSimpleWorld().updater()) .execute( - CodeFactory.createCode(Bytes.fromHexString("0x6001600255"), 1, false), + CodeFactory.createCode(Bytes.fromHexString("0x6001600255"), 1), Bytes.EMPTY, Wei.ZERO, Address.ZERO); @@ -180,7 +180,7 @@ class EVMExecutorTest { .blobGasPrice(Wei.ONE) .callData(Bytes.fromHexString("0x12345678")) .ethValue(Wei.fromEth(1)) - .code(CodeFactory.createCode(Bytes.fromHexString("0x6001600255"), 0, false)) + .code(CodeFactory.createCode(Bytes.fromHexString("0x6001600255"), 0)) .blockValues(new SimpleBlockValues()) .difficulty(Bytes.ofUnsignedLong(1L)) .mixHash(Bytes32.ZERO) diff --git a/evm/src/test/java/org/hyperledger/besu/evm/gascalculator/PragueEOFGasCalculatorTest.java b/evm/src/test/java/org/hyperledger/besu/evm/gascalculator/PragueEOFGasCalculatorTest.java new file mode 100644 index 0000000000..46fe1dcba8 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/gascalculator/PragueEOFGasCalculatorTest.java @@ -0,0 +1,41 @@ +/* + * 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.gascalculator; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.datatypes.Address; + +import org.junit.jupiter.api.Test; + +class PragueEOFGasCalculatorTest { + + @Test + void testPrecompileSize() { + PragueEOFGasCalculator subject = new PragueEOFGasCalculator(); + assertThat(subject.isPrecompile(Address.precompiled(0x14))).isFalse(); + assertThat(subject.isPrecompile(Address.BLS12_MAP_FP2_TO_G2)).isTrue(); + } + + @Test + void testNewConstants() { + CancunGasCalculator cancunGas = new CancunGasCalculator(); + PragueEOFGasCalculator praugeGasCalculator = new PragueEOFGasCalculator(); + + assertThat(praugeGasCalculator.getMinCalleeGas()).isGreaterThan(cancunGas.getMinCalleeGas()); + assertThat(praugeGasCalculator.getMinRetainedGas()) + .isGreaterThan(cancunGas.getMinRetainedGas()); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/internal/CodeCacheTest.java b/evm/src/test/java/org/hyperledger/besu/evm/internal/CodeCacheTest.java index c18bef8889..94f616898a 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/internal/CodeCacheTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/internal/CodeCacheTest.java @@ -32,7 +32,7 @@ class CodeCacheTest { final Bytes contractBytes = Bytes.fromHexString("0xDEAD" + op + "BEEF" + op + "B0B0" + op + "C0DE" + op + "FACE"); final CodeScale scale = new CodeScale(); - final Code contractCode = CodeFactory.createCode(contractBytes, 0, false); + final Code contractCode = CodeFactory.createCode(contractBytes, 0); final int weight = scale.weigh(contractCode.getCodeHash(), contractCode); assertThat(weight) .isEqualTo(contractCode.getCodeHash().size() + (contractBytes.size() * 9 + 7) / 8); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/AbstractCreateOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/AbstractCreateOperationTest.java index 2ba4ea918f..79f07e11af 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/AbstractCreateOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/AbstractCreateOperationTest.java @@ -78,7 +78,7 @@ class AbstractCreateOperationTest { public static final Bytes INVALID_EOF = Bytes.fromHexString( "0x" - + "73EF99010100040200010001030000000000000000" // PUSH20 contract + + "73EF00990100040200010001030000000000000000" // PUSH20 contract + "6000" // PUSH1 0x00 + "52" // MSTORE + "6014" // PUSH1 20 @@ -104,7 +104,7 @@ class AbstractCreateOperationTest { * @param maxInitcodeSize Maximum init code size */ public FakeCreateOperation(final GasCalculator gasCalculator, final int maxInitcodeSize) { - super(0xEF, "FAKECREATE", 3, 1, gasCalculator, maxInitcodeSize); + super(0xEF, "FAKECREATE", 3, 1, gasCalculator, maxInitcodeSize, 0); } @Override @@ -166,7 +166,7 @@ class AbstractCreateOperationTest { .sender(Address.fromHexString(SENDER)) .value(Wei.ZERO) .apparentValue(Wei.ZERO) - .code(CodeFactory.createCode(SIMPLE_CREATE, 0, true)) + .code(CodeFactory.createCode(SIMPLE_CREATE, 0)) .completer(__ -> {}) .address(Address.fromHexString(SENDER)) .blockHashLookup(n -> Hash.hash(Words.longBytes(n))) 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 index cc896f010d..fe662893e1 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/CallFOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/CallFOperationTest.java @@ -15,12 +15,12 @@ package org.hyperledger.besu.evm.operations; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.testutils.OperationsTestUtils.mockCode; 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; @@ -36,9 +36,7 @@ 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.getBytes()).thenReturn(code); + final Code mockCode = mockCode("00" + "b0" + "0001" + "00"); final CodeSection codeSection = new CodeSection(0, 1, 2, 3, 0); when(mockCode.getCodeSection(1)).thenReturn(codeSection); @@ -59,98 +57,7 @@ class CallFOperationTest { 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.getBytes()).thenReturn(code); - - final CodeSection codeSection = new CodeSection(0, 1, 2, 3, 0); - 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.getBytes()).thenReturn(code); - - final CodeSection codeSection = new CodeSection(0, 1, 2, 1023, 0); - 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.getBytes()).thenReturn(code); - - final CodeSection codeSection = new CodeSection(0, 5, 2, 5, 0); - 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)); + assertThat(messageFrame.peekReturnStack()).isEqualTo(new ReturnStack.ReturnStackItem(0, 3)); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/Create2OperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/Create2OperationTest.java index 6298aa245a..eb04ccb741 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/Create2OperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/Create2OperationTest.java @@ -17,6 +17,7 @@ package org.hyperledger.besu.evm.operations; import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.evm.MainnetEVMs.DEV_NET_CHAIN_ID; import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.CODE_TOO_LARGE; +import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.INVALID_OPERATION; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -24,6 +25,7 @@ import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.MainnetEVMs; import org.hyperledger.besu.evm.account.MutableAccount; @@ -81,7 +83,7 @@ public class Create2OperationTest { + "F3" // RETURN ); public static final Bytes SIMPLE_EOF = - Bytes.fromHexString("0xEF00010100040200010001030000000000000000"); + Bytes.fromHexString("0xEF00010100040200010001040000000080000000"); public static final String SENDER = "0xdeadc0de00000000000000000000000000000000"; private static final int SHANGHAI_CREATE_GAS = 41240 + (0xc000 / 32) * 6; @@ -152,7 +154,7 @@ public class Create2OperationTest { .sender(Address.fromHexString(sender)) .value(Wei.ZERO) .apparentValue(Wei.ZERO) - .code(CodeFactory.createCode(codeBytes, 0, true)) + .code(CodeFactory.createCode(codeBytes, 0)) .completer(__ -> {}) .address(Address.fromHexString(sender)) .blockHashLookup(n -> Hash.hash(Words.longBytes(n))) @@ -174,7 +176,7 @@ public class Create2OperationTest { when(worldUpdater.getAccount(any())).thenReturn(account); when(worldUpdater.updater()).thenReturn(worldUpdater); when(evm.getCode(any(), any())) - .thenAnswer(invocation -> CodeFactory.createCode(invocation.getArgument(1), 0, true)); + .thenAnswer(invocation -> CodeFactory.createCode(invocation.getArgument(1), 0)); } @ParameterizedTest @@ -188,7 +190,7 @@ public class Create2OperationTest { setUp(sender, salt, code); final Address targetContractAddress = operation.targetContractAddress( - messageFrame, CodeFactory.createCode(Bytes.fromHexString(code), 0, true)); + messageFrame, CodeFactory.createCode(Bytes.fromHexString(code), 0)); assertThat(targetContractAddress).isEqualTo(Address.fromHexString(expectedAddress)); } @@ -266,7 +268,7 @@ public class Create2OperationTest { .sender(Address.fromHexString(SENDER)) .value(Wei.ZERO) .apparentValue(Wei.ZERO) - .code(CodeFactory.createCode(SIMPLE_CREATE, 0, true)) + .code(CodeFactory.createCode(SIMPLE_CREATE, 0)) .completer(__ -> {}) .address(Address.fromHexString(SENDER)) .blockHashLookup(n -> Hash.hash(Words.longBytes(n))) @@ -292,54 +294,30 @@ public class Create2OperationTest { } @Test - void eofV1CannotCreateLegacy() { + void eofV1CannotCall() { final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); final UInt256 memoryLength = UInt256.valueOf(SIMPLE_CREATE.size()); - final MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code(CodeFactory.createCode(SIMPLE_EOF, 1, true)) - .pushStackItem(Bytes.EMPTY) - .pushStackItem(memoryLength) - .pushStackItem(memoryOffset) - .pushStackItem(Bytes.EMPTY) - .worldUpdater(worldUpdater) - .build(); - messageFrame.writeMemory(memoryOffset.toLong(), memoryLength.toLong(), SIMPLE_CREATE); - - when(account.getBalance()).thenReturn(Wei.ZERO); - when(worldUpdater.getAccount(any())).thenReturn(account); - final EVM evm = MainnetEVMs.cancun(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); - var result = operation.execute(messageFrame, evm); - assertThat(result.getHaltReason()).isNull(); - assertThat(messageFrame.getStackItem(0).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); - } + Code eofCode = CodeFactory.createCode(SIMPLE_EOF, 1); + assertThat(eofCode.isValid()).isTrue(); - @Test - void legacyCanCreateEOFv1() { - final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); - final UInt256 memoryLength = UInt256.valueOf(SIMPLE_EOF.size()); final MessageFrame messageFrame = new TestMessageFrameBuilder() - .code(CodeFactory.createCode(SIMPLE_CREATE, 1, true)) + .code(eofCode) .pushStackItem(Bytes.EMPTY) .pushStackItem(memoryLength) .pushStackItem(memoryOffset) .pushStackItem(Bytes.EMPTY) .worldUpdater(worldUpdater) .build(); - messageFrame.writeMemory(memoryOffset.toLong(), memoryLength.toLong(), SIMPLE_EOF); + messageFrame.writeMemory(memoryOffset.toLong(), memoryLength.toLong(), SIMPLE_CREATE); - when(account.getNonce()).thenReturn(55L); when(account.getBalance()).thenReturn(Wei.ZERO); when(worldUpdater.getAccount(any())).thenReturn(account); - when(worldUpdater.get(any())).thenReturn(account); - when(worldUpdater.getSenderAccount(any())).thenReturn(account); - when(worldUpdater.updater()).thenReturn(worldUpdater); final EVM evm = MainnetEVMs.cancun(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); var result = operation.execute(messageFrame, evm); - assertThat(result.getHaltReason()).isNull(); - assertThat(messageFrame.getStackItem(0)).isNotEqualTo(UInt256.ZERO); + assertThat(result.getHaltReason()).isEqualTo(INVALID_OPERATION); + assertThat(messageFrame.getStackItem(0).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/CreateOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/CreateOperationTest.java index f1de36cc56..d95875e492 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/CreateOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/CreateOperationTest.java @@ -17,6 +17,7 @@ package org.hyperledger.besu.evm.operations; import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.evm.MainnetEVMs.DEV_NET_CHAIN_ID; import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.CODE_TOO_LARGE; +import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.INVALID_OPERATION; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -74,7 +75,7 @@ class CreateOperationTest { + "F3" // RETURN ); public static final Bytes SIMPLE_EOF = - Bytes.fromHexString("0xEF00010100040200010001030000000000000000"); + Bytes.fromHexString("0xEF00010100040200010001040000000080000000"); public static final String SENDER = "0xdeadc0de00000000000000000000000000000000"; private static final int SHANGHAI_CREATE_GAS = 41240; @@ -223,12 +224,12 @@ class CreateOperationTest { } @Test - void eofV1CannotCreateLegacy() { + void eofV1CannotCall() { final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); final UInt256 memoryLength = UInt256.valueOf(SIMPLE_CREATE.size()); final MessageFrame messageFrame = new TestMessageFrameBuilder() - .code(CodeFactory.createCode(SIMPLE_EOF, 1, true)) + .code(CodeFactory.createCode(SIMPLE_EOF, 1)) .pushStackItem(memoryLength) .pushStackItem(memoryOffset) .pushStackItem(Bytes.EMPTY) @@ -236,40 +237,14 @@ class CreateOperationTest { .build(); messageFrame.writeMemory(memoryOffset.toLong(), memoryLength.toLong(), SIMPLE_CREATE); - when(account.getBalance()).thenReturn(Wei.ZERO); - when(worldUpdater.getAccount(any())).thenReturn(account); - - final EVM evm = MainnetEVMs.cancun(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); - var result = operation.execute(messageFrame, evm); - assertThat(result.getHaltReason()).isNull(); - assertThat(messageFrame.getStackItem(0).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); - } - - @Test - void legacyCanCreateEOFv1() { - final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); - final UInt256 memoryLength = UInt256.valueOf(SIMPLE_EOF.size()); - final MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code(CodeFactory.createCode(SIMPLE_CREATE, 1, true)) - .pushStackItem(memoryLength) - .pushStackItem(memoryOffset) - .pushStackItem(Bytes.EMPTY) - .worldUpdater(worldUpdater) - .build(); - messageFrame.writeMemory(memoryOffset.toLong(), memoryLength.toLong(), SIMPLE_EOF); - - when(account.getNonce()).thenReturn(55L); when(account.getBalance()).thenReturn(Wei.ZERO); when(worldUpdater.getAccount(any())).thenReturn(account); when(worldUpdater.get(any())).thenReturn(account); - when(worldUpdater.getSenderAccount(any())).thenReturn(account); - when(worldUpdater.updater()).thenReturn(worldUpdater); final EVM evm = MainnetEVMs.cancun(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); var result = operation.execute(messageFrame, evm); - assertThat(result.getHaltReason()).isNull(); - assertThat(messageFrame.getState()).isEqualTo(MessageFrame.State.CODE_SUSPENDED); + assertThat(result.getHaltReason()).isEqualTo(INVALID_OPERATION); + assertThat(messageFrame.getStackItem(0).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); } @Nonnull @@ -286,7 +261,7 @@ class CreateOperationTest { .sender(Address.fromHexString(SENDER)) .value(Wei.ZERO) .apparentValue(Wei.ZERO) - .code(CodeFactory.createCode(SIMPLE_CREATE, 0, true)) + .code(CodeFactory.createCode(SIMPLE_CREATE, 0)) .completer(__ -> {}) .address(Address.fromHexString(SENDER)) .blockHashLookup(n -> Hash.hash(Words.longBytes(n))) diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/EofCreateOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/EofCreateOperationTest.java new file mode 100644 index 0000000000..2e41b49665 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/EofCreateOperationTest.java @@ -0,0 +1,160 @@ +/* + * 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.assertj.core.api.Assertions.fail; +import static org.hyperledger.besu.evm.EOFTestConstants.EOF_CREATE_CONTRACT; +import static org.hyperledger.besu.evm.EOFTestConstants.INNER_CONTRACT; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.MainnetEVMs; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.code.CodeFactory; +import org.hyperledger.besu.evm.code.CodeInvalid; +import org.hyperledger.besu.evm.frame.BlockValues; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.log.Log; +import org.hyperledger.besu.evm.precompile.MainnetPrecompiledContracts; +import org.hyperledger.besu.evm.processor.ContractCreationProcessor; +import org.hyperledger.besu.evm.processor.MessageCallProcessor; +import org.hyperledger.besu.evm.tracing.OperationTracer; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; + +class EofCreateOperationTest { + + private final WorldUpdater worldUpdater = mock(WorldUpdater.class); + private final MutableAccount account = mock(MutableAccount.class); + private final MutableAccount newAccount = mock(MutableAccount.class); + + static final Bytes CALL_DATA = + Bytes.fromHexString( + "cafebaba600dbaadc0de57aff60061e5cafebaba600dbaadc0de57aff60061e5"); // 32 bytes + + public static final String SENDER = "0xdeadc0de00000000000000000000000000000000"; + + // private static final int SHANGHAI_CREATE_GAS = 41240; + + @Test + void innerContractIsCorrect() { + Code code = CodeFactory.createCode(INNER_CONTRACT, 1); + assertThat(code.isValid()).isTrue(); + + final MessageFrame messageFrame = testMemoryFrame(code, CALL_DATA); + + when(account.getNonce()).thenReturn(55L); + when(account.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getSenderAccount(any())).thenReturn(account); + when(worldUpdater.getOrCreate(any())).thenReturn(newAccount); + when(newAccount.getCode()).thenReturn(Bytes.EMPTY); + when(newAccount.isStorageEmpty()).thenReturn(true); + when(worldUpdater.updater()).thenReturn(worldUpdater); + + final EVM evm = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); + final MessageFrame createFrame = messageFrame.getMessageFrameStack().peek(); + assertThat(createFrame).isNotNull(); + final ContractCreationProcessor ccp = + new ContractCreationProcessor(evm.getGasCalculator(), evm, false, List.of(), 0, List.of()); + ccp.process(createFrame, OperationTracer.NO_TRACING); + + final Log log = createFrame.getLogs().get(0); + final Bytes calculatedTopic = log.getTopics().get(0); + assertThat(calculatedTopic).isEqualTo(CALL_DATA); + } + + @Test + void eofCreatePassesInCallData() { + Bytes outerContract = EOF_CREATE_CONTRACT; + + Code code = CodeFactory.createCode(outerContract, 1); + if (!code.isValid()) { + System.out.println(outerContract); + fail(((CodeInvalid) code).getInvalidReason()); + } + + final MessageFrame messageFrame = testMemoryFrame(code, CALL_DATA); + + when(account.getNonce()).thenReturn(55L); + when(account.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getSenderAccount(any())).thenReturn(account); + when(worldUpdater.getOrCreate(any())).thenReturn(newAccount); + when(newAccount.getCode()).thenReturn(Bytes.EMPTY); + when(newAccount.isStorageEmpty()).thenReturn(true); + when(worldUpdater.updater()).thenReturn(worldUpdater); + + final EVM evm = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); + var precompiles = MainnetPrecompiledContracts.prague(evm.getGasCalculator()); + final MessageFrame createFrame = messageFrame.getMessageFrameStack().peek(); + assertThat(createFrame).isNotNull(); + final MessageCallProcessor mcp = new MessageCallProcessor(evm, precompiles); + final ContractCreationProcessor ccp = + new ContractCreationProcessor(evm.getGasCalculator(), evm, false, List.of(), 0, List.of()); + while (!createFrame.getMessageFrameStack().isEmpty()) { + var frame = createFrame.getMessageFrameStack().peek(); + assert frame != null; + (switch (frame.getType()) { + case CONTRACT_CREATION -> ccp; + case MESSAGE_CALL -> mcp; + }) + .process(frame, OperationTracer.NO_TRACING); + } + + final Log log = createFrame.getLogs().get(0); + final String calculatedTopic = log.getTopics().get(0).slice(0, 2).toHexString(); + assertThat(calculatedTopic).isEqualTo("0xc0de"); + + assertThat(createFrame.getCreates()) + .containsExactly(Address.fromHexString("0x8c308e96997a8052e3aaab5af624cb827218687a")); + } + + private MessageFrame testMemoryFrame(final Code code, final Bytes initData) { + return MessageFrame.builder() + .type(MessageFrame.Type.MESSAGE_CALL) + .contract(Address.ZERO) + .inputData(initData) + .sender(Address.fromHexString(SENDER)) + .value(Wei.ZERO) + .apparentValue(Wei.ZERO) + .code(code) + .completer(__ -> {}) + .address(Address.fromHexString(SENDER)) + .blockHashLookup(n -> Hash.hash(Words.longBytes(n))) + .blockValues(mock(BlockValues.class)) + .gasPrice(Wei.ZERO) + .miningBeneficiary(Address.ZERO) + .originator(Address.ZERO) + .initialGas(100000L) + .worldUpdater(worldUpdater) + .build(); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/ExtCallOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/ExtCallOperationTest.java new file mode 100644 index 0000000000..3a2ddde7e4 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/ExtCallOperationTest.java @@ -0,0 +1,274 @@ +/* + * 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.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.code.CodeFactory; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.PragueEOFGasCalculator; +import org.hyperledger.besu.evm.operation.AbstractExtCallOperation; +import org.hyperledger.besu.evm.operation.ExtCallOperation; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class ExtCallOperationTest { + + private final WorldUpdater worldUpdater = mock(WorldUpdater.class); + private final MutableAccount account = mock(MutableAccount.class); + private final EVM evm = mock(EVM.class); + public static final Code SIMPLE_EOF = + CodeFactory.createCode(Bytes.fromHexString("0xEF00010100040200010001040000000080000000"), 1); + public static final Code INVALID_EOF = + CodeFactory.createCode(Bytes.fromHexString("0xEF00010100040200010001040000000080000023"), 1); + private static final Address CONTRACT_ADDRESS = Address.fromHexString("0xc0de"); + + static Iterable data() { + return List.of( + Arguments.of( + "gas", + 99, + 100, + 99, + ExceptionalHaltReason.INSUFFICIENT_GAS, + CONTRACT_ADDRESS, + true, + true), + Arguments.of( + "gas", + 5000, + 100, + 5000, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + true), + Arguments.of( + "gas", + 7300, + 100, + 7300, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + true), + Arguments.of( + "Cold Address", + 7300, + 2600, + 7300, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + false), + Arguments.of("gas", 64000, 59000, 58900, null, CONTRACT_ADDRESS, true, true), + Arguments.of("gas", 384100, 378100, 378000, null, CONTRACT_ADDRESS, true, true), + Arguments.of( + "Invalid code", + 384100, + 100, + 384100, + ExceptionalHaltReason.INVALID_CODE, + CONTRACT_ADDRESS, + false, + true)); + } + + @ParameterizedTest(name = "{index}: {0} {1}") + @MethodSource("data") + void gasTest( + final String name, + final long parentGas, + final long chargedGas, + final long childGas, + final ExceptionalHaltReason haltReason, + final Bytes stackItem, + final boolean validCode, + final boolean warmAddress) { + final ExtCallOperation operation = new ExtCallOperation(new PragueEOFGasCalculator()); + + final var messageFrame = + new TestMessageFrameBuilder() + .initialGas(parentGas) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .build(); + if (warmAddress) { + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + } + when(account.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + when(evm.getCode(any(), any())).thenReturn(validCode ? SIMPLE_EOF : INVALID_EOF); + + var result = operation.execute(messageFrame, evm); + + assertThat(result.getGasCost()).isEqualTo(chargedGas); + assertThat(result.getHaltReason()).isEqualTo(haltReason); + + MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst(); + assertThat(childFrame.getRemainingGas()).isEqualTo(childGas); + + MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast(); + assertThat(parentFrame.getStackItem(0)).isEqualTo(stackItem); + } + + static Iterable valueData() { + return List.of( + Arguments.of( + "enough value", + 40000, + 35000, + 25900, + null, + CONTRACT_ADDRESS, + Wei.of(100), + Wei.of(200), + false), + Arguments.of( + "static context", + 40000, + 9000, + 40000, + ExceptionalHaltReason.ILLEGAL_STATE_CHANGE, + CONTRACT_ADDRESS, + Wei.of(100), + Wei.of(200), + true), + Arguments.of( + "not enough value", + 40000, + 9100, + 40000, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + Wei.of(1000), + Wei.of(200), + false), + Arguments.of( + "too little gas", + 5000, + 9100, + 5000, + ExceptionalHaltReason.INSUFFICIENT_GAS, + CONTRACT_ADDRESS, + Wei.of(100), + Wei.of(200), + false)); + } + + @ParameterizedTest(name = "{index}: {0} {1}") + @MethodSource("valueData") + void callWithValueTest( + final String name, + final long parentGas, + final long chargedGas, + final long childGas, + final ExceptionalHaltReason haltReason, + final Bytes stackItem, + final Wei valueSent, + final Wei valueWeiHave, + final boolean isStatic) { + final ExtCallOperation operation = new ExtCallOperation(new PragueEOFGasCalculator()); + + final var messageFrame = + new TestMessageFrameBuilder() + .initialGas(parentGas) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(valueSent) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .isStatic(isStatic) + .build(); + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + when(account.getBalance()).thenReturn(valueWeiHave); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + when(evm.getCode(any(), any())).thenReturn(SIMPLE_EOF); + + var result = operation.execute(messageFrame, evm); + + assertThat(result.getGasCost()).isEqualTo(chargedGas); + assertThat(result.getHaltReason()).isEqualTo(haltReason); + + MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst(); + assertThat(childFrame.getRemainingGas()).isEqualTo(childGas); + + MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast(); + assertThat(parentFrame.getStackItem(0)).isEqualTo(stackItem); + } + + @Test + void overflowTest() { + final ExtCallOperation operation = new ExtCallOperation(new PragueEOFGasCalculator()); + + final var messageFrame = + new TestMessageFrameBuilder() + .initialGas(400000) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .build(); + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + when(account.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + when(evm.getCode(any(), any())).thenReturn(SIMPLE_EOF); + while (messageFrame.getDepth() < 1024) { + messageFrame.getMessageFrameStack().add(messageFrame); + } + + var result = operation.execute(messageFrame, evm); + + assertThat(result.getGasCost()).isEqualTo(100); + assertThat(result.getHaltReason()).isNull(); + + MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst(); + assertThat(childFrame.getRemainingGas()).isEqualTo(400000L); + + MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast(); + assertThat(parentFrame.getStackItem(0)) + .isEqualTo(AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/ExtDelegateCallOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/ExtDelegateCallOperationTest.java new file mode 100644 index 0000000000..7a46654039 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/ExtDelegateCallOperationTest.java @@ -0,0 +1,258 @@ +/* + * 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.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.code.CodeFactory; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.PragueEOFGasCalculator; +import org.hyperledger.besu.evm.operation.AbstractExtCallOperation; +import org.hyperledger.besu.evm.operation.ExtDelegateCallOperation; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class ExtDelegateCallOperationTest { + + private final WorldUpdater worldUpdater = mock(WorldUpdater.class); + private final MutableAccount account = mock(MutableAccount.class); + // private final MutableAccount targetAccount = mock(MutableAccount.class); + private final EVM evm = mock(EVM.class); + public static final Code SIMPLE_EOF = + CodeFactory.createCode(Bytes.fromHexString("0xEF00010100040200010001040000000080000000"), 1); + public static final Code SIMPLE_LEGACY = CodeFactory.createCode(Bytes.fromHexString("0x00"), 1); + public static final Code EMPTY_CODE = CodeFactory.createCode(Bytes.fromHexString(""), 1); + public static final Code INVALID_EOF = + CodeFactory.createCode(Bytes.fromHexString("0xEF00010100040200010001040000000080000023"), 1); + private static final Address CONTRACT_ADDRESS = Address.fromHexString("0xc0de"); + + static Iterable data() { + return List.of( + Arguments.of( + "gas", + 99, + 100, + 99, + ExceptionalHaltReason.INSUFFICIENT_GAS, + CONTRACT_ADDRESS, + true, + true), + Arguments.of( + "gas", + 5000, + 100, + 5000, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + true), + Arguments.of( + "gas", + 7300, + 100, + 7300, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + true), + Arguments.of( + "Cold Address", + 7300, + 2600, + 7300, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + false), + Arguments.of("gas", 64000, 59000, 58900, null, CONTRACT_ADDRESS, true, true), + Arguments.of("gas", 384100, 378100, 378000, null, CONTRACT_ADDRESS, true, true), + Arguments.of( + "Invalid code", + 384100, + 100, + 384100, + ExceptionalHaltReason.INVALID_CODE, + CONTRACT_ADDRESS, + false, + true)); + } + + @ParameterizedTest(name = "{index}: {0} {1}") + @MethodSource("data") + void gasTest( + final String name, + final long parentGas, + final long chargedGas, + final long childGas, + final ExceptionalHaltReason haltReason, + final Bytes stackItem, + final boolean validCode, + final boolean warmAddress) { + final ExtDelegateCallOperation operation = + new ExtDelegateCallOperation(new PragueEOFGasCalculator()); + + final var messageFrame = + new TestMessageFrameBuilder() + .code(SIMPLE_EOF) + .initialGas(parentGas) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .build(); + if (warmAddress) { + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + } + when(account.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + when(evm.getCode(any(), any())).thenReturn(validCode ? SIMPLE_EOF : INVALID_EOF); + + var result = operation.execute(messageFrame, evm); + + assertThat(result.getGasCost()).isEqualTo(chargedGas); + assertThat(result.getHaltReason()).isEqualTo(haltReason); + + MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst(); + assertThat(childFrame.getRemainingGas()).isEqualTo(childGas); + + MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast(); + assertThat(parentFrame.getStackItem(0)).isEqualTo(stackItem); + } + + static Iterable delegateData() { + return List.of( + Arguments.of("EOF", 40000, 35000, 34900L, null, CONTRACT_ADDRESS), + Arguments.of( + "Legacy", 40000, 100, 40000, null, AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM), + Arguments.of( + "Empty", + 40000, + 100, + 40000, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + CONTRACT_ADDRESS), + Arguments.of( + "EOA", 5000, 100, 5000, null, AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM)); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("delegateData") + void callTypes( + final String name, + final long parentGas, + final long chargedGas, + final long childGas, + final ExceptionalHaltReason haltReason, + final Bytes stackItem) { + final ExtDelegateCallOperation operation = + new ExtDelegateCallOperation(new PragueEOFGasCalculator()); + + final var messageFrame = + new TestMessageFrameBuilder() + .code(SIMPLE_EOF) + .initialGas(parentGas) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .build(); + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + when(account.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.get(TestMessageFrameBuilder.DEFAUT_ADDRESS)).thenReturn(account); + when(worldUpdater.getAccount(TestMessageFrameBuilder.DEFAUT_ADDRESS)).thenReturn(account); + + when(account.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.get(CONTRACT_ADDRESS)).thenReturn("Empty".equals(name) ? null : account); + when(worldUpdater.getAccount(CONTRACT_ADDRESS)) + .thenReturn("Empty".equals(name) ? null : account); + when(evm.getCode(any(), any())) + .thenReturn( + switch (name) { + case "EOF" -> SIMPLE_EOF; + case "Legacy" -> SIMPLE_LEGACY; + default -> EMPTY_CODE; + }); + when(worldUpdater.updater()).thenReturn(worldUpdater); + + var result = operation.execute(messageFrame, evm); + + assertThat(result.getGasCost()).isEqualTo(chargedGas); + assertThat(result.getHaltReason()).isEqualTo(haltReason); + + MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst(); + assertThat(childFrame.getRemainingGas()).isEqualTo(childGas); + + MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast(); + assertThat(parentFrame.getStackItem(0)).isEqualTo(stackItem); + } + + @Test + void overflowTest() { + final ExtDelegateCallOperation operation = + new ExtDelegateCallOperation(new PragueEOFGasCalculator()); + final var messageFrame = + new TestMessageFrameBuilder() + .initialGas(400000) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .build(); + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + when(account.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + when(evm.getCode(any(), any())).thenReturn(SIMPLE_EOF); + while (messageFrame.getDepth() < 1024) { + messageFrame.getMessageFrameStack().add(messageFrame); + } + + var result = operation.execute(messageFrame, evm); + + assertThat(result.getGasCost()).isEqualTo(100); + assertThat(result.getHaltReason()).isNull(); + + MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst(); + assertThat(childFrame.getRemainingGas()).isEqualTo(400000L); + + MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast(); + assertThat(parentFrame.getStackItem(0)) + .isEqualTo(AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/ExtStaticCallOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/ExtStaticCallOperationTest.java new file mode 100644 index 0000000000..25be1d1dfe --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/ExtStaticCallOperationTest.java @@ -0,0 +1,185 @@ +/* + * 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.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.code.CodeFactory; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.PragueEOFGasCalculator; +import org.hyperledger.besu.evm.operation.AbstractExtCallOperation; +import org.hyperledger.besu.evm.operation.ExtStaticCallOperation; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class ExtStaticCallOperationTest { + + private final WorldUpdater worldUpdater = mock(WorldUpdater.class); + private final MutableAccount account = mock(MutableAccount.class); + private final EVM evm = mock(EVM.class); + public static final Code SIMPLE_EOF = + CodeFactory.createCode(Bytes.fromHexString("0xEF00010100040200010001040000000080000000"), 1); + public static final Code INVALID_EOF = + CodeFactory.createCode(Bytes.fromHexString("0xEF00010100040200010001040000000080000023"), 1); + private static final Address CONTRACT_ADDRESS = Address.fromHexString("0xc0de"); + + static Iterable data() { + return List.of( + Arguments.of( + "gas", + 99, + 100, + 99, + ExceptionalHaltReason.INSUFFICIENT_GAS, + CONTRACT_ADDRESS, + true, + true), + Arguments.of( + "gas", + 5000, + 100, + 5000, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + true), + Arguments.of( + "gas", + 7300, + 100, + 7300, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + true), + Arguments.of( + "Cold Address", + 7300, + 2600, + 7300, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + false), + Arguments.of("gas", 64000, 59000, 58900, null, CONTRACT_ADDRESS, true, true), + Arguments.of("gas", 384100, 378100, 378000, null, CONTRACT_ADDRESS, true, true), + Arguments.of( + "Invalid code", + 384100, + 100, + 384100, + ExceptionalHaltReason.INVALID_CODE, + CONTRACT_ADDRESS, + false, + true)); + } + + @ParameterizedTest(name = "{index}: {0} {1}") + @MethodSource("data") + void gasTest( + final String name, + final long parentGas, + final long chargedGas, + final long childGas, + final ExceptionalHaltReason haltReason, + final Bytes stackItem, + final boolean validCode, + final boolean warmAddress) { + final ExtStaticCallOperation operation = + new ExtStaticCallOperation(new PragueEOFGasCalculator()); + + final var messageFrame = + new TestMessageFrameBuilder() + .initialGas(parentGas) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .build(); + if (warmAddress) { + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + } + when(account.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + when(evm.getCode(any(), any())).thenReturn(validCode ? SIMPLE_EOF : INVALID_EOF); + + var result = operation.execute(messageFrame, evm); + + assertThat(result.getGasCost()).isEqualTo(chargedGas); + assertThat(result.getHaltReason()).isEqualTo(haltReason); + + MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst(); + assertThat(childFrame.getRemainingGas()).isEqualTo(childGas); + + MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast(); + assertThat(parentFrame.getStackItem(0)).isEqualTo(stackItem); + } + + @Test + void overflowTest() { + final ExtStaticCallOperation operation = + new ExtStaticCallOperation(new PragueEOFGasCalculator()); + final var messageFrame = + new TestMessageFrameBuilder() + .initialGas(400000) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .build(); + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + when(account.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + when(evm.getCode(any(), any())).thenReturn(SIMPLE_EOF); + while (messageFrame.getDepth() < 1024) { + messageFrame.getMessageFrameStack().add(messageFrame); + } + + var result = operation.execute(messageFrame, evm); + + assertThat(result.getGasCost()).isEqualTo(100); + assertThat(result.getHaltReason()).isNull(); + + MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst(); + assertThat(childFrame.getRemainingGas()).isEqualTo(400000L); + + MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast(); + assertThat(parentFrame.getStackItem(0)) + .isEqualTo(AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM); + } +} 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 index 7efbb8e928..52a7211050 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/JumpFOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/JumpFOperationTest.java @@ -15,15 +15,14 @@ package org.hyperledger.besu.evm.operations; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.testutils.OperationsTestUtils.mockCode; 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; @@ -36,9 +35,7 @@ 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.getBytes()).thenReturn(code); + final Code mockCode = mockCode("00" + "b2" + "0001" + "00"); final CodeSection codeSection = new CodeSection(0, 1, 2, 3, 0); when(mockCode.getCodeSection(1)).thenReturn(codeSection); @@ -60,70 +57,7 @@ class JumpFOperationTest { 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.getBytes()).thenReturn(code); - - final CodeSection codeSection = new CodeSection(0, 1, 2, 3, 0); - 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.getBytes()).thenReturn(code); - - final CodeSection codeSection1 = new CodeSection(0, 1, 2, 3, 0); - when(mockCode.getCodeSection(1)).thenReturn(codeSection1); - final CodeSection codeSection2 = new CodeSection(0, 2, 2, 3, 0); - 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)); + assertThat(messageFrame.returnStackSize()).isZero(); + assertThat(messageFrame.peekReturnStack()).isNull(); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/JumpOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/JumpOperationTest.java index 37b5998309..e04b0d16f6 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/JumpOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/JumpOperationTest.java @@ -74,7 +74,7 @@ class JumpOperationTest { final MessageFrame frame = createMessageFrameBuilder(10_000L) .pushStackItem(UInt256.fromHexString("0x03")) - .code(CodeFactory.createCode(jumpBytes, 0, false)) + .code(CodeFactory.createCode(jumpBytes, 0)) .build(); frame.setPC(CURRENT_PC); @@ -89,7 +89,7 @@ class JumpOperationTest { final MessageFrame frame = createMessageFrameBuilder(10_000L) .pushStackItem(UInt256.fromHexString("0x03")) - .code(CodeFactory.createCode(jumpBytes, 0, false)) + .code(CodeFactory.createCode(jumpBytes, 0)) .build(); frame.setPC(CURRENT_PC); @@ -104,7 +104,7 @@ class JumpOperationTest { final MessageFrame frameDestinationGreaterThanCodeSize = createMessageFrameBuilder(100L) .pushStackItem(UInt256.fromHexString("0xFFFFFFFF")) - .code(CodeFactory.createCode(jumpBytes, 0, false)) + .code(CodeFactory.createCode(jumpBytes, 0)) .build(); frameDestinationGreaterThanCodeSize.setPC(CURRENT_PC); @@ -114,7 +114,7 @@ class JumpOperationTest { final MessageFrame frameDestinationEqualsToCodeSize = createMessageFrameBuilder(100L) .pushStackItem(UInt256.fromHexString("0x04")) - .code(CodeFactory.createCode(badJump, 0, false)) + .code(CodeFactory.createCode(badJump, 0)) .build(); frameDestinationEqualsToCodeSize.setPC(CURRENT_PC); @@ -132,7 +132,7 @@ class JumpOperationTest { final MessageFrame longContract = createMessageFrameBuilder(100L) .pushStackItem(UInt256.fromHexString("0x12c")) - .code(CodeFactory.createCode(longCode, 0, false)) + .code(CodeFactory.createCode(longCode, 0)) .build(); longContract.setPC(255); 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 f55b7629ba..3104225775 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.hyperledger.besu.evm.testutils.OperationsTestUtils.mockCode; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -41,13 +42,11 @@ class RelativeJumpOperationTest { void rjumpOperation(final int jumpLength) { final GasCalculator gasCalculator = mock(GasCalculator.class); final MessageFrame messageFrame = mock(MessageFrame.class, Mockito.RETURNS_DEEP_STUBS); - final Code mockCode = mock(Code.class); final String twosComplementJump = String.format("%08x", jumpLength).substring(4); final int rjumpOperationIndex = 3; - final Bytes code = Bytes.fromHexString("00".repeat(3) + "5c" + twosComplementJump); + final Code mockCode = mockCode("00".repeat(3) + "5c" + twosComplementJump); when(messageFrame.getCode()).thenReturn(mockCode); - when(mockCode.getBytes()).thenReturn(code); when(messageFrame.getRemainingGas()).thenReturn(3L); when(messageFrame.getPC()).thenReturn(rjumpOperationIndex); @@ -55,15 +54,14 @@ class RelativeJumpOperationTest { Operation.OperationResult rjumpResult = rjump.execute(messageFrame, null); assertThat(rjumpResult.getPcIncrement()) - .isEqualTo(code.size() - rjumpOperationIndex + jumpLength); + .isEqualTo(mockCode.getBytes().size() - rjumpOperationIndex + jumpLength); } @Test void rjumpiOperation() { final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); final int rjumpOperationIndex = 3; - final Bytes code = Bytes.fromHexString("00".repeat(rjumpOperationIndex) + "5d0004"); + final Code mockCode = mockCode("00".repeat(rjumpOperationIndex) + "5d0004"); MessageFrame messageFrame = new TestMessageFrameBuilder() @@ -72,7 +70,6 @@ class RelativeJumpOperationTest { .initialGas(5L) .pushStackItem(Bytes.EMPTY) .build(); - when(mockCode.getBytes()).thenReturn(code); RelativeJumpIfOperation rjumpi = new RelativeJumpIfOperation(gasCalculator); Operation.OperationResult rjumpResult = rjumpi.execute(messageFrame, null); @@ -83,9 +80,8 @@ class RelativeJumpOperationTest { @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"); + final Code mockCode = mockCode("00".repeat(rjumpOperationIndex) + "5dfffc00"); MessageFrame messageFrame = new TestMessageFrameBuilder() @@ -94,7 +90,6 @@ class RelativeJumpOperationTest { .initialGas(5L) .pushStackItem(Words.intBytes(1)) .build(); - when(mockCode.getBytes()).thenReturn(code); RelativeJumpIfOperation rjumpi = new RelativeJumpIfOperation(gasCalculator); Operation.OperationResult rjumpResult = rjumpi.execute(messageFrame, null); @@ -105,14 +100,13 @@ class RelativeJumpOperationTest { @Test void rjumpvOperation() { final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); final int rjumpOperationIndex = 3; final int jumpVectorSize = 1; final int jumpLength = 4; - final Bytes code = - Bytes.fromHexString( + final Code mockCode = + mockCode( "00".repeat(rjumpOperationIndex) - + String.format("5e%02x%04x", jumpVectorSize, jumpLength)); + + String.format("e2%02x%04x", jumpVectorSize - 1, jumpLength)); MessageFrame messageFrame = new TestMessageFrameBuilder() @@ -121,7 +115,6 @@ class RelativeJumpOperationTest { .initialGas(5L) .pushStackItem(Bytes.of(jumpVectorSize)) .build(); - when(mockCode.getBytes()).thenReturn(code); RelativeJumpVectorOperation rjumpv = new RelativeJumpVectorOperation(gasCalculator); Operation.OperationResult rjumpResult = rjumpv.execute(messageFrame, null); @@ -156,17 +149,15 @@ class RelativeJumpOperationTest { }) void rjumpvOverflowOperation(final String stackValue) { final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); final int rjumpOperationIndex = 3; final int jumpVectorSize = 255; final int jumpLength = 400; - final Bytes code = - Bytes.fromHexString( + final Code mockCode = + mockCode( "00".repeat(rjumpOperationIndex) - + String.format("5e%02x", jumpVectorSize) + + String.format("e2%02x", jumpVectorSize - 1) + String.format("%04x", jumpLength).repeat(jumpVectorSize)); - when(mockCode.getBytes()).thenReturn(code); RelativeJumpVectorOperation rjumpv = new RelativeJumpVectorOperation(gasCalculator); MessageFrame messageFrame = new TestMessageFrameBuilder() @@ -185,17 +176,15 @@ class RelativeJumpOperationTest { @ValueSource(strings = {"0x7f", "0xf5", "0x5f", "0xfe"}) void rjumpvIndexOperation(final String stackValue) { final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); final int rjumpOperationIndex = 3; final int jumpVectorSize = 255; final int jumpLength = 400; - final Bytes code = - Bytes.fromHexString( + final Code mockCode = + mockCode( "00".repeat(rjumpOperationIndex) - + String.format("5e%02x", jumpVectorSize) + + String.format("e2%02x", jumpVectorSize - 1) + String.format("%04x", jumpLength).repeat(jumpVectorSize)); - when(mockCode.getBytes()).thenReturn(code); RelativeJumpVectorOperation rjumpv = new RelativeJumpVectorOperation(gasCalculator); MessageFrame messageFrame = new TestMessageFrameBuilder() @@ -213,11 +202,10 @@ class RelativeJumpOperationTest { @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"); + final Code mockCode = + mockCode("00".repeat(rjumpOperationIndex) + "e2" + "01" + "1234" + "5678"); MessageFrame messageFrame = new TestMessageFrameBuilder() @@ -226,7 +214,6 @@ class RelativeJumpOperationTest { .initialGas(5L) .pushStackItem(Bytes.of(jumpVectorSize - 1)) .build(); - when(mockCode.getBytes()).thenReturn(code); RelativeJumpVectorOperation rjumpv = new RelativeJumpVectorOperation(gasCalculator); Operation.OperationResult rjumpResult = rjumpv.execute(messageFrame, null); 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 index 193900ede7..672628140c 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/RetFOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/RetFOperationTest.java @@ -15,12 +15,12 @@ package org.hyperledger.besu.evm.operations; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.testutils.OperationsTestUtils.mockCode; 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; @@ -36,9 +36,7 @@ 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.getBytes()).thenReturn(code); + final Code mockCode = mockCode("00" + "b1" + "00"); final CodeSection codeSection = new CodeSection(0, 1, 2, 3, 0); when(mockCode.getCodeSection(1)).thenReturn(codeSection); @@ -53,7 +51,7 @@ class RetFOperationTest { .pushStackItem(Bytes.EMPTY) .pushStackItem(Bytes.EMPTY) .build(); - messageFrame.pushReturnStackItem(new ReturnStack.ReturnStackItem(2, 3, 1)); + messageFrame.pushReturnStackItem(new ReturnStack.ReturnStackItem(2, 3)); RetFOperation retF = new RetFOperation(gasCalculator); Operation.OperationResult retFResult = retF.execute(messageFrame, null); @@ -62,68 +60,6 @@ class RetFOperationTest { 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.getBytes()).thenReturn(code); - - final CodeSection codeSection = new CodeSection(0, 1, 2, 3, 0); - 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.getBytes()).thenReturn(code); - - final CodeSection codeSection = new CodeSection(0, 1, 2, 3, 0); - 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/operations/SelfDestructOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/SelfDestructOperationTest.java index 66598ab5c7..a87c254b0e 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/SelfDestructOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/SelfDestructOperationTest.java @@ -79,7 +79,7 @@ public class SelfDestructOperationTest { .sender(beneficiaryAddress) .value(Wei.ZERO) .apparentValue(Wei.ZERO) - .code(CodeFactory.createCode(SELFDESTRUCT_CODE, 0, true)) + .code(CodeFactory.createCode(SELFDESTRUCT_CODE, 0)) .completer(__ -> {}) .address(originatorAddress) .blockHashLookup(n -> Hash.hash(Words.longBytes(n))) diff --git a/evm/src/test/java/org/hyperledger/besu/evm/processor/ContractCreationProcessorTest.java b/evm/src/test/java/org/hyperledger/besu/evm/processor/ContractCreationProcessorTest.java index 975cd0e5fa..32f5ecc323 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/processor/ContractCreationProcessorTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/processor/ContractCreationProcessorTest.java @@ -15,14 +15,14 @@ package org.hyperledger.besu.evm.processor; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.EOFTestConstants.EOF_CREATE_CONTRACT; +import static org.hyperledger.besu.evm.EOFTestConstants.INNER_CONTRACT; import static org.hyperledger.besu.evm.frame.MessageFrame.State.COMPLETED_SUCCESS; import static org.hyperledger.besu.evm.frame.MessageFrame.State.EXCEPTIONAL_HALT; import static org.mockito.Mockito.when; import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.EvmSpecVersion; import org.hyperledger.besu.evm.code.CodeFactory; -import org.hyperledger.besu.evm.contractvalidation.CachedInvalidCodeRule; import org.hyperledger.besu.evm.contractvalidation.EOFValidationCodeRule; import org.hyperledger.besu.evm.contractvalidation.MaxCodeSizeRule; import org.hyperledger.besu.evm.contractvalidation.PrefixCodeRule; @@ -35,6 +35,7 @@ import org.hyperledger.besu.evm.tracing.OperationTracer; import java.util.Collections; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -115,7 +116,7 @@ class ContractCreationProcessorTest gasCalculator, evm, true, - Collections.singletonList(EOFValidationCodeRule.of(1, false)), + Collections.singletonList(EOFValidationCodeRule.of(1)), 1, Collections.emptyList()); final Bytes contractCode = Bytes.fromHexString("EF00010101010101"); @@ -131,13 +132,13 @@ class ContractCreationProcessorTest } @Test - void eofValidationShouldAllowLegacyCode() { + void eofValidationShouldAllowLegacyDeployFromLegacyInit() { processor = new ContractCreationProcessor( gasCalculator, evm, true, - Collections.singletonList(EOFValidationCodeRule.of(1, false)), + Collections.singletonList(EOFValidationCodeRule.of(1)), 1, Collections.emptyList()); final Bytes contractCode = Bytes.fromHexString("0101010101010101"); @@ -157,13 +158,12 @@ class ContractCreationProcessorTest gasCalculator, evm, true, - Collections.singletonList(EOFValidationCodeRule.of(1, false)), + Collections.singletonList(EOFValidationCodeRule.of(1)), 1, Collections.emptyList()); - final Bytes contractCode = - Bytes.fromHexString( - "0xEF000101000C020003000b000200080300000000000002020100020100000260016002e30001e30002e401e460005360106000f3"); - final MessageFrame messageFrame = new TestMessageFrameBuilder().build(); + final Bytes contractCode = INNER_CONTRACT; + final MessageFrame messageFrame = + new TestMessageFrameBuilder().code(CodeFactory.createCode(EOF_CREATE_CONTRACT, 1)).build(); messageFrame.setOutputData(contractCode); messageFrame.setGasRemaining(100L); @@ -173,21 +173,17 @@ class ContractCreationProcessorTest } @Test - void eofValidationShouldPreventLegacyCodeDeployment() { + void prefixValidationShouldPreventEOFCode() { processor = new ContractCreationProcessor( gasCalculator, evm, true, - Collections.singletonList(EOFValidationCodeRule.of(1, false)), + Collections.singletonList(PrefixCodeRule.of()), 1, Collections.emptyList()); - final Bytes contractCode = Bytes.fromHexString("6030602001"); - final Bytes initCode = - Bytes.fromHexString( - "0xEF000101000C020003000b000200080300000000000002020100020100000260016002e30001e30002e401e460005360106000f3"); - final MessageFrame messageFrame = - new TestMessageFrameBuilder().code(CodeFactory.createCode(initCode, 1, true)).build(); + final Bytes contractCode = INNER_CONTRACT; + final MessageFrame messageFrame = new TestMessageFrameBuilder().build(); messageFrame.setOutputData(contractCode); messageFrame.setGasRemaining(100L); @@ -197,19 +193,19 @@ class ContractCreationProcessorTest } @Test - void eofValidationPreventsInvalidEOFCode() { + void eofValidationShouldPreventLegacyDeployFromEOFInit() { processor = new ContractCreationProcessor( gasCalculator, evm, true, - Collections.singletonList(EOFValidationCodeRule.of(1, false)), + Collections.singletonList(EOFValidationCodeRule.of(1)), 1, Collections.emptyList()); - final Bytes contractCode = - Bytes.fromHexString( - "0xEF000101000C020003000b000200080300000000000000020100020100000260016002b00001b00002b101b160005360106000f3"); - final MessageFrame messageFrame = new TestMessageFrameBuilder().build(); + final Bytes contractCode = Bytes.fromHexString("6030602001"); + final Bytes initCode = EOF_CREATE_CONTRACT; + final MessageFrame messageFrame = + new TestMessageFrameBuilder().code(CodeFactory.createCode(initCode, 1)).build(); messageFrame.setOutputData(contractCode); messageFrame.setGasRemaining(100L); @@ -219,16 +215,17 @@ class ContractCreationProcessorTest } @Test - void shouldThrowAnExceptionWhenCodeContractTooLarge() { + @Disabled("This is what's changing") + void eofValidationPreventsEOFDeployFromLegacyInit() { processor = new ContractCreationProcessor( gasCalculator, evm, true, - Collections.singletonList(MaxCodeSizeRule.of(24 * 1024)), + Collections.singletonList(EOFValidationCodeRule.of(1)), 1, Collections.emptyList()); - final Bytes contractCode = Bytes.fromHexString("00".repeat(24 * 1024 + 1)); + final Bytes contractCode = EOF_CREATE_CONTRACT; final MessageFrame messageFrame = new TestMessageFrameBuilder().build(); messageFrame.setOutputData(contractCode); messageFrame.setGasRemaining(100L); @@ -236,31 +233,28 @@ class ContractCreationProcessorTest when(gasCalculator.codeDepositGasCost(contractCode.size())).thenReturn(10L); processor.codeSuccess(messageFrame, OperationTracer.NO_TRACING); assertThat(messageFrame.getState()).isEqualTo(EXCEPTIONAL_HALT); - assertThat(messageFrame.getExceptionalHaltReason()) - .contains(ExceptionalHaltReason.CODE_TOO_LARGE); } @Test - void shouldThrowAnExceptionWhenDeployingInvalidContract() { - EvmSpecVersion evmSpecVersion = EvmSpecVersion.FUTURE_EIPS; + void shouldThrowAnExceptionWhenCodeContractTooLarge() { processor = new ContractCreationProcessor( gasCalculator, evm, true, - Collections.singletonList(CachedInvalidCodeRule.of(evmSpecVersion)), + Collections.singletonList(MaxCodeSizeRule.of(24 * 1024)), 1, Collections.emptyList()); - final Bytes contractCreateCode = Bytes.fromHexString("0x67ef0001010001006060005260086018f3"); - final MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code( - CodeFactory.createCode(contractCreateCode, evmSpecVersion.getMaxEofVersion(), true)) - .build(); - messageFrame.setOutputData(Bytes.fromHexString("0xef00010100010060")); + final Bytes contractCode = Bytes.fromHexString("00".repeat(24 * 1024 + 1)); + final MessageFrame messageFrame = new TestMessageFrameBuilder().build(); + messageFrame.setOutputData(contractCode); + messageFrame.setGasRemaining(100L); + when(gasCalculator.codeDepositGasCost(contractCode.size())).thenReturn(10L); processor.codeSuccess(messageFrame, OperationTracer.NO_TRACING); assertThat(messageFrame.getState()).isEqualTo(EXCEPTIONAL_HALT); + assertThat(messageFrame.getExceptionalHaltReason()) + .contains(ExceptionalHaltReason.CODE_TOO_LARGE); } @Test diff --git a/evm/src/test/java/org/hyperledger/besu/evm/testutils/OperationsTestUtils.java b/evm/src/test/java/org/hyperledger/besu/evm/testutils/OperationsTestUtils.java new file mode 100644 index 0000000000..778459a6f2 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/testutils/OperationsTestUtils.java @@ -0,0 +1,48 @@ +/* + * 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.testutils; + +import static org.hyperledger.besu.evm.internal.Words.readBigEndianI16; +import static org.hyperledger.besu.evm.internal.Words.readBigEndianU16; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.evm.Code; + +import org.apache.tuweni.bytes.Bytes; + +public class OperationsTestUtils { + + public static Code mockCode(final String codeString) { + Code mockCode = mock(Code.class); + final Bytes codeBytes = Bytes.fromHexString(codeString); + when(mockCode.getBytes()).thenReturn(codeBytes); + when(mockCode.getEofVersion()).thenReturn(1); + when(mockCode.readBigEndianI16(anyInt())) + .thenAnswer( + invocationOnMock -> + readBigEndianI16(invocationOnMock.getArgument(0), codeBytes.toArrayUnsafe())); + when(mockCode.readBigEndianU16(anyInt())) + .thenAnswer( + invocationOnMock -> + readBigEndianU16(invocationOnMock.getArgument(0), codeBytes.toArrayUnsafe())); + when(mockCode.readU8(anyInt())) + .thenAnswer( + invocationOnMock -> + codeBytes.toArrayUnsafe()[(int) invocationOnMock.getArgument(0)] & 0xff); + return mockCode; + } +} 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 fb4f56dbea..801fcae8e2 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 @@ -56,6 +56,7 @@ public class TestMessageFrameBuilder { private final List stackItems = new ArrayList<>(); private Optional blockHashLookup = Optional.empty(); private Bytes memory = Bytes.EMPTY; + private boolean isStatic = false; public TestMessageFrameBuilder worldUpdater(final WorldUpdater worldUpdater) { this.worldUpdater = Optional.of(worldUpdater); @@ -102,7 +103,7 @@ public class TestMessageFrameBuilder { return this; } - TestMessageFrameBuilder inputData(final Bytes inputData) { + public TestMessageFrameBuilder inputData(final Bytes inputData) { this.inputData = inputData; return this; } @@ -142,6 +143,11 @@ public class TestMessageFrameBuilder { return this; } + public TestMessageFrameBuilder isStatic(final boolean isStatic) { + this.isStatic = isStatic; + return this; + } + public MessageFrame build() { final MessageFrame frame = MessageFrame.builder() @@ -163,6 +169,7 @@ public class TestMessageFrameBuilder { .miningBeneficiary(Address.ZERO) .blockHashLookup(blockHashLookup.orElse(number -> Hash.hash(Words.longBytes(number)))) .maxStackSize(maxStackSize) + .isStatic(isStatic) .build(); frame.setPC(pc); frame.setSection(section); diff --git a/testutil/src/main/java/org/hyperledger/besu/testutil/JsonTestParameters.java b/testutil/src/main/java/org/hyperledger/besu/testutil/JsonTestParameters.java index 88588d5218..062e14cfc7 100644 --- a/testutil/src/main/java/org/hyperledger/besu/testutil/JsonTestParameters.java +++ b/testutil/src/main/java/org/hyperledger/besu/testutil/JsonTestParameters.java @@ -42,6 +42,7 @@ import com.fasterxml.jackson.core.StreamReadConstraints; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import org.apache.tuweni.bytes.Bytes; /** * Utility class for generating JUnit test parameters from json files. Each set of test parameters @@ -75,7 +76,7 @@ public class JsonTestParameters { private final List testParameters = new ArrayList<>(256); /** - * Add. + * Add standard reference test. * * @param name the name * @param fullPath the full path of the test @@ -88,6 +89,27 @@ public class JsonTestParameters { new Object[] {name, value, runTest && includes(name) && includes(fullPath)}); } + /** + * Add EOF test. + * + * @param name the name + * @param fullPath the full path of the test + * @param fork the fork to be tested + * @param code the code to be tested + * @param value the value + * @param runTest the run test + */ + public void add( + final String name, + final String fullPath, + final String fork, + final Bytes code, + final S value, + final boolean runTest) { + testParameters.add( + new Object[] {name, fork, code, value, runTest && includes(name) && includes(fullPath)}); + } + private boolean includes(final String name) { // If there is no specific includes, everything is included unless it is ignored, otherwise, // only what is in includes is included whether or not it is ignored.