From 80bebf2b7357542fc442c19547c80c915b8081dc Mon Sep 17 00:00:00 2001 From: Danno Ferrin Date: Fri, 13 Jan 2023 07:58:32 -0700 Subject: [PATCH] EIP-3860 Initcode Size Reference Test fixes (#4911) * EIP-3860 Initcode Size Reference Test fixes Fix corner cases around initcode size checking in reference tests. Signed-off-by: Danno Ferrin --- .../mainnet/MainnetProtocolSpecs.java | 28 ++- .../mainnet/MainnetTransactionValidator.java | 17 +- .../transaction/TransactionInvalidReason.java | 1 + .../MainnetTransactionValidatorTest.java | 50 ++++- .../org/hyperledger/besu/evm/MainnetEVMs.java | 9 +- .../operation/AbstractCreateOperation.java | 10 +- .../besu/evm/operation/Create2Operation.java | 4 +- .../besu/evm/operation/CreateOperation.java | 4 +- .../evm/operations/Create2OperationTest.java | 181 +++++++++++++++--- .../evm/operations/CreateOperationTest.java | 14 +- 10 files changed, 250 insertions(+), 68 deletions(-) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java index 5d48d2fd6d..1de90fd339 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java @@ -36,10 +36,8 @@ import org.hyperledger.besu.ethereum.privacy.PrivateTransactionProcessor; import org.hyperledger.besu.ethereum.privacy.PrivateTransactionValidator; import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; -import org.hyperledger.besu.evm.EvmSpecVersion; import org.hyperledger.besu.evm.MainnetEVMs; import org.hyperledger.besu.evm.account.MutableAccount; -import org.hyperledger.besu.evm.contractvalidation.CachedInvalidCodeRule; import org.hyperledger.besu.evm.contractvalidation.MaxCodeSizeRule; import org.hyperledger.besu.evm.contractvalidation.PrefixCodeRule; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -532,7 +530,8 @@ public abstract class MainnetProtocolSpecs { TransactionType.FRONTIER, TransactionType.ACCESS_LIST, TransactionType.EIP1559), - quorumCompatibilityMode)) + quorumCompatibilityMode, + Integer.MAX_VALUE)) .transactionProcessorBuilder( (gasCalculator, transactionValidator, @@ -655,9 +654,6 @@ public abstract class MainnetProtocolSpecs { ? FeeMarket.zeroBaseFee(londonForkBlockNumber) : FeeMarket.london(londonForkBlockNumber, genesisConfigOptions.getBaseFeePerGas()); - // constant for max initcode size for EIP-3860 limit and meter initcode - final int contractSizeLimit = configContractSizeLimit.orElse(SHANGHAI_INIT_CODE_SIZE_LIMIT); - return parisDefinition( chainId, configContractSizeLimit, @@ -690,17 +686,19 @@ public abstract class MainnetProtocolSpecs { londonFeeMarket, CoinbaseFeePriceCalculator.eip1559())) // Contract creation rules for EIP-3860 Limit and meter intitcode - .contractCreationProcessorBuilder( - (gasCalculator, evm) -> - new ContractCreationProcessor( + .transactionValidatorBuilder( + gasCalculator -> + new MainnetTransactionValidator( gasCalculator, - evm, + londonFeeMarket, true, - List.of( - MaxCodeSizeRule.of(contractSizeLimit), - CachedInvalidCodeRule.of(EvmSpecVersion.SHANGHAI)), - 1, - SPURIOUS_DRAGON_FORCE_DELETE_WHEN_EMPTY_ADDRESSES)) + chainId, + Set.of( + TransactionType.FRONTIER, + TransactionType.ACCESS_LIST, + TransactionType.EIP1559), + quorumCompatibilityMode, + SHANGHAI_INIT_CODE_SIZE_LIMIT)) .name("Shanghai"); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java index 2d6783e3bf..e9781b4b47 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java @@ -53,6 +53,8 @@ public class MainnetTransactionValidator { private final Set acceptedTransactionTypes; private final boolean goQuorumCompatibilityMode; + private final int maxInitcodeSize; + public MainnetTransactionValidator( final GasCalculator gasCalculator, final boolean checkSignatureMalleability, @@ -78,7 +80,8 @@ public class MainnetTransactionValidator { checkSignatureMalleability, chainId, acceptedTransactionTypes, - quorumCompatibilityMode); + quorumCompatibilityMode, + Integer.MAX_VALUE); } public MainnetTransactionValidator( @@ -87,13 +90,15 @@ public class MainnetTransactionValidator { final boolean checkSignatureMalleability, final Optional chainId, final Set acceptedTransactionTypes, - final boolean goQuorumCompatibilityMode) { + final boolean goQuorumCompatibilityMode, + final int maxInitcodeSize) { this.gasCalculator = gasCalculator; this.feeMarket = feeMarket; this.disallowSignatureMalleability = checkSignatureMalleability; this.chainId = chainId; this.acceptedTransactionTypes = acceptedTransactionTypes; this.goQuorumCompatibilityMode = goQuorumCompatibilityMode; + this.maxInitcodeSize = maxInitcodeSize; } /** @@ -184,6 +189,14 @@ public class MainnetTransactionValidator { intrinsicGasCost, transaction.getGasLimit())); } + if (transaction.isContractCreation() && transaction.getPayload().size() > maxInitcodeSize) { + return ValidationResult.invalid( + TransactionInvalidReason.INITCODE_TOO_LARGE, + String.format( + "Initcode size of %d exceeds maximum size of %s", + transaction.getPayload().size(), maxInitcodeSize)); + } + return ValidationResult.valid(); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java index c66b1aaa6b..d4297b7fa3 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java @@ -37,6 +37,7 @@ public enum TransactionInvalidReason { TRANSACTION_ALREADY_KNOWN, TRANSACTION_REPLACEMENT_UNDERPRICED, MAX_PRIORITY_FEE_PER_GAS_EXCEEDS_MAX_FEE_PER_GAS, + INITCODE_TOO_LARGE, // Private Transaction Invalid Reasons PRIVATE_TRANSACTION_FAILED, PRIVATE_NONCE_TOO_LOW, diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidatorTest.java index 0fb4016acc..a42d41b1fa 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidatorTest.java @@ -45,8 +45,8 @@ import org.hyperledger.besu.plugin.data.TransactionType; import java.math.BigInteger; import java.util.Optional; import java.util.Set; +import java.util.function.Supplier; -import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import org.apache.tuweni.bytes.Bytes; import org.junit.Test; @@ -259,7 +259,8 @@ public class MainnetTransactionValidatorTest { false, Optional.of(BigInteger.ONE), Set.of(TransactionType.values()), - defaultGoQuorumCompatibilityMode); + defaultGoQuorumCompatibilityMode, + Integer.MAX_VALUE); validator.setTransactionFilter(transactionFilter(true)); final Transaction transaction = @@ -342,7 +343,8 @@ public class MainnetTransactionValidatorTest { false, Optional.of(BigInteger.ONE), Set.of(TransactionType.FRONTIER), - defaultGoQuorumCompatibilityMode); + defaultGoQuorumCompatibilityMode, + Integer.MAX_VALUE); final MainnetTransactionValidator eip1559Validator = new MainnetTransactionValidator( @@ -351,7 +353,8 @@ public class MainnetTransactionValidatorTest { false, Optional.of(BigInteger.ONE), Set.of(TransactionType.FRONTIER, TransactionType.EIP1559), - defaultGoQuorumCompatibilityMode); + defaultGoQuorumCompatibilityMode, + Integer.MAX_VALUE); final Transaction transaction = new TransactionTestFixture() @@ -383,7 +386,8 @@ public class MainnetTransactionValidatorTest { false, Optional.of(BigInteger.ONE), Set.of(TransactionType.FRONTIER, TransactionType.EIP1559), - defaultGoQuorumCompatibilityMode); + defaultGoQuorumCompatibilityMode, + Integer.MAX_VALUE); final Transaction transaction = new TransactionTestFixture() .type(TransactionType.EIP1559) @@ -406,7 +410,8 @@ public class MainnetTransactionValidatorTest { false, Optional.of(BigInteger.ONE), Set.of(TransactionType.FRONTIER, TransactionType.EIP1559), - defaultGoQuorumCompatibilityMode); + defaultGoQuorumCompatibilityMode, + Integer.MAX_VALUE); final Transaction transaction = new TransactionTestFixture() .type(TransactionType.EIP1559) @@ -428,7 +433,8 @@ public class MainnetTransactionValidatorTest { false, Optional.of(BigInteger.ONE), Set.of(TransactionType.FRONTIER, TransactionType.EIP1559), - defaultGoQuorumCompatibilityMode); + defaultGoQuorumCompatibilityMode, + Integer.MAX_VALUE); final Transaction transaction = new TransactionTestFixture() .maxPriorityFeePerGas(Optional.of(Wei.of(1))) @@ -452,7 +458,8 @@ public class MainnetTransactionValidatorTest { false, Optional.of(BigInteger.ONE), Set.of(TransactionType.FRONTIER, TransactionType.EIP1559), - defaultGoQuorumCompatibilityMode); + defaultGoQuorumCompatibilityMode, + Integer.MAX_VALUE); final Transaction transaction = new TransactionTestFixture() .maxPriorityFeePerGas(Optional.of(Wei.of(1))) @@ -468,6 +475,33 @@ public class MainnetTransactionValidatorTest { .isEqualTo(ValidationResult.valid()); } + @Test + public void shouldRejectTooLargeInitcode() { + final MainnetTransactionValidator validator = + new MainnetTransactionValidator( + gasCalculator, + FeeMarket.london(0L), + false, + Optional.of(BigInteger.ONE), + Set.of(TransactionType.FRONTIER, TransactionType.EIP1559), + defaultGoQuorumCompatibilityMode, + 0xc000); + + var bigPayload = + new TransactionTestFixture() + .payload(Bytes.fromHexString("0x" + "00".repeat(0xc001))) + .chainId(Optional.of(BigInteger.ONE)) + .createTransaction(senderKeys); + var validationResult = + validator.validate(bigPayload, Optional.empty(), transactionValidationParams); + + assertThat(validationResult.isValid()).isFalse(); + assertThat(validationResult.getInvalidReason()) + .isEqualTo(TransactionInvalidReason.INITCODE_TOO_LARGE); + assertThat(validationResult.getErrorMessage()) + .isEqualTo("Initcode size of 49153 exceeds maximum size of 49152"); + } + @Test public void goQuorumCompatibilityModeRejectNonZeroGasPrice() { final MainnetTransactionValidator validator = diff --git a/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java b/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java index 936cb7c585..501510c673 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java @@ -123,6 +123,9 @@ public class MainnetEVMs { public static final BigInteger DEV_NET_CHAIN_ID = BigInteger.valueOf(1337); + public static final int SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT = 0x6000; + public static final int SHANGHAI_INIT_CODE_SIZE_LIMIT = 2 * SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT; + private MainnetEVMs() { // utility class } @@ -209,7 +212,7 @@ public class MainnetEVMs { registry.put(new InvalidOperation(gasCalculator)); registry.put(new StopOperation(gasCalculator)); registry.put(new SelfDestructOperation(gasCalculator)); - registry.put(new CreateOperation(gasCalculator)); + registry.put(new CreateOperation(gasCalculator, Integer.MAX_VALUE)); registry.put(new CallOperation(gasCalculator)); registry.put(new CallCodeOperation(gasCalculator)); @@ -317,7 +320,7 @@ public class MainnetEVMs { public static void registerConstantinopleOperations( final OperationRegistry registry, final GasCalculator gasCalculator) { registerByzantiumOperations(registry, gasCalculator); - registry.put(new Create2Operation(gasCalculator)); + registry.put(new Create2Operation(gasCalculator, Integer.MAX_VALUE)); registry.put(new SarOperation(gasCalculator)); registry.put(new ShlOperation(gasCalculator)); registry.put(new ShrOperation(gasCalculator)); @@ -461,6 +464,8 @@ public class MainnetEVMs { final BigInteger chainID) { registerParisOperations(registry, gasCalculator, chainID); registry.put(new Push0Operation(gasCalculator)); + registry.put(new CreateOperation(gasCalculator, SHANGHAI_INIT_CODE_SIZE_LIMIT)); + registry.put(new Create2Operation(gasCalculator, SHANGHAI_INIT_CODE_SIZE_LIMIT)); } public static EVM cancun( diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java index e91399c56d..e7913fc808 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java @@ -36,13 +36,17 @@ public abstract class AbstractCreateOperation extends AbstractOperation { protected static final OperationResult UNDERFLOW_RESPONSE = new OperationResult(0L, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + protected int maxInitcodeSize; + protected AbstractCreateOperation( final int opcode, final String name, final int stackItemsConsumed, final int stackItemsProduced, - final GasCalculator gasCalculator) { + final GasCalculator gasCalculator, + final int maxInitcodeSize) { super(opcode, name, stackItemsConsumed, stackItemsProduced, gasCalculator); + this.maxInitcodeSize = maxInitcodeSize; } @Override @@ -74,6 +78,10 @@ public abstract class AbstractCreateOperation extends AbstractOperation { 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); + } final Bytes inputData = frame.readMemory(inputOffset, inputSize); Code code = evm.getCode(Hash.hash(inputData), inputData); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java index 2546073293..f32359adc5 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java @@ -29,8 +29,8 @@ public class Create2Operation extends AbstractCreateOperation { private static final Bytes PREFIX = Bytes.fromHexString("0xFF"); - public Create2Operation(final GasCalculator gasCalculator) { - super(0xF5, "CREATE2", 4, 1, gasCalculator); + public Create2Operation(final GasCalculator gasCalculator, final int maxInitcodeSize) { + super(0xF5, "CREATE2", 4, 1, gasCalculator, maxInitcodeSize); } @Override diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java index 079b66f338..2cf784cdfa 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java @@ -21,8 +21,8 @@ import org.hyperledger.besu.evm.gascalculator.GasCalculator; public class CreateOperation extends AbstractCreateOperation { - public CreateOperation(final GasCalculator gasCalculator) { - super(0xF0, "CREATE", 3, 1, gasCalculator); + public CreateOperation(final GasCalculator gasCalculator, final int maxInitcodeSize) { + super(0xF0, "CREATE", 3, 1, gasCalculator, maxInitcodeSize); } @Override diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/Create2OperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/Create2OperationTest.java index 15792d7c67..32816bc134 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/Create2OperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/Create2OperationTest.java @@ -15,6 +15,8 @@ package org.hyperledger.besu.evm.operations; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.MainnetEVMs.DEV_NET_CHAIN_ID; +import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.CODE_TOO_LARGE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -23,43 +25,65 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.MainnetEVMs; import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.code.CodeFactory; import org.hyperledger.besu.evm.frame.BlockValues; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.log.Log; import org.hyperledger.besu.evm.operation.Create2Operation; import org.hyperledger.besu.evm.operation.Operation.OperationResult; +import org.hyperledger.besu.evm.processor.ContractCreationProcessor; +import org.hyperledger.besu.evm.tracing.OperationTracer; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.evm.worldstate.WrappedEvmAccount; import java.util.ArrayDeque; +import java.util.List; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; -@RunWith(Parameterized.class) public class Create2OperationTest { - private final String sender; - private final String salt; - private final String code; - private final String expectedAddress; - private final int expectedGas; private MessageFrame messageFrame; private final WorldUpdater worldUpdater = mock(WorldUpdater.class); private final WrappedEvmAccount account = mock(WrappedEvmAccount.class); private final MutableAccount mutableAccount = mock(MutableAccount.class); private final EVM evm = mock(EVM.class); + private final WrappedEvmAccount newAccount = mock(WrappedEvmAccount.class); + private final MutableAccount newMutableAccount = mock(MutableAccount.class); + private final Create2Operation operation = - new Create2Operation(new ConstantinopleGasCalculator()); + new Create2Operation(new ConstantinopleGasCalculator(), Integer.MAX_VALUE); + + private final Create2Operation maxInitCodeOperation = + new Create2Operation( + new ConstantinopleGasCalculator(), MainnetEVMs.SHANGHAI_INIT_CODE_SIZE_LIMIT); + + private static final String TOPIC = + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; // 32 FFs + private static final Bytes SIMPLE_CREATE = + Bytes.fromHexString( + "0x" + + "7f" // push32 + + TOPIC + + "6000" // PUSH1 0x00 + + "6000" // PUSH1 0x00 + + "A1" // LOG1 + + "6000" // PUSH1 0x00 + + "6000" // PUSH1 0x00 + + "F3" // RETURN + ); + public static final String SENDER = "0xdeadc0de00000000000000000000000000000000"; + private static final int SHANGHAI_CREATE_GAS = 41240 + (0xc000 / 32) * 6; - @Parameters(name = "sender: {0}, salt: {1}, code: {2}") public static Object[][] params() { return new Object[][] { { @@ -114,21 +138,8 @@ public class Create2OperationTest { }; } - public Create2OperationTest( - final String sender, - final String salt, - final String code, - final String expectedAddress, - final int expectedGas) { - this.sender = sender; - this.salt = salt; - this.code = code; - this.expectedAddress = expectedAddress; - this.expectedGas = expectedGas; - } + public void setUp(final String sender, final String salt, final String code) { - @Before - public void setUp() { final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); final Bytes codeBytes = Bytes.fromHexString(code); final UInt256 memoryLength = UInt256.valueOf(codeBytes.size()); @@ -171,16 +182,124 @@ public class Create2OperationTest { invocation.getArgument(1), invocation.getArgument(0), 0, true)); } - @Test - public void shouldCalculateAddress() { + @ParameterizedTest + @MethodSource("params") + void shouldCalculateAddress( + final String sender, + final String salt, + final String code, + final String expectedAddress, + final int ignoredExpectedGas) { + setUp(sender, salt, code); final Address targetContractAddress = operation.targetContractAddress(messageFrame); assertThat(targetContractAddress).isEqualTo(Address.fromHexString(expectedAddress)); } - @Test - public void shouldCalculateGasPrice() { + @ParameterizedTest + @MethodSource("params") + void shouldCalculateGasPrice( + final String sender, + final String salt, + final String code, + final String ignoredExpectedAddress, + final int expectedGas) { + setUp(sender, salt, code); final OperationResult result = operation.execute(messageFrame, evm); assertThat(result.getHaltReason()).isNull(); assertThat(result.getGasCost()).isEqualTo(expectedGas); } + + @Test + void shanghaiMaxInitCodeSizeCreate() { + final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); + final UInt256 memoryLength = UInt256.fromHexString("0xc000"); + final ArrayDeque messageFrameStack = new ArrayDeque<>(); + final MessageFrame messageFrame = + testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1, messageFrameStack); + + when(account.getMutable()).thenReturn(mutableAccount); + when(account.getNonce()).thenReturn(55L); + when(mutableAccount.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getSenderAccount(any())).thenReturn(account); + when(worldUpdater.getOrCreate(any())).thenReturn(newAccount); + when(newAccount.getMutable()).thenReturn(newMutableAccount); + when(newMutableAccount.getCode()).thenReturn(Bytes.EMPTY); + when(worldUpdater.updater()).thenReturn(worldUpdater); + + final EVM evm = MainnetEVMs.shanghai(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); + var result = maxInitCodeOperation.execute(messageFrame, evm); + final MessageFrame createFrame = messageFrameStack.peek(); + final ContractCreationProcessor ccp = + new ContractCreationProcessor(evm.getGasCalculator(), evm, false, List.of(), 0, List.of()); + ccp.process(createFrame, OperationTracer.NO_TRACING); + + final Log log = createFrame.getLogs().get(0); + final String calculatedTopic = log.getTopics().get(0).toUnprefixedHexString(); + assertThat(calculatedTopic).isEqualTo(TOPIC); + assertThat(result.getGasCost()).isEqualTo(SHANGHAI_CREATE_GAS); + } + + @Test + void shanghaiMaxInitCodeSizePlus1Create() { + final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); + final UInt256 memoryLength = UInt256.fromHexString("0xc001"); + final ArrayDeque messageFrameStack = new ArrayDeque<>(); + final MessageFrame messageFrame = + testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1, messageFrameStack); + + when(account.getMutable()).thenReturn(mutableAccount); + when(account.getNonce()).thenReturn(55L); + when(mutableAccount.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getSenderAccount(any())).thenReturn(account); + when(worldUpdater.getOrCreate(any())).thenReturn(newAccount); + when(newAccount.getMutable()).thenReturn(newMutableAccount); + when(newMutableAccount.getCode()).thenReturn(Bytes.EMPTY); + when(worldUpdater.updater()).thenReturn(worldUpdater); + + final EVM evm = MainnetEVMs.shanghai(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); + var result = maxInitCodeOperation.execute(messageFrame, evm); + assertThat(result.getHaltReason()).isEqualTo(CODE_TOO_LARGE); + } + + @NotNull + private MessageFrame testMemoryFrame( + final UInt256 memoryOffset, + final UInt256 memoryLength, + final UInt256 value, + final int depth, + final ArrayDeque messageFrameStack) { + final MessageFrame messageFrame = + MessageFrame.builder() + .type(MessageFrame.Type.CONTRACT_CREATION) + .contract(Address.ZERO) + .inputData(Bytes.EMPTY) + .sender(Address.fromHexString(SENDER)) + .value(Wei.ZERO) + .apparentValue(Wei.ZERO) + .code(CodeFactory.createCode(SIMPLE_CREATE, Hash.hash(SIMPLE_CREATE), 0, true)) + .depth(depth) + .completer(__ -> {}) + .address(Address.fromHexString(SENDER)) + .blockHashLookup(n -> Hash.hash(Bytes.ofUnsignedLong(n))) + .blockValues(mock(BlockValues.class)) + .gasPrice(Wei.ZERO) + .messageFrameStack(messageFrameStack) + .miningBeneficiary(Address.ZERO) + .originator(Address.ZERO) + .initialGas(100000L) + .worldUpdater(worldUpdater) + .build(); + messageFrame.pushStackItem(Bytes.EMPTY); + messageFrame.pushStackItem(memoryLength); + messageFrame.pushStackItem(memoryOffset); + messageFrame.pushStackItem(value); + messageFrame.expandMemory(0, 500); + messageFrame.writeMemory( + memoryOffset.trimLeadingZeros().toInt(), SIMPLE_CREATE.size(), SIMPLE_CREATE); + return messageFrame; + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/CreateOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/CreateOperationTest.java index 6463db4d37..2f774717ae 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/CreateOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/CreateOperationTest.java @@ -17,6 +17,7 @@ package org.hyperledger.besu.evm.operations; import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.evm.MainnetEVMs.DEV_NET_CHAIN_ID; +import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.CODE_TOO_LARGE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -54,7 +55,11 @@ public class CreateOperationTest { private final WrappedEvmAccount newAccount = mock(WrappedEvmAccount.class); private final MutableAccount mutableAccount = mock(MutableAccount.class); private final MutableAccount newMutableAccount = mock(MutableAccount.class); - private final CreateOperation operation = new CreateOperation(new ConstantinopleGasCalculator()); + private final CreateOperation operation = + new CreateOperation(new ConstantinopleGasCalculator(), Integer.MAX_VALUE); + private final CreateOperation maxInitCodeOperation = + new CreateOperation( + new ConstantinopleGasCalculator(), MainnetEVMs.SHANGHAI_INIT_CODE_SIZE_LIMIT); private static final String TOPIC = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; // 32 FFs @@ -196,7 +201,7 @@ public class CreateOperationTest { when(worldUpdater.updater()).thenReturn(worldUpdater); final EVM evm = MainnetEVMs.shanghai(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); - var result = operation.execute(messageFrame, evm); + var result = maxInitCodeOperation.execute(messageFrame, evm); final MessageFrame createFrame = messageFrameStack.peek(); final ContractCreationProcessor ccp = new ContractCreationProcessor(evm.getGasCalculator(), evm, false, List.of(), 0, List.of()); @@ -228,9 +233,8 @@ public class CreateOperationTest { when(worldUpdater.updater()).thenReturn(worldUpdater); final EVM evm = MainnetEVMs.shanghai(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); - var result = operation.execute(messageFrame, evm); - assertThat(messageFrame.getStackItem(0)).isEqualTo(UInt256.ZERO); - assertThat(result.getGasCost()).isEqualTo(SHANGHAI_CREATE_GAS); + var result = maxInitCodeOperation.execute(messageFrame, evm); + assertThat(result.getHaltReason()).isEqualTo(CODE_TOO_LARGE); } @NotNull