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 a44ddd11e0f..4c9a8f70b64 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 @@ -52,6 +52,7 @@ import org.hyperledger.besu.evm.gascalculator.LondonGasCalculator; import org.hyperledger.besu.evm.gascalculator.PetersburgGasCalculator; import org.hyperledger.besu.evm.gascalculator.ShandongGasCalculator; +import org.hyperledger.besu.evm.gascalculator.ShanghaiGasCalculator; import org.hyperledger.besu.evm.gascalculator.SpuriousDragonGasCalculator; import org.hyperledger.besu.evm.gascalculator.TangerineWhistleGasCalculator; import org.hyperledger.besu.evm.internal.EvmConfiguration; @@ -81,6 +82,7 @@ public abstract class MainnetProtocolSpecs { public static final int FRONTIER_CONTRACT_SIZE_LIMIT = Integer.MAX_VALUE; public static final int SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT = 24576; + public static final int SHANGHAI_CONTRACT_SIZE_LIMIT = 2 * SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT; public static final int SHANDONG_CONTRACT_SIZE_LIMIT = 2 * SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT; private static final Address RIPEMD160_PRECOMPILE = @@ -649,6 +651,7 @@ static ProtocolSpecBuilder shanghaiDefinition( final int stackSizeLimit = configStackSizeLimit.orElse(MessageFrame.DEFAULT_MAX_STACK_SIZE); final BaseFeeMarket baseFeeMarket = getBaseFeeMarket(genesisConfigOptions); + final int contractSizeLimit = configContractSizeLimit.orElse(SHANGHAI_CONTRACT_SIZE_LIMIT); return parisDefinition( chainId, @@ -658,6 +661,7 @@ static ProtocolSpecBuilder shanghaiDefinition( genesisConfigOptions, quorumCompatibilityMode, evmConfiguration) + .gasCalculator(ShanghaiGasCalculator::new) .evmBuilder( (gasCalculator, jdCacheConfig) -> MainnetEVMs.shanghai( @@ -677,6 +681,15 @@ static ProtocolSpecBuilder shanghaiDefinition( stackSizeLimit, baseFeeMarket, CoinbaseFeePriceCalculator.eip1559())) + .contractCreationProcessorBuilder( + (gasCalculator, evm) -> + new ContractCreationProcessor( + gasCalculator, + evm, + true, + List.of(MaxCodeSizeRule.of(contractSizeLimit), PrefixCodeRule.of()), + 1, + SPURIOUS_DRAGON_FORCE_DELETE_WHEN_EMPTY_ADDRESSES)) .withdrawalsProcessorBuilder(WithdrawalsProcessor.AllowedWithdrawalsProcessor::new) .withdrawalsValidatorBuilder(WithdrawalsValidator.AllowedWithdrawals::new) .name("Shanghai"); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/CreateOperationTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/CreateOperationTest.java index 2eb2f4a2b2d..74bc64c0097 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/CreateOperationTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/CreateOperationTest.java @@ -17,6 +17,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSpecs.SHANDONG_CONTRACT_SIZE_LIMIT; +import static org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSpecs.SHANGHAI_CONTRACT_SIZE_LIMIT; import static org.hyperledger.besu.evm.MainnetEVMs.DEV_NET_CHAIN_ID; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; @@ -73,6 +74,7 @@ public class CreateOperationTest { + "F3" // RETURN ); public static final String SENDER = "0xdeadc0de00000000000000000000000000000000"; + private static final int SHANGHAI_CREATE_GAS = 41240; private static final int SHANDONG_CREATE_GAS = 41240; @Test @@ -178,6 +180,63 @@ public void notEnoughValue() { assertThat(messageFrame.getStackItem(0)).isEqualTo(UInt256.ZERO); } + @Test + public void shanghaiMaxInitCodeSizeCreate() { + final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); + final UInt256 memoryLength = UInt256.valueOf(SHANGHAI_CONTRACT_SIZE_LIMIT); + 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 = operation.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 + public void shanghaiMaxInitCodeSizePlus1Create() { + final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); + final UInt256 memoryLength = UInt256.valueOf(SHANGHAI_CONTRACT_SIZE_LIMIT + 1); + 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.shandong(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); + } + @Test public void shandongMaxInitCodeSizeCreate() { final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); 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 252b321402d..967b38356f6 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java @@ -427,6 +427,10 @@ public static void registerParisOperations( registry.put(new PrevRanDaoOperation(gasCalculator)); } + public static EVM shanghai(final BigInteger chainId, final EvmConfiguration evmConfiguration) { + return shanghai(new LondonGasCalculator(), chainId, evmConfiguration); + } + public static EVM shanghai( final GasCalculator gasCalculator, final BigInteger chainId, diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/ShanghaiGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/ShanghaiGasCalculator.java new file mode 100644 index 00000000000..d313d54b759 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/ShanghaiGasCalculator.java @@ -0,0 +1,48 @@ +package org.hyperledger.besu.evm.gascalculator; +/* + * 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 + */ + +import static org.hyperledger.besu.evm.internal.Words.clampedAdd; +import static org.hyperledger.besu.evm.internal.Words.clampedToLong; + +import org.hyperledger.besu.evm.frame.MessageFrame; + +import org.apache.tuweni.bytes.Bytes; + +public class ShanghaiGasCalculator extends LondonGasCalculator { + + private static final long INIT_CODE_COST = 2L; + + @Override + public long transactionIntrinsicGasCost(final Bytes payload, final boolean isContractCreation) { + long intrinsicGasCost = super.transactionIntrinsicGasCost(payload, isContractCreation); + if (isContractCreation) { + return clampedAdd(intrinsicGasCost, calculateInitGasCost(payload.size())); + } else { + return intrinsicGasCost; + } + } + + @Override + public long createOperationGasCost(final MessageFrame frame) { + final long initCodeLength = clampedToLong(frame.getStackItem(2)); + return clampedAdd(super.createOperationGasCost(frame), calculateInitGasCost(initCodeLength)); + } + + private static long calculateInitGasCost(final long initCodeLength) { + final int dataLength = (int) Math.ceil(initCodeLength / 32.0); + return dataLength * INIT_CODE_COST; + } +}