From b7bb0458dd9e82cc6f1d5d4b0ba77a4ce5b14d00 Mon Sep 17 00:00:00 2001 From: Luke Lee Date: Thu, 4 Dec 2025 07:39:37 -0800 Subject: [PATCH 1/6] feat: contract service interface - first attempt Signed-off-by: Luke Lee --- .../impl/ContractServiceApiProvider.java | 60 +++++++++++++++++++ .../service/contract/ContractServiceApi.java | 20 +++++++ 2 files changed, 80 insertions(+) create mode 100644 hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceApiProvider.java create mode 100644 hedera-node/hedera-smart-contract-service/src/main/java/com/hedera/node/app/service/contract/ContractServiceApi.java diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceApiProvider.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceApiProvider.java new file mode 100644 index 000000000000..f33e0dbf1889 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceApiProvider.java @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.hedera.node.app.service.contract.impl; + +import static java.util.Objects.requireNonNull; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.ContractID; +import com.hedera.node.app.service.contract.ContractService; +import com.hedera.node.app.service.contract.ContractServiceApi; +import com.hedera.node.app.service.contract.impl.state.WritableContractStateStore; +import com.hedera.node.app.service.entityid.WritableEntityCounters; +import com.hedera.node.app.spi.api.ServiceApiProvider; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.config.api.Configuration; +import com.swirlds.state.spi.WritableStates; +import edu.umd.cs.findbugs.annotations.NonNull; +import javax.inject.Singleton; +import org.jetbrains.annotations.NotNull; + +@Singleton +public class ContractServiceApiProvider implements ServiceApiProvider { + + @Override + public String serviceName() { + return ContractService.NAME; + } + + @Override + public ContractServiceApi newInstance( + @NonNull final Configuration configuration, + @NonNull final WritableStates writableStates, + @NonNull final WritableEntityCounters entityCounters) { + requireNonNull(configuration); + requireNonNull(writableStates); + requireNonNull(entityCounters); + return new ContractServiceApiImpl(new WritableContractStateStore(writableStates, entityCounters)); + } + + /** + * Default implementation of the {@link ContractServiceApi} interface. + */ + public class ContractServiceApiImpl implements ContractServiceApi { + private final WritableContractStateStore contractStateStore; + + public ContractServiceApiImpl(@NonNull final WritableContractStateStore contractStateStore) { + this.contractStateStore = requireNonNull(contractStateStore); + } + + @Override + public void setAccountBytecode(@NotNull AccountID accountID, @NotNull Bytes bytecode) { + final var contractID = ContractID.newBuilder() + .shardNum(accountID.shardNum()) + .realmNum(accountID.realmNum()) + .contractNum(accountID.accountNumOrThrow()) + .build(); + contractStateStore.putBytecode( + contractID, new com.hedera.hapi.node.state.contract.Bytecode(requireNonNull(bytecode))); + } + } +} diff --git a/hedera-node/hedera-smart-contract-service/src/main/java/com/hedera/node/app/service/contract/ContractServiceApi.java b/hedera-node/hedera-smart-contract-service/src/main/java/com/hedera/node/app/service/contract/ContractServiceApi.java new file mode 100644 index 000000000000..8254c6c0654e --- /dev/null +++ b/hedera-node/hedera-smart-contract-service/src/main/java/com/hedera/node/app/service/contract/ContractServiceApi.java @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.hedera.node.app.service.contract; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * Additional API for the Smart Contract Service beyond its dispatchable handlers. + *

Currently only used by the token service. + */ +public interface ContractServiceApi { + /** + * Sets the account identified by the accountID with the associated runtime bytecode in the ContractStateStore. + * + * @param accountID id of the account to set the bytecode for + * @param bytecode the runtime bytecode to set for the account + */ + void setAccountBytecode(@NonNull AccountID accountID, @NonNull Bytes bytecode); +} From 08b54075fafbb66d7d245d55eebcdb8b21fff835 Mon Sep 17 00:00:00 2001 From: Luke Lee Date: Thu, 4 Dec 2025 07:51:41 -0800 Subject: [PATCH 2/6] feat: Contract service interface Signed-off-by: Luke Lee --- .../main/proto/services/crypto_create.proto | 7 ++++++ .../main/proto/services/crypto_update.proto | 7 ++++++ .../app/services/ServicesInjectionModule.java | 5 ++++ .../app/workflows/FacilityInitModule.java | 10 ++++++-- .../impl/StandaloneDispatchFactory.java | 9 +++++-- .../impl/ContractServiceApiProvider.java | 16 +++++++++---- .../impl/ContractServiceComponent.java | 11 ++++++++- .../contract/impl/ContractServiceImpl.java | 11 ++++++++- .../delegation/CodeDelegationProcessor.java | 2 ++ .../exec/delegation/CodeDelegationResult.java | 6 ++++- .../impl/exec/gas/HederaGasCalculator.java | 19 +++++++++++++++ .../exec/gas/HederaGasCalculatorImpl.java | 24 ++++++++++++++++--- .../handlers/EthereumTransactionHandler.java | 4 +++- .../impl/state/ContractStateStore.java | 8 +++++++ .../state/ReadableContractStateStore.java | 5 ++++ .../state/WritableContractStateStore.java | 6 +++++ .../exec/gas/HederaGasCalculatorImplTest.java | 19 +++++++++++++++ .../app/service/contract/ContractService.java | 6 +++++ .../service/contract/ContractServiceApi.java | 2 +- .../impl/handlers/CryptoCreateHandler.java | 12 ++++++++++ .../impl/handlers/CryptoUpdateHandler.java | 13 ++++++++++ .../src/main/java/module-info.java | 1 + 22 files changed, 187 insertions(+), 16 deletions(-) diff --git a/hapi/hedera-protobuf-java-api/src/main/proto/services/crypto_create.proto b/hapi/hedera-protobuf-java-api/src/main/proto/services/crypto_create.proto index 108b8b0de257..6c87bf7c8664 100644 --- a/hapi/hedera-protobuf-java-api/src/main/proto/services/crypto_create.proto +++ b/hapi/hedera-protobuf-java-api/src/main/proto/services/crypto_create.proto @@ -204,4 +204,11 @@ message CryptoCreateTransactionBody { * Details of hooks to add immediately after creating this account. */ repeated com.hedera.hapi.node.hooks.HookCreationDetails hook_creation_details = 19; + + /** + * The delegation contract address for the account. + * If this field is set, a call to the account's address within a smart contract will + * result in the code of the authorized contract being executed. + */ + bytes delegation_address = 20; } diff --git a/hapi/hedera-protobuf-java-api/src/main/proto/services/crypto_update.proto b/hapi/hedera-protobuf-java-api/src/main/proto/services/crypto_update.proto index 539b712639c6..66db2aa19725 100644 --- a/hapi/hedera-protobuf-java-api/src/main/proto/services/crypto_update.proto +++ b/hapi/hedera-protobuf-java-api/src/main/proto/services/crypto_update.proto @@ -226,4 +226,11 @@ message CryptoUpdateTransactionBody { * The hooks to create for the account. */ repeated com.hedera.hapi.node.hooks.HookCreationDetails hook_creation_details = 20; + + /** + * The delegated contract address for the account. + * If this field is set, a call to the account's address within a smart contract will + * result in the code of the authorized contract being executed. + */ + bytes delegation_address = 21; } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/ServicesInjectionModule.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/ServicesInjectionModule.java index a8512f6c8a5c..9766cc5e3532 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/ServicesInjectionModule.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/ServicesInjectionModule.java @@ -1,6 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 package com.hedera.node.app.services; +import com.hedera.node.app.service.contract.ContractService; +import com.hedera.node.app.service.contract.impl.ContractServiceImpl; import com.hedera.node.app.service.file.impl.FileServiceInjectionModule; import com.hedera.node.app.service.schedule.ScheduleService; import com.hedera.node.app.service.schedule.impl.ScheduleServiceImpl; @@ -19,4 +21,7 @@ public interface ServicesInjectionModule { @Binds ScheduleService bindScheduleService(ScheduleServiceImpl impl); + + @Binds + ContractService bindContractService(ContractServiceImpl impl); } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/FacilityInitModule.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/FacilityInitModule.java index 4711c44acf0f..1298afc2343d 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/FacilityInitModule.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/FacilityInitModule.java @@ -22,6 +22,8 @@ import com.hedera.node.app.fees.ExchangeRateManager; import com.hedera.node.app.fees.FeeManager; import com.hedera.node.app.records.BlockRecordService; +import com.hedera.node.app.service.contract.ContractService; +import com.hedera.node.app.service.contract.ContractServiceApi; import com.hedera.node.app.service.file.ReadableFileStore; import com.hedera.node.app.service.file.impl.FileServiceImpl; import com.hedera.node.app.service.schedule.ScheduleService; @@ -75,13 +77,17 @@ static Supplier provideBaseFeeCharging(@NonNull final AppContext ap @Provides @Singleton - static Map, ServiceApiProvider> provideApiProviders(@NonNull final ScheduleService scheduleService) { + static Map, ServiceApiProvider> provideApiProviders( + @NonNull final ScheduleService scheduleService, @NonNull final ContractService contractService) { requireNonNull(scheduleService); + requireNonNull(contractService); return Map.of( TokenServiceApi.class, TOKEN_SERVICE_API_PROVIDER, ScheduleServiceApi.class, - scheduleService.apiProvider()); + scheduleService.apiProvider(), + ContractServiceApi.class, + contractService.apiProvider()); } @Binds diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/impl/StandaloneDispatchFactory.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/impl/StandaloneDispatchFactory.java index 9ee3d43e0994..dd4e58f95290 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/impl/StandaloneDispatchFactory.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/impl/StandaloneDispatchFactory.java @@ -22,6 +22,8 @@ import com.hedera.node.app.fees.ResourcePriceCalculatorImpl; import com.hedera.node.app.info.NodeInfoImpl; import com.hedera.node.app.records.impl.BlockRecordInfoImpl; +import com.hedera.node.app.service.contract.ContractServiceApi; +import com.hedera.node.app.service.contract.impl.ContractServiceImpl; import com.hedera.node.app.service.entityid.EntityIdService; import com.hedera.node.app.service.entityid.impl.EntityNumGeneratorImpl; import com.hedera.node.app.service.entityid.impl.WritableEntityIdStoreImpl; @@ -107,7 +109,8 @@ public StandaloneDispatchFactory( @NonNull final TransactionDispatcher transactionDispatcher, @NonNull final NetworkUtilizationManager networkUtilizationManager, @NonNull final TransactionChecker transactionChecker, - @NonNull final ScheduleServiceImpl scheduleService) { + @NonNull final ScheduleServiceImpl scheduleService, + @NonNull final ContractServiceImpl contractService) { this.feeManager = requireNonNull(feeManager); this.appFeeCharging = requireNonNull(appFeeCharging); this.authorizer = requireNonNull(authorizer); @@ -127,7 +130,9 @@ public StandaloneDispatchFactory( TokenServiceApi.class, TOKEN_SERVICE_API_PROVIDER, ScheduleServiceApi.class, - scheduleService.apiProvider()); + scheduleService.apiProvider(), + ContractServiceApi.class, + contractService.apiProvider()); } /** diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceApiProvider.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceApiProvider.java index f33e0dbf1889..7c85d42664c8 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceApiProvider.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceApiProvider.java @@ -2,6 +2,7 @@ package com.hedera.node.app.service.contract.impl; import static java.util.Objects.requireNonNull; +import static org.hyperledger.besu.evm.worldstate.CodeDelegationHelper.CODE_DELEGATION_PREFIX; import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.ContractID; @@ -15,7 +16,6 @@ import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import javax.inject.Singleton; -import org.jetbrains.annotations.NotNull; @Singleton public class ContractServiceApiProvider implements ServiceApiProvider { @@ -47,14 +47,22 @@ public ContractServiceApiImpl(@NonNull final WritableContractStateStore contract } @Override - public void setAccountBytecode(@NotNull AccountID accountID, @NotNull Bytes bytecode) { + public void setAccountDelegationTarget(@NonNull final AccountID accountID, @NonNull final Bytes bytecode) { + requireNonNull(accountID); + requireNonNull(bytecode); final var contractID = ContractID.newBuilder() .shardNum(accountID.shardNum()) .realmNum(accountID.realmNum()) .contractNum(accountID.accountNumOrThrow()) .build(); - contractStateStore.putBytecode( - contractID, new com.hedera.hapi.node.state.contract.Bytecode(requireNonNull(bytecode))); + if (bytecode.equals(Bytes.EMPTY)) { + // Remove the bytecode if the bytecode is empty + contractStateStore.removeBytecode(contractID); + } else { + final var delegationIndicator = Bytes.merge(Bytes.wrap(CODE_DELEGATION_PREFIX.toArray()), bytecode); + contractStateStore.putBytecode( + contractID, new com.hedera.hapi.node.state.contract.Bytecode(delegationIndicator)); + } } } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceComponent.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceComponent.java index eafd18387c0e..10b1e2790a7a 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceComponent.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceComponent.java @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 package com.hedera.node.app.service.contract.impl; +import com.hedera.node.app.service.contract.ContractServiceApi; import com.hedera.node.app.service.contract.impl.annotations.CustomOps; import com.hedera.node.app.service.contract.impl.exec.metrics.ContractMetrics; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategies; @@ -12,6 +13,7 @@ import com.hedera.node.app.service.contract.impl.handlers.ContractHandlers; import com.hedera.node.app.service.contract.impl.nativelibverification.NativeLibVerifier; import com.hedera.node.app.service.entityid.EntityIdFactory; +import com.hedera.node.app.spi.api.ServiceApiProvider; import com.hedera.node.app.spi.signatures.SignatureVerifier; import dagger.BindsInstance; import dagger.Component; @@ -46,6 +48,7 @@ interface Factory { * @param systemContractMethodRegistry registry of all system contract methods * @param customOps any additional custom operations to use when constructing the EVM * @param entityIdFactory a factory for creating entity IDs + * @param nativeLibVerifier the native library verifier * @return the contract service component */ ContractServiceComponent create( @@ -57,7 +60,8 @@ ContractServiceComponent create( @BindsInstance SystemContractMethodRegistry systemContractMethodRegistry, @BindsInstance @CustomOps Set customOps, @BindsInstance EntityIdFactory entityIdFactory, - @BindsInstance NativeLibVerifier nativeLibVerifier); + @BindsInstance NativeLibVerifier nativeLibVerifier, + @BindsInstance ServiceApiProvider contractServiceApiProvider); } /** @@ -80,6 +84,11 @@ ContractServiceComponent create( */ SystemContractMethodRegistry systemContractMethodRegistry(); + /** + * Provides the {@link ContractServiceApi} provider. + */ + ServiceApiProvider contractServiceApiProvider(); + @Named("HasTranslators") Provider>> hasCallTranslators(); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceImpl.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceImpl.java index b05709928e8b..6657d36406fb 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceImpl.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceImpl.java @@ -4,6 +4,7 @@ import static java.util.Objects.requireNonNull; import com.hedera.node.app.service.contract.ContractService; +import com.hedera.node.app.service.contract.ContractServiceApi; import com.hedera.node.app.service.contract.impl.exec.metrics.ContractMetrics; import com.hedera.node.app.service.contract.impl.exec.scope.DefaultVerificationStrategies; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategies; @@ -15,6 +16,7 @@ import com.hedera.node.app.service.contract.impl.schemas.V0490ContractSchema; import com.hedera.node.app.service.contract.impl.schemas.V065ContractSchema; import com.hedera.node.app.spi.AppContext; +import com.hedera.node.app.spi.api.ServiceApiProvider; import com.hedera.node.config.data.ContractsConfig; import com.swirlds.metrics.api.Metrics; import com.swirlds.state.lifecycle.SchemaRegistry; @@ -67,6 +69,7 @@ public ContractServiceImpl( final var systemContractMethodRegistry = new SystemContractMethodRegistry(); final var contractMetrics = new ContractMetrics(metrics, contractsConfigSupplier, systemContractMethodRegistry); final var nativeLibVerifier = new NativeLibVerifier(contractsConfigSupplier); + final var contractServiceApiProvider = new ContractServiceApiProvider(); this.component = DaggerContractServiceComponent.factory() .create( @@ -80,7 +83,8 @@ public ContractServiceImpl( systemContractMethodRegistry, customOps, appContext.idFactory(), - nativeLibVerifier); + nativeLibVerifier, + contractServiceApiProvider); } @Override @@ -123,4 +127,9 @@ public NativeLibVerifier nativeLibVerifier() { allCallTranslators.addAll(component.htsCallTranslators().get()); return allCallTranslators; } + + @Override + public ServiceApiProvider apiProvider() { + return component.contractServiceApiProvider(); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/delegation/CodeDelegationProcessor.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/delegation/CodeDelegationProcessor.java index 1999f75a8786..efc3fdfd531a 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/delegation/CodeDelegationProcessor.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/delegation/CodeDelegationProcessor.java @@ -6,6 +6,7 @@ import com.hedera.node.app.hapi.utils.ethereum.CodeDelegation; import com.hedera.node.app.hapi.utils.ethereum.EthTxSigs; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransaction; +import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; import java.math.BigInteger; import java.util.Optional; import org.apache.tuweni.bytes.Bytes; @@ -111,6 +112,7 @@ private void setAccountCodeToDelegationIndicator( if (codeDelegation.nonce() != 0) { return; } + ((ProxyWorldUpdater) worldUpdater).setupTopLevelLazyCreate(authorizerAddress); authority = worldUpdater.createAccount(authorizerAddress); } else { authority = maybeAuthorityAccount.get(); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/delegation/CodeDelegationResult.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/delegation/CodeDelegationResult.java index e033cd77c5da..9fd7e94d7a4b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/delegation/CodeDelegationResult.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/delegation/CodeDelegationResult.java @@ -17,10 +17,14 @@ public void addAccessedDelegatorAddress(final Address address) { } public void incrementAlreadyExistingDelegators() { - alreadyExistingDelegators += 1; + this.alreadyExistingDelegators += 1; } public Set

accessedDelegatorAddresses() { return accessedDelegatorAddresses; } + + public long alreadyExistingDelegators() { + return alreadyExistingDelegators; + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/HederaGasCalculator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/HederaGasCalculator.java index 7963d8fa6db0..917525741f37 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/HederaGasCalculator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/HederaGasCalculator.java @@ -21,4 +21,23 @@ public interface HederaGasCalculator extends GasCalculator { */ GasCharges transactionGasRequirements( @NonNull final Bytes payload, final boolean isContractCreate, final long baselineCost); + + /** + * Calculate gas requirements of the transaction. + * This method mirrors {{@link GasCalculator#transactionIntrinsicGasCost(Transaction, long)}, + * but does not require a full Transaction object and uses + * {@link GasCalculator#transactionFloorCost(Bytes, long)} for `minimumGasUsed` calculation. + * This overloaded also takes into account the size of the authorization list for type 4 transactions. + * + * @param payload the payload of the transaction + * @param isContractCreate is this call a 'contract creation' + * @param baselineCost the gas used by access lists and code delegation authorizations + * @param authorizationListSize the number of entries in the authorization list for type 4 transactions + * @return The gas requirements of the transaction + */ + GasCharges transactionGasRequirements( + @NonNull final Bytes payload, + final boolean isContractCreate, + final long baselineCost, + final long authorizationListSize); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/HederaGasCalculatorImpl.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/HederaGasCalculatorImpl.java index ad8cd6c7276b..c75a12e7ac4c 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/HederaGasCalculatorImpl.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/HederaGasCalculatorImpl.java @@ -29,6 +29,7 @@ public class HederaGasCalculatorImpl extends PragueGasCalculator implements Hede private static final int LOG_CONTRACT_ID_SIZE = 24; private static final int LOG_TOPIC_SIZE = 32; private static final int LOG_BLOOM_SIZE = 256; + public static final long INTRINSIC_DELEGATION_GAS_COST = 25_000L; /** * Default constructor for injection. @@ -41,8 +42,18 @@ public HederaGasCalculatorImpl() { @Override public GasCharges transactionGasRequirements( @NonNull final Bytes payload, final boolean isContractCreate, final long baselineCost) { + return transactionGasRequirements(payload, isContractCreate, baselineCost, 0L); + } + + @Override + public GasCharges transactionGasRequirements( + @NonNull final Bytes payload, + final boolean isContractCreate, + final long baselineCost, + final long authorizationListSize) { int zeros = payloadZeroBytes(payload); - final long intrinsicGas = transactionIntrinsicGas(payload, zeros, isContractCreate, baselineCost); + final long intrinsicGas = + transactionIntrinsicGas(payload, zeros, isContractCreate, baselineCost, authorizationListSize); // gasUsed described at https://eips.ethereum.org/EIPS/eip-7623 final long floorGas = transactionFloorCost(payload, zeros); return new GasCharges(intrinsicGas, Math.max(intrinsicGas, floorGas), 0L); @@ -60,9 +71,16 @@ protected int payloadZeroBytes(@NonNull final Bytes payload) { // TODO We won't use the baseline cost for now, should revisit with the Pectra support epic protected long transactionIntrinsicGas( - @NonNull final Bytes payload, final int zeros, final boolean isContractCreate, final long baselineCost) { + @NonNull final Bytes payload, + final int zeros, + final boolean isContractCreate, + final long baselineCost, + final long authorizationListSize) { final int nonZeros = payload.size() - zeros; - long cost = TX_BASE_COST + TX_DATA_ZERO_COST * zeros + ISTANBUL_TX_DATA_NON_ZERO_COST * nonZeros; + long cost = TX_BASE_COST + + TX_DATA_ZERO_COST * zeros + + ISTANBUL_TX_DATA_NON_ZERO_COST * nonZeros + + INTRINSIC_DELEGATION_GAS_COST * authorizationListSize; return isContractCreate ? (cost + contractCreationCost(payload.size())) : cost; } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java index a54041e05589..f5a3bb9794bd 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java @@ -115,8 +115,10 @@ public void pureChecks(@NonNull final PureChecksContext context) throws PreCheck final byte[] callData = ethTxData.hasCallData() ? ethTxData.callData() : new byte[0]; final var isContractCreate = !ethTxData.hasToAddress(); // TODO: Revisit baselineGas with Pectra support epic + final var authorizationListSize = + ethTxData.authorizationList() == null ? 0L : ethTxData.authorizationList().length; final var gasRequirements = gasCalculator.transactionGasRequirements( - org.apache.tuweni.bytes.Bytes.wrap(callData), isContractCreate, 0L); + org.apache.tuweni.bytes.Bytes.wrap(callData), isContractCreate, 0L, authorizationListSize); validateTruePreCheck(ethTxData.gasLimit() >= gasRequirements.minimumGasUsed(), INSUFFICIENT_GAS); } catch (@NonNull final Exception e) { bumpExceptionMetrics(ETHEREUM_TRANSACTION, e); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ContractStateStore.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ContractStateStore.java index bad35e764869..88d91eac764a 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ContractStateStore.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ContractStateStore.java @@ -30,6 +30,14 @@ public interface ContractStateStore { */ void putBytecode(@NonNull ContractID contractID, @NonNull Bytecode code); + /** + * Removes the {@link Bytecode} for the given contract number. + * This should only be used with an EOA with a code delegation indicator. + * + * @param contractID the contract id to remove the {@link Bytecode} for + */ + void removeBytecode(@NonNull ContractID contractID); + /** * Removes the given {@link SlotKey}. * diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ReadableContractStateStore.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ReadableContractStateStore.java index 395bbb3f4b2f..6d511bcda707 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ReadableContractStateStore.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ReadableContractStateStore.java @@ -52,6 +52,11 @@ public void putBytecode(@NonNull final ContractID contractId, @NonNull final Byt throw new UnsupportedOperationException("Cannot put bytecode in a read-only store"); } + @Override + public void removeBytecode(@NonNull ContractID contractID) { + throw new UnsupportedOperationException("Cannot remove bytecode in a read-only store"); + } + /** * Refuses to remove slots. * diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/WritableContractStateStore.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/WritableContractStateStore.java index 2e875387a4b4..9ea1b5d35f38 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/WritableContractStateStore.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/WritableContractStateStore.java @@ -59,6 +59,12 @@ public void putBytecode(@NonNull final ContractID contractID, @NonNull final Byt /** * {@inheritDoc} */ + @Override + public void removeBytecode(@NonNull final ContractID contractID) { + bytecode.remove(requireNonNull(contractID)); + entityCounters.decrementEntityTypeCounter(EntityType.CONTRACT_BYTECODE); + } + @Override public void removeSlot(@NonNull final SlotKey key) { storage.remove(requireNonNull(key)); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/HederaGasCalculatorImplTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/HederaGasCalculatorImplTest.java index 8fc059f88a98..6d1c7a74afd3 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/HederaGasCalculatorImplTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/HederaGasCalculatorImplTest.java @@ -73,6 +73,25 @@ void transactionGasRequirements() { gasRequirements.minimumGasUsed()); } + @Test + void transactionWithAuthorizationGasRequirements() { + final var payloadLength = 2048; + byte[] randomPayload = new byte[payloadLength]; + ThreadLocalRandom.current().nextBytes(randomPayload); + final var zeros = IntStream.range(0, randomPayload.length) + .filter(idx -> randomPayload[idx] == 0) + .count(); + // regular transaction + final var gasRequirements = subject.transactionGasRequirements(Bytes.of(randomPayload), false, 0L, 4L); + // Add cost for each authorization as defined by https://eips.ethereum.org/EIPS/eip-7702 + assertEquals( + HederaGasCalculatorImpl.TX_BASE_COST + + HederaGasCalculatorImpl.TX_DATA_ZERO_COST * zeros + + HederaGasCalculatorImpl.ISTANBUL_TX_DATA_NON_ZERO_COST * (randomPayload.length - zeros) + + HederaGasCalculatorImpl.INTRINSIC_DELEGATION_GAS_COST * 4L, + gasRequirements.intrinsicGas()); + } + @Test void transactionGasRequirementsContractCreate() { final var payloadLength = 2048; diff --git a/hedera-node/hedera-smart-contract-service/src/main/java/com/hedera/node/app/service/contract/ContractService.java b/hedera-node/hedera-smart-contract-service/src/main/java/com/hedera/node/app/service/contract/ContractService.java index 5185ae2dca23..d4b298df3e7e 100644 --- a/hedera-node/hedera-smart-contract-service/src/main/java/com/hedera/node/app/service/contract/ContractService.java +++ b/hedera-node/hedera-smart-contract-service/src/main/java/com/hedera/node/app/service/contract/ContractService.java @@ -2,6 +2,7 @@ package com.hedera.node.app.service.contract; import com.hedera.node.app.spi.RpcService; +import com.hedera.node.app.spi.api.ServiceApiProvider; import com.hedera.pbj.runtime.RpcServiceDefinition; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Set; @@ -40,4 +41,9 @@ default String getServiceName() { default Set rpcDefinitions() { return Set.of(SmartContractServiceDefinition.INSTANCE); } + + /** + * Returns the API provider for the contract service. + */ + ServiceApiProvider apiProvider(); } diff --git a/hedera-node/hedera-smart-contract-service/src/main/java/com/hedera/node/app/service/contract/ContractServiceApi.java b/hedera-node/hedera-smart-contract-service/src/main/java/com/hedera/node/app/service/contract/ContractServiceApi.java index 8254c6c0654e..0c5d279e09df 100644 --- a/hedera-node/hedera-smart-contract-service/src/main/java/com/hedera/node/app/service/contract/ContractServiceApi.java +++ b/hedera-node/hedera-smart-contract-service/src/main/java/com/hedera/node/app/service/contract/ContractServiceApi.java @@ -16,5 +16,5 @@ public interface ContractServiceApi { * @param accountID id of the account to set the bytecode for * @param bytecode the runtime bytecode to set for the account */ - void setAccountBytecode(@NonNull AccountID accountID, @NonNull Bytes bytecode); + void setAccountDelegationTarget(@NonNull AccountID accountID, @NonNull Bytes bytecode); } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoCreateHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoCreateHandler.java index 5925091b6f7c..3de3bf314fb2 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoCreateHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoCreateHandler.java @@ -8,6 +8,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ALIAS_KEY; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_CONTRACT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_INITIAL_BALANCE; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_MAX_AUTO_ASSOCIATIONS; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_PAYER_ACCOUNT_ID; @@ -58,6 +59,7 @@ import com.hedera.hapi.node.token.CryptoUpdateTransactionBody; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.hapi.utils.CommonPbjConverters; +import com.hedera.node.app.service.contract.ContractServiceApi; import com.hedera.node.app.service.entityid.EntityIdFactory; import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.service.token.impl.WritableAccountStore; @@ -170,6 +172,10 @@ public void pureChecks(@NonNull final PureChecksContext context) throws PreCheck validateTruePreCheck(key != null, KEY_NOT_PROVIDED); // since pure evm hooks are being removed, just added validations for lambda evm hooks for now validateHookDuplicates(op.hookCreationDetails()); + // If a delegation address is set, it must be of EVM address size + validateTruePreCheck( + op.delegationAddress().length() == 0 || isOfEvmAddressSize(op.delegationAddress()), + INVALID_CONTRACT_ID); } @Override @@ -311,6 +317,12 @@ public void handle(@NonNull final HandleContext context) { accountStore.putAndIncrementCountAlias(alias, createdAccountID); } } + + // If delegation address is set, update state in ContractServiceApi to reflect the delegation + if (op.delegationAddress().length() > 0) { + final var contractServiceApi = context.storeFactory().serviceApi(ContractServiceApi.class); + contractServiceApi.setAccountDelegationTarget(createdAccountID, op.delegationAddress()); + } } /* ----------- Helper Methods ----------- */ diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoUpdateHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoUpdateHandler.java index 82c1a0b68bfa..70dd2c8b6719 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoUpdateHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoUpdateHandler.java @@ -8,6 +8,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.HOOK_ID_REPEATED_IN_CREATION_DETAILS; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ADMIN_KEY; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_CONTRACT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_MAX_AUTO_ASSOCIATIONS; import static com.hedera.hapi.node.base.ResponseCodeEnum.PROXY_ACCOUNT_ID_FIELD_IS_DEPRECATED; import static com.hedera.hapi.node.base.ResponseCodeEnum.REQUESTED_NUM_AUTOMATIC_ASSOCIATIONS_EXCEEDS_ASSOCIATION_LIMIT; @@ -20,6 +21,7 @@ import static com.hedera.node.app.hapi.utils.fee.FeeBuilder.INT_SIZE; import static com.hedera.node.app.hapi.utils.fee.FeeBuilder.LONG_SIZE; import static com.hedera.node.app.hapi.utils.fee.FeeBuilder.getAccountKeyStorageSize; +import static com.hedera.node.app.service.token.AliasUtils.isOfEvmAddressSize; import static com.hedera.node.app.service.token.HookDispatchUtils.dispatchHookCreations; import static com.hedera.node.app.service.token.HookDispatchUtils.dispatchHookDeletions; import static com.hedera.node.app.service.token.HookDispatchUtils.validateHookDuplicates; @@ -43,6 +45,7 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.hapi.utils.CommonPbjConverters; import com.hedera.node.app.hapi.utils.EntityType; +import com.hedera.node.app.service.contract.ContractServiceApi; import com.hedera.node.app.service.token.CryptoSignatureWaivers; import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.service.token.impl.WritableAccountStore; @@ -101,6 +104,10 @@ public void pureChecks(@NonNull final PureChecksContext context) throws PreCheck final var distinctHookIds = op.hookIdsToDelete().stream().distinct().count(); validateTruePreCheck(distinctHookIds == op.hookIdsToDelete().size(), HOOK_ID_REPEATED_IN_CREATION_DETAILS); } + // If a delegation address is set, it must be of EVM address size + validateTruePreCheck( + op.delegationAddress().length() == 0 || isOfEvmAddressSize(op.delegationAddress()), + INVALID_CONTRACT_ID); } @Override @@ -170,6 +177,12 @@ public void handle(@NonNull final HandleContext context) { context.savepointStack() .getBaseBuilder(CryptoUpdateStreamBuilder.class) .accountID(targetAccount.accountIdOrThrow()); + + // If delegation address is set, update state in ContractServiceApi to reflect the delegation + if (op.delegationAddress().length() > 0) { + final var contractServiceApi = context.storeFactory().serviceApi(ContractServiceApi.class); + contractServiceApi.setAccountDelegationTarget(target, op.delegationAddress()); + } } /** diff --git a/hedera-node/hedera-token-service-impl/src/main/java/module-info.java b/hedera-node/hedera-token-service-impl/src/main/java/module-info.java index 2ab9d7ef66b1..6c4a9e2f9911 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/module-info.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/module-info.java @@ -19,6 +19,7 @@ requires transitive dagger; requires transitive javax.inject; requires transitive org.apache.commons.lang3; + requires com.hedera.node.app.service.contract; requires com.swirlds.base; requires com.swirlds.common; requires com.github.spotbugs.annotations; From acbc3aafe9141a95f56d3e06c7f6be80853b4761 Mon Sep 17 00:00:00 2001 From: Luke Lee Date: Thu, 4 Dec 2025 07:58:06 -0800 Subject: [PATCH 3/6] fix comment Signed-off-by: Luke Lee --- .../app/service/contract/impl/ContractServiceApiProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceApiProvider.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceApiProvider.java index 7c85d42664c8..5fd1f05f653a 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceApiProvider.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceApiProvider.java @@ -56,7 +56,7 @@ public void setAccountDelegationTarget(@NonNull final AccountID accountID, @NonN .contractNum(accountID.accountNumOrThrow()) .build(); if (bytecode.equals(Bytes.EMPTY)) { - // Remove the bytecode if the bytecode is empty + // Remove the delegation if the bytecode is empty contractStateStore.removeBytecode(contractID); } else { final var delegationIndicator = Bytes.merge(Bytes.wrap(CODE_DELEGATION_PREFIX.toArray()), bytecode); From 067f6be1689d4ab788ebc2bd2eec5865779cc5ed Mon Sep 17 00:00:00 2001 From: Luke Lee Date: Thu, 4 Dec 2025 09:17:21 -0800 Subject: [PATCH 4/6] fix checkAllModuleInfo issue Signed-off-by: Luke Lee --- hedera-node/hedera-app/src/main/java/module-info.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hedera-node/hedera-app/src/main/java/module-info.java b/hedera-node/hedera-app/src/main/java/module-info.java index db08a10f7475..0275cd0bc474 100644 --- a/hedera-node/hedera-app/src/main/java/module-info.java +++ b/hedera-node/hedera-app/src/main/java/module-info.java @@ -6,6 +6,7 @@ requires transitive com.hedera.node.app.service.addressbook.impl; requires transitive com.hedera.node.app.service.consensus.impl; requires transitive com.hedera.node.app.service.contract.impl; + requires transitive com.hedera.node.app.service.contract; requires transitive com.hedera.node.app.service.entityid; requires transitive com.hedera.node.app.service.file.impl; requires transitive com.hedera.node.app.service.network.admin.impl; @@ -44,7 +45,6 @@ requires com.hedera.node.app.hapi.fees; requires com.hedera.node.app.service.addressbook; requires com.hedera.node.app.service.consensus; - requires com.hedera.node.app.service.contract; requires com.hedera.node.app.service.entityid.impl; requires com.hedera.node.app.service.file; requires com.hedera.node.app.service.network.admin; From 0aca70c58dc418eab31ca8ccf10ef904b990dea7 Mon Sep 17 00:00:00 2001 From: Luke Lee Date: Mon, 8 Dec 2025 07:23:15 -0800 Subject: [PATCH 5/6] dispatch crypto create and crypto update Signed-off-by: Luke Lee --- .../app/spi/workflows/DispatchOptions.java | 36 ++++++++++++++++ .../impl/ContractServiceApiProvider.java | 30 ++++++++++--- .../delegation/CodeDelegationProcessor.java | 42 +++++++++---------- .../exec/scope/HandleHederaOperations.java | 42 +++++++++++++++++++ .../impl/exec/scope/HederaOperations.java | 16 +++++++ .../exec/scope/QueryHederaOperations.java | 11 +++++ .../impl/hevm/HederaWorldUpdater.java | 16 +++++++ .../impl/state/ProxyWorldUpdater.java | 18 ++++++++ .../src/main/java/module-info.java | 1 + .../service/contract/ContractServiceApi.java | 3 ++ 10 files changed, 189 insertions(+), 26 deletions(-) diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/workflows/DispatchOptions.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/workflows/DispatchOptions.java index 5627f66d14ff..a573a296630c 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/workflows/DispatchOptions.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/workflows/DispatchOptions.java @@ -179,6 +179,42 @@ public static DispatchOptions independentDispatch( UNIVERSAL_NOOP_FEE_CHARGING); } + /** + * Returns options for a dispatch that is logically independent of the parent dispatch but should still produce a child record. + * Use cases include, + *
    + *
  • Updating the delegation indicator for an account
  • + *
  • Creating a new account that includes a delegation indicator
  • + *
+ * Using this type as setting code authorization on an account is independent of the parent transaction, + * but we still want to have a record of it as a child record. It seems unlikely that other use cases will arise for this type of dispatch. + * + * @param payerId the account to pay for the dispatch + * @param body the transaction to dispatch + * @param streamBuilderType the type of stream builder to use for the dispatch + * @return the options for the setup dispatch + * @param the type of stream builder to use for the dispatch + */ + public static DispatchOptions independentChildDispatch( + @NonNull final AccountID payerId, + @NonNull final TransactionBody body, + @NonNull final Class streamBuilderType) { + return new DispatchOptions<>( + Commit.IMMEDIATELY, + payerId, + body, + UsePresetTxnId.NO, + PREAUTHORIZED_KEYS, + emptySet(), + TransactionCategory.CHILD, + ConsensusThrottling.OFF, + streamBuilderType, + ReversingBehavior.IRREVERSIBLE, + NOOP_SIGNED_TX_CUSTOMIZER, + EMPTY_METADATA, + UNIVERSAL_NOOP_FEE_CHARGING); + } + /** * Returns options for a dispatch that is part of the parent dispatch's transactional unit, but in a setup role * that logically precedes the parent transaction's effects. Use cases include, diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceApiProvider.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceApiProvider.java index 5fd1f05f653a..4b38ca584784 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceApiProvider.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceApiProvider.java @@ -15,6 +15,7 @@ import com.swirlds.config.api.Configuration; import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Optional; import javax.inject.Singleton; @Singleton @@ -50,11 +51,7 @@ public ContractServiceApiImpl(@NonNull final WritableContractStateStore contract public void setAccountDelegationTarget(@NonNull final AccountID accountID, @NonNull final Bytes bytecode) { requireNonNull(accountID); requireNonNull(bytecode); - final var contractID = ContractID.newBuilder() - .shardNum(accountID.shardNum()) - .realmNum(accountID.realmNum()) - .contractNum(accountID.accountNumOrThrow()) - .build(); + final var contractID = toContractID(accountID); if (bytecode.equals(Bytes.EMPTY)) { // Remove the delegation if the bytecode is empty contractStateStore.removeBytecode(contractID); @@ -64,5 +61,28 @@ public void setAccountDelegationTarget(@NonNull final AccountID accountID, @NonN contractID, new com.hedera.hapi.node.state.contract.Bytecode(delegationIndicator)); } } + + @Override + public Optional getAccountDelegationTarget(@NonNull AccountID accountID) { + final var contractID = toContractID(accountID); + final var bytecode = contractStateStore.getBytecode(contractID); + if (bytecode != null) { + // remove the delegation prefix before returning address bytes + final var addressBytes = bytecode.code() + .getBytes( + CODE_DELEGATION_PREFIX.size(), + bytecode.code().length() - CODE_DELEGATION_PREFIX.size()); + return Optional.of(addressBytes); + } + return Optional.empty(); + } + + private static ContractID toContractID(@NonNull AccountID accountID) { + return ContractID.newBuilder() + .shardNum(accountID.shardNum()) + .realmNum(accountID.realmNum()) + .contractNum(accountID.accountNumOrThrow()) + .build(); + } } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/delegation/CodeDelegationProcessor.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/delegation/CodeDelegationProcessor.java index efc3fdfd531a..30ea13749893 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/delegation/CodeDelegationProcessor.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/delegation/CodeDelegationProcessor.java @@ -6,6 +6,7 @@ import com.hedera.node.app.hapi.utils.ethereum.CodeDelegation; import com.hedera.node.app.hapi.utils.ethereum.EthTxSigs; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransaction; +import com.hedera.node.app.service.contract.impl.state.HederaEvmAccount; import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; import java.math.BigInteger; import java.util.Optional; @@ -106,14 +107,20 @@ private void setAccountCodeToDelegationIndicator( result.addAccessedDelegatorAddress(authorizerAddress); MutableAccount authority; - boolean authorityDoesAlreadyExist = false; if (maybeAuthorityAccount.isEmpty()) { // only create an account if nonce is valid if (codeDelegation.nonce() != 0) { return; } - ((ProxyWorldUpdater) worldUpdater).setupTopLevelLazyCreate(authorizerAddress); - authority = worldUpdater.createAccount(authorizerAddress); + // TODO: check for sufficient gas to create account + + if (!((ProxyWorldUpdater) worldUpdater).createAccountCodeDelegationIndicator(authorizerAddress)) { + return; + } + authority = worldUpdater.getAccount(authorizerAddress); + if (authority == null) { + return; + } } else { authority = maybeAuthorityAccount.get(); @@ -121,18 +128,22 @@ private void setAccountCodeToDelegationIndicator( return; } - authorityDoesAlreadyExist = true; - } + if (codeDelegation.nonce() != authority.getNonce()) { + return; + } - if (codeDelegation.nonce() != authority.getNonce()) { - return; - } + // Ensure that the account is a regular account + if (!((HederaEvmAccount) authority).isRegularAccount()) { + return; + } - if (authorityDoesAlreadyExist) { + if (!((ProxyWorldUpdater) worldUpdater) + .setAccountCodeDelegationIndicator(((HederaEvmAccount) authority).hederaId(), authorizerAddress)) { + return; + } result.incrementAlreadyExistingDelegators(); } - setAccountCodeToDelegationIndicator(authority, authorizerAddress); authority.incrementNonce(); } @@ -145,15 +156,4 @@ private boolean hasCodeDelegation(final Bytes code) { && code.size() == DELEGATED_CODE_SIZE && code.slice(0, CODE_DELEGATION_PREFIX.size()).equals(CODE_DELEGATION_PREFIX); } - - private void setAccountCodeToDelegationIndicator( - final MutableAccount account, final Address codeDelegationAddress) { - // code delegation to zero address removes any delegated code - if (codeDelegationAddress.equals(Address.ZERO)) { - account.setCode(Bytes.EMPTY); - return; - } - - account.setCode(Bytes.concatenate(CODE_DELEGATION_PREFIX, codeDelegationAddress)); - } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaOperations.java index 0ff56fe4617f..38e8db38eb2e 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaOperations.java @@ -11,6 +11,7 @@ import static com.hedera.node.app.service.contract.impl.utils.SynthTxnUtils.synthAccountCreationFromHapi; import static com.hedera.node.app.service.contract.impl.utils.SynthTxnUtils.synthContractCreationForExternalization; import static com.hedera.node.app.service.contract.impl.utils.SynthTxnUtils.synthContractCreationFromParent; +import static com.hedera.node.app.spi.workflows.DispatchOptions.independentChildDispatch; import static com.hedera.node.app.spi.workflows.DispatchOptions.stepDispatch; import static com.hedera.node.app.spi.workflows.record.StreamBuilder.SignedTxCustomizer.SUPPRESSING_SIGNED_TX_CUSTOMIZER; import static com.hedera.node.app.spi.workflows.record.StreamBuilder.signedTxWith; @@ -25,6 +26,7 @@ import com.hedera.hapi.node.contract.ContractFunctionResult; import com.hedera.hapi.node.contract.EvmTransactionResult; import com.hedera.hapi.node.token.CryptoCreateTransactionBody; +import com.hedera.hapi.node.token.CryptoUpdateTransactionBody; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.annotations.TransactionScope; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; @@ -40,6 +42,8 @@ import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.service.token.api.ContractChangeSummary; import com.hedera.node.app.service.token.api.TokenServiceApi; +import com.hedera.node.app.service.token.records.CryptoCreateStreamBuilder; +import com.hedera.node.app.service.token.records.CryptoUpdateStreamBuilder; import com.hedera.node.app.spi.fees.FeeCharging; import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.throttle.ThrottleAdviser; @@ -62,6 +66,7 @@ import java.util.Map; import javax.inject.Inject; import org.hyperledger.besu.datatypes.Address; +import org.jetbrains.annotations.NotNull; /** * A fully mutable {@link HederaOperations} implementation based on a {@link HandleContext}. @@ -558,4 +563,41 @@ public ThrottleAdviser getThrottleAdviser() { public ContractMetrics contractMetrics() { return contractMetrics; } + + /** + * {@inheritDoc} + */ + @Override + public boolean setAccountCodeDelegation(@NotNull AccountID accountID, @NotNull Address delegationAddress) { + // Dispatch a synthetic transaction to set the delegation + final var cryptoUpdate = CryptoUpdateTransactionBody.newBuilder() + .accountIDToUpdate(accountID) + .delegationAddress(tuweniToPbjBytes(delegationAddress)) + .build(); + final var body = + TransactionBody.newBuilder().cryptoUpdateAccount(cryptoUpdate).build(); + + final var streamBuilder = + context.dispatch(independentChildDispatch(context.payer(), body, CryptoUpdateStreamBuilder.class)); + + return streamBuilder.status() == SUCCESS; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean createAccountCodeDelegationIndicator(@NotNull Address delegationAddress) { + // Dispatch a synthetic transaction create account and to set the delegation + final var cryptoCreate = CryptoCreateTransactionBody.newBuilder() + .delegationAddress(tuweniToPbjBytes(delegationAddress)) + .build(); + final var body = + TransactionBody.newBuilder().cryptoCreateAccount(cryptoCreate).build(); + + final var streamBuilder = + context.dispatch(independentChildDispatch(context.payer(), body, CryptoCreateStreamBuilder.class)); + + return streamBuilder.status() == SUCCESS; + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaOperations.java index 7f58b79b7cc0..125ec6a07436 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaOperations.java @@ -21,6 +21,7 @@ import edu.umd.cs.findbugs.annotations.Nullable; import java.util.List; import org.hyperledger.besu.datatypes.Address; +import org.jetbrains.annotations.NotNull; /** * Provides the Hedera operations that only a {@link ProxyWorldUpdater} needs (but not a {@link DispatchingEvmFrameState}. @@ -306,4 +307,19 @@ default ContractID configValidated(@NonNull final ContractID contractId, @NonNul @Nullable ContractMetrics contractMetrics(); + + /** + * Sets the account code delegation to the given address for the given account ID. + * @param accountID the account ID to set the delegation for + * @param delegationAddress the address to set as the delegation indicator + * @return true if the code delegation was set successfully, false otherwise + */ + boolean setAccountCodeDelegation(@NonNull final AccountID accountID, @NonNull final Address delegationAddress); + + /** + * Create a new account with code delegation to the given address for the new account. + * @param delegationAddress the address to set as the delegation indicator + * @return true if the account was created and the code delegation was set successfully, false otherwise + */ + boolean createAccountCodeDelegationIndicator(@NotNull Address delegationAddress); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QueryHederaOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QueryHederaOperations.java index aa9a21324456..c4bad1d23e95 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QueryHederaOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QueryHederaOperations.java @@ -23,6 +23,7 @@ import java.util.List; import javax.inject.Inject; import org.hyperledger.besu.datatypes.Address; +import org.jetbrains.annotations.NotNull; /** * A read-only {@link HederaOperations} implementation based on a {@link QueryContext}. @@ -287,4 +288,14 @@ public ContractMetrics contractMetrics() { // As we do not have throttle adviser it makes no sense to use the metrics return null; } + + @Override + public boolean setAccountCodeDelegation(@NotNull AccountID accountID, @NotNull Address delegationAddress) { + throw new UnsupportedOperationException("Queries cannot set code delegation"); + } + + @Override + public boolean createAccountCodeDelegationIndicator(@NotNull Address delegationAddress) { + throw new UnsupportedOperationException("Queries cannot create account and set code delegation"); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaWorldUpdater.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaWorldUpdater.java index 9beac3a85b7b..0e7430e7db8b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaWorldUpdater.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaWorldUpdater.java @@ -296,4 +296,20 @@ Optional tryTrackingSelfDestructBeneficiary( default EntityIdFactory entityIdFactory() { return enhancement().nativeOperations().entityIdFactory(); } + + /** + * Sets the account code delegation to the given address for the given account ID. + * @param accountID the account ID to set the delegation for + * @param delegationAddress the address to set as the delegation indicator + * @return true if the code delegation was set successfully, false otherwise + */ + boolean setAccountCodeDelegationIndicator( + @NonNull final AccountID accountID, @NonNull final Address delegationAddress); + + /** + * Create a new account with code delegation to the given address for the new account. + * @param delegationAddress the address to set as the delegation indicator + * @return true if the account was created and the code delegation was set successfully, false otherwise + */ + boolean createAccountCodeDelegationIndicator(@NonNull final Address delegationAddress); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java index f1b70741835f..06312ef13273 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java @@ -36,6 +36,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.worldstate.WorldUpdater; +import org.jetbrains.annotations.NotNull; /** * A {@link WorldUpdater} that delegates to a given {@link HandleHederaOperations} for state management. @@ -480,6 +481,23 @@ public ExchangeRate currentExchangeRate() { return enhancement().systemOperations().currentExchangeRate(); } + /** + * {@inheritDoc} + */ + @Override + public boolean setAccountCodeDelegationIndicator( + @NonNull final AccountID accountID, @NonNull final Address delegationAddress) { + return enhancement.operations().setAccountCodeDelegation(accountID, delegationAddress); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean createAccountCodeDelegationIndicator(@NotNull Address delegationAddress) { + return enhancement.operations().createAccountCodeDelegationIndicator(delegationAddress); + } + private long getValidatedCreationNumber( @NonNull final Address address, @NonNull final Wei balance, diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/module-info.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/module-info.java index b04c9c753404..15f316526969 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/module-info.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/module-info.java @@ -35,6 +35,7 @@ requires org.apache.commons.lang3; requires org.bouncycastle.provider; requires org.hyperledger.besu.internal.crypto; + requires org.jetbrains.annotations; requires org.slf4j; requires static transitive com.github.spotbugs.annotations; requires static java.compiler; diff --git a/hedera-node/hedera-smart-contract-service/src/main/java/com/hedera/node/app/service/contract/ContractServiceApi.java b/hedera-node/hedera-smart-contract-service/src/main/java/com/hedera/node/app/service/contract/ContractServiceApi.java index 0c5d279e09df..8784585f8c08 100644 --- a/hedera-node/hedera-smart-contract-service/src/main/java/com/hedera/node/app/service/contract/ContractServiceApi.java +++ b/hedera-node/hedera-smart-contract-service/src/main/java/com/hedera/node/app/service/contract/ContractServiceApi.java @@ -4,6 +4,7 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.pbj.runtime.io.buffer.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Optional; /** * Additional API for the Smart Contract Service beyond its dispatchable handlers. @@ -17,4 +18,6 @@ public interface ContractServiceApi { * @param bytecode the runtime bytecode to set for the account */ void setAccountDelegationTarget(@NonNull AccountID accountID, @NonNull Bytes bytecode); + + Optional getAccountDelegationTarget(@NonNull AccountID accountID); } From ad457f972277bc44319d27c482867affa3cb9eca Mon Sep 17 00:00:00 2001 From: Luke Lee Date: Mon, 8 Dec 2025 17:24:36 -0800 Subject: [PATCH 6/6] reuse the lazy creation flow when possible Signed-off-by: Luke Lee --- .../delegation/CodeDelegationProcessor.java | 10 ++++-- .../scope/HandleHederaNativeOperations.java | 35 +++++++++++++++---- .../exec/scope/HandleHederaOperations.java | 22 +----------- .../exec/scope/HederaNativeOperations.java | 5 ++- .../impl/exec/scope/HederaOperations.java | 8 ----- .../scope/QueryHederaNativeOperations.java | 4 ++- .../exec/scope/QueryHederaOperations.java | 8 +---- .../impl/hevm/HederaWorldUpdater.java | 4 ++- .../impl/state/DispatchingEvmFrameState.java | 8 +++-- .../contract/impl/state/EvmFrameState.java | 5 ++- .../impl/state/ProxyWorldUpdater.java | 12 ++++--- .../contract/impl/utils/SynthTxnUtils.java | 7 +++- .../src/main/java/module-info.java | 1 - .../HandleHederaNativeOperationsTest.java | 9 ++--- .../QueryHederaNativeOperationsTest.java | 2 +- .../state/DispatchingEvmFrameStateTest.java | 14 ++++---- .../test/state/ProxyWorldUpdaterTest.java | 4 +-- .../impl/test/utils/SynthTxnUtilsTest.java | 6 ++-- 18 files changed, 90 insertions(+), 74 deletions(-) diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/delegation/CodeDelegationProcessor.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/delegation/CodeDelegationProcessor.java index 30ea13749893..077ef6379d9d 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/delegation/CodeDelegationProcessor.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/delegation/CodeDelegationProcessor.java @@ -106,17 +106,20 @@ private void setAccountCodeToDelegationIndicator( result.addAccessedDelegatorAddress(authorizerAddress); + final var delegatedContractAddress = Address.wrap(Bytes.wrap(codeDelegation.address())); + MutableAccount authority; if (maybeAuthorityAccount.isEmpty()) { // only create an account if nonce is valid if (codeDelegation.nonce() != 0) { return; } - // TODO: check for sufficient gas to create account - if (!((ProxyWorldUpdater) worldUpdater).createAccountCodeDelegationIndicator(authorizerAddress)) { + if (!((ProxyWorldUpdater) worldUpdater) + .createAccountWithCodeDelegationIndicator(authorizerAddress, delegatedContractAddress)) { return; } + authority = worldUpdater.getAccount(authorizerAddress); if (authority == null) { return; @@ -138,7 +141,8 @@ private void setAccountCodeToDelegationIndicator( } if (!((ProxyWorldUpdater) worldUpdater) - .setAccountCodeDelegationIndicator(((HederaEvmAccount) authority).hederaId(), authorizerAddress)) { + .setAccountCodeDelegationIndicator( + ((HederaEvmAccount) authority).hederaId(), delegatedContractAddress)) { return; } result.incrementAlreadyExistingDelegators(); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java index c03a60dd24f6..f87ff3b4c7aa 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java @@ -6,6 +6,7 @@ import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.selfDestructBeneficiariesFor; import static com.hedera.node.app.service.contract.impl.utils.SynthTxnUtils.synthHollowAccountCreation; import static com.hedera.node.app.spi.fees.NoopFeeCharging.DISPATCH_ONLY_NOOP_FEE_CHARGING; +import static com.hedera.node.app.spi.workflows.DispatchOptions.independentChildDispatch; import static com.hedera.node.app.spi.workflows.DispatchOptions.setupDispatch; import static java.util.Objects.requireNonNull; @@ -26,6 +27,7 @@ import com.hedera.node.app.service.token.ReadableTokenStore; import com.hedera.node.app.service.token.api.TokenServiceApi; import com.hedera.node.app.service.token.records.CryptoCreateStreamBuilder; +import com.hedera.node.app.spi.workflows.DispatchOptions; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.config.data.EntitiesConfig; @@ -128,19 +130,18 @@ public void setNonce(final long contractNumber, final long nonce) { * {@inheritDoc} */ @Override - public @NonNull ResponseCodeEnum createHollowAccount(@NonNull final Bytes evmAddress) { + public @NonNull ResponseCodeEnum createHollowAccount( + @NonNull final Bytes evmAddress, @NonNull final Bytes delegationAddress) { final boolean unlimitedAutoAssociations = context.configuration().getConfigData(EntitiesConfig.class).unlimitedAutoAssociationsEnabled(); final var synthTxn = TransactionBody.newBuilder() - .cryptoCreateAccount(synthHollowAccountCreation(evmAddress, unlimitedAutoAssociations)) + .cryptoCreateAccount( + synthHollowAccountCreation(evmAddress, unlimitedAutoAssociations, delegationAddress)) .build(); try { - return context.dispatch(setupDispatch( - context.payer(), - synthTxn, - CryptoCreateStreamBuilder.class, - DISPATCH_ONLY_NOOP_FEE_CHARGING)) + return context.dispatch( + dispatchOptionsOf(!delegationAddress.equals(Bytes.EMPTY), context.payer(), synthTxn)) .status(); } catch (HandleException e) { // It is critically important we don't let HandleExceptions propagate to the workflow because @@ -151,6 +152,26 @@ public void setNonce(final long contractNumber, final long nonce) { } } + /** + * Returns the {@link DispatchOptions} to use. If the transaction has a delegation address, it will + * dispatch an independent transaction that immediately writes to state but will create a child record. + * Otherwise, it will dispatch a transaction that is linked to the parent transaction and any state changes will + * be rolled back if the parent transaction fails. + * + * @param hasDelegationAddress whether the transaction has a delegation address + * @param payerId the ID of the account that will pay for the transaction + * @param body the transaction body to dispatch + * @return the dispatch options to use for the dispatch + */ + private static DispatchOptions dispatchOptionsOf( + final boolean hasDelegationAddress, @NonNull final AccountID payerId, @NonNull final TransactionBody body) { + if (hasDelegationAddress) { + return independentChildDispatch(payerId, body, CryptoCreateStreamBuilder.class); + } else { + return setupDispatch(payerId, body, CryptoCreateStreamBuilder.class, DISPATCH_ONLY_NOOP_FEE_CHARGING); + } + } + /** * {@inheritDoc} */ diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaOperations.java index 38e8db38eb2e..d00429860b8d 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaOperations.java @@ -42,7 +42,6 @@ import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.service.token.api.ContractChangeSummary; import com.hedera.node.app.service.token.api.TokenServiceApi; -import com.hedera.node.app.service.token.records.CryptoCreateStreamBuilder; import com.hedera.node.app.service.token.records.CryptoUpdateStreamBuilder; import com.hedera.node.app.spi.fees.FeeCharging; import com.hedera.node.app.spi.fees.Fees; @@ -66,7 +65,6 @@ import java.util.Map; import javax.inject.Inject; import org.hyperledger.besu.datatypes.Address; -import org.jetbrains.annotations.NotNull; /** * A fully mutable {@link HederaOperations} implementation based on a {@link HandleContext}. @@ -568,7 +566,7 @@ public ContractMetrics contractMetrics() { * {@inheritDoc} */ @Override - public boolean setAccountCodeDelegation(@NotNull AccountID accountID, @NotNull Address delegationAddress) { + public boolean setAccountCodeDelegation(@NonNull AccountID accountID, @NonNull Address delegationAddress) { // Dispatch a synthetic transaction to set the delegation final var cryptoUpdate = CryptoUpdateTransactionBody.newBuilder() .accountIDToUpdate(accountID) @@ -582,22 +580,4 @@ public boolean setAccountCodeDelegation(@NotNull AccountID accountID, @NotNull A return streamBuilder.status() == SUCCESS; } - - /** - * {@inheritDoc} - */ - @Override - public boolean createAccountCodeDelegationIndicator(@NotNull Address delegationAddress) { - // Dispatch a synthetic transaction create account and to set the delegation - final var cryptoCreate = CryptoCreateTransactionBody.newBuilder() - .delegationAddress(tuweniToPbjBytes(delegationAddress)) - .build(); - final var body = - TransactionBody.newBuilder().cryptoCreateAccount(cryptoCreate).build(); - - final var streamBuilder = - context.dispatch(independentChildDispatch(context.payer(), body, CryptoCreateStreamBuilder.class)); - - return streamBuilder.status() == SUCCESS; - } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaNativeOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaNativeOperations.java index 1eb3efd75010..e9bb2fe863e3 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaNativeOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaNativeOperations.java @@ -195,11 +195,14 @@ default long resolveAlias(long shard, long realm, @NonNull final Bytes evmAddres *

* If this fails due to some non-EVM resource constraint (for example, insufficient preceding child * records), returns the corresponding failure code, and {@link ResponseCodeEnum#OK} otherwise. + * If a non-empty the delegation address is provided, it will be set in the dispatched transaction body + * and the dispatch options will be set to immediately save the state. * * @param evmAddress the EVM address of the new hollow account + * @param delegationAddress the address to delegate the created account to, or {@code null} if no delegation needs to be set * @return the result of the creation */ - ResponseCodeEnum createHollowAccount(@NonNull Bytes evmAddress); + ResponseCodeEnum createHollowAccount(@NonNull Bytes evmAddress, @NonNull Bytes delegationAddress); /** * Finalizes an existing hollow account with the given address as a contract by setting diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaOperations.java index 125ec6a07436..6a70eca70757 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaOperations.java @@ -21,7 +21,6 @@ import edu.umd.cs.findbugs.annotations.Nullable; import java.util.List; import org.hyperledger.besu.datatypes.Address; -import org.jetbrains.annotations.NotNull; /** * Provides the Hedera operations that only a {@link ProxyWorldUpdater} needs (but not a {@link DispatchingEvmFrameState}. @@ -315,11 +314,4 @@ default ContractID configValidated(@NonNull final ContractID contractId, @NonNul * @return true if the code delegation was set successfully, false otherwise */ boolean setAccountCodeDelegation(@NonNull final AccountID accountID, @NonNull final Address delegationAddress); - - /** - * Create a new account with code delegation to the given address for the new account. - * @param delegationAddress the address to set as the delegation indicator - * @return true if the account was created and the code delegation was set successfully, false otherwise - */ - boolean createAccountCodeDelegationIndicator(@NotNull Address delegationAddress); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QueryHederaNativeOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QueryHederaNativeOperations.java index 5756923b4a87..327a4f0db7bc 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QueryHederaNativeOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QueryHederaNativeOperations.java @@ -17,6 +17,7 @@ import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.config.api.Configuration; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Objects; import javax.inject.Inject; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -108,7 +109,8 @@ public void setNonce(final long contractNumber, final long nonce) { * @throws UnsupportedOperationException always */ @Override - public ResponseCodeEnum createHollowAccount(@NonNull final Bytes evmAddress) { + public ResponseCodeEnum createHollowAccount( + @NonNull final Bytes evmAddress, @Nullable final Bytes delegationAddress) { throw new UnsupportedOperationException("Cannot create hollow account in query context"); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QueryHederaOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QueryHederaOperations.java index c4bad1d23e95..7f6a8ce816e3 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QueryHederaOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QueryHederaOperations.java @@ -23,7 +23,6 @@ import java.util.List; import javax.inject.Inject; import org.hyperledger.besu.datatypes.Address; -import org.jetbrains.annotations.NotNull; /** * A read-only {@link HederaOperations} implementation based on a {@link QueryContext}. @@ -290,12 +289,7 @@ public ContractMetrics contractMetrics() { } @Override - public boolean setAccountCodeDelegation(@NotNull AccountID accountID, @NotNull Address delegationAddress) { + public boolean setAccountCodeDelegation(@NonNull AccountID accountID, @NonNull Address delegationAddress) { throw new UnsupportedOperationException("Queries cannot set code delegation"); } - - @Override - public boolean createAccountCodeDelegationIndicator(@NotNull Address delegationAddress) { - throw new UnsupportedOperationException("Queries cannot create account and set code delegation"); - } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaWorldUpdater.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaWorldUpdater.java index 0e7430e7db8b..7fa534fe69a5 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaWorldUpdater.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaWorldUpdater.java @@ -308,8 +308,10 @@ boolean setAccountCodeDelegationIndicator( /** * Create a new account with code delegation to the given address for the new account. + * @param authority the alias address of the new account to create * @param delegationAddress the address to set as the delegation indicator * @return true if the account was created and the code delegation was set successfully, false otherwise */ - boolean createAccountCodeDelegationIndicator(@NonNull final Address delegationAddress); + boolean createAccountWithCodeDelegationIndicator( + @NonNull final Address authority, @NonNull final Address delegationAddress); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/DispatchingEvmFrameState.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/DispatchingEvmFrameState.java index 28d8525264f3..2cb1258296b4 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/DispatchingEvmFrameState.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/DispatchingEvmFrameState.java @@ -468,7 +468,8 @@ public Optional tryTransfer( * {@inheritDoc} */ @Override - public Optional tryLazyCreation(@NonNull final Address address) { + public Optional tryLazyCreation( + @NonNull final Address address, @Nullable final Address delegationAddress) { if (isLongZero(address)) { return Optional.of(INVALID_ALIAS_KEY); } @@ -486,7 +487,10 @@ public Optional tryLazyCreation(@NonNull final Address ad } } } - final var status = nativeOperations.createHollowAccount(tuweniToPbjBytes(address)); + final var delegationAddressBytes = delegationAddress == null + ? com.hedera.pbj.runtime.io.buffer.Bytes.EMPTY + : tuweniToPbjBytes(delegationAddress); + final var status = nativeOperations.createHollowAccount(tuweniToPbjBytes(address), delegationAddressBytes); if (status != SUCCESS) { return status == MAX_CHILD_RECORDS_EXCEEDED ? Optional.of(INSUFFICIENT_CHILD_RECORDS) diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/EvmFrameState.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/EvmFrameState.java index b8c243e6bbf2..9d3fb07f7116 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/EvmFrameState.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/EvmFrameState.java @@ -73,11 +73,14 @@ Optional tryTransfer( * * Note the {@link CustomCallOperation} will have already confirmed that the Hedera EVM in use supports * lazy creation, and that it is enabled by properties. + * If the delegation address is not null, the state change will be immediately applied and a normal child record will be created + * to reflect the requirement in EIP-7702 to set code delegation independently of the main transaction. * * @param address the address of the account to try to lazy-create + * @param delegationAddress if not null, the address to set as the delegation address for the lazy-created account. * @return an optional {@link ExceptionalHaltReason} with the reason lazy creation could not be done */ - Optional tryLazyCreation(@NonNull Address address); + Optional tryLazyCreation(@NonNull Address address, @Nullable Address delegationAddress); /** * Returns whether the account with the given address is a "hollow account"; that is, an account diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java index 06312ef13273..b70aea5e97e1 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java @@ -36,7 +36,6 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.worldstate.WorldUpdater; -import org.jetbrains.annotations.NotNull; /** * A {@link WorldUpdater} that delegates to a given {@link HandleHederaOperations} for state management. @@ -219,7 +218,7 @@ public Optional tryLazyCreation( if (gasCost > frame.getRemainingGas()) { return Optional.of(INSUFFICIENT_GAS); } - final var maybeHaltReason = evmFrameState.tryLazyCreation(recipient); + final var maybeHaltReason = evmFrameState.tryLazyCreation(recipient, null); if (maybeHaltReason.isPresent()) { return maybeHaltReason; } @@ -494,8 +493,13 @@ public boolean setAccountCodeDelegationIndicator( * {@inheritDoc} */ @Override - public boolean createAccountCodeDelegationIndicator(@NotNull Address delegationAddress) { - return enhancement.operations().createAccountCodeDelegationIndicator(delegationAddress); + public boolean createAccountWithCodeDelegationIndicator( + @NonNull final Address authority, @NonNull final Address delegationAddress) { + + // TODO: check for sufficient gas to create account + + final var maybeHaltReason = evmFrameState.tryLazyCreation(authority, delegationAddress); + return maybeHaltReason.isEmpty(); } private long getValidatedCreationNumber( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/SynthTxnUtils.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/SynthTxnUtils.java index 6910400b5d02..25e58399ae37 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/SynthTxnUtils.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/SynthTxnUtils.java @@ -132,10 +132,14 @@ public static boolean hasNonDegenerateAutoRenewAccountId(@NonNull final Account * to dispatch. * * @param evmAddress the EVM address + * @param unlimitedAutoAssociations whether the account should have unlimited automatic token associations + * @param delegationAddress the contract address to delegate the account to if any * @return the corresponding {@link CryptoCreateTransactionBody} */ public static CryptoCreateTransactionBody synthHollowAccountCreation( - @NonNull final Bytes evmAddress, final boolean unlimitedAutoAssociations) { + @NonNull final Bytes evmAddress, + final boolean unlimitedAutoAssociations, + @NonNull final Bytes delegationAddress) { requireNonNull(evmAddress); return CryptoCreateTransactionBody.newBuilder() .initialBalance(0L) @@ -143,6 +147,7 @@ public static CryptoCreateTransactionBody synthHollowAccountCreation( .maxAutomaticTokenAssociations(unlimitedAutoAssociations ? -1 : 0) .key(IMMUTABILITY_SENTINEL_KEY) .autoRenewPeriod(DEFAULT_AUTO_RENEW_PERIOD) + .delegationAddress(delegationAddress) .build(); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/module-info.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/module-info.java index 15f316526969..b04c9c753404 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/module-info.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/module-info.java @@ -35,7 +35,6 @@ requires org.apache.commons.lang3; requires org.bouncycastle.provider; requires org.hyperledger.besu.internal.crypto; - requires org.jetbrains.annotations; requires org.slf4j; requires static transitive com.github.spotbugs.annotations; requires static java.compiler; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java index ff834302f4e7..aa3cc5bd15e2 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java @@ -58,6 +58,7 @@ import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.record.DeleteCapableTransactionStreamBuilder; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; +import com.hedera.pbj.runtime.io.buffer.Bytes; import java.time.Instant; import java.util.ArrayDeque; import java.util.Deque; @@ -182,7 +183,7 @@ void getTokenUsesStore() { @Test void createsHollowAccountByDispatching() { final var synthLazyCreate = TransactionBody.newBuilder() - .cryptoCreateAccount(synthHollowAccountCreation(CANONICAL_ALIAS, true)) + .cryptoCreateAccount(synthHollowAccountCreation(CANONICAL_ALIAS, true, Bytes.EMPTY)) .build(); given(context.payer()).willReturn(A_NEW_ACCOUNT_ID); @@ -191,7 +192,7 @@ void createsHollowAccountByDispatching() { given(cryptoCreateRecordBuilder.status()).willReturn(OK); given(context.configuration()).willReturn(DEFAULT_CONFIG); - final var status = subject.createHollowAccount(CANONICAL_ALIAS); + final var status = subject.createHollowAccount(CANONICAL_ALIAS, null); assertEquals(OK, status); verify(cryptoCreateRecordBuilder, never()).memo(any()); @@ -200,7 +201,7 @@ void createsHollowAccountByDispatching() { @Test void createsHollowAccountByDispatchingDoesNotThrowErrors() { final var synthLazyCreate = TransactionBody.newBuilder() - .cryptoCreateAccount(synthHollowAccountCreation(CANONICAL_ALIAS, true)) + .cryptoCreateAccount(synthHollowAccountCreation(CANONICAL_ALIAS, true, Bytes.EMPTY)) .build(); given(context.payer()).willReturn(A_NEW_ACCOUNT_ID); given(context.dispatch(assertArg(options -> assertEquals(synthLazyCreate, options.body())))) @@ -208,7 +209,7 @@ void createsHollowAccountByDispatchingDoesNotThrowErrors() { given(cryptoCreateRecordBuilder.status()).willReturn(MAX_ENTITIES_IN_PRICE_REGIME_HAVE_BEEN_CREATED); given(context.configuration()).willReturn(DEFAULT_CONFIG); - final var status = assertDoesNotThrow(() -> subject.createHollowAccount(CANONICAL_ALIAS)); + final var status = assertDoesNotThrow(() -> subject.createHollowAccount(CANONICAL_ALIAS, null)); assertThat(status).isEqualTo(MAX_ENTITIES_IN_PRICE_REGIME_HAVE_BEEN_CREATED); verify(cryptoCreateRecordBuilder, never()).memo(any()); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/QueryHederaNativeOperationsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/QueryHederaNativeOperationsTest.java index a17f53999fa8..8d607a5661ec 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/QueryHederaNativeOperationsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/QueryHederaNativeOperationsTest.java @@ -82,7 +82,7 @@ void doesNotSupportAnyMutations() { UnsupportedOperationException.class, () -> subject.checkForCustomFees(CryptoTransferTransactionBody.DEFAULT)); assertThrows(UnsupportedOperationException.class, () -> subject.setNonce(1L, 2L)); - assertThrows(UnsupportedOperationException.class, () -> subject.createHollowAccount(Bytes.EMPTY)); + assertThrows(UnsupportedOperationException.class, () -> subject.createHollowAccount(Bytes.EMPTY, null)); assertThrows(UnsupportedOperationException.class, () -> subject.finalizeHollowAccountAsContract(Bytes.EMPTY)); assertThrows(UnsupportedOperationException.class, () -> subject.finalizeHollowAccountAsContract(Bytes.EMPTY)); assertThrows( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/DispatchingEvmFrameStateTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/DispatchingEvmFrameStateTest.java index ed050c04ebc3..fea33bb3ef05 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/DispatchingEvmFrameStateTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/DispatchingEvmFrameStateTest.java @@ -592,7 +592,7 @@ void cannotLazyCreateOverExpiredAccount() { .willReturn(ACCOUNT_NUM); given(nativeOperations.configuration()).willReturn(configuration); - final var reasonLazyCreationFailed = subject.tryLazyCreation(EVM_ADDRESS); + final var reasonLazyCreationFailed = subject.tryLazyCreation(EVM_ADDRESS, null); assertTrue(reasonLazyCreationFailed.isPresent()); assertEquals(FAILURE_DURING_LAZY_ACCOUNT_CREATION, reasonLazyCreationFailed.get()); @@ -600,20 +600,20 @@ void cannotLazyCreateOverExpiredAccount() { @Test void noHaltIfLazyCreationOk() { - given(nativeOperations.createHollowAccount(tuweniToPbjBytes(EVM_ADDRESS))) + given(nativeOperations.createHollowAccount(tuweniToPbjBytes(EVM_ADDRESS), null)) .willReturn(ResponseCodeEnum.SUCCESS); given(nativeOperations.configuration()).willReturn(configuration); - final var reasonLazyCreationFailed = subject.tryLazyCreation(EVM_ADDRESS); + final var reasonLazyCreationFailed = subject.tryLazyCreation(EVM_ADDRESS, null); assertTrue(reasonLazyCreationFailed.isEmpty()); } @Test void translatesMaxAccountsCreated() { - given(nativeOperations.createHollowAccount(tuweniToPbjBytes(EVM_ADDRESS))) + given(nativeOperations.createHollowAccount(tuweniToPbjBytes(EVM_ADDRESS), null)) .willReturn(ResponseCodeEnum.MAX_ENTITIES_IN_PRICE_REGIME_HAVE_BEEN_CREATED); given(nativeOperations.configuration()).willReturn(configuration); - final var reasonLazyCreationFailed = subject.tryLazyCreation(EVM_ADDRESS); + final var reasonLazyCreationFailed = subject.tryLazyCreation(EVM_ADDRESS, null); assertTrue(reasonLazyCreationFailed.isPresent()); assertEquals(FAILURE_DURING_LAZY_ACCOUNT_CREATION, reasonLazyCreationFailed.get()); @@ -621,7 +621,7 @@ void translatesMaxAccountsCreated() { @Test void throwsOnLazyCreateOfLongZeroAddress() { - final var reasonLazyCreationFailed = subject.tryLazyCreation(LONG_ZERO_ADDRESS); + final var reasonLazyCreationFailed = subject.tryLazyCreation(LONG_ZERO_ADDRESS, null); assertTrue(reasonLazyCreationFailed.isPresent()); assertEquals(INVALID_ALIAS_KEY, reasonLazyCreationFailed.get()); } @@ -633,7 +633,7 @@ void throwsOnLazyCreateOfNonExpiredAccount() { given(nativeOperations.resolveAlias(anyLong(), anyLong(), eq(Bytes.wrap(EVM_ADDRESS.toArrayUnsafe())))) .willReturn(ACCOUNT_NUM); - assertThrows(IllegalArgumentException.class, () -> subject.tryLazyCreation(EVM_ADDRESS)); + assertThrows(IllegalArgumentException.class, () -> subject.tryLazyCreation(EVM_ADDRESS, null)); } @Test diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ProxyWorldUpdaterTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ProxyWorldUpdaterTest.java index 75459d39a17a..9451bf18f6e9 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ProxyWorldUpdaterTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ProxyWorldUpdaterTest.java @@ -438,7 +438,7 @@ void delegatesLazyCreationAndDecrementsGasCostOnSuccess() { given(frame.getRemainingGas()).willReturn(pretendCost * 2); given(frame.getMessageFrameStack()).willReturn(new ArrayDeque<>()); given(frame.getContextVariable(FrameUtils.OPS_DURATION_COUNTER)).willReturn(OpsDurationCounter.disabled()); - given(evmFrameState.tryLazyCreation(SOME_EVM_ADDRESS)).willReturn(Optional.empty()); + given(evmFrameState.tryLazyCreation(SOME_EVM_ADDRESS, null)).willReturn(Optional.empty()); final var maybeHaltReason = subject.tryLazyCreation(SOME_EVM_ADDRESS, frame); assertTrue(maybeHaltReason.isEmpty()); verify(frame).decrementRemainingGas(pretendCost); @@ -450,7 +450,7 @@ void doesntBothDecrementingGasOnLazyCreationFailureSinceAboutToHalt() { final var haltReason = Optional.of(FAILURE_DURING_LAZY_ACCOUNT_CREATION); given(hederaOperations.lazyCreationCostInGas(SOME_EVM_ADDRESS)).willReturn(pretendCost); given(frame.getRemainingGas()).willReturn(pretendCost * 2); - given(evmFrameState.tryLazyCreation(SOME_EVM_ADDRESS)).willReturn(haltReason); + given(evmFrameState.tryLazyCreation(SOME_EVM_ADDRESS, null)).willReturn(haltReason); final var maybeHaltReason = subject.tryLazyCreation(SOME_EVM_ADDRESS, frame); assertEquals(haltReason, maybeHaltReason); verify(frame, never()).decrementRemainingGas(pretendCost); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/utils/SynthTxnUtilsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/utils/SynthTxnUtilsTest.java index ea094e5378e6..6a9a4db550fd 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/utils/SynthTxnUtilsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/utils/SynthTxnUtilsTest.java @@ -25,6 +25,7 @@ import com.hedera.hapi.node.contract.ContractCreateTransactionBody; import com.hedera.hapi.node.state.token.Account; import com.hedera.hapi.node.token.CryptoCreateTransactionBody; +import com.hedera.pbj.runtime.io.buffer.Bytes; import org.junit.jupiter.api.Test; class SynthTxnUtilsTest { @@ -40,7 +41,7 @@ void createsExpectedHollowSynthBody() { .autoRenewPeriod(DEFAULT_AUTO_RENEW_PERIOD) .maxAutomaticTokenAssociations(-1) .build(); - assertEquals(expected, synthHollowAccountCreation(CANONICAL_ALIAS, true)); + assertEquals(expected, synthHollowAccountCreation(CANONICAL_ALIAS, true, Bytes.EMPTY)); final var expectedWithoutUnlimitedAssociations = CryptoCreateTransactionBody.newBuilder() .key(IMMUTABILITY_SENTINEL_KEY) @@ -48,7 +49,8 @@ void createsExpectedHollowSynthBody() { .alias(CANONICAL_ALIAS) .autoRenewPeriod(DEFAULT_AUTO_RENEW_PERIOD) .build(); - assertEquals(expectedWithoutUnlimitedAssociations, synthHollowAccountCreation(CANONICAL_ALIAS, false)); + assertEquals( + expectedWithoutUnlimitedAssociations, synthHollowAccountCreation(CANONICAL_ALIAS, false, Bytes.EMPTY)); } @Test