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 - extends AbstractLedgerEvmWorldUpdater, UpdateTrackingAccount> { - - protected AbstractEvmStackedLedgerUpdater( - final AbstractLedgerEvmWorldUpdater world, - final AccountAccessor accountAccessor, - final HederaEvmEntityAccess entityAccess) { - super(world, accountAccessor, entityAccess); - } - - @Override - public UpdateTrackingAccount getForMutation(Address address) { - final var wrapped = wrappedWorldView(); - final A account = wrapped.getForMutation(address); - return account == null ? null : new UpdateTrackingAccount<>(account, null); - } -} diff --git a/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/store/contracts/AbstractLedgerEvmWorldUpdater.java b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/store/contracts/AbstractLedgerEvmWorldUpdater.java index 9d68dbdaf79b..9a26d32ed228 100644 --- a/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/store/contracts/AbstractLedgerEvmWorldUpdater.java +++ b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/store/contracts/AbstractLedgerEvmWorldUpdater.java @@ -18,6 +18,7 @@ import com.hedera.node.app.service.evm.accounts.AccountAccessor; import com.hedera.node.app.service.evm.store.models.UpdateTrackingAccount; +import com.hedera.node.app.service.evm.store.tokens.TokenAccessor; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -56,6 +57,8 @@ public abstract class AbstractLedgerEvmWorldUpdater> updatedAccounts = new HashMap<>(); private HederaEvmEntityAccess hederaEvmEntityAccess; + private TokenAccessor tokenAccessor; + protected Set

deletedAccounts = new HashSet<>(); protected AbstractLedgerEvmWorldUpdater(final W world, final AccountAccessor accountAccessor) { @@ -64,9 +67,13 @@ protected AbstractLedgerEvmWorldUpdater(final W world, final AccountAccessor acc } protected AbstractLedgerEvmWorldUpdater( - final W world, final AccountAccessor accountAccessor, final HederaEvmEntityAccess hederaEvmEntityAccess) { + final W world, + final AccountAccessor accountAccessor, + final TokenAccessor tokenAccessor, + final HederaEvmEntityAccess hederaEvmEntityAccess) { this(world, accountAccessor); this.hederaEvmEntityAccess = hederaEvmEntityAccess; + this.tokenAccessor = tokenAccessor; } /** @@ -169,4 +176,8 @@ public UpdateTrackingAccount track(final UpdateTrackingAccount account) { deletedAccounts.remove(address); return account; } + + public TokenAccessor tokenAccessor() { + return this.tokenAccessor; + } } diff --git a/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/store/contracts/HederaEvmStackedWorldStateUpdater.java b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/store/contracts/HederaEvmStackedWorldStateUpdater.java deleted file mode 100644 index fa3c4bb907bd..000000000000 --- a/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/store/contracts/HederaEvmStackedWorldStateUpdater.java +++ /dev/null @@ -1,74 +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.contracts.execution.EvmProperties; -import com.hedera.node.app.service.evm.store.models.UpdateTrackingAccount; -import com.hedera.node.app.service.evm.store.tokens.TokenAccessor; -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.evm.account.Account; -import org.hyperledger.besu.evm.account.EvmAccount; -import org.hyperledger.besu.evm.worldstate.WrappedEvmAccount; - -public class HederaEvmStackedWorldStateUpdater - extends AbstractEvmStackedLedgerUpdater { - - protected final HederaEvmEntityAccess hederaEvmEntityAccess; - protected final TokenAccessor tokenAccessor; - private final EvmProperties evmProperties; - - public HederaEvmStackedWorldStateUpdater( - final AbstractLedgerEvmWorldUpdater updater, - final AccountAccessor accountAccessor, - final HederaEvmEntityAccess hederaEvmEntityAccess, - final TokenAccessor tokenAccessor, - final EvmProperties evmProperties) { - super(updater, accountAccessor, hederaEvmEntityAccess); - this.hederaEvmEntityAccess = hederaEvmEntityAccess; - this.tokenAccessor = tokenAccessor; - this.evmProperties = evmProperties; - } - - public TokenAccessor tokenAccessor() { - return tokenAccessor; - } - - @Override - public Account get(final Address address) { - if (isTokenRedirect(address)) { - return new HederaEvmWorldStateTokenAccount(address); - } - return super.get(address); - } - - @Override - public EvmAccount getAccount(final Address address) { - if (isTokenRedirect(address)) { - final var proxyAccount = new HederaEvmWorldStateTokenAccount(address); - final var newMutable = new UpdateTrackingAccount<>(proxyAccount, null); - newMutable.setEvmEntityAccess(hederaEvmEntityAccess); - return new WrappedEvmAccount(newMutable); - } - - return super.getAccount(address); - } - - private boolean isTokenRedirect(final Address address) { - return hederaEvmEntityAccess.isTokenAccount(address) && evmProperties.isRedirectTokenCallsEnabled(); - } -} diff --git a/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/store/contracts/HederaEvmStackedWorldUpdater.java b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/store/contracts/HederaEvmStackedWorldUpdater.java new file mode 100644 index 000000000000..76f681e15b47 --- /dev/null +++ b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/store/contracts/HederaEvmStackedWorldUpdater.java @@ -0,0 +1,29 @@ +/* + * 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.store.contracts; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +/** + * Common interface for Hedera stacked world updaters, to be shared between services and mirror node. + */ +public interface HederaEvmStackedWorldUpdater extends WorldUpdater { + Address priorityAddress(final Address addressOrAlias); + + Address newAliasedContractAddress(final Address sponsor, final Address alias); +} diff --git a/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/store/contracts/HederaEvmWorldState.java b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/store/contracts/HederaEvmWorldState.java deleted file mode 100644 index e35c18e7f93c..000000000000 --- a/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/store/contracts/HederaEvmWorldState.java +++ /dev/null @@ -1,129 +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.contracts.execution.EvmProperties; -import com.hedera.node.app.service.evm.store.tokens.TokenAccessor; -import java.util.stream.Stream; -import org.apache.tuweni.bytes.Bytes32; -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.evm.account.Account; -import org.hyperledger.besu.evm.worldstate.WorldUpdater; - -public class HederaEvmWorldState implements HederaEvmMutableWorldState { - - private final HederaEvmEntityAccess hederaEvmEntityAccess; - private final EvmProperties evmProperties; - private final AbstractCodeCache abstractCodeCache; - - private AccountAccessor accountAccessor; - private TokenAccessor tokenAccessor; - - public HederaEvmWorldState( - final HederaEvmEntityAccess hederaEvmEntityAccess, - final EvmProperties evmProperties, - final AbstractCodeCache abstractCodeCache) { - this.hederaEvmEntityAccess = hederaEvmEntityAccess; - this.evmProperties = evmProperties; - this.abstractCodeCache = abstractCodeCache; - } - - public HederaEvmWorldState( - final HederaEvmEntityAccess hederaEvmEntityAccess, - final EvmProperties evmProperties, - final AbstractCodeCache abstractCodeCache, - final AccountAccessor accountAccessor, - final TokenAccessor tokenAccessor) { - this(hederaEvmEntityAccess, evmProperties, abstractCodeCache); - this.accountAccessor = accountAccessor; - this.tokenAccessor = tokenAccessor; - } - - public Account get(final Address address) { - if (address == null) { - return null; - } - if (hederaEvmEntityAccess.isTokenAccount(address) && evmProperties.isRedirectTokenCallsEnabled()) { - return new HederaEvmWorldStateTokenAccount(address); - } - if (!hederaEvmEntityAccess.isUsable(address)) { - return null; - } - final long balance = hederaEvmEntityAccess.getBalance(address); - return new WorldStateAccount(address, Wei.of(balance), abstractCodeCache, hederaEvmEntityAccess); - } - - @Override - public Hash rootHash() { - return Hash.EMPTY; - } - - @Override - public Hash frontierRootHash() { - return rootHash(); - } - - @Override - public Stream streamAccounts(Bytes32 startKeyHash, int limit) { - throw new UnsupportedOperationException(); - } - - @Override - public HederaEvmWorldUpdater updater() { - return new Updater(this, accountAccessor, hederaEvmEntityAccess, tokenAccessor, evmProperties); - } - - public static class Updater extends AbstractLedgerEvmWorldUpdater - implements HederaEvmWorldUpdater { - - private final HederaEvmEntityAccess hederaEvmEntityAccess; - private final TokenAccessor tokenAccessor; - private final EvmProperties evmProperties; - - protected Updater( - final HederaEvmWorldState world, - final AccountAccessor accountAccessor, - final HederaEvmEntityAccess hederaEvmEntityAccess, - final TokenAccessor tokenAccessor, - final EvmProperties evmProperties) { - super(world, accountAccessor); - this.tokenAccessor = tokenAccessor; - this.hederaEvmEntityAccess = hederaEvmEntityAccess; - this.evmProperties = evmProperties; - } - - @Override - public long getSbhRefund() { - return 0; - } - - @Override - public Account getForMutation(final Address address) { - final HederaEvmWorldState wrapped = (HederaEvmWorldState) wrappedWorldView(); - return wrapped.get(address); - } - - @Override - public WorldUpdater updater() { - return new HederaEvmStackedWorldStateUpdater( - this, accountAccessor, hederaEvmEntityAccess, tokenAccessor, evmProperties); - } - } -} diff --git a/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/store/contracts/HederaEvmWorldUpdater.java b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/store/contracts/HederaEvmWorldUpdater.java index 1846c27cdaeb..c9dc8272b118 100644 --- a/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/store/contracts/HederaEvmWorldUpdater.java +++ b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/store/contracts/HederaEvmWorldUpdater.java @@ -16,6 +16,7 @@ package com.hedera.node.app.service.evm.store.contracts; +import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.worldstate.WorldUpdater; /** @@ -24,6 +25,15 @@ * of the state during EVM transaction execution */ public interface HederaEvmWorldUpdater extends WorldUpdater { + /** + * Allocates new Contract address based on the realm and shard of the sponsor IMPORTANT - The Id + * must be reclaimed if the MessageFrame reverts + * + * @param sponsor sponsor of the new contract + * @return newly generated contract {@link Address} + */ + Address newContractAddress(Address sponsor); + /** * Tracks how much Gas should be refunded to the sender account for the TX. SBH price is * refunded for the first allocation of new contract storage in order to prevent double charging diff --git a/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/contracts/execution/HederaEvmMessageCallProcessorTest.java b/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/contracts/execution/HederaEvmMessageCallProcessorTest.java index a7c2ca09a229..993ca2f76ea7 100644 --- a/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/contracts/execution/HederaEvmMessageCallProcessorTest.java +++ b/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/contracts/execution/HederaEvmMessageCallProcessorTest.java @@ -32,7 +32,6 @@ import com.hedera.node.app.service.evm.contracts.execution.traceability.DefaultHederaTracer; 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.Map; import java.util.Optional; @@ -94,9 +93,6 @@ class HederaEvmMessageCallProcessorTest { @Mock private EvmHTSPrecompiledContract evmHTSPrecompiledContract; - @Mock - private HederaEvmStackedWorldStateUpdater hederaEvmStackedWorldStateUpdater; - @Mock private AbstractLedgerEvmWorldUpdater updater; @@ -132,7 +128,7 @@ void callsEvmPrecompile() { given(frame.getRemainingGas()).willReturn(1337L); given(frame.getInputData()).willReturn(Bytes.of(1)); given(frame.getContractAddress()).willReturn(HEDERA_PRECOMPILE_ADDRESS); - given(frame.getWorldUpdater()).willReturn(hederaEvmStackedWorldStateUpdater); + given(frame.getWorldUpdater()).willReturn(updater); given(evmHTSPrecompiledContract.getName()).willReturn("EvmHTS"); given(evmHTSPrecompiledContract.computeCosted(any(), any(), any(), any())) .willReturn(Pair.of(1L, Bytes.EMPTY)); diff --git a/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/contracts/operations/AbstractEvmRecordingCreateOperationTest.java b/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/contracts/operations/AbstractEvmRecordingCreateOperationTest.java new file mode 100644 index 000000000000..d9039c948bd8 --- /dev/null +++ b/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/contracts/operations/AbstractEvmRecordingCreateOperationTest.java @@ -0,0 +1,270 @@ +/* + * 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 static com.hedera.node.app.service.evm.contracts.operations.AbstractEvmRecordingCreateOperation.haltWith; +import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.ILLEGAL_STATE_CHANGE; +import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.INSUFFICIENT_GAS; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +import com.hedera.node.app.service.evm.store.contracts.HederaEvmWorldUpdater; +import java.util.Deque; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt256; +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.account.EvmAccount; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.frame.BlockValues; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.operation.Operation; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class AbstractEvmRecordingCreateOperationTest { + @Mock + private GasCalculator gasCalculator; + + @Mock + private EVM evm; + + @Mock + private MessageFrame frame; + + @Mock + private EvmAccount recipientAccount; + + @Mock + private MutableAccount mutableAccount; + + @Mock + private HederaEvmWorldUpdater updater; + + @Mock + private BlockValues blockValues; + + @Mock + private Deque stack; + + @Mock + private CreateOperationExternalizer externalizer; + + private static final long childStipend = 1_000_000L; + private static final Wei gasPrice = Wei.of(1000L); + private static final long value = 123_456L; + private static final Address recipient = Address.BLAKE2B_F_COMPRESSION; + private static final Operation.OperationResult EMPTY_HALT_RESULT = + new Operation.OperationResult(Subject.PRETEND_COST, null); + private Subject subject; + + @BeforeEach + void setUp() { + subject = new Subject(0xF0, "ħCREATE", 3, 1, 1, gasCalculator, externalizer); + } + + @Test + void returnsUnderflowWhenStackSizeTooSmall() { + given(frame.stackSize()).willReturn(2); + + assertSame(Subject.UNDERFLOW_RESPONSE, subject.execute(frame, evm)); + } + + @Test + void returnsInvalidWhenDisabled() { + subject.isEnabled = false; + + assertSame(Subject.INVALID_RESPONSE, subject.execute(frame, evm)); + } + + @Test + void haltsOnStaticFrame() { + given(frame.stackSize()).willReturn(3); + given(frame.isStatic()).willReturn(true); + + final var expected = haltWith(Subject.PRETEND_COST, ILLEGAL_STATE_CHANGE); + + assertSameResult(expected, subject.execute(frame, evm)); + } + + @Test + void haltsOnInsufficientGas() { + given(frame.stackSize()).willReturn(3); + given(frame.getRemainingGas()).willReturn(Subject.PRETEND_GAS_COST - 1); + + final var expected = haltWith(Subject.PRETEND_COST, INSUFFICIENT_GAS); + + assertSameResult(expected, subject.execute(frame, evm)); + } + + @Test + void failsWithInsufficientRecipientBalanceForValue() { + given(frame.stackSize()).willReturn(3); + given(frame.getStackItem(anyInt())).willReturn(Bytes.ofUnsignedLong(1)); + given(frame.getRemainingGas()).willReturn(Subject.PRETEND_GAS_COST); + given(frame.getStackItem(0)).willReturn(Bytes.ofUnsignedLong(value)); + given(frame.getRecipientAddress()).willReturn(recipient); + given(frame.getWorldUpdater()).willReturn(updater); + given(updater.getAccount(recipient)).willReturn(recipientAccount); + given(recipientAccount.getMutable()).willReturn(mutableAccount); + given(mutableAccount.getBalance()).willReturn(Wei.ONE); + + assertSameResult(EMPTY_HALT_RESULT, subject.execute(frame, evm)); + verify(frame).pushStackItem(UInt256.ZERO); + } + + @Test + void failsWithExcessStackDepth() { + givenSpawnPrereqs(); + given(frame.getMessageStackDepth()).willReturn(1024); + + assertSameResult(EMPTY_HALT_RESULT, subject.execute(frame, evm)); + verify(frame).pushStackItem(UInt256.ZERO); + } + + @Test + void hasExpectedChildCompletionOnSuccessWithSidecarEnabled() { + final var frameCaptor = ArgumentCaptor.forClass(MessageFrame.class); + givenSpawnPrereqs(); + givenBuilderPrereqs(); + final var initCode = "initCode".getBytes(); + given(frame.readMemory(anyLong(), anyLong())).willReturn(Bytes.wrap(initCode)); + assertSameResult(EMPTY_HALT_RESULT, subject.execute(frame, evm)); + + verify(stack).addFirst(frameCaptor.capture()); + final var childFrame = frameCaptor.getValue(); + // when: + childFrame.setState(MessageFrame.State.COMPLETED_SUCCESS); + childFrame.notifyCompletion(); + // then: + verify(frame).pushStackItem(Words.fromAddress(Subject.PRETEND_CONTRACT_ADDRESS)); + } + + @Test + void hasExpectedChildCompletionOnFailure() { + final var captor = ArgumentCaptor.forClass(MessageFrame.class); + givenSpawnPrereqs(); + givenBuilderPrereqs(); + + assertSameResult(EMPTY_HALT_RESULT, subject.execute(frame, evm)); + + verify(stack).addFirst(captor.capture()); + final var childFrame = captor.getValue(); + // when: + childFrame.setState(MessageFrame.State.COMPLETED_FAILED); + childFrame.notifyCompletion(); + verify(frame).pushStackItem(UInt256.ZERO); + } + + @Test + void failsWhenMatchingHollowAccountExistsAndLazyCreationDisabled() { + given(frame.stackSize()).willReturn(3); + given(frame.getRemainingGas()).willReturn(Subject.PRETEND_GAS_COST); + given(frame.getStackItem(0)).willReturn(Bytes.ofUnsignedLong(value)); + given(frame.getRecipientAddress()).willReturn(recipient); + given(frame.getWorldUpdater()).willReturn(updater); + given(updater.getAccount(recipient)).willReturn(recipientAccount); + given(recipientAccount.getMutable()).willReturn(mutableAccount); + given(mutableAccount.getBalance()).willReturn(Wei.of(value)); + given(frame.getMessageStackDepth()).willReturn(1023); + given(frame.getStackItem(anyInt())).willReturn(Bytes.ofUnsignedLong(1)); + given(externalizer.shouldFailBasedOnLazyCreation(eq(frame), any())).willReturn(true); + + subject.execute(frame, evm); + + verify(frame).readMutableMemory(1L, 1L); + verify(frame).popStackItems(3); + verify(frame).pushStackItem(UInt256.ZERO); + } + + private void givenBuilderPrereqs() { + given(frame.getMessageFrameStack()).willReturn(stack); + given(updater.updater()).willReturn(updater); + given(gasCalculator.gasAvailableForChildCreate(anyLong())).willReturn(childStipend); + given(frame.getOriginatorAddress()).willReturn(recipient); + given(frame.getGasPrice()).willReturn(gasPrice); + given(frame.getBlockValues()).willReturn(blockValues); + given(frame.getMiningBeneficiary()).willReturn(recipient); + given(frame.getBlockHashLookup()).willReturn(l -> Hash.ZERO); + } + + private void givenSpawnPrereqs() { + given(frame.stackSize()).willReturn(3); + given(frame.getStackItem(anyInt())).willReturn(Bytes.ofUnsignedLong(1)); + given(frame.getRemainingGas()).willReturn(Subject.PRETEND_GAS_COST); + given(frame.getStackItem(0)).willReturn(Bytes.ofUnsignedLong(value)); + given(frame.getRecipientAddress()).willReturn(recipient); + given(frame.getWorldUpdater()).willReturn(updater); + given(updater.getAccount(recipient)).willReturn(recipientAccount); + given(recipientAccount.getMutable()).willReturn(mutableAccount); + given(mutableAccount.getBalance()).willReturn(Wei.of(value)); + given(frame.getMessageStackDepth()).willReturn(1023); + } + + private void assertSameResult(final Operation.OperationResult expected, final Operation.OperationResult actual) { + assertEquals(expected.getGasCost(), actual.getGasCost()); + assertEquals(expected.getHaltReason(), actual.getHaltReason()); + } + + static class Subject extends AbstractEvmRecordingCreateOperation { + static final long PRETEND_GAS_COST = 123L; + static final Address PRETEND_CONTRACT_ADDRESS = Address.ALTBN128_ADD; + static final long PRETEND_COST = PRETEND_GAS_COST; + + boolean isEnabled = true; + + protected Subject( + final int opcode, + final String name, + final int stackItemsConsumed, + final int stackItemsProduced, + final int opSize, + final GasCalculator gasCalculator, + final CreateOperationExternalizer externalizer) { + super(opcode, name, stackItemsConsumed, stackItemsProduced, opSize, gasCalculator, externalizer); + } + + @Override + protected boolean isEnabled() { + return isEnabled; + } + + @Override + protected long cost(final MessageFrame frame) { + return PRETEND_GAS_COST; + } + + @Override + protected Address targetContractAddress(final MessageFrame frame) { + return PRETEND_CONTRACT_ADDRESS; + } + } +} diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/contracts/operation/HederaCreate2OperationTest.java b/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/contracts/operations/HederaEvmCreate2OperationTest.java similarity index 74% rename from hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/contracts/operation/HederaCreate2OperationTest.java rename to hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/contracts/operations/HederaEvmCreate2OperationTest.java index 66968789fbc9..cb22c7ebe90a 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/contracts/operation/HederaCreate2OperationTest.java +++ b/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/contracts/operations/HederaEvmCreate2OperationTest.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,18 +14,15 @@ * limitations under the License. */ -package com.hedera.node.app.service.mono.contracts.operation; +package com.hedera.node.app.service.evm.contracts.operations; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.BDDMockito.given; -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 org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.MutableBytes; import org.hyperledger.besu.datatypes.Address; @@ -39,16 +36,16 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class HederaCreate2OperationTest { +class HederaEvmCreate2OperationTest { private static final long baseGas = 100L; private static final Bytes salt = Bytes.fromHexString("0x2a"); private static final Bytes oneOffsetStackItem = Bytes.of(10); private static final Bytes twoOffsetStackItem = Bytes.of(20); private static final MutableBytes initcode = MutableBytes.of((byte) 0xaa); - private Address recipientAddr = Address.fromHexString("0x0102030405060708090a0b0c0d0e0f1011121314"); + private static final Address recipientAddr = Address.fromHexString("0x0102030405060708090a0b0c0d0e0f1011121314"); @Mock - private GlobalDynamicProperties dynamicProperties; + private EvmProperties evmProperties; @Mock private MessageFrame frame; @@ -57,23 +54,16 @@ class HederaCreate2OperationTest { private GasCalculator gasCalculator; @Mock - private HederaStackedWorldStateUpdater stackedUpdater; + private HederaEvmStackedWorldUpdater stackedUpdater; - @Mock - private SyntheticTxnFactory syntheticTxnFactory; - - @Mock - private EntityCreator creator; + private HederaEvmCreate2Operation subject; @Mock - private RecordsHistorian recordsHistorian; - - private HederaCreate2Operation subject; + private CreateOperationExternalizer externalizer; @BeforeEach void setup() { - subject = new HederaCreate2Operation( - gasCalculator, creator, syntheticTxnFactory, recordsHistorian, dynamicProperties); + subject = new HederaEvmCreate2Operation(gasCalculator, evmProperties, externalizer); } @Test @@ -89,7 +79,7 @@ void computesExpectedCost() { void enabledOnlyIfCreate2IsEnabled() { assertFalse(subject.isEnabled()); - given(dynamicProperties.isCreate2Enabled()).willReturn(true); + given(evmProperties.isCreate2Enabled()).willReturn(true); assertTrue(subject.isEnabled()); } diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/contracts/operation/HederaCreateOperationTest.java b/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/contracts/operations/HederaEvmCreateOperationTest.java similarity index 56% rename from hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/contracts/operation/HederaCreateOperationTest.java rename to hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/contracts/operations/HederaEvmCreateOperationTest.java index c63a9ec517e3..823879ec3e14 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/contracts/operation/HederaCreateOperationTest.java +++ b/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/contracts/operations/HederaEvmCreateOperationTest.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,38 +14,12 @@ * limitations under the License. */ -package com.hedera.node.app.service.mono.contracts.operation; - -/* - * - - * ‌ - * Hedera Services Node - * ​ - * Copyright (C) 2018 - 2021 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 static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.BDDMockito.given; -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.HederaWorldUpdater; -import com.hedera.node.app.service.mono.store.contracts.precompile.SyntheticTxnFactory; +import com.hedera.node.app.service.evm.store.contracts.HederaEvmWorldUpdater; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -57,7 +31,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class HederaCreateOperationTest { +class HederaEvmCreateOperationTest { private static final long baseGas = 100L; private final Address recipientAddr = Address.fromHexString("0x0102030405060708090a0b0c0d0e0f1011121314"); @@ -69,26 +43,16 @@ class HederaCreateOperationTest { private GasCalculator gasCalculator; @Mock - private HederaWorldUpdater hederaWorldUpdater; - - @Mock - private SyntheticTxnFactory syntheticTxnFactory; + private HederaEvmWorldUpdater hederaWorldUpdater; - @Mock - private EntityCreator creator; + private HederaEvmCreateOperation subject; @Mock - private RecordsHistorian recordsHistorian; - - @Mock - private GlobalDynamicProperties dynamicProperties; - - private HederaCreateOperation subject; + private CreateOperationExternalizer externalizer; @BeforeEach void setup() { - subject = new HederaCreateOperation( - gasCalculator, creator, syntheticTxnFactory, recordsHistorian, dynamicProperties); + subject = new HederaEvmCreateOperation(gasCalculator, externalizer); } @Test diff --git a/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/store/contracts/HederaEvmWorldStateTest.java b/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/store/contracts/HederaEvmWorldStateTest.java deleted file mode 100644 index 03d84e689af0..000000000000 --- a/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/store/contracts/HederaEvmWorldStateTest.java +++ /dev/null @@ -1,132 +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 static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; - -import com.hedera.node.app.service.evm.contracts.execution.EvmProperties; -import com.hedera.node.app.service.evm.store.models.MockAccountAccessor; -import com.hedera.node.app.service.evm.store.models.MockTokenAccessor; -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -class HederaEvmWorldStateTest { - - @Mock - private HederaEvmEntityAccess hederaEvmEntityAccess; - - @Mock - private EvmProperties evmProperties; - - @Mock - private AbstractCodeCache abstractCodeCache; - - private final Address address = Address.fromHexString("0x000000000000000000000000000000000000077e"); - final long balance = 1_234L; - - MockAccountAccessor accountAccessor = new MockAccountAccessor(); - MockTokenAccessor tokenAccessor = new MockTokenAccessor(); - - private HederaEvmWorldState subject; - - private HederaEvmWorldState subject2; - - @BeforeEach - void setUp() { - subject = new HederaEvmWorldState(hederaEvmEntityAccess, evmProperties, abstractCodeCache); - - subject2 = new HederaEvmWorldState( - hederaEvmEntityAccess, evmProperties, abstractCodeCache, accountAccessor, tokenAccessor); - } - - @Test - void rootHash() { - assertEquals(Hash.EMPTY, subject.rootHash()); - } - - @Test - void frontierRootHash() { - assertEquals(Hash.EMPTY, subject.frontierRootHash()); - } - - @Test - void streamAccounts() { - assertThrows(UnsupportedOperationException.class, () -> subject.streamAccounts(null, 10)); - } - - @Test - void returnsNullForNull() { - assertNull(subject.get(null)); - } - - @Test - void returnsNull() { - assertNull(subject.get(address)); - } - - @Test - void returnsWorldStateAccount() { - final var address = Address.RIPEMD160; - given(hederaEvmEntityAccess.getBalance(address)).willReturn(balance); - given(hederaEvmEntityAccess.isUsable(any())).willReturn(true); - - final var account = subject.get(address); - - assertTrue(account.getCode().isEmpty()); - assertFalse(account.hasCode()); - } - - @Test - void returnsHederaEvmWorldStateTokenAccount() { - final var address = Address.RIPEMD160; - given(hederaEvmEntityAccess.isTokenAccount(address)).willReturn(true); - given(evmProperties.isRedirectTokenCallsEnabled()).willReturn(true); - - final var account = subject.get(address); - - assertFalse(account.getCode().isEmpty()); - assertTrue(account.hasCode()); - } - - @Test - void returnsNull2() { - final var address = Address.RIPEMD160; - given(hederaEvmEntityAccess.isTokenAccount(address)).willReturn(true); - given(evmProperties.isRedirectTokenCallsEnabled()).willReturn(false); - - assertNull(subject.get(address)); - } - - @Test - void updater() { - var actualSubject = subject2.updater(); - assertEquals(0, actualSubject.getSbhRefund()); - assertNull(actualSubject.updater().get(Address.RIPEMD160)); - } -} diff --git a/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/store/models/HederaEvmStackedWorldStateUpdaterTest.java b/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/store/models/HederaEvmStackedWorldStateUpdaterTest.java deleted file mode 100644 index bc548df5e190..000000000000 --- a/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/store/models/HederaEvmStackedWorldStateUpdaterTest.java +++ /dev/null @@ -1,147 +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.models; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.mockito.BDDMockito.given; - -import com.hedera.node.app.service.evm.contracts.execution.EvmProperties; -import com.hedera.node.app.service.evm.store.contracts.AbstractLedgerEvmWorldUpdater; -import com.hedera.node.app.service.evm.store.contracts.HederaEvmMutableWorldState; -import com.hedera.node.app.service.evm.store.contracts.HederaEvmStackedWorldStateUpdater; -import java.util.Collections; -import java.util.Optional; -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.evm.account.Account; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -class HederaEvmStackedWorldStateUpdaterTest { - private final Address address = Address.fromHexString("0x000000000000000000000000000000000000077e"); - private final MockAccountAccessor accountAccessor = new MockAccountAccessor(); - private final MockTokenAccessor tokenAccessor = new MockTokenAccessor(); - private final MockEntityAccess entityAccess = new MockEntityAccess(); - - @Mock - private AbstractLedgerEvmWorldUpdater updater; - - @Mock - private EvmProperties properties; - - private HederaEvmStackedWorldStateUpdater subject; - private final UpdateTrackingAccount updatedHederaEvmAccount = new UpdateTrackingAccount<>(address, null); - - @BeforeEach - void setUp() { - subject = new HederaEvmStackedWorldStateUpdater( - updater, accountAccessor, entityAccess, tokenAccessor, properties); - } - - @Test - void accountTests() { - given(updater.getForMutation(address)).willReturn(updatedHederaEvmAccount); - updatedHederaEvmAccount.setBalance(Wei.of(100)); - assertNull(subject.createAccount(address, 1, Wei.ONE)); - assertEquals(Wei.of(100L), subject.getAccount(address).getBalance()); - assertFalse(subject.getTouchedAccounts().isEmpty()); - assertEquals(Collections.emptyList(), subject.getDeletedAccountAddresses()); - subject.commit(); - subject.revert(); - subject.deleteAccount(address); - } - - @Test - void get() { - given(updater.get(address)).willReturn(updatedHederaEvmAccount); - assertEquals(updatedHederaEvmAccount.getAddress(), subject.get(address).getAddress()); - } - - @Test - void getForRedirect() { - givenForRedirect(); - assertEquals(updatedHederaEvmAccount.getAddress(), subject.get(address).getAddress()); - } - - @Test - void getWithTrack() { - given(updater.getForMutation(address)).willReturn(updatedHederaEvmAccount); - subject.getAccount(address); - subject.get(address); - assertEquals(updatedHederaEvmAccount.getAddress(), subject.get(address).getAddress()); - } - - @Test - void getWithNonCanonicalAddress() { - accountAccessor.changeAddress(Address.ZERO); - assertNull(subject.get(address)); - } - - @Test - void getAccount() { - given(updater.getForMutation(address)).willReturn(updatedHederaEvmAccount); - assertEquals( - updatedHederaEvmAccount.getAddress(), - subject.getAccount(address).getAddress()); - } - - @Test - void getAccountWithTrack() { - given(updater.getForMutation(address)).willReturn(updatedHederaEvmAccount); - subject.getAccount(address); - assertEquals( - updatedHederaEvmAccount.getAddress(), - subject.getAccount(address).getAddress()); - } - - @Test - void getAccountWithMissingWorldReturnsNull() { - assertNull(subject.getAccount(address)); - } - - @Test - void getAccountForRedirect() { - givenForRedirect(); - assertEquals( - updatedHederaEvmAccount.getAddress(), - subject.getAccount(address).getAddress()); - } - - @Test - void updaterTest() { - assertEquals(tokenAccessor, subject.tokenAccessor()); - assertEquals(Optional.empty(), subject.parentUpdater()); - assertEquals(subject, subject.updater()); - } - - @Test - void namedelegatesTokenAccountTest() { - final var someAddress = Address.BLS12_MAP_FP2_TO_G2; - assertFalse(subject.isTokenAddress(someAddress)); - } - - private void givenForRedirect() { - given(properties.isRedirectTokenCallsEnabled()).willReturn(true); - entityAccess.setIsTokenFor(address); - } -} diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/contracts/ContractsModule.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/contracts/ContractsModule.java index 7bf6e1983fb0..cf8ecb494bdd 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/contracts/ContractsModule.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/contracts/ContractsModule.java @@ -22,12 +22,14 @@ import static com.hedera.node.app.service.mono.store.contracts.precompile.PrngSystemPrecompiledContract.PRNG_PRECOMPILE_ADDRESS; import com.hedera.node.app.service.evm.contracts.execution.EvmProperties; +import com.hedera.node.app.service.evm.contracts.operations.CreateOperationExternalizer; import com.hedera.node.app.service.mono.context.TransactionContext; import com.hedera.node.app.service.mono.context.properties.GlobalDynamicProperties; import com.hedera.node.app.service.mono.contracts.execution.CallLocalEvmTxProcessor; import com.hedera.node.app.service.mono.contracts.execution.HederaMessageCallProcessor; import com.hedera.node.app.service.mono.contracts.execution.LivePricesSource; import com.hedera.node.app.service.mono.contracts.gascalculator.GasCalculatorHederaV22; +import com.hedera.node.app.service.mono.contracts.operation.HederaCreateOperationExternalizer; import com.hedera.node.app.service.mono.ledger.HederaLedger; import com.hedera.node.app.service.mono.ledger.TransactionalLedger; import com.hedera.node.app.service.mono.ledger.accounts.AliasManager; @@ -130,6 +132,11 @@ static EntityAccess provideMutableEntityAccess( @Singleton EvmProperties bindEvmProperties(GlobalDynamicProperties evmProperties); + @Binds + @Singleton + CreateOperationExternalizer bindCreateOperationExternalizer( + HederaCreateOperationExternalizer createOperationExternalizer); + @Binds @Singleton @IntoMap diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/contracts/ContractsV_0_30Module.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/contracts/ContractsV_0_30Module.java index d3b266606bf5..44f669783d7b 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/contracts/ContractsV_0_30Module.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/contracts/ContractsV_0_30Module.java @@ -22,6 +22,8 @@ import com.hedera.node.app.service.evm.contracts.operations.HederaBalanceOperation; import com.hedera.node.app.service.evm.contracts.operations.HederaDelegateCallOperation; import com.hedera.node.app.service.evm.contracts.operations.HederaEvmChainIdOperation; +import com.hedera.node.app.service.evm.contracts.operations.HederaEvmCreate2Operation; +import com.hedera.node.app.service.evm.contracts.operations.HederaEvmCreateOperation; import com.hedera.node.app.service.evm.contracts.operations.HederaExtCodeCopyOperation; import com.hedera.node.app.service.evm.contracts.operations.HederaExtCodeHashOperation; import com.hedera.node.app.service.evm.contracts.operations.HederaExtCodeSizeOperation; @@ -30,8 +32,6 @@ import com.hedera.node.app.service.mono.contracts.ContractsModule.V_0_30; import com.hedera.node.app.service.mono.contracts.operation.HederaCallCodeOperation; import com.hedera.node.app.service.mono.contracts.operation.HederaCallOperation; -import com.hedera.node.app.service.mono.contracts.operation.HederaCreate2Operation; -import com.hedera.node.app.service.mono.contracts.operation.HederaCreateOperation; import com.hedera.node.app.service.mono.contracts.operation.HederaLogOperation; import com.hedera.node.app.service.mono.contracts.operation.HederaSLoadOperation; import com.hedera.node.app.service.mono.contracts.operation.HederaSStoreOperation; @@ -127,13 +127,13 @@ static Operation provideLog4Operation(final GasCalculator gasCalculator) { @Singleton @IntoSet @V_0_30 - Operation bindCreateOperation(HederaCreateOperation createOperation); + Operation bindCreateOperation(HederaEvmCreateOperation createOperation); @Binds @Singleton @IntoSet @V_0_30 - Operation bindCreate2Operation(HederaCreate2Operation create2Operation); + Operation bindCreate2Operation(HederaEvmCreate2Operation create2Operation); @Provides @Singleton diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/contracts/ContractsV_0_34Module.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/contracts/ContractsV_0_34Module.java index e8ac3bdb3484..7d2d9ce21bbf 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/contracts/ContractsV_0_34Module.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/contracts/ContractsV_0_34Module.java @@ -22,6 +22,8 @@ import com.hedera.node.app.service.evm.contracts.operations.HederaBalanceOperation; import com.hedera.node.app.service.evm.contracts.operations.HederaDelegateCallOperation; import com.hedera.node.app.service.evm.contracts.operations.HederaEvmChainIdOperation; +import com.hedera.node.app.service.evm.contracts.operations.HederaEvmCreate2Operation; +import com.hedera.node.app.service.evm.contracts.operations.HederaEvmCreateOperation; import com.hedera.node.app.service.evm.contracts.operations.HederaExtCodeCopyOperation; import com.hedera.node.app.service.evm.contracts.operations.HederaExtCodeHashOperation; import com.hedera.node.app.service.evm.contracts.operations.HederaExtCodeSizeOperation; @@ -30,8 +32,6 @@ import com.hedera.node.app.service.mono.contracts.ContractsModule.V_0_34; import com.hedera.node.app.service.mono.contracts.operation.HederaCallCodeOperation; import com.hedera.node.app.service.mono.contracts.operation.HederaCallOperationV034; -import com.hedera.node.app.service.mono.contracts.operation.HederaCreate2Operation; -import com.hedera.node.app.service.mono.contracts.operation.HederaCreateOperation; import com.hedera.node.app.service.mono.contracts.operation.HederaLogOperation; import com.hedera.node.app.service.mono.contracts.operation.HederaPrngSeedOperator; import com.hedera.node.app.service.mono.contracts.operation.HederaSLoadOperation; @@ -128,13 +128,13 @@ static Operation provideLog4Operation(final GasCalculator gasCalculator) { @Singleton @IntoSet @V_0_34 - Operation bindCreateOperation(HederaCreateOperation createOperation); + Operation bindCreateOperation(HederaEvmCreateOperation createOperation); @Binds @Singleton @IntoSet @V_0_34 - Operation bindCreate2Operation(HederaCreate2Operation create2Operation); + Operation bindCreate2Operation(HederaEvmCreate2Operation create2Operation); @Provides @Singleton diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/contracts/operation/HederaCreateOperation.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/contracts/operation/HederaCreateOperation.java deleted file mode 100644 index eed290ae79fe..000000000000 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/contracts/operation/HederaCreateOperation.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2021-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 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.HederaWorldUpdater; -import com.hedera.node.app.service.mono.store.contracts.precompile.SyntheticTxnFactory; -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 the {@link org.hyperledger.besu.evm.operation.CreateOperation}. - * - *

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 - implements HederaWorldUpdater { + implements HederaWorldUpdater, HederaEvmStackedWorldUpdater { // Returned when a client tries to un-alias a mirror address that has an EIP-1014 address private static final byte[] NON_CANONICAL_REFERENCE = new byte[20]; diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/store/contracts/HederaWorldState.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/store/contracts/HederaWorldState.java index 8bdb51f1722f..c334a82f2e2b 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/store/contracts/HederaWorldState.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/store/contracts/HederaWorldState.java @@ -25,9 +25,9 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONSENSUS_GAS_EXHAUSTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.FAIL_INVALID; -import com.hedera.node.app.service.evm.store.contracts.HederaEvmWorldState; import com.hedera.node.app.service.evm.store.contracts.HederaEvmWorldStateTokenAccount; import com.hedera.node.app.service.evm.store.contracts.HederaEvmWorldUpdater; +import com.hedera.node.app.service.evm.store.contracts.WorldStateAccount; import com.hedera.node.app.service.evm.store.models.UpdateTrackingAccount; import com.hedera.node.app.service.mono.context.properties.GlobalDynamicProperties; import com.hedera.node.app.service.mono.ledger.SigImpactHistorian; @@ -47,6 +47,7 @@ import java.util.Map; import java.util.Objects; import java.util.TreeMap; +import java.util.stream.Stream; import javax.inject.Inject; import javax.inject.Singleton; import org.apache.commons.lang3.tuple.ImmutablePair; @@ -54,13 +55,16 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.worldstate.WorldUpdater; @Singleton -public class HederaWorldState extends HederaEvmWorldState implements HederaMutableWorldState { +public class HederaWorldState implements HederaMutableWorldState { private static final Logger log = LogManager.getLogger(HederaWorldState.class); private final UsageLimits usageLimits; @@ -74,6 +78,8 @@ public class HederaWorldState extends HederaEvmWorldState implements HederaMutab // If non-null, the new contract customizations requested by the HAPI contractCreate sender private ContractCustomizer hapiSenderCustomizer; + private final CodeCache codeCache; + @Inject public HederaWorldState( final UsageLimits usageLimits, @@ -83,10 +89,10 @@ public HederaWorldState( final SigImpactHistorian sigImpactHistorian, final GlobalDynamicProperties dynamicProperties, final @HandleThrottle FunctionalityThrottling handleThrottling) { - super(entityAccess, dynamicProperties, codeCache); this.ids = ids; this.usageLimits = usageLimits; this.entityAccess = entityAccess; + this.codeCache = codeCache; this.sigImpactHistorian = sigImpactHistorian; this.dynamicProperties = dynamicProperties; this.handleThrottling = handleThrottling; @@ -98,15 +104,29 @@ public HederaWorldState( final EntityAccess entityAccess, final CodeCache codeCache, final GlobalDynamicProperties dynamicProperties) { - super(entityAccess, dynamicProperties, codeCache); this.ids = ids; this.entityAccess = entityAccess; this.usageLimits = null; this.handleThrottling = null; this.sigImpactHistorian = null; + this.codeCache = codeCache; this.dynamicProperties = dynamicProperties; } + public Account get(final Address address) { + if (address == null) { + return null; + } + if (entityAccess.isTokenAccount(address) && dynamicProperties.isRedirectTokenCallsEnabled()) { + return new HederaEvmWorldStateTokenAccount(address); + } + if (!entityAccess.isUsable(address)) { + return null; + } + final long balance = entityAccess.getBalance(address); + return new WorldStateAccount(address, Wei.of(balance), codeCache, entityAccess); + } + /** {@inheritDoc} */ @Override public ContractCustomizer hapiSenderCustomizer() { @@ -158,6 +178,21 @@ public Updater updater() { return new Updater(this, entityAccess.worldLedgers().wrapped(), dynamicProperties); } + @Override + public Hash rootHash() { + return Hash.EMPTY; + } + + @Override + public Hash frontierRootHash() { + return rootHash(); + } + + @Override + public Stream streamAccounts(Bytes32 startKeyHash, int limit) { + throw new UnsupportedOperationException(); + } + public static class Updater extends AbstractLedgerWorldUpdater implements HederaEvmWorldUpdater, HederaWorldUpdater { diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/store/contracts/HederaWorldUpdater.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/store/contracts/HederaWorldUpdater.java index e095901ddfd6..571f661a33fb 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/store/contracts/HederaWorldUpdater.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/store/contracts/HederaWorldUpdater.java @@ -39,32 +39,13 @@ */ import com.hedera.node.app.service.evm.store.contracts.HederaEvmWorldUpdater; -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.evm.worldstate.WorldUpdater; /** * Provides a stacked Hedera adapted world view. Utilised by {@link * org.hyperledger.besu.evm.frame.MessageFrame} in order to provide a layered view for read/writes * of the state during EVM transaction execution */ -public interface HederaWorldUpdater extends HederaEvmWorldUpdater, WorldUpdater { - /** - * Allocates new Contract address based on the realm and shard of the sponsor IMPORTANT - The Id - * must be reclaimed if the MessageFrame reverts - * - * @param sponsor sponsor of the new contract - * @return newly generated contract {@link Address} - */ - Address newContractAddress(Address sponsor); - - /** - * Tracks how much Gas should be refunded to the sender account for the TX. SBH price is - * refunded for the first allocation of new contract storage in order to prevent double charging - * the client. - * - * @return the amount of Gas to refund; - */ - long getSbhRefund(); +public interface HederaWorldUpdater extends HederaEvmWorldUpdater { /** * Used to keep track of SBH gas refunds between all instances of HederaWorldUpdater. Lower diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/contracts/operation/AbstractRecordingCreateOperationTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/contracts/operation/HederaCreateOperationExternalizerTest.java similarity index 55% rename from hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/contracts/operation/AbstractRecordingCreateOperationTest.java rename to hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/contracts/operation/HederaCreateOperationExternalizerTest.java index 73b20efb6bca..1eb58d16a0f7 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/contracts/operation/AbstractRecordingCreateOperationTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/contracts/operation/HederaCreateOperationExternalizerTest.java @@ -17,14 +17,11 @@ 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.contracts.operation.AbstractRecordingCreateOperation.haltWith; 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.txns.contract.ContractCreateTransitionLogic.STANDIN_CONTRACT_ID_KEY; -import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.ILLEGAL_STATE_CHANGE; -import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.INSUFFICIENT_GAS; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.BDDMockito.given; @@ -59,23 +56,14 @@ import com.hederahashgraph.api.proto.java.Timestamp; import com.hederahashgraph.api.proto.java.TransactionBody; import java.util.Collections; -import java.util.Deque; import java.util.List; import java.util.Set; import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.units.bigints.UInt256; 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.account.Account; -import org.hyperledger.besu.evm.account.EvmAccount; -import org.hyperledger.besu.evm.account.MutableAccount; -import org.hyperledger.besu.evm.frame.BlockValues; +import org.hyperledger.besu.evm.code.CodeFactory; import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.Words; -import org.hyperledger.besu.evm.operation.Operation; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -84,34 +72,19 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class AbstractRecordingCreateOperationTest { +class HederaCreateOperationExternalizerTest { @Mock private SyntheticTxnFactory syntheticTxnFactory; - @Mock - private GasCalculator gasCalculator; - - @Mock - private EVM evm; - @Mock private MessageFrame frame; @Mock - private EvmAccount recipientAccount; - - @Mock - private MutableAccount mutableAccount; + private MessageFrame childFrame; @Mock private HederaStackedWorldStateUpdater updater; - @Mock - private Deque stack; - - @Mock - private BlockValues blockValues; - @Mock private EntityCreator creator; @@ -130,92 +103,21 @@ class AbstractRecordingCreateOperationTest { @Mock private TransactionalLedger accounts; - private static final long childStipend = 1_000_000L; - private static final Wei gasPrice = Wei.of(1000L); - private static final long value = 123_456L; private static final ContractID lastAllocated = IdUtils.asContract("0.0.1234"); - private static final Address recipient = Address.BLAKE2B_F_COMPRESSION; - private static final Operation.OperationResult EMPTY_HALT_RESULT = - new Operation.OperationResult(Subject.PRETEND_COST, null); + private static final EntityId autoRenewId = new EntityId(0, 0, 8); private static final JKey nonEmptyKey = MiscUtils.asFcKeyUnchecked(Key.newBuilder() .setEd25519(ByteString.copyFrom("01234567890123456789012345678901".getBytes())) .build()); - private Subject subject; + private HederaCreateOperationExternalizer subject; + + static final Address PRETEND_CONTRACT_ADDRESS = Address.ALTBN128_ADD; @BeforeEach void setUp() { - subject = new Subject( - 0xF0, - "ħCREATE", - 3, - 1, - 1, - gasCalculator, - creator, - syntheticTxnFactory, - recordsHistorian, - dynamicProperties); - } - - @Test - void returnsUnderflowWhenStackSizeTooSmall() { - given(frame.stackSize()).willReturn(2); - - assertSame(Subject.UNDERFLOW_RESPONSE, subject.execute(frame, evm)); - } - - @Test - void returnsInvalidWhenDisabled() { - subject.isEnabled = false; - - assertSame(Subject.INVALID_RESPONSE, subject.execute(frame, evm)); - } - - @Test - void haltsOnStaticFrame() { - given(frame.stackSize()).willReturn(3); - given(frame.isStatic()).willReturn(true); - - final var expected = haltWith(Subject.PRETEND_COST, ILLEGAL_STATE_CHANGE); - - assertSameResult(expected, subject.execute(frame, evm)); - } - - @Test - void haltsOnInsufficientGas() { - given(frame.stackSize()).willReturn(3); - given(frame.getRemainingGas()).willReturn(Subject.PRETEND_GAS_COST - 1); - - final var expected = haltWith(Subject.PRETEND_COST, INSUFFICIENT_GAS); - - assertSameResult(expected, subject.execute(frame, evm)); - } - - @Test - void failsWithInsufficientRecipientBalanceForValue() { - given(frame.stackSize()).willReturn(3); - given(frame.getStackItem(anyInt())).willReturn(Bytes.ofUnsignedLong(1)); - given(frame.getRemainingGas()).willReturn(Subject.PRETEND_GAS_COST); - given(frame.getStackItem(0)).willReturn(Bytes.ofUnsignedLong(value)); - given(frame.getRecipientAddress()).willReturn(recipient); - given(frame.getWorldUpdater()).willReturn(updater); - given(updater.getAccount(recipient)).willReturn(recipientAccount); - given(recipientAccount.getMutable()).willReturn(mutableAccount); - given(mutableAccount.getBalance()).willReturn(Wei.ONE); - - assertSameResult(EMPTY_HALT_RESULT, subject.execute(frame, evm)); - verify(frame).pushStackItem(UInt256.ZERO); - } - - @Test - void failsWithExcessStackDepth() { - givenSpawnPrereqs(); - given(frame.getMessageStackDepth()).willReturn(1024); - - assertSameResult(EMPTY_HALT_RESULT, subject.execute(frame, evm)); - verify(frame).pushStackItem(UInt256.ZERO); + subject = new HederaCreateOperationExternalizer( + creator, syntheticTxnFactory, recordsHistorian, dynamicProperties); } @Test @@ -226,20 +128,17 @@ void hasExpectedChildCompletionOnSuccessWithSidecarEnabled() { final var mockCreation = TransactionBody.newBuilder() .setContractCreateInstance(ContractCreateTransactionBody.newBuilder() .setAutoRenewAccountId(autoRenewId.toGrpcAccountId())); - final var frameCaptor = ArgumentCaptor.forClass(MessageFrame.class); - givenSpawnPrereqs(); - givenBuilderPrereqs(); + given(frame.getWorldUpdater()).willReturn(updater); givenUpdaterWithAliases(EntityIdUtils.parseAccount("0.0.1234"), nonEmptyKey); given(updater.customizerForPendingCreation()).willReturn(contractCustomizer); given(updater.idOfLastNewAddress()).willReturn(lastAllocated); given(syntheticTxnFactory.contractCreation(contractCustomizer)).willReturn(mockCreation); given(creator.createSuccessfulSyntheticRecord(any(), any(), any())).willReturn(liveRecord); final var initCode = "initCode".getBytes(); - given(frame.readMemory(anyLong(), anyLong())).willReturn(Bytes.wrap(initCode)); final var newContractMock = mock(Account.class); final var runtimeCode = "runtimeCode".getBytes(); given(newContractMock.getCode()).willReturn(Bytes.of(runtimeCode)); - given(updater.get(Subject.PRETEND_CONTRACT_ADDRESS)).willReturn(newContractMock); + given(updater.get(PRETEND_CONTRACT_ADDRESS)).willReturn(newContractMock); final var sidecarRecord = TransactionSidecarRecord.newBuilder() .setConsensusTimestamp(Timestamp.newBuilder().setSeconds(666L).build()); final var sidecarUtilsMockedStatic = mockStatic(SidecarUtils.class); @@ -247,16 +146,13 @@ void hasExpectedChildCompletionOnSuccessWithSidecarEnabled() { .when(() -> SidecarUtils.createContractBytecodeSidecarFrom(lastAllocated, initCode, runtimeCode)) .thenReturn(sidecarRecord); given(dynamicProperties.enabledSidecars()).willReturn(Set.of(SidecarType.CONTRACT_BYTECODE)); + given(childFrame.getContractAddress()).willReturn(PRETEND_CONTRACT_ADDRESS); + given(childFrame.getCode()).willReturn(CodeFactory.createCode(Bytes.wrap(initCode), Hash.EMPTY, 0, false)); - assertSameResult(EMPTY_HALT_RESULT, subject.execute(frame, evm)); + // when + subject.externalize(frame, childFrame); - verify(stack).addFirst(frameCaptor.capture()); - final var childFrame = frameCaptor.getValue(); - // when: - childFrame.setState(MessageFrame.State.COMPLETED_SUCCESS); - childFrame.notifyCompletion(); // then: - verify(frame).pushStackItem(Words.fromAddress(Subject.PRETEND_CONTRACT_ADDRESS)); verify(creator) .createSuccessfulSyntheticRecord(eq(Collections.emptyList()), trackerCaptor.capture(), eq(EMPTY_MEMO)); verify(updater).manageInProgressRecord(recordsHistorian, liveRecord, mockCreation, List.of(sidecarRecord)); @@ -265,7 +161,7 @@ void hasExpectedChildCompletionOnSuccessWithSidecarEnabled() { assertTrue(tracker.hasTrackedContractCreation()); assertEquals(lastAllocated, tracker.getTrackedNewContractId()); assertArrayEquals( - Subject.PRETEND_CONTRACT_ADDRESS.toArrayUnsafe(), + PRETEND_CONTRACT_ADDRESS.toArrayUnsafe(), tracker.getNewEntityAlias().toByteArray()); // and: assertTrue(liveRecord.shouldNotBeExternalized()); @@ -280,25 +176,19 @@ void hasExpectedChildCompletionOnSuccessWithoutSidecarEnabled() { final var mockCreation = TransactionBody.newBuilder() .setContractCreateInstance(ContractCreateTransactionBody.newBuilder() .setAutoRenewAccountId(autoRenewId.toGrpcAccountId())); - final var frameCaptor = ArgumentCaptor.forClass(MessageFrame.class); - givenSpawnPrereqs(); - givenBuilderPrereqs(); + given(frame.getWorldUpdater()).willReturn(updater); givenUpdaterWithAliases(EntityIdUtils.parseAccount("0.0.1234"), nonEmptyKey); given(updater.customizerForPendingCreation()).willReturn(contractCustomizer); given(updater.idOfLastNewAddress()).willReturn(lastAllocated); given(syntheticTxnFactory.contractCreation(contractCustomizer)).willReturn(mockCreation); given(creator.createSuccessfulSyntheticRecord(any(), any(), any())).willReturn(liveRecord); given(dynamicProperties.enabledSidecars()).willReturn(Set.of()); + given(childFrame.getContractAddress()).willReturn(PRETEND_CONTRACT_ADDRESS); - assertSameResult(EMPTY_HALT_RESULT, subject.execute(frame, evm)); - - verify(stack).addFirst(frameCaptor.capture()); - final var childFrame = frameCaptor.getValue(); // when: - childFrame.setState(MessageFrame.State.COMPLETED_SUCCESS); - childFrame.notifyCompletion(); + subject.externalize(frame, childFrame); + // then: - verify(frame).pushStackItem(Words.fromAddress(Subject.PRETEND_CONTRACT_ADDRESS)); verify(creator) .createSuccessfulSyntheticRecord(eq(Collections.emptyList()), trackerCaptor.capture(), eq(EMPTY_MEMO)); verify(updater).manageInProgressRecord(recordsHistorian, liveRecord, mockCreation, Collections.emptyList()); @@ -307,7 +197,7 @@ void hasExpectedChildCompletionOnSuccessWithoutSidecarEnabled() { assertTrue(tracker.hasTrackedContractCreation()); assertEquals(lastAllocated, tracker.getTrackedNewContractId()); assertArrayEquals( - Subject.PRETEND_CONTRACT_ADDRESS.toArrayUnsafe(), + PRETEND_CONTRACT_ADDRESS.toArrayUnsafe(), tracker.getNewEntityAlias().toByteArray()); // and: assertTrue(liveRecord.shouldNotBeExternalized()); @@ -321,21 +211,17 @@ void hasExpectedHollowAccountCompletionOnSuccessWithSidecarEnabled() { final var mockCreation = TransactionBody.newBuilder() .setContractCreateInstance(ContractCreateTransactionBody.newBuilder() .setAutoRenewAccountId(autoRenewId.toGrpcAccountId())); - final var frameCaptor = ArgumentCaptor.forClass(MessageFrame.class); - givenSpawnPrereqs(); - givenBuilderPrereqs(); - given(dynamicProperties.isLazyCreationEnabled()).willReturn(true); + given(frame.getWorldUpdater()).willReturn(updater); final var hollowAccountId = EntityIdUtils.parseAccount("0.0.5678"); givenUpdaterWithAliases(hollowAccountId, EMPTY_KEY); given(updater.customizerForPendingCreation()).willReturn(contractCustomizer); given(syntheticTxnFactory.contractCreation(contractCustomizer)).willReturn(mockCreation); given(creator.createSuccessfulSyntheticRecord(any(), any(), any())).willReturn(liveRecord); final var initCode = "initCode".getBytes(); - given(frame.readMemory(anyLong(), anyLong())).willReturn(Bytes.wrap(initCode)); final var newContractMock = mock(Account.class); final var runtimeCode = "runtimeCode".getBytes(); given(newContractMock.getCode()).willReturn(Bytes.of(runtimeCode)); - given(updater.get(Subject.PRETEND_CONTRACT_ADDRESS)).willReturn(newContractMock); + given(updater.get(PRETEND_CONTRACT_ADDRESS)).willReturn(newContractMock); final var sidecarRecord = TransactionSidecarRecord.newBuilder() .setConsensusTimestamp(Timestamp.newBuilder().setSeconds(666L).build()); final var sidecarUtilsMockedStatic = mockStatic(SidecarUtils.class); @@ -344,16 +230,13 @@ void hasExpectedHollowAccountCompletionOnSuccessWithSidecarEnabled() { EntityIdUtils.asContract(hollowAccountId), initCode, runtimeCode)) .thenReturn(sidecarRecord); given(dynamicProperties.enabledSidecars()).willReturn(Set.of(SidecarType.CONTRACT_BYTECODE)); + given(childFrame.getContractAddress()).willReturn(PRETEND_CONTRACT_ADDRESS); + given(childFrame.getCode()).willReturn(CodeFactory.createCode(Bytes.wrap(initCode), Hash.EMPTY, 0, false)); - assertSameResult(EMPTY_HALT_RESULT, subject.execute(frame, evm)); - - verify(stack).addFirst(frameCaptor.capture()); - final var childFrame = frameCaptor.getValue(); // when: - childFrame.setState(MessageFrame.State.COMPLETED_SUCCESS); - childFrame.notifyCompletion(); + subject.externalize(frame, childFrame); + // then: - verify(frame).pushStackItem(Words.fromAddress(Subject.PRETEND_CONTRACT_ADDRESS)); verify(creator) .createSuccessfulSyntheticRecord(eq(Collections.emptyList()), trackerCaptor.capture(), eq(EMPTY_MEMO)); verify(updater).reclaimLatestContractId(); @@ -366,7 +249,7 @@ void hasExpectedHollowAccountCompletionOnSuccessWithSidecarEnabled() { assertTrue(tracker.hasTrackedContractCreation()); assertEquals(EntityIdUtils.asContract(hollowAccountId), tracker.getTrackedNewContractId()); assertArrayEquals( - Subject.PRETEND_CONTRACT_ADDRESS.toArrayUnsafe(), + PRETEND_CONTRACT_ADDRESS.toArrayUnsafe(), tracker.getNewEntityAlias().toByteArray()); // and: assertTrue(liveRecord.shouldNotBeExternalized()); @@ -381,26 +264,19 @@ void hasExpectedHollowAccountCompletionOnSuccessWithoutSidecarEnabled() { final var mockCreation = TransactionBody.newBuilder() .setContractCreateInstance(ContractCreateTransactionBody.newBuilder() .setAutoRenewAccountId(autoRenewId.toGrpcAccountId())); - final var frameCaptor = ArgumentCaptor.forClass(MessageFrame.class); - givenSpawnPrereqs(); - givenBuilderPrereqs(); - given(dynamicProperties.isLazyCreationEnabled()).willReturn(true); + given(frame.getWorldUpdater()).willReturn(updater); final var hollowAccountId = EntityIdUtils.parseAccount("0.0.5678"); givenUpdaterWithAliases(hollowAccountId, EMPTY_KEY); given(updater.customizerForPendingCreation()).willReturn(contractCustomizer); given(syntheticTxnFactory.contractCreation(contractCustomizer)).willReturn(mockCreation); given(creator.createSuccessfulSyntheticRecord(any(), any(), any())).willReturn(liveRecord); given(dynamicProperties.enabledSidecars()).willReturn(Set.of()); + given(childFrame.getContractAddress()).willReturn(PRETEND_CONTRACT_ADDRESS); - assertSameResult(EMPTY_HALT_RESULT, subject.execute(frame, evm)); - - verify(stack).addFirst(frameCaptor.capture()); - final var childFrame = frameCaptor.getValue(); // when: - childFrame.setState(MessageFrame.State.COMPLETED_SUCCESS); - childFrame.notifyCompletion(); + subject.externalize(frame, childFrame); + // then: - verify(frame).pushStackItem(Words.fromAddress(Subject.PRETEND_CONTRACT_ADDRESS)); verify(creator) .createSuccessfulSyntheticRecord(eq(Collections.emptyList()), trackerCaptor.capture(), eq(EMPTY_MEMO)); verify(updater).reclaimLatestContractId(); @@ -413,29 +289,23 @@ void hasExpectedHollowAccountCompletionOnSuccessWithoutSidecarEnabled() { assertTrue(tracker.hasTrackedContractCreation()); assertEquals(EntityIdUtils.asContract(hollowAccountId), tracker.getTrackedNewContractId()); assertArrayEquals( - Subject.PRETEND_CONTRACT_ADDRESS.toArrayUnsafe(), + PRETEND_CONTRACT_ADDRESS.toArrayUnsafe(), tracker.getNewEntityAlias().toByteArray()); // and: assertTrue(liveRecord.shouldNotBeExternalized()); } + private void givenUpdaterWithAliases(final AccountID expectedAccountId, final JKey expectedKey) { + given(updater.aliases()).willReturn(aliases); + given(aliases.resolveForEvm(any())).willReturn(EntityIdUtils.asTypedEvmAddress(expectedAccountId)); + given(updater.trackingAccounts()).willReturn(accounts); + given(accounts.contains(expectedAccountId)).willReturn((expectedKey != null)); + given(accounts.get(expectedAccountId, AccountProperty.KEY)).willReturn(expectedKey); + } + @Test - void hasExpectedHollowAccountCompletionWithoutLazyCreationEnabled() { - given(frame.stackSize()).willReturn(3); - given(frame.getStackItem(anyInt())).willReturn(Bytes.ofUnsignedLong(1)); - given(frame.getRemainingGas()).willReturn(Subject.PRETEND_GAS_COST); - given(frame.getRecipientAddress()).willReturn(recipient); + void shouldFailBasedOnlazyCreationIsFalseWhenEnabledAndNoMatchingHollowAccount() { given(frame.getWorldUpdater()).willReturn(updater); - given(updater.getAccount(recipient)).willReturn(recipientAccount); - given(recipientAccount.getMutable()).willReturn(mutableAccount); - given(mutableAccount.getBalance()).willReturn(Wei.of(value)); - given(frame.getMessageFrameStack()).willReturn(stack); - given(updater.updater()).willReturn(updater); - given(frame.getOriginatorAddress()).willReturn(recipient); - given(frame.getGasPrice()).willReturn(gasPrice); - given(frame.getBlockValues()).willReturn(blockValues); - given(frame.getMiningBeneficiary()).willReturn(recipient); - given(frame.getBlockHashLookup()).willReturn(l -> Hash.ZERO); given(dynamicProperties.isLazyCreationEnabled()).willReturn(false); final var hollowAccountId = EntityIdUtils.parseAccount("0.0.5678"); given(updater.aliases()).willReturn(aliases); @@ -443,129 +313,27 @@ void hasExpectedHollowAccountCompletionWithoutLazyCreationEnabled() { given(updater.trackingAccounts()).willReturn(accounts); given(accounts.contains(hollowAccountId)).willReturn(false); - assertSameResult(EMPTY_HALT_RESULT, subject.execute(frame, evm)); + assertFalse(subject.shouldFailBasedOnLazyCreation(frame, PRETEND_CONTRACT_ADDRESS)); } @Test - void hasExpectedChildCompletionOnFailure() { - final var captor = ArgumentCaptor.forClass(MessageFrame.class); - givenSpawnPrereqs(); - givenBuilderPrereqs(); - givenUpdaterWithAliases(EntityIdUtils.parseAccount("0.0.1234"), nonEmptyKey); - - assertSameResult(EMPTY_HALT_RESULT, subject.execute(frame, evm)); - - verify(stack).addFirst(captor.capture()); - final var childFrame = captor.getValue(); - // when: - childFrame.setState(MessageFrame.State.COMPLETED_FAILED); - childFrame.notifyCompletion(); - verify(frame).pushStackItem(UInt256.ZERO); - } - - @Test - void failsWhenMatchingHollowAccountExistsAndLazyCreationDisabled() { - given(frame.stackSize()).willReturn(3); - given(frame.getRemainingGas()).willReturn(Subject.PRETEND_GAS_COST); - given(frame.getStackItem(0)).willReturn(Bytes.ofUnsignedLong(value)); - given(frame.getRecipientAddress()).willReturn(recipient); + void shouldFailBasedOnlazyCreationIsTrueWhenEnabledAndMatchingHollowAccount() { given(frame.getWorldUpdater()).willReturn(updater); - given(updater.getAccount(recipient)).willReturn(recipientAccount); - given(recipientAccount.getMutable()).willReturn(mutableAccount); - given(mutableAccount.getBalance()).willReturn(Wei.of(value)); - given(frame.getMessageStackDepth()).willReturn(1023); - given(frame.getStackItem(anyInt())).willReturn(Bytes.ofUnsignedLong(1)); + given(dynamicProperties.isLazyCreationEnabled()).willReturn(false); final var hollowAccountId = EntityIdUtils.parseAccount("0.0.5678"); - givenUpdaterWithAliases(hollowAccountId, EMPTY_KEY); - - subject.execute(frame, evm); - - verify(frame).readMutableMemory(1L, 1L); - verify(frame).popStackItems(3); - verify(frame).pushStackItem(UInt256.ZERO); - } - - private void givenBuilderPrereqs() { - given(frame.getMessageFrameStack()).willReturn(stack); - given(updater.updater()).willReturn(updater); - given(gasCalculator.gasAvailableForChildCreate(anyLong())).willReturn(childStipend); - given(frame.getOriginatorAddress()).willReturn(recipient); - given(frame.getGasPrice()).willReturn(gasPrice); - given(frame.getBlockValues()).willReturn(blockValues); - given(frame.getMiningBeneficiary()).willReturn(recipient); - given(frame.getBlockHashLookup()).willReturn(l -> Hash.ZERO); - } - - private void givenSpawnPrereqs() { - given(frame.stackSize()).willReturn(3); - given(frame.getStackItem(anyInt())).willReturn(Bytes.ofUnsignedLong(1)); - given(frame.getRemainingGas()).willReturn(Subject.PRETEND_GAS_COST); - given(frame.getStackItem(0)).willReturn(Bytes.ofUnsignedLong(value)); - given(frame.getRecipientAddress()).willReturn(recipient); - given(frame.getWorldUpdater()).willReturn(updater); - given(updater.getAccount(recipient)).willReturn(recipientAccount); - given(recipientAccount.getMutable()).willReturn(mutableAccount); - given(mutableAccount.getBalance()).willReturn(Wei.of(value)); - given(frame.getMessageStackDepth()).willReturn(1023); - } - - private void givenUpdaterWithAliases(final AccountID expectedAccountId, final JKey expectedKey) { given(updater.aliases()).willReturn(aliases); - given(aliases.resolveForEvm(any())).willReturn(EntityIdUtils.asTypedEvmAddress(expectedAccountId)); + given(aliases.resolveForEvm(any())).willReturn(EntityIdUtils.asTypedEvmAddress(hollowAccountId)); given(updater.trackingAccounts()).willReturn(accounts); - given(accounts.contains(expectedAccountId)).willReturn((expectedKey != null)); - given(accounts.get(expectedAccountId, AccountProperty.KEY)).willReturn(expectedKey); - } + given(accounts.contains(hollowAccountId)).willReturn(true); + given(accounts.get(any(), any())).willReturn(EMPTY_KEY); - private void assertSameResult(final Operation.OperationResult expected, final Operation.OperationResult actual) { - assertEquals(expected.getGasCost(), actual.getGasCost()); - assertEquals(expected.getHaltReason(), actual.getHaltReason()); + assertTrue(subject.shouldFailBasedOnLazyCreation(frame, PRETEND_CONTRACT_ADDRESS)); } - static class Subject extends AbstractRecordingCreateOperation { - static final long PRETEND_GAS_COST = 123L; - static final Address PRETEND_CONTRACT_ADDRESS = Address.ALTBN128_ADD; - static final long PRETEND_COST = PRETEND_GAS_COST; - - boolean isEnabled = true; - - protected Subject( - 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) { - super( - opcode, - name, - stackItemsConsumed, - stackItemsProduced, - opSize, - gasCalculator, - creator, - syntheticTxnFactory, - recordsHistorian, - dynamicProperties); - } - - @Override - protected boolean isEnabled() { - return isEnabled; - } - - @Override - protected long cost(final MessageFrame frame) { - return PRETEND_GAS_COST; - } - - @Override - protected Address targetContractAddress(final MessageFrame frame) { - return PRETEND_CONTRACT_ADDRESS; - } + @Test + void shouldFailBasedOnLazyCreationIsFalseWhenLazyCreationEnabled() { + given(dynamicProperties.isLazyCreationEnabled()).willReturn(true); + + assertFalse(subject.shouldFailBasedOnLazyCreation(frame, PRETEND_CONTRACT_ADDRESS)); } }