diff --git a/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/execution/EvmProperties.java b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/execution/EvmProperties.java index 3237e510a7af..ac505a2f7b57 100644 --- a/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/execution/EvmProperties.java +++ b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/execution/EvmProperties.java @@ -35,4 +35,11 @@ public interface EvmProperties { int maxGasRefundPercentage(); boolean isRedirectTokenCallsEnabled(); + + boolean isLazyCreationEnabled(); + + /** + * Enables or disables Create2 operation. + */ + boolean isCreate2Enabled(); } diff --git a/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/execution/HederaEvmMessageCallProcessor.java b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/execution/HederaEvmMessageCallProcessor.java index fcb0fd49924d..7bc382eaf745 100644 --- a/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/execution/HederaEvmMessageCallProcessor.java +++ b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/execution/HederaEvmMessageCallProcessor.java @@ -22,7 +22,6 @@ import static org.hyperledger.besu.evm.frame.MessageFrame.State.REVERT; import com.hedera.node.app.service.evm.store.contracts.AbstractLedgerEvmWorldUpdater; -import com.hedera.node.app.service.evm.store.contracts.HederaEvmStackedWorldStateUpdater; import com.hedera.node.app.service.evm.store.contracts.precompile.EvmHTSPrecompiledContract; import java.util.HashMap; import java.util.Map; @@ -84,7 +83,7 @@ public void start(final MessageFrame frame, final OperationTracer operationTrace protected void executeHederaPrecompile( final PrecompiledContract contract, final MessageFrame frame, final OperationTracer operationTracer) { if (contract instanceof EvmHTSPrecompiledContract htsPrecompile) { - var updater = (HederaEvmStackedWorldStateUpdater) frame.getWorldUpdater(); + var updater = (AbstractLedgerEvmWorldUpdater) frame.getWorldUpdater(); final var costedResult = htsPrecompile.computeCosted( frame.getInputData(), frame, diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/contracts/operation/AbstractRecordingCreateOperation.java b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/operations/AbstractEvmRecordingCreateOperation.java similarity index 53% rename from hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/contracts/operation/AbstractRecordingCreateOperation.java rename to hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/operations/AbstractEvmRecordingCreateOperation.java index a11ab2210d1e..91a6bafcca1f 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/contracts/operation/AbstractRecordingCreateOperation.java +++ b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/operations/AbstractEvmRecordingCreateOperation.java @@ -14,32 +14,11 @@ * limitations under the License. */ -package com.hedera.node.app.service.mono.contracts.operation; - -import static com.hedera.node.app.service.mono.context.BasicTransactionContext.EMPTY_KEY; -import static com.hedera.node.app.service.mono.ledger.properties.AccountProperty.ETHEREUM_NONCE; -import static com.hedera.node.app.service.mono.ledger.properties.AccountProperty.IS_SMART_CONTRACT; -import static com.hedera.node.app.service.mono.ledger.properties.AccountProperty.KEY; -import static com.hedera.node.app.service.mono.state.EntityCreator.EMPTY_MEMO; -import static com.hedera.node.app.service.mono.state.EntityCreator.NO_CUSTOM_FEES; -import static com.hedera.node.app.service.mono.txns.contract.ContractCreateTransitionLogic.STANDIN_CONTRACT_ID_KEY; -import static com.hedera.node.app.service.mono.utils.EntityIdUtils.accountIdFromEvmAddress; +package com.hedera.node.app.service.evm.contracts.operations; + import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.ILLEGAL_STATE_CHANGE; import static org.hyperledger.besu.evm.internal.Words.clampedToLong; -import com.hedera.node.app.service.mono.context.SideEffectsTracker; -import com.hedera.node.app.service.mono.context.properties.GlobalDynamicProperties; -import com.hedera.node.app.service.mono.records.RecordsHistorian; -import com.hedera.node.app.service.mono.state.EntityCreator; -import com.hedera.node.app.service.mono.store.contracts.HederaStackedWorldStateUpdater; -import com.hedera.node.app.service.mono.store.contracts.precompile.SyntheticTxnFactory; -import com.hedera.node.app.service.mono.utils.EntityIdUtils; -import com.hedera.node.app.service.mono.utils.SidecarUtils; -import com.hedera.services.stream.proto.SidecarType; -import com.hederahashgraph.api.proto.java.AccountID; -import com.hederahashgraph.api.proto.java.ContractID; -import java.util.Collections; -import java.util.List; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; import org.hyperledger.besu.datatypes.Address; @@ -55,35 +34,29 @@ import org.hyperledger.besu.evm.operation.AbstractOperation; import org.hyperledger.besu.evm.operation.Operation; -public abstract class AbstractRecordingCreateOperation extends AbstractOperation { - private static final int MAX_STACK_DEPTH = 1024; +/** + * Common logic for Hedera Create or Create2 operation. + *
Externalizing child records for newly created contract as well as sidecar creation using a {@link CreateOperationExternalizer} + */ +public abstract class AbstractEvmRecordingCreateOperation extends AbstractOperation { + protected static final int MAX_STACK_DEPTH = 1024; protected static final Operation.OperationResult INVALID_RESPONSE = new OperationResult(0L, ExceptionalHaltReason.INVALID_OPERATION); protected static final Operation.OperationResult UNDERFLOW_RESPONSE = new Operation.OperationResult(0, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + private final CreateOperationExternalizer createOperationExternalizer; - protected final GlobalDynamicProperties dynamicProperties; - private final EntityCreator creator; - private final SyntheticTxnFactory syntheticTxnFactory; - private final RecordsHistorian recordsHistorian; - - protected AbstractRecordingCreateOperation( + protected AbstractEvmRecordingCreateOperation( final int opcode, final String name, final int stackItemsConsumed, final int stackItemsProduced, final int opSize, final GasCalculator gasCalculator, - final EntityCreator creator, - final SyntheticTxnFactory syntheticTxnFactory, - final RecordsHistorian recordsHistorian, - final GlobalDynamicProperties dynamicProperties) { + final CreateOperationExternalizer createOperationExternalizer) { super(opcode, name, stackItemsConsumed, stackItemsProduced, opSize, gasCalculator); - this.creator = creator; - this.recordsHistorian = recordsHistorian; - this.syntheticTxnFactory = syntheticTxnFactory; - this.dynamicProperties = dynamicProperties; + this.createOperationExternalizer = createOperationExternalizer; } @Override @@ -121,7 +94,7 @@ public Operation.OperationResult execute(final MessageFrame frame, final EVM evm return new Operation.OperationResult(cost, null); } - static Operation.OperationResult haltWith(final long cost, final ExceptionalHaltReason reason) { + public static Operation.OperationResult haltWith(final long cost, final ExceptionalHaltReason reason) { return new Operation.OperationResult(cost, reason); } @@ -131,7 +104,7 @@ static Operation.OperationResult haltWith(final long cost, final ExceptionalHalt protected abstract Address targetContractAddress(MessageFrame frame); - private void fail(final MessageFrame frame) { + protected void fail(final MessageFrame frame) { final long inputOffset = clampedToLong(frame.getStackItem(1)); final long inputSize = clampedToLong(frame.getStackItem(2)); frame.readMutableMemory(inputOffset, inputSize); @@ -157,14 +130,9 @@ private void spawnChildMessage(final MessageFrame frame) { final Address contractAddress = targetContractAddress(frame); - if (!dynamicProperties.isLazyCreationEnabled()) { - final var hollowAccountID = - matchingHollowAccountId((HederaStackedWorldStateUpdater) frame.getWorldUpdater(), contractAddress); - - if (hollowAccountID != null) { - fail(frame); - return; - } + if (createOperationExternalizer.shouldFailBasedOnLazyCreation(frame, contractAddress)) { + fail(frame); + return; } final long childGasStipend = gasCalculator().gasAvailableForChildCreate(frame.getRemainingGas()); @@ -210,40 +178,7 @@ private void complete(final MessageFrame frame, final MessageFrame childFrame) { if (childFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { frame.mergeWarmedUpFields(childFrame); frame.pushStackItem(Words.fromAddress(childFrame.getContractAddress())); - - // Add an in-progress record so that if everything succeeds, we can externalize the - // newly - // created contract in the record stream with both its 0.0.X id and its EVM address. - // C.f. https://github.com/hashgraph/hedera-services/issues/2807 - final var updater = (HederaStackedWorldStateUpdater) frame.getWorldUpdater(); - final var sideEffects = new SideEffectsTracker(); - - ContractID createdContractId; - final var hollowAccountID = matchingHollowAccountId(updater, childFrame.getContractAddress()); - - // if a hollow account exists at the alias address, finalize it to a contract - if (hollowAccountID != null) { - finalizeHollowAccountIntoContract(hollowAccountID, updater); - createdContractId = EntityIdUtils.asContract(hollowAccountID); - } else { - createdContractId = updater.idOfLastNewAddress(); - } - - sideEffects.trackNewContract(createdContractId, childFrame.getContractAddress()); - final var childRecord = creator.createSuccessfulSyntheticRecord(NO_CUSTOM_FEES, sideEffects, EMPTY_MEMO); - childRecord.onlyExternalizeIfSuccessful(); - final var opCustomizer = updater.customizerForPendingCreation(); - final var syntheticOp = syntheticTxnFactory.contractCreation(opCustomizer); - if (dynamicProperties.enabledSidecars().contains(SidecarType.CONTRACT_BYTECODE)) { - final var contractBytecodeSidecar = SidecarUtils.createContractBytecodeSidecarFrom( - createdContractId, - childFrame.getCode().getContainerBytes().toArrayUnsafe(), - updater.get(childFrame.getContractAddress()).getCode().toArrayUnsafe()); - updater.manageInProgressRecord( - recordsHistorian, childRecord, syntheticOp, List.of(contractBytecodeSidecar)); - } else { - updater.manageInProgressRecord(recordsHistorian, childRecord, syntheticOp, Collections.emptyList()); - } + createOperationExternalizer.externalize(frame, childFrame); } else { frame.setReturnData(childFrame.getOutputData()); frame.pushStackItem(UInt256.ZERO); @@ -252,29 +187,4 @@ private void complete(final MessageFrame frame, final MessageFrame childFrame) { final int currentPC = frame.getPC(); frame.setPC(currentPC + 1); } - - private AccountID matchingHollowAccountId(HederaStackedWorldStateUpdater updater, Address contract) { - final var accountID = accountIdFromEvmAddress(updater.aliases().resolveForEvm(contract)); - final var trackingAccounts = updater.trackingAccounts(); - if (trackingAccounts.contains(accountID)) { - final var accountKey = updater.trackingAccounts().get(accountID, KEY); - return EMPTY_KEY.equals(accountKey) ? accountID : null; - } else { - return null; - } - } - - private void finalizeHollowAccountIntoContract(AccountID hollowAccountID, HederaStackedWorldStateUpdater updater) { - // reclaim the id for the contract - updater.reclaimLatestContractId(); - - // update the hollow account to be a contract - updater.trackingAccounts().set(hollowAccountID, IS_SMART_CONTRACT, true); - - // update the hollow account key to be the default contract key - updater.trackingAccounts().set(hollowAccountID, KEY, STANDIN_CONTRACT_ID_KEY); - - // set initial contract nonce to 1 - updater.trackingAccounts().set(hollowAccountID, ETHEREUM_NONCE, 1L); - } } diff --git a/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/operations/CreateOperationExternalizer.java b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/operations/CreateOperationExternalizer.java new file mode 100644 index 000000000000..f852a354fadd --- /dev/null +++ b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/operations/CreateOperationExternalizer.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * 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. + */ + +package com.hedera.node.app.service.evm.contracts.operations; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.frame.MessageFrame; + +/** + * Handles side effects for {@link AbstractEvmRecordingCreateOperation} + */ +public interface CreateOperationExternalizer { + /** + * Handle external side effects + * @param frame current message frame + * @param childFrame child message frame to be created + */ + void externalize(MessageFrame frame, MessageFrame childFrame); + + /** + * Should lazy creation fail based on environment + * @param frame current message frame + * @param contractAddress the target contract address + * @return should it fail + */ + boolean shouldFailBasedOnLazyCreation(MessageFrame frame, Address contractAddress); +} diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/contracts/operation/HederaCreate2Operation.java b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/operations/HederaEvmCreate2Operation.java similarity index 58% rename from hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/contracts/operation/HederaCreate2Operation.java rename to hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/operations/HederaEvmCreate2Operation.java index 81e9f0279895..1de79d03ee7a 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/contracts/operation/HederaCreate2Operation.java +++ b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/operations/HederaEvmCreate2Operation.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2023 Hedera Hashgraph, LLC + * Copyright (C) 2023 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,64 +14,57 @@ * limitations under the License. */ -package com.hedera.node.app.service.mono.contracts.operation; +package com.hedera.node.app.service.evm.contracts.operations; -import static com.hedera.node.app.service.mono.sigs.utils.MiscCryptoUtils.keccak256DigestOf; import static org.hyperledger.besu.evm.internal.Words.clampedToLong; -import com.hedera.node.app.service.mono.context.properties.GlobalDynamicProperties; -import com.hedera.node.app.service.mono.records.RecordsHistorian; -import com.hedera.node.app.service.mono.state.EntityCreator; -import com.hedera.node.app.service.mono.store.contracts.HederaStackedWorldStateUpdater; -import com.hedera.node.app.service.mono.store.contracts.precompile.SyntheticTxnFactory; +import com.hedera.node.app.service.evm.contracts.execution.EvmProperties; +import com.hedera.node.app.service.evm.store.contracts.HederaEvmStackedWorldUpdater; import javax.inject.Inject; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; +import org.bouncycastle.jcajce.provider.digest.Keccak; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -public class HederaCreate2Operation extends AbstractRecordingCreateOperation { +/** + * Hedera adapted version of Create2Operation. + * + *
Can be disabled using dynamic properties.
+ */
+public class HederaEvmCreate2Operation extends AbstractEvmRecordingCreateOperation {
+ protected final EvmProperties evmProperties;
+
private static final Bytes PREFIX = Bytes.fromHexString("0xFF");
@Inject
- public HederaCreate2Operation(
+ public HederaEvmCreate2Operation(
final GasCalculator gasCalculator,
- final EntityCreator creator,
- final SyntheticTxnFactory syntheticTxnFactory,
- final RecordsHistorian recordsHistorian,
- final GlobalDynamicProperties dynamicProperties) {
- super(
- 0xF5,
- "ħCREATE2",
- 4,
- 1,
- 1,
- gasCalculator,
- creator,
- syntheticTxnFactory,
- recordsHistorian,
- dynamicProperties);
+ final EvmProperties evmProperties,
+ final CreateOperationExternalizer createOperationExternalizer) {
+ super(0xF5, "ħCREATE2", 4, 1, 1, gasCalculator, createOperationExternalizer);
+ this.evmProperties = evmProperties;
}
@Override
- protected long cost(final MessageFrame frame) {
- return gasCalculator().create2OperationGasCost(frame);
+ public boolean isEnabled() {
+ return evmProperties.isCreate2Enabled();
}
@Override
- protected boolean isEnabled() {
- return dynamicProperties.isCreate2Enabled();
+ public long cost(final MessageFrame frame) {
+ return gasCalculator().create2OperationGasCost(frame);
}
@Override
- protected Address targetContractAddress(final MessageFrame frame) {
+ public Address targetContractAddress(final MessageFrame frame) {
final var sourceAddressOrAlias = frame.getRecipientAddress();
final var offset = clampedToLong(frame.getStackItem(1));
final var length = clampedToLong(frame.getStackItem(2));
- final var updater = (HederaStackedWorldStateUpdater) frame.getWorldUpdater();
+ final var updater = (HederaEvmStackedWorldUpdater) frame.getWorldUpdater();
final var source = updater.priorityAddress(sourceAddressOrAlias);
final Bytes32 salt = UInt256.fromBytes(frame.getStackItem(3));
@@ -88,4 +81,8 @@ protected Address targetContractAddress(final MessageFrame frame) {
private static Bytes32 keccak256(final Bytes input) {
return Bytes32.wrap(keccak256DigestOf(input.toArrayUnsafe()));
}
+
+ private static byte[] keccak256DigestOf(final byte[] msg) {
+ return new Keccak.Digest256().digest(msg);
+ }
}
diff --git a/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/operations/HederaEvmCreateOperation.java b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/operations/HederaEvmCreateOperation.java
new file mode 100644
index 000000000000..0c3a97112a86
--- /dev/null
+++ b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/operations/HederaEvmCreateOperation.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 Hedera Hashgraph, LLC
+ *
+ * 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.
+ */
+
+package com.hedera.node.app.service.evm.contracts.operations;
+
+import com.hedera.node.app.service.evm.store.contracts.HederaEvmWorldUpdater;
+import javax.inject.Inject;
+import org.hyperledger.besu.datatypes.Address;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+
+/**
+ * Hedera adapted version of CreateOperation.
+ */
+public class HederaEvmCreateOperation extends AbstractEvmRecordingCreateOperation {
+ @Inject
+ public HederaEvmCreateOperation(
+ final GasCalculator gasCalculator, final CreateOperationExternalizer createOperationExternalizer) {
+ super(0xF0, "ħCREATE", 3, 1, 1, gasCalculator, createOperationExternalizer);
+ }
+
+ @Override
+ public long cost(final MessageFrame frame) {
+ return gasCalculator().createOperationGasCost(frame);
+ }
+
+ @Override
+ protected boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ protected Address targetContractAddress(MessageFrame frame) {
+ final var updater = (HederaEvmWorldUpdater) frame.getWorldUpdater();
+ final Address address = updater.newContractAddress(frame.getRecipientAddress());
+ frame.warmUpAddress(address);
+ return address;
+ }
+}
diff --git a/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/store/contracts/AbstractEvmStackedLedgerUpdater.java b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/store/contracts/AbstractEvmStackedLedgerUpdater.java
deleted file mode 100644
index 04a1a4b63bb2..000000000000
--- a/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/store/contracts/AbstractEvmStackedLedgerUpdater.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2022-2023 Hedera Hashgraph, LLC
- *
- * 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.
- */
-
-package com.hedera.node.app.service.evm.store.contracts;
-
-import com.hedera.node.app.service.evm.accounts.AccountAccessor;
-import com.hedera.node.app.service.evm.store.models.UpdateTrackingAccount;
-import org.hyperledger.besu.datatypes.Address;
-import org.hyperledger.besu.evm.account.Account;
-import org.hyperledger.besu.evm.worldstate.WorldView;
-
-public class AbstractEvmStackedLedgerUpdater Addresses are allocated through {@link HederaWorldUpdater#newContractAddress(Address)}
- *
- * Gas costs are based on the expiry of the parent and the provided storage bytes per hour
- * variable
- */
-public class HederaCreateOperation extends AbstractRecordingCreateOperation {
- @Inject
- public HederaCreateOperation(
- final GasCalculator gasCalculator,
- final EntityCreator creator,
- final SyntheticTxnFactory syntheticTxnFactory,
- final RecordsHistorian recordsHistorian,
- final GlobalDynamicProperties dynamicProperties) {
- super(
- 0xF0,
- "ħCREATE",
- 3,
- 1,
- 1,
- gasCalculator,
- creator,
- syntheticTxnFactory,
- recordsHistorian,
- dynamicProperties);
- }
-
- @Override
- public long cost(final MessageFrame frame) {
- return gasCalculator().createOperationGasCost(frame);
- }
-
- @Override
- protected boolean isEnabled() {
- return true;
- }
-
- @Override
- protected Address targetContractAddress(final MessageFrame frame) {
- final var updater = (HederaWorldUpdater) frame.getWorldUpdater();
- final Address address = updater.newContractAddress(frame.getRecipientAddress());
- frame.warmUpAddress(address);
- return address;
- }
-}
diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/contracts/operation/HederaCreateOperationExternalizer.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/contracts/operation/HederaCreateOperationExternalizer.java
new file mode 100644
index 000000000000..7cd2d838517e
--- /dev/null
+++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/contracts/operation/HederaCreateOperationExternalizer.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2023 Hedera Hashgraph, LLC
+ *
+ * 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.
+ */
+
+package com.hedera.node.app.service.mono.contracts.operation;
+
+import static com.hedera.node.app.service.mono.context.BasicTransactionContext.EMPTY_KEY;
+import static com.hedera.node.app.service.mono.ledger.properties.AccountProperty.ETHEREUM_NONCE;
+import static com.hedera.node.app.service.mono.ledger.properties.AccountProperty.IS_SMART_CONTRACT;
+import static com.hedera.node.app.service.mono.ledger.properties.AccountProperty.KEY;
+import static com.hedera.node.app.service.mono.state.EntityCreator.EMPTY_MEMO;
+import static com.hedera.node.app.service.mono.state.EntityCreator.NO_CUSTOM_FEES;
+import static com.hedera.node.app.service.mono.txns.contract.ContractCreateTransitionLogic.STANDIN_CONTRACT_ID_KEY;
+import static com.hedera.node.app.service.mono.utils.EntityIdUtils.accountIdFromEvmAddress;
+
+import com.hedera.node.app.service.evm.contracts.operations.CreateOperationExternalizer;
+import com.hedera.node.app.service.mono.context.SideEffectsTracker;
+import com.hedera.node.app.service.mono.context.properties.GlobalDynamicProperties;
+import com.hedera.node.app.service.mono.records.RecordsHistorian;
+import com.hedera.node.app.service.mono.state.EntityCreator;
+import com.hedera.node.app.service.mono.store.contracts.HederaStackedWorldStateUpdater;
+import com.hedera.node.app.service.mono.store.contracts.precompile.SyntheticTxnFactory;
+import com.hedera.node.app.service.mono.utils.EntityIdUtils;
+import com.hedera.node.app.service.mono.utils.SidecarUtils;
+import com.hedera.services.stream.proto.SidecarType;
+import com.hederahashgraph.api.proto.java.AccountID;
+import com.hederahashgraph.api.proto.java.ContractID;
+import java.util.Collections;
+import java.util.List;
+import javax.inject.Inject;
+import org.hyperledger.besu.datatypes.Address;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+
+/**
+ * Externalizes and handles child record, sidecars and lazy creation logic for create operations
+ */
+public class HederaCreateOperationExternalizer implements CreateOperationExternalizer {
+ protected final GlobalDynamicProperties dynamicProperties;
+ private final EntityCreator creator;
+ private final SyntheticTxnFactory syntheticTxnFactory;
+ private final RecordsHistorian recordsHistorian;
+
+ @Inject
+ public HederaCreateOperationExternalizer(
+ final EntityCreator creator,
+ final SyntheticTxnFactory syntheticTxnFactory,
+ final RecordsHistorian recordsHistorian,
+ final GlobalDynamicProperties dynamicProperties) {
+ super();
+ this.creator = creator;
+ this.recordsHistorian = recordsHistorian;
+ this.syntheticTxnFactory = syntheticTxnFactory;
+ this.dynamicProperties = dynamicProperties;
+ }
+
+ /**
+ * Handle the child record and hollow account completion of a create operation.
+ * Create sidecar with bytecodes of the newly created contract, if sidecards are enabled.
+ *
+ * @param frame current message frame
+ * @param childFrame child message frame to be created
+ */
+ @Override
+ public void externalize(MessageFrame frame, MessageFrame childFrame) {
+ // Add an in-progress record so that if everything succeeds, we can externalize the
+ // newly
+ // created contract in the record stream with both its 0.0.X id and its EVM address.
+ // C.f. https://github.com/hashgraph/hedera-services/issues/2807
+ final var updater = (HederaStackedWorldStateUpdater) frame.getWorldUpdater();
+ final var sideEffects = new SideEffectsTracker();
+
+ ContractID createdContractId;
+ final var hollowAccountID = matchingHollowAccountId(updater, childFrame.getContractAddress());
+
+ // if a hollow account exists at the alias address, finalize it to a contract
+ if (hollowAccountID != null) {
+ finalizeHollowAccountIntoContract(hollowAccountID, updater);
+ createdContractId = EntityIdUtils.asContract(hollowAccountID);
+ } else {
+ createdContractId = updater.idOfLastNewAddress();
+ }
+
+ sideEffects.trackNewContract(createdContractId, childFrame.getContractAddress());
+ final var childRecord = creator.createSuccessfulSyntheticRecord(NO_CUSTOM_FEES, sideEffects, EMPTY_MEMO);
+ childRecord.onlyExternalizeIfSuccessful();
+ final var opCustomizer = updater.customizerForPendingCreation();
+ final var syntheticOp = syntheticTxnFactory.contractCreation(opCustomizer);
+ if (dynamicProperties.enabledSidecars().contains(SidecarType.CONTRACT_BYTECODE)) {
+ final var contractBytecodeSidecar = SidecarUtils.createContractBytecodeSidecarFrom(
+ createdContractId,
+ childFrame.getCode().getContainerBytes().toArrayUnsafe(),
+ updater.get(childFrame.getContractAddress()).getCode().toArrayUnsafe());
+ updater.manageInProgressRecord(
+ recordsHistorian, childRecord, syntheticOp, List.of(contractBytecodeSidecar));
+ } else {
+ updater.manageInProgressRecord(recordsHistorian, childRecord, syntheticOp, Collections.emptyList());
+ }
+ }
+
+ /**
+ * Fails new contract creation when lazy creation is disabled and the new contract matches the address of an existing hollow account.
+ *
+ * @param frame current message frame
+ * @param contractAddress the target contract address
+ * @return should creation fail
+ */
+ @Override
+ public boolean shouldFailBasedOnLazyCreation(MessageFrame frame, Address contractAddress) {
+ if (!dynamicProperties.isLazyCreationEnabled()) {
+ final var hollowAccountID =
+ matchingHollowAccountId((HederaStackedWorldStateUpdater) frame.getWorldUpdater(), contractAddress);
+
+ return hollowAccountID != null;
+ }
+ return false;
+ }
+
+ private AccountID matchingHollowAccountId(HederaStackedWorldStateUpdater updater, Address contract) {
+ final var accountID = accountIdFromEvmAddress(updater.aliases().resolveForEvm(contract));
+ final var trackingAccounts = updater.trackingAccounts();
+ if (trackingAccounts.contains(accountID)) {
+ final var accountKey = updater.trackingAccounts().get(accountID, KEY);
+ return EMPTY_KEY.equals(accountKey) ? accountID : null;
+ } else {
+ return null;
+ }
+ }
+
+ private void finalizeHollowAccountIntoContract(AccountID hollowAccountID, HederaStackedWorldStateUpdater updater) {
+ // reclaim the id for the contract
+ updater.reclaimLatestContractId();
+
+ // update the hollow account to be a contract
+ updater.trackingAccounts().set(hollowAccountID, IS_SMART_CONTRACT, true);
+
+ // update the hollow account key to be the default contract key
+ updater.trackingAccounts().set(hollowAccountID, KEY, STANDIN_CONTRACT_ID_KEY);
+
+ // set initial contract nonce to 1
+ updater.trackingAccounts().set(hollowAccountID, ETHEREUM_NONCE, 1L);
+ }
+}
diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/store/contracts/HederaStackedWorldStateUpdater.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/store/contracts/HederaStackedWorldStateUpdater.java
index c71a3ea07426..2950b0c7ab6a 100644
--- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/store/contracts/HederaStackedWorldStateUpdater.java
+++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/store/contracts/HederaStackedWorldStateUpdater.java
@@ -45,6 +45,7 @@
import static com.hedera.node.app.service.mono.utils.EntityIdUtils.contractIdFromEvmAddress;
import com.google.common.annotations.VisibleForTesting;
+import com.hedera.node.app.service.evm.store.contracts.HederaEvmStackedWorldUpdater;
import com.hedera.node.app.service.evm.store.contracts.HederaEvmWorldStateTokenAccount;
import com.hedera.node.app.service.evm.store.models.UpdateTrackingAccount;
import com.hedera.node.app.service.mono.context.properties.GlobalDynamicProperties;
@@ -63,7 +64,7 @@
import org.hyperledger.besu.evm.worldstate.WrappedEvmAccount;
public class HederaStackedWorldStateUpdater extends AbstractStackedLedgerUpdater