EIP-7692 "Mega" EOF Implementation (#7169)

A complete and up to date implementation of EIP-7692 EOF v.1. For genesis 
file activation use "PragueEOFTime", for references tests it activates as 
part of Prague.

Signed-off-by: Danno Ferrin <danno@numisight.com>
revert-7203-patch-1
Danno Ferrin 5 months ago committed by GitHub
parent 365737c2eb
commit 85d286aa85
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 3
      besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
  3. 7
      config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java
  4. 7
      config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java
  5. 18
      config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java
  6. 8
      config/src/test/java/org/hyperledger/besu/config/GenesisConfigOptionsTest.java
  7. 8
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java
  8. 11
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecFactory.java
  9. 82
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java
  10. 14
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java
  11. 2
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilder.java
  12. 6
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionProcessor.java
  13. 1
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java
  14. 21
      ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/CodeValidateSubCommand.java
  15. 226
      ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EOFTestSubCommand.java
  16. 28
      ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java
  17. 3
      ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MainnetGenesisFileModule.java
  18. 81
      ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/PrettyPrintSubCommand.java
  19. 4
      ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/StateTestSubCommand.java
  20. 7
      ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/BenchmarkExecutor.java
  21. 50
      ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java
  22. 6
      ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/EvmToolSpecTests.java
  23. 8
      ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/rjumpv-max.json
  24. 8
      ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/rjumpv.json
  25. 8
      ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/subcontainers.json
  26. 2
      ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/blockhash.json
  27. 86
      ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-eof.json
  28. 78
      ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-invalid-eof.json
  29. 25
      ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/create-eof.json
  30. 28
      ethereum/referencetests/build.gradle
  31. 53
      ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/EOFTestCaseSpec.java
  32. 2
      ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java
  33. 2
      ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/StateTestVersionedTransaction.java
  34. 142
      ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/eof/EOFReferenceTestTools.java
  35. 15
      ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/BlockchainReferenceTestTools.java
  36. 2
      ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java
  37. 14
      ethereum/referencetests/src/reference-test/templates/BlockchainReferenceTest.java.template
  38. 42
      ethereum/referencetests/src/reference-test/templates/EOFReferenceTest.java.template
  39. 14
      ethereum/referencetests/src/reference-test/templates/GeneralStateReferenceTest.java.template
  40. 68
      evm/src/main/java/org/hyperledger/besu/evm/Code.java
  41. 12
      evm/src/main/java/org/hyperledger/besu/evm/EVM.java
  42. 16
      evm/src/main/java/org/hyperledger/besu/evm/EvmSpecVersion.java
  43. 331
      evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java
  44. 79
      evm/src/main/java/org/hyperledger/besu/evm/code/CodeFactory.java
  45. 44
      evm/src/main/java/org/hyperledger/besu/evm/code/CodeInvalid.java
  46. 18
      evm/src/main/java/org/hyperledger/besu/evm/code/CodeSection.java
  47. 47
      evm/src/main/java/org/hyperledger/besu/evm/code/CodeV0.java
  48. 99
      evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1.java
  49. 1104
      evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1Validation.java
  50. 538
      evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java
  51. 336
      evm/src/main/java/org/hyperledger/besu/evm/code/OpcodeInfo.java
  52. 95
      evm/src/main/java/org/hyperledger/besu/evm/code/WorkList.java
  53. 2
      evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/CachedInvalidCodeRule.java
  54. 14
      evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/EOFValidationCodeRule.java
  55. 75
      evm/src/main/java/org/hyperledger/besu/evm/fluent/EVMExecutor.java
  56. 36
      evm/src/main/java/org/hyperledger/besu/evm/frame/ExceptionalHaltReason.java
  57. 5
      evm/src/main/java/org/hyperledger/besu/evm/frame/Memory.java
  58. 114
      evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java
  59. 33
      evm/src/main/java/org/hyperledger/besu/evm/gascalculator/FrontierGasCalculator.java
  60. 28
      evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java
  61. 57
      evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueEOFGasCalculator.java
  62. 79
      evm/src/main/java/org/hyperledger/besu/evm/internal/ReturnStack.java
  63. 47
      evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java
  64. 83
      evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java
  65. 201
      evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java
  66. 2
      evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java
  67. 2
      evm/src/main/java/org/hyperledger/besu/evm/operation/AddModOperation.java
  68. 33
      evm/src/main/java/org/hyperledger/besu/evm/operation/CallFOperation.java
  69. 2
      evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java
  70. 2
      evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java
  71. 67
      evm/src/main/java/org/hyperledger/besu/evm/operation/DataCopyOperation.java
  72. 54
      evm/src/main/java/org/hyperledger/besu/evm/operation/DataLoadNOperation.java
  73. 52
      evm/src/main/java/org/hyperledger/besu/evm/operation/DataLoadOperation.java
  74. 47
      evm/src/main/java/org/hyperledger/besu/evm/operation/DataSizeOperation.java
  75. 5
      evm/src/main/java/org/hyperledger/besu/evm/operation/DelegateCallOperation.java
  76. 55
      evm/src/main/java/org/hyperledger/besu/evm/operation/DupNOperation.java
  77. 91
      evm/src/main/java/org/hyperledger/besu/evm/operation/EOFCreateOperation.java
  78. 60
      evm/src/main/java/org/hyperledger/besu/evm/operation/ExchangeOperation.java
  79. 69
      evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCallOperation.java
  80. 9
      evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java
  81. 10
      evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java
  82. 17
      evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java
  83. 73
      evm/src/main/java/org/hyperledger/besu/evm/operation/ExtDelegateCallOperation.java
  84. 73
      evm/src/main/java/org/hyperledger/besu/evm/operation/ExtStaticCallOperation.java
  85. 32
      evm/src/main/java/org/hyperledger/besu/evm/operation/JumpFOperation.java
  86. 14
      evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpIfOperation.java
  87. 12
      evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpOperation.java
  88. 31
      evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpVectorOperation.java
  89. 15
      evm/src/main/java/org/hyperledger/besu/evm/operation/RetFOperation.java
  90. 76
      evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnContractOperation.java
  91. 2
      evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataCopyOperation.java
  92. 56
      evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataLoadOperation.java
  93. 5
      evm/src/main/java/org/hyperledger/besu/evm/operation/StopOperation.java
  94. 59
      evm/src/main/java/org/hyperledger/besu/evm/operation/SwapNOperation.java
  95. 3
      evm/src/main/java/org/hyperledger/besu/evm/operation/TLoadOperation.java
  96. 6
      evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java
  97. 3
      evm/src/main/java/org/hyperledger/besu/evm/processor/ContractCreationProcessor.java
  98. 20
      evm/src/main/java/org/hyperledger/besu/evm/tracing/StandardJsonTracer.java
  99. 95
      evm/src/test/java/org/hyperledger/besu/evm/EOFTestConstants.java
  100. 11
      evm/src/test/java/org/hyperledger/besu/evm/code/CodeFactoryTest.java
  101. Some files were not shown because too many files have changed in this diff Show More

@ -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)

@ -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 {

@ -249,6 +249,13 @@ public interface GenesisConfigOptions {
*/
OptionalLong getPragueTime();
/**
* Gets Prague EOF time.
*
* @return the prague time
*/
OptionalLong getPragueEOFTime();
/**
* Gets future eips time.
*

@ -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

@ -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.
*

@ -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();
}

@ -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);
}

@ -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

@ -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<BigInteger> 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

@ -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,

@ -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)),

@ -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();

@ -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,

@ -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";

@ -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<Path> 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<String, EOFTestCaseSpec> 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<String, EOFTestCaseSpec> 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<String, EOFTestCaseSpec> eofTests) {
List<TestExecutionResult> 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();
}
}

@ -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 = "<address>",
@ -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);
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)

@ -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))),

@ -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 = "<boolean>")
private final Boolean force = false;
// picocli does it magically
@CommandLine.Parameters private final List<String> 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());
}
}
}
}
}

@ -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);
}

@ -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();
};
}

@ -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
""");
}
}

@ -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,

@ -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"
}

@ -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"
}

@ -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"}
]
}

@ -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"}
]
}

@ -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"}
]
}

@ -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"}
]
}

@ -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,12 +263,8 @@ 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 ->
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)) {
@ -261,10 +273,10 @@ def generateTestFiles(FileTree jsonPath, File templateFile, String pathstrip, St
}
}
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)

@ -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<String, TestResult> 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<String, TestVector> vector;
@JsonCreator
public EOFTestCaseSpec(@JsonProperty("vectors") final NavigableMap<String, TestVector> vector) {
this.vector = vector;
}
public NavigableMap<String, TestVector> getVector() {
return vector;
}
}

@ -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());

@ -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 =

@ -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<String> 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<String, EOFTestCaseSpec.TestVector> 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<Object[]> 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());
}
}
}
}

@ -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

@ -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(","));
}

@ -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<Arguments> 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);
}

@ -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<Arguments> 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);
}
}

@ -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<Arguments> 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);
}

@ -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<Code> 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();
}

@ -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);
}
}

@ -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;

@ -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));
}
/**

@ -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 EOFLayout layout = EOFLayout.parseEOF(bytes, !createTransaction);
if (createTransaction) {
layout.containerMode().set(INITCODE);
}
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);
}
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);
}
}

@ -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<Code> 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();
}
}

@ -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;
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.
*

@ -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<Code> 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();
}
}

@ -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<Code> 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;
}
}

@ -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<EOFContainerMode> 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) {
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();
}
}

@ -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),
};
}

@ -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.
*
* <p>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.
*
* <p>`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;
}
}

@ -41,7 +41,7 @@ public class CachedInvalidCodeRule implements ContractValidationRule {
@Override
public Optional<ExceptionalHaltReason> 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 {

@ -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<ExceptionalHaltReason> 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);
}
}

@ -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<MessageFrame> 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();

@ -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;

@ -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;
}

@ -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> 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.
*

@ -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.
*

@ -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.
*

@ -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
*
* <p>Placeholder for new gas schedule items. If Prague finalzies without changes this can be
* removed
*
* <UL>
* <LI>TBD
* </UL>
*/
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;
}
}

@ -14,91 +14,16 @@
*/
package org.hyperledger.besu.evm.internal;
import java.util.Objects;
/** The type Return stack. */
public class ReturnStack extends FlexStack<ReturnStack.ReturnStackItem> {
/** 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.
* The type 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
+ '}';
}
}
public record ReturnStackItem(int codeSectionIndex, int pc) {}
/**
* Max return stack size specified in <a

@ -24,6 +24,7 @@ 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.frame.MessageFrame.State;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import org.apache.tuweni.bytes.Bytes;
@ -40,6 +41,9 @@ public abstract class AbstractCallOperation extends AbstractOperation {
protected static final OperationResult UNDERFLOW_RESPONSE =
new OperationResult(0L, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS);
static final Bytes LEGACY_SUCCESS_STACK_ITEM = BYTES_ONE;
static final Bytes LEGACY_FAILURE_STACK_ITEM = Bytes.EMPTY;
/**
* Instantiates a new Abstract call operation.
*
@ -158,6 +162,15 @@ public abstract class AbstractCallOperation extends AbstractOperation {
return frame.isStatic();
}
/**
* Returns whether the child message call is a delegate call.
*
* @return {@code true} if the child message call is a delegate call; otherwise {@code false}
*/
protected boolean isDelegate() {
return false;
}
@Override
public OperationResult execute(final MessageFrame frame, final EVM evm) {
// manual check because some reads won't come until the "complete" step.
@ -184,9 +197,11 @@ public abstract class AbstractCallOperation extends AbstractOperation {
if (value(frame).compareTo(balance) > 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,8 +212,11 @@ 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
// 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)
@ -213,13 +231,11 @@ public abstract class AbstractCallOperation extends AbstractOperation {
.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);
} else {
return new OperationResult(cost, ExceptionalHaltReason.INVALID_CODE, 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;
}
}
}

@ -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<Code> codeSupplier = () -> getInitCode(frame, evm);
Supplier<Code> 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());
}
/**

@ -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.
*
* <p>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;
};
}
}

@ -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;

@ -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());

@ -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());
Code code = frame.getCode();
if (code.getEofVersion() == 0) {
return InvalidOperation.INVALID_RESULT;
}
/**
* 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) {
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;
} else {
return new OperationResult(callfSuccess.gasCost, exception);
}
}
}

@ -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

@ -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

@ -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);
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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<Code> 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;
}
}

@ -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;
}
}

@ -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();
}
}

@ -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;
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);
}
}

@ -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.
*
@ -64,9 +69,14 @@ public class ExtCodeHashOperation extends AbstractOperation {
final Account account = frame.getWorldUpdater().get(address);
if (account == null || account.isEmpty()) {
frame.pushStackItem(Bytes.EMPTY);
} else {
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);
}
} catch (final UnderflowException ufe) {

@ -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) {

@ -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;
}
}

@ -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;
}
}

@ -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());
Code code = frame.getCode();
if (code.getEofVersion() == 0) {
return InvalidOperation.INVALID_RESULT;
}
/**
* 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) {
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;
} else {
return new OperationResult(jumpfSuccess.gasCost, exception);
}
}
}

@ -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);
}
}
}

@ -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);
}
}

@ -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;
}
}

@ -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;
}
}

@ -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<Code> 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);
}
}

@ -51,6 +51,7 @@ public class ReturnDataCopyOperation extends AbstractOperation {
final Bytes returnData = frame.getReturnData();
final int returnDataLength = returnData.size();
if (frame.getCode().getEofVersion() < 1) {
try {
final long end = Math.addExact(sourceOffset, numBytes);
if (end > returnDataLength) {
@ -59,6 +60,7 @@ public class ReturnDataCopyOperation extends AbstractOperation {
} catch (final ArithmeticException ae) {
return OUT_OF_BOUNDS;
}
}
final long cost = gasCalculator().dataCopyOperationGasCost(frame, memOffset, numBytes);
if (frame.getRemainingGas() < cost) {

@ -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);
}
}

@ -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

@ -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;
}
}

@ -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);
}
}
}

@ -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);
}
}

@ -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());

@ -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) {

@ -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", ""));
}
}

@ -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();
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save