diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/dispatch/ChildDispatchFactory.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/dispatch/ChildDispatchFactory.java index 432c6176f4a8..f373139f6330 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/dispatch/ChildDispatchFactory.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/dispatch/ChildDispatchFactory.java @@ -23,7 +23,6 @@ import static com.hedera.node.app.workflows.prehandle.PreHandleResult.Status.PRE_HANDLE_FAILURE; import static com.hedera.node.app.workflows.prehandle.PreHandleResult.Status.SO_FAR_SO_GOOD; import static java.util.Collections.emptyMap; -import static java.util.Collections.emptySet; import static java.util.Collections.emptySortedSet; import static java.util.Collections.unmodifiableSortedSet; import static java.util.Objects.requireNonNull; @@ -169,7 +168,7 @@ public Dispatch createChildDispatch( requireNonNull(options); final var preHandleResult = preHandleChild(options.body(), options.payerId(), config, readableStoreFactory); - final var childVerifier = getKeyVerifier(options.effectiveKeyVerifier(), config, emptySet()); + final var childVerifier = getKeyVerifier(options.effectiveKeyVerifier(), config, options.authorizingKeys()); final var childTxnInfo = getTxnInfoFrom(options.payerId(), options.body()); final var streamMode = config.getConfigData(BlockStreamConfig.class).streamMode(); final var childStack = SavepointStackImpl.newChildStack( @@ -438,8 +437,7 @@ public SignatureVerification verificationFor(@NonNull final Key key) { @Override public SignatureVerification verificationFor( @NonNull final Key key, @NonNull final VerificationAssistant callback) { - // We do not yet support signing scheduled transactions from within the EVM - throw new UnsupportedOperationException(); + return verifier.verificationFor(key, callback); } @NonNull diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/dispatch/ChildDispatchFactoryTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/dispatch/ChildDispatchFactoryTest.java index 66ffd6d27255..8f3a7928d736 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/dispatch/ChildDispatchFactoryTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/dispatch/ChildDispatchFactoryTest.java @@ -184,14 +184,28 @@ void keyVerifierWithNullCallbackAndAuthorizingKeysAsExpected() { assertThat(derivedVerifier.authorizingSimpleKeys()).containsExactly(A_CONTRACT_ID_KEY); } + @Test + void keyVerifierWithCallbackAndAuthorizingKeysAsExpected() { + final var derivedVerifier = + ChildDispatchFactory.getKeyVerifier(a -> true, DEFAULT_CONFIG, Set.of(A_CONTRACT_ID_KEY)); + assertThat(derivedVerifier.verificationFor(Key.DEFAULT).passed()).isTrue(); + assertThat(derivedVerifier.verificationFor(Key.DEFAULT, (k, v) -> true).passed()) + .isTrue(); + assertThatThrownBy(() -> derivedVerifier.verificationFor(Bytes.EMPTY)) + .isInstanceOf(UnsupportedOperationException.class); + assertThat(derivedVerifier.numSignaturesVerified()).isZero(); + assertThat(derivedVerifier.authorizingSimpleKeys()).containsExactly(A_CONTRACT_ID_KEY); + assertThat(derivedVerifier.authorizingSimpleKeys()).isNotEmpty(); + } + @Test void keyVerifierOnlySupportsKeyVerification() { final var derivedVerifier = ChildDispatchFactory.getKeyVerifier(verifierCallback, DEFAULT_CONFIG, emptySet()); - assertThatThrownBy(() -> derivedVerifier.verificationFor(Key.DEFAULT, assistant)) - .isInstanceOf(UnsupportedOperationException.class); + assertThat(derivedVerifier.verificationFor(Key.DEFAULT, assistant).passed()) + .isFalse(); assertThatThrownBy(() -> derivedVerifier.verificationFor(Bytes.EMPTY)) .isInstanceOf(UnsupportedOperationException.class); - assertThat(derivedVerifier.numSignaturesVerified()).isEqualTo(0L); + assertThat(derivedVerifier.numSignaturesVerified()).isZero(); } @Test diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java index 6c1a026e02eb..63c62b7a6bf1 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java @@ -78,8 +78,14 @@ public record ContractsConfig( boolean precompileHrcFacadeAssociateEnabled, @ConfigProperty(value = "systemContract.accountService.enabled", defaultValue = "true") @NetworkProperty boolean systemContractAccountServiceEnabled, - @ConfigProperty(value = "systemContract.scheduleService.enabled", defaultValue = "false") @NetworkProperty + @ConfigProperty(value = "systemContract.scheduleService.enabled", defaultValue = "true") @NetworkProperty boolean systemContractScheduleServiceEnabled, + @ConfigProperty(value = "systemContract.scheduleService.signSchedule.enabled", defaultValue = "true") + @NetworkProperty + boolean systemContractSignScheduleEnabled, + @ConfigProperty(value = "systemContract.scheduleService.authorizeSchedule.enabled", defaultValue = "false") + @NetworkProperty + boolean systemContractAuthorizeScheduleEnabled, @ConfigProperty(value = "systemContract.accountService.isAuthorizedRawEnabled", defaultValue = "true") @NetworkProperty boolean systemContractAccountServiceIsAuthorizedRawEnabled, diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java index aa2b3cac65e9..7664b27ad0ac 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java @@ -258,8 +258,9 @@ private InvolvedParties computeInvolvedParties( } private boolean contractNotRequired(@Nullable final HederaEvmAccount to, @NonNull final Configuration config) { - final var maybeGrandfatheredNumber = - (to == null) ? null : to.isTokenFacade() ? null : to.hederaId().accountNumOrThrow(); + final var maybeGrandfatheredNumber = (to == null || to.isTokenFacade() || to.isScheduleTxnFacade()) + ? null + : to.hederaId().accountNumOrThrow(); return featureFlags.isAllowCallsToNonContractAccountsEnabled( config.getConfigData(ContractsConfig.class), maybeGrandfatheredNumber); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/DispatchType.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/DispatchType.java index d339bfbd6069..69dbd3c8c484 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/DispatchType.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/DispatchType.java @@ -170,7 +170,11 @@ public enum DispatchType { /** * Dispatch for Hedera token reject functionality with resource prices on a non-fungible token. */ - TOKEN_REJECT_NFT(HederaFunctionality.TOKEN_REJECT, TOKEN_NON_FUNGIBLE_UNIQUE); + TOKEN_REJECT_NFT(HederaFunctionality.TOKEN_REJECT, TOKEN_NON_FUNGIBLE_UNIQUE), + /** + * Dispatch for Hedera schedule sign functionality with default resource prices. + */ + SCHEDULE_SIGN(HederaFunctionality.SCHEDULE_SIGN, DEFAULT); private final HederaFunctionality functionality; private final SubType subtype; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleSystemContractOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleSystemContractOperations.java index 86aedbafdfed..ae7aca537bd2 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleSystemContractOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleSystemContractOperations.java @@ -149,4 +149,10 @@ public Transaction syntheticTransactionForNativeCall( public ExchangeRate currentExchangeRate() { return context.exchangeRateInfo().activeRate(context.consensusNow()); } + + @Override + @Nullable + public Key maybeEthSenderKey() { + return maybeEthSenderKey; + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QuerySystemContractOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QuerySystemContractOperations.java index 5eafa796eade..cc59396dd119 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QuerySystemContractOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QuerySystemContractOperations.java @@ -107,4 +107,10 @@ public Transaction syntheticTransactionForNativeCall( public ExchangeRate currentExchangeRate() { return context.exchangeRateInfo().activeRate(instantSource.instant()); } + + @Override + @Nullable + public Key maybeEthSenderKey() { + return null; + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/SystemContractOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/SystemContractOperations.java index a7bb5d85719f..aba37ad95bc1 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/SystemContractOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/SystemContractOperations.java @@ -30,6 +30,7 @@ import com.hedera.node.app.service.contract.impl.records.ContractCallStreamBuilder; import com.hedera.node.app.spi.workflows.record.StreamBuilder; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Set; import java.util.function.Predicate; import org.apache.tuweni.bytes.Bytes; @@ -154,4 +155,10 @@ Transaction syntheticTransactionForNativeCall( */ @NonNull ExchangeRate currentExchangeRate(); + + /** + * Returns the ecdsa eth key for the sender of current transaction if one exists. + */ + @Nullable + Key maybeEthSenderKey(); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HssSystemContract.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HssSystemContract.java index ee4d8adc8a55..beb29fd7d3be 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HssSystemContract.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HssSystemContract.java @@ -24,7 +24,7 @@ import com.hedera.hapi.node.base.ContractID; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractNativeSystemContract; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.HasCallFactory; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hss.HssCallFactory; import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils; import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.EntityType; import edu.umd.cs.findbugs.annotations.NonNull; @@ -45,7 +45,7 @@ public class HssSystemContract extends AbstractNativeSystemContract implements H public static final ContractID HSS_CONTRACT_ID = asNumberedContractId(Address.fromHexString(HSS_EVM_ADDRESS)); @Inject - public HssSystemContract(@NonNull final GasCalculator gasCalculator, @NonNull final HasCallFactory callFactory) { + public HssSystemContract(@NonNull final GasCalculator gasCalculator, @NonNull final HssCallFactory callFactory) { super(HSS_SYSTEM_CONTRACT_NAME, callFactory, HSS_CONTRACT_ID, gasCalculator); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hss/DispatchForResponseCodeHssCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hss/DispatchForResponseCodeHssCall.java new file mode 100644 index 000000000000..ef39c4f348ad --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hss/DispatchForResponseCodeHssCall.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.exec.systemcontracts.hss; + +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TRANSACTION_BODY; +import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; +import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.ERROR_DECODING_PRECOMPILE_INPUT; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.haltResult; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.encodedRc; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.contractsConfigOf; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.Key; +import com.hedera.hapi.node.base.ResponseCodeEnum; +import com.hedera.hapi.node.transaction.TransactionBody; +import com.hedera.node.app.service.contract.impl.exec.gas.DispatchGasCalculator; +import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; +import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCall; +import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; +import com.hedera.node.app.service.contract.impl.records.ContractCallStreamBuilder; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.Objects; +import java.util.Set; +import org.hyperledger.besu.evm.frame.MessageFrame; + +/** + * An HSS call that simply dispatches a synthetic transaction body and returns a result that is + * an encoded {@link ResponseCodeEnum}. + */ +public class DispatchForResponseCodeHssCall extends AbstractCall { + + private final AccountID senderId; + + @Nullable + private final TransactionBody syntheticBody; + + private final VerificationStrategy verificationStrategy; + private final DispatchGasCalculator dispatchGasCalculator; + private final Set authorizingKeys; + + /** + * Convenience overload that slightly eases construction for the most common case. + * + * @param attempt the attempt to translate to a dispatching + * @param syntheticBody the synthetic body to dispatch + * @param dispatchGasCalculator the dispatch gas calculator to use + */ + public DispatchForResponseCodeHssCall( + @NonNull final HssCallAttempt attempt, + @Nullable final TransactionBody syntheticBody, + @NonNull final DispatchGasCalculator dispatchGasCalculator, + @NonNull final Set authorizingKeys) { + this( + attempt.enhancement(), + attempt.systemContractGasCalculator(), + attempt.addressIdConverter().convertSender(attempt.senderAddress()), + syntheticBody, + attempt.defaultVerificationStrategy(), + dispatchGasCalculator, + authorizingKeys); + } + + /** + * More general constructor, for cases where perhaps a custom {@link VerificationStrategy} is needed. + * + * @param enhancement the enhancement to use + * @param senderId the id of the spender + * @param syntheticBody the synthetic body to dispatch + * @param verificationStrategy the verification strategy to use + * @param dispatchGasCalculator the dispatch gas calculator to use + */ + // too many parameters + @SuppressWarnings("java:S107") + public DispatchForResponseCodeHssCall( + @NonNull final HederaWorldUpdater.Enhancement enhancement, + @NonNull final SystemContractGasCalculator gasCalculator, + @NonNull final AccountID senderId, + @Nullable final TransactionBody syntheticBody, + @NonNull final VerificationStrategy verificationStrategy, + @NonNull final DispatchGasCalculator dispatchGasCalculator, + @NonNull final Set authorizingKeys) { + super(gasCalculator, enhancement, false); + this.senderId = Objects.requireNonNull(senderId); + this.syntheticBody = syntheticBody; + this.verificationStrategy = Objects.requireNonNull(verificationStrategy); + this.dispatchGasCalculator = Objects.requireNonNull(dispatchGasCalculator); + this.authorizingKeys = authorizingKeys; + } + + @Override + public @NonNull PricedResult execute(@NonNull final MessageFrame frame) { + if (syntheticBody == null) { + return gasOnly( + haltResult( + ERROR_DECODING_PRECOMPILE_INPUT, + contractsConfigOf(frame).precompileHtsDefaultGasCost()), + INVALID_TRANSACTION_BODY, + false); + } + final var recordBuilder = systemContractOperations() + .dispatch( + syntheticBody, + verificationStrategy, + senderId, + ContractCallStreamBuilder.class, + authorizingKeys); + final var gasRequirement = + dispatchGasCalculator.gasRequirement(syntheticBody, gasCalculator, enhancement, senderId); + var status = recordBuilder.status(); + if (status != SUCCESS) { + recordBuilder.status(status); + } + return completionWith(gasRequirement, recordBuilder, encodedRc(recordBuilder.status())); + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hss/HssCallAttempt.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hss/HssCallAttempt.java index 7f9f87a3e949..78f21b4233d2 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hss/HssCallAttempt.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hss/HssCallAttempt.java @@ -17,10 +17,12 @@ package com.hedera.node.app.service.contract.impl.exec.systemcontracts.hss; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.isLongZeroAddress; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.maybeMissingNumberOf; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.numberOfLongZero; import static java.util.Objects.requireNonNull; import com.esaulpaugh.headlong.abi.Function; +import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.ScheduleID; import com.hedera.hapi.node.state.schedule.Schedule; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; @@ -51,6 +53,9 @@ public class HssCallAttempt extends AbstractCallAttempt { @Nullable private final Schedule redirectScheduleTxn; + @NonNull + private final Address originatorAddress; + // too many parameters @SuppressWarnings("java:S107") public HssCallAttempt( @@ -63,7 +68,8 @@ public HssCallAttempt( @NonNull final VerificationStrategies verificationStrategies, @NonNull final SystemContractGasCalculator gasCalculator, @NonNull final List> callTranslators, - final boolean isStaticCall) { + final boolean isStaticCall, + @NonNull final Address originatorAddress) { super( input, senderAddress, @@ -82,6 +88,7 @@ public HssCallAttempt( } else { this.redirectScheduleTxn = null; } + this.originatorAddress = requireNonNull(originatorAddress); } @Override @@ -149,4 +156,14 @@ public boolean isScheduleRedirect() { } return null; } + + /** + * Returns the AccountID of the originator of this call. + * + * @return the AccountID of the originator of this call + */ + public @NonNull AccountID originatorAccount() { + final var number = maybeMissingNumberOf(originatorAddress, this.enhancement.nativeOperations()); + return AccountID.newBuilder().accountNum(number).build(); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hss/HssCallFactory.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hss/HssCallFactory.java index b503840b2730..2eafeb55199b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hss/HssCallFactory.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hss/HssCallFactory.java @@ -85,6 +85,7 @@ public HssCallFactory( verificationStrategies, systemContractGasCalculatorOf(frame), callTranslators, - frame.isStatic()); + frame.isStatic(), + frame.getOriginatorAddress()); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hss/signschedule/SignScheduleCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hss/signschedule/SignScheduleCall.java deleted file mode 100644 index 4ce02d8d03e0..000000000000 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hss/signschedule/SignScheduleCall.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.node.app.service.contract.impl.exec.systemcontracts.hss.signschedule; - -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCall; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hss.HssCallAttempt; -import edu.umd.cs.findbugs.annotations.NonNull; - -public class SignScheduleCall extends AbstractCall { - - // Future: implement fully in a future pr. This stub class only used for testing purposes - public SignScheduleCall(@NonNull final HssCallAttempt attempt) { - super(attempt.systemContractGasCalculator(), attempt.enhancement(), false); - } -} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hss/signschedule/SignScheduleTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hss/signschedule/SignScheduleTranslator.java index b112c35a968d..9beae83b20ec 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hss/signschedule/SignScheduleTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hss/signschedule/SignScheduleTranslator.java @@ -16,14 +16,35 @@ package com.hedera.node.app.service.contract.impl.exec.systemcontracts.hss.signschedule; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_SCHEDULE_ID; +import static com.hedera.node.app.service.contract.impl.exec.scope.HederaNativeOperations.MISSING_ENTITY_NUMBER; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.explicitFromHeadlong; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.maybeMissingNumberOf; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.numberOfLongZero; +import static com.hedera.node.app.spi.workflows.HandleException.validateTrue; +import static java.util.Collections.emptySet; import static java.util.Objects.requireNonNull; +import com.esaulpaugh.headlong.abi.Address; import com.esaulpaugh.headlong.abi.Function; +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.ContractID; +import com.hedera.hapi.node.base.Key; +import com.hedera.hapi.node.base.ScheduleID; +import com.hedera.hapi.node.scheduled.ScheduleSignTransactionBody; +import com.hedera.hapi.node.transaction.TransactionBody; +import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; +import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hss.DispatchForResponseCodeHssCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hss.HssCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; +import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; +import com.hedera.node.config.data.ContractsConfig; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Set; +import javax.inject.Inject; import javax.inject.Singleton; /** @@ -31,17 +52,135 @@ */ @Singleton public class SignScheduleTranslator extends AbstractCallTranslator { - /** Selector for signSchedule(address,bytes) method. */ public static final Function SIGN_SCHEDULE = new Function("signSchedule(address,bytes)", ReturnTypes.INT_64); + public static final Function SIGN_SCHEDULE_PROXY = new Function("signSchedule()", ReturnTypes.INT_64); + public static final Function AUTHORIZE_SCHEDULE = new Function("authorizeSchedule(address)", ReturnTypes.INT_64); + + @Inject + public SignScheduleTranslator() { + // Dagger2 + } @Override public boolean matches(@NonNull final HssCallAttempt attempt) { requireNonNull(attempt); - return attempt.isSelector(SIGN_SCHEDULE); + final var signScheduleEnabled = + attempt.configuration().getConfigData(ContractsConfig.class).systemContractSignScheduleEnabled(); + final var authorizeScheduleEnabled = + attempt.configuration().getConfigData(ContractsConfig.class).systemContractAuthorizeScheduleEnabled(); + return attempt.isSelectorIfConfigEnabled(signScheduleEnabled, SIGN_SCHEDULE_PROXY) + || attempt.isSelectorIfConfigEnabled(authorizeScheduleEnabled, AUTHORIZE_SCHEDULE); } @Override public Call callFrom(@NonNull HssCallAttempt attempt) { - return new SignScheduleCall(attempt); + final var body = bodyFor(scheduleIdFor(attempt)); + return new DispatchForResponseCodeHssCall( + attempt, body, SignScheduleTranslator::gasRequirement, keySetFor(attempt)); + } + + /** + * Calculates the gas requirement for a {@code signSchedule()} call. + * + * @param body the transaction body + * @param systemContractGasCalculator the gas calculator + * @param enhancement the enhancement + * @param payerId the payer ID + * @return the gas requirement + */ + public static long gasRequirement( + @NonNull final TransactionBody body, + @NonNull final SystemContractGasCalculator systemContractGasCalculator, + @NonNull final HederaWorldUpdater.Enhancement enhancement, + @NonNull final AccountID payerId) { + return systemContractGasCalculator.gasRequirement(body, DispatchType.SCHEDULE_SIGN, payerId); + } + + /** + * Creates a transaction body for a {@code signSchedule()} or call. + * + * @param scheduleID the schedule ID + * @return the transaction body + */ + private TransactionBody bodyFor(@NonNull ScheduleID scheduleID) { + requireNonNull(scheduleID); + return TransactionBody.newBuilder() + .scheduleSign(ScheduleSignTransactionBody.newBuilder() + .scheduleID(scheduleID) + .build()) + .build(); + } + + /** + * Extracts the schedule ID from a {@code authorizeSchedule()} call or return the redirect schedule ID + * if the call via the proxy contract + * + * @param attempt the call attempt + * @return the schedule ID + */ + private ScheduleID scheduleIdFor(@NonNull HssCallAttempt attempt) { + requireNonNull(attempt); + if (attempt.isSelector(SIGN_SCHEDULE_PROXY)) { + return getScheduleIDForSignSchedule(attempt); + } else if (attempt.isSelector(AUTHORIZE_SCHEDULE)) { + return getScheduleIDForAuthorizeSchedule(attempt); + } + throw new IllegalStateException("Unexpected function selector"); + } + + private static ScheduleID getScheduleIDForSignSchedule(@NonNull HssCallAttempt attempt) { + final var scheduleID = attempt.redirectScheduleId(); + validateTrue(scheduleID != null, INVALID_SCHEDULE_ID); + return attempt.redirectScheduleId(); + } + + private static ScheduleID getScheduleIDForAuthorizeSchedule(@NonNull HssCallAttempt attempt) { + final var call = AUTHORIZE_SCHEDULE.decodeCall(attempt.inputBytes()); + final Address scheduleAddress = call.get(0); + final var number = numberOfLongZero(explicitFromHeadlong(scheduleAddress)); + final var schedule = attempt.enhancement().nativeOperations().getSchedule(number); + validateTrue(schedule != null, INVALID_SCHEDULE_ID); + return schedule.scheduleId(); + } + + /** + * Extracts the key set for a {@code signSchedule()} call. + * + * @param attempt the call attempt + * @return the key set + */ + private Set keySetFor(@NonNull HssCallAttempt attempt) { + requireNonNull(attempt); + if (attempt.isSelector(SIGN_SCHEDULE_PROXY)) { + return getKeysForSignSchedule(attempt); + } else if (attempt.isSelector(AUTHORIZE_SCHEDULE)) { + return getKeysForAuthorizeSchedule(attempt); + } + throw new IllegalStateException("Unexpected function selector"); + } + + @NonNull + private static Set getKeysForSignSchedule(@NonNull HssCallAttempt attempt) { + // If an Eth sender key is present, use it. Otherwise, use the account key if present. + Key key = attempt.enhancement().systemOperations().maybeEthSenderKey(); + if (key != null) { + return Set.of(key); + } + key = attempt.enhancement().nativeOperations().getAccountKey(attempt.originatorAccount()); + if (key != null) { + return Set.of(key); + } + return emptySet(); + } + + @NonNull + private static Set getKeysForAuthorizeSchedule(@NonNull HssCallAttempt attempt) { + final var contractNum = maybeMissingNumberOf(attempt.senderAddress(), attempt.nativeOperations()); + if (contractNum == MISSING_ENTITY_NUMBER) { + return emptySet(); + } + return Set.of(Key.newBuilder() + .contractID(ContractID.newBuilder().contractNum(contractNum).build()) + .build()); } } 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 647a282b87b5..82fc4a30e7b7 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 @@ -110,7 +110,7 @@ public class DispatchingEvmFrameState implements EvmFrameState { // The only exception is that the function selector for `redirectForScheduleTxn` (0x5c3889ca) // has been pre substituted before the ADDRESS_BYTECODE_PATTERN. private static final String SCHEDULE_CALL_REDIRECT_CONTRACT_BINARY = - "6080604052348015600f57600080fd5b50600061016a9050775c3889cafefefefefefefefefefefefefefefefefefefefe600052366000602037600080366018016008845af43d806000803e8160008114605857816000f35b816000fdfea2646970667358221220d8378feed472ba49a0005514ef7087017f707b45fb9bf56bb81bb93ff19a238b64736f6c634300080b0033"; + "6080604052348015600f57600080fd5b50600061016b9050775c3889cafefefefefefefefefefefefefefefefefefefefe600052366000602037600080366018016008845af43d806000803e8160008114605857816000f35b816000fdfea2646970667358221220d8378feed472ba49a0005514ef7087017f707b45fb9bf56bb81bb93ff19a238b64736f6c634300080b0033"; private final HederaNativeOperations nativeOperations; private final ContractStateStore contractStateStore; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/QuerySystemContractOperationsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/QuerySystemContractOperationsTest.java index 9b481ddb2ef9..4d718812be79 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/QuerySystemContractOperationsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/QuerySystemContractOperationsTest.java @@ -21,6 +21,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; @@ -100,4 +101,9 @@ void externalizingResultsAreNoop() { assertSame( Transaction.DEFAULT, subject.syntheticTransactionForNativeCall(Bytes.EMPTY, ContractID.DEFAULT, true)); } + + @Test + void maybeEthSenderKeyIsNullTest() { + assertNull(subject.maybeEthSenderKey()); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/CallAttemptHelpers.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/CallAttemptHelpers.java index cca8664cd8a2..187e78fdb1ea 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/CallAttemptHelpers.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/CallAttemptHelpers.java @@ -25,6 +25,7 @@ import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategies; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallTranslator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.HasCallAttempt; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hss.HssCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; @@ -33,6 +34,7 @@ import com.swirlds.config.api.Configuration; import java.util.List; import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.datatypes.Address; /** * Helper utility class to generate {@link HtsCallAttempt} object in different scenarios @@ -243,4 +245,64 @@ public static HasCallAttempt prepareHasAttemptWithSelectorAndInputAndCustomConfi List.of(translator), false); } + + /** + * @param function the selector to match against + * @param translator the translator for this specific call attempt + * @param enhancement the enhancement that is used + * @param addressIdConverter the address ID converter for this call + * @param verificationStrategies the verification strategy currently used + * @param gasCalculator the gas calculator used for the system contract + * @param config the current configuration that is used + * @param originatorAddress the address of the originator of the call + * @return the call attempt + */ + public static HssCallAttempt prepareHssAttemptWithSelectorAndCustomConfig( + final Function function, + final CallTranslator translator, + final HederaWorldUpdater.Enhancement enhancement, + final AddressIdConverter addressIdConverter, + final VerificationStrategies verificationStrategies, + final SystemContractGasCalculator gasCalculator, + final Configuration config, + final Address originatorAddress) { + final var input = Bytes.wrap(function.selector()); + + return new HssCallAttempt( + input, + OWNER_BESU_ADDRESS, + false, + enhancement, + config, + addressIdConverter, + verificationStrategies, + gasCalculator, + List.of(translator), + false, + originatorAddress); + } + + public static HssCallAttempt prepareHssAttemptWithBytesAndCustomConfig( + final Bytes input, + final CallTranslator translator, + final HederaWorldUpdater.Enhancement enhancement, + final AddressIdConverter addressIdConverter, + final VerificationStrategies verificationStrategies, + final SystemContractGasCalculator gasCalculator, + final Configuration config, + final Address originatorAddress) { + + return new HssCallAttempt( + input, + OWNER_BESU_ADDRESS, + false, + enhancement, + config, + addressIdConverter, + verificationStrategies, + gasCalculator, + List.of(translator), + false, + originatorAddress); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/HssSystemContractTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/HssSystemContractTest.java index a2f075ddc5fc..88be76d82ffb 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/HssSystemContractTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/HssSystemContractTest.java @@ -23,7 +23,7 @@ import static org.mockito.Mockito.when; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.HssSystemContract; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.HasCallFactory; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hss.HssCallFactory; import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils; import com.hedera.node.config.data.ContractsConfig; import org.apache.tuweni.bytes.Bytes; @@ -47,7 +47,7 @@ class HssSystemContractTest { private ContractsConfig contractsConfig; @Mock - private HasCallFactory attemptFactory; + private HssCallFactory attemptFactory; @Mock private GasCalculator gasCalculator; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/DispatchForResponseCodeHssCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/DispatchForResponseCodeHssCallTest.java new file mode 100644 index 000000000000..536adb0ba629 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/DispatchForResponseCodeHssCallTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hss; + +import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; +import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.ERROR_DECODING_PRECOMPILE_INPUT; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CONFIG_CONTEXT_VARIABLE; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.DEFAULT_CONFIG; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.DEFAULT_CONTRACTS_CONFIG; +import static java.util.Collections.emptySet; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.BDDMockito.given; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.transaction.TransactionBody; +import com.hedera.node.app.service.contract.impl.exec.gas.DispatchGasCalculator; +import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hss.DispatchForResponseCodeHssCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; +import com.hedera.node.app.service.contract.impl.records.ContractCallStreamBuilder; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.Deque; +import java.util.Optional; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class DispatchForResponseCodeHssCallTest extends CallTestBase { + @Mock + private VerificationStrategy verificationStrategy; + + @Mock + private DispatchGasCalculator dispatchGasCalculator; + + @Mock + private ContractCallStreamBuilder recordBuilder; + + private final Deque stack = new ArrayDeque<>(); + + private DispatchForResponseCodeHssCall subject; + + @BeforeEach + void setUp() { + subject = new DispatchForResponseCodeHssCall( + mockEnhancement(), + gasCalculator, + AccountID.DEFAULT, + TransactionBody.DEFAULT, + verificationStrategy, + dispatchGasCalculator, + emptySet()); + } + + @Test + void successResult() { + given(systemContractOperations.dispatch( + TransactionBody.DEFAULT, + verificationStrategy, + AccountID.DEFAULT, + ContractCallStreamBuilder.class, + Collections.emptySet())) + .willReturn(recordBuilder); + given(dispatchGasCalculator.gasRequirement( + TransactionBody.DEFAULT, gasCalculator, mockEnhancement(), AccountID.DEFAULT)) + .willReturn(123L); + given(recordBuilder.status()).willReturn(SUCCESS); + + final var pricedResult = subject.execute(frame); + final var contractResult = pricedResult.fullResult().result().getOutput(); + assertArrayEquals(ReturnTypes.encodedRc(SUCCESS).array(), contractResult.toArray()); + } + + @Test + void haltsImmediatelyWithNullDispatch() { + given(frame.getMessageFrameStack()).willReturn(stack); + given(frame.getContextVariable(CONFIG_CONTEXT_VARIABLE)).willReturn(DEFAULT_CONFIG); + given(frame.getMessageFrameStack()).willReturn(stack); + + subject = new DispatchForResponseCodeHssCall( + mockEnhancement(), + gasCalculator, + AccountID.DEFAULT, + null, + verificationStrategy, + dispatchGasCalculator, + emptySet()); + + final var pricedResult = subject.execute(frame); + final var fullResult = pricedResult.fullResult(); + + assertEquals( + Optional.of(ERROR_DECODING_PRECOMPILE_INPUT), + fullResult.result().getHaltReason()); + assertEquals(DEFAULT_CONTRACTS_CONFIG.precompileHtsDefaultGasCost(), fullResult.gasRequirement()); + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/HssCallAttemptTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/HssCallAttemptTest.java index 01227c9d8038..6ea39b8c5c2f 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/HssCallAttemptTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/HssCallAttemptTest.java @@ -64,7 +64,8 @@ void returnNullScheduleIfScheduleNotFound() { verificationStrategies, gasCalculator, callTranslators, - false); + false, + NON_SYSTEM_LONG_ZERO_ADDRESS); assertNull(subject.redirectScheduleTxn()); } @@ -81,7 +82,8 @@ void invalidSelectorLeadsToMissingCall() { verificationStrategies, gasCalculator, callTranslators, - false); + false, + NON_SYSTEM_LONG_ZERO_ADDRESS); assertNull(subject.asExecutableCall()); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/HssCallFactoryTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/HssCallFactoryTest.java index ccba60fce8a9..8242fc8acb67 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/HssCallFactoryTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/HssCallFactoryTest.java @@ -22,15 +22,19 @@ import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EIP_1014_ADDRESS; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.bytesForRedirectScheduleTxn; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asLongZeroAddress; +import static org.hyperledger.besu.datatypes.Address.ALTBN128_ADD; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.mockito.BDDMockito.given; +import com.hedera.hapi.node.base.Key; +import com.hedera.hapi.node.state.schedule.Schedule; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategies; +import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallAddressChecks; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hss.DispatchForResponseCodeHssCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hss.HssCallFactory; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hss.signschedule.SignScheduleCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hss.signschedule.SignScheduleTranslator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.SyntheticIds; @@ -42,7 +46,6 @@ import java.util.Deque; import java.util.List; import java.util.Objects; -import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -58,6 +61,9 @@ class HssCallFactoryTest extends CallTestBase { @Mock private VerificationStrategies verificationStrategies; + @Mock + private VerificationStrategy verificationStrategy; + @Mock private SignatureVerifier signatureVerifier; @@ -78,6 +84,12 @@ class HssCallFactoryTest extends CallTestBase { @Mock private ProxyWorldUpdater updater; + @Mock + private Schedule schedule; + + @Mock + private Key maybeEthSenderKey; + private HssCallFactory subject; @BeforeEach @@ -102,15 +114,22 @@ void instantiatesCallWithInContextEnhancementAndDelegateCallInfo() { given(frame.getWorldUpdater()).willReturn(updater); given(updater.enhancement()).willReturn(mockEnhancement()); given(frame.getSenderAddress()).willReturn(EIP_1014_ADDRESS); + given(frame.getOriginatorAddress()).willReturn(EIP_1014_ADDRESS); given(addressChecks.hasParentDelegateCall(frame)).willReturn(true); given(syntheticIds.converterFor(nativeOperations)).willReturn(idConverter); + given(nativeOperations.getSchedule(CALLED_SCHEDULE_ID.scheduleNum())).willReturn(schedule); + given(schedule.scheduleId()).willReturn(CALLED_SCHEDULE_ID); + given(idConverter.convertSender(EIP_1014_ADDRESS)).willReturn(A_NEW_ACCOUNT_ID); + given(verificationStrategies.activatingOnlyContractKeysFor(EIP_1014_ADDRESS, true, nativeOperations)) + .willReturn(verificationStrategy); final var input = bytesForRedirectScheduleTxn( - SignScheduleTranslator.SIGN_SCHEDULE.selector(), asLongZeroAddress(CALLED_SCHEDULE_ID.scheduleNum())); + SignScheduleTranslator.SIGN_SCHEDULE_PROXY.selector(), + asLongZeroAddress(CALLED_SCHEDULE_ID.scheduleNum())); final var attempt = subject.createCallAttemptFrom(input, FrameUtils.CallType.DIRECT_OR_PROXY_REDIRECT, frame); final var call = Objects.requireNonNull(attempt.asExecutableCall()); - assertInstanceOf(SignScheduleCall.class, call); + assertInstanceOf(DispatchForResponseCodeHssCall.class, call); } @Test @@ -124,17 +143,24 @@ void instantiatesDirectCall() { given(frame.getMessageFrameStack()).willReturn(stack); given(frame.getWorldUpdater()).willReturn(updater); given(updater.enhancement()).willReturn(mockEnhancement()); - given(frame.getSenderAddress()).willReturn(Address.ALTBN128_ADD); - given(idConverter.convertSender(Address.ALTBN128_ADD)).willReturn(A_NEW_ACCOUNT_ID); + given(frame.getSenderAddress()).willReturn(ALTBN128_ADD); + given(frame.getOriginatorAddress()).willReturn(EIP_1014_ADDRESS); + given(idConverter.convertSender(ALTBN128_ADD)).willReturn(A_NEW_ACCOUNT_ID); given(addressChecks.hasParentDelegateCall(frame)).willReturn(true); given(syntheticIds.converterFor(nativeOperations)).willReturn(idConverter); + given(nativeOperations.getSchedule(CALLED_SCHEDULE_ID.scheduleNum())).willReturn(schedule); + given(schedule.scheduleId()).willReturn(CALLED_SCHEDULE_ID); + given(idConverter.convertSender(ALTBN128_ADD)).willReturn(A_NEW_ACCOUNT_ID); + given(verificationStrategies.activatingOnlyContractKeysFor(ALTBN128_ADD, true, nativeOperations)) + .willReturn(verificationStrategy); final var input = bytesForRedirectScheduleTxn( - SignScheduleTranslator.SIGN_SCHEDULE.selector(), asLongZeroAddress(CALLED_SCHEDULE_ID.scheduleNum())); + SignScheduleTranslator.SIGN_SCHEDULE_PROXY.selector(), + asLongZeroAddress(CALLED_SCHEDULE_ID.scheduleNum())); final var attempt = subject.createCallAttemptFrom(input, FrameUtils.CallType.DIRECT_OR_PROXY_REDIRECT, frame); final var call = Objects.requireNonNull(attempt.asExecutableCall()); - assertInstanceOf(SignScheduleCall.class, call); + assertInstanceOf(DispatchForResponseCodeHssCall.class, call); assertEquals(A_NEW_ACCOUNT_ID, attempt.senderId()); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/signschedule/SignScheduleTranslatorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/signschedule/SignScheduleTranslatorTest.java new file mode 100644 index 000000000000..0164d16171bd --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/signschedule/SignScheduleTranslatorTest.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hss.signschedule; + +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.APPROVED_HEADLONG_ADDRESS; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.NON_SYSTEM_LONG_ZERO_ADDRESS; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.OWNER_BESU_ADDRESS; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.bytesForRedirectScheduleTxn; +import static com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.CallAttemptHelpers.prepareHssAttemptWithBytesAndCustomConfig; +import static com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.CallAttemptHelpers.prepareHssAttemptWithSelectorAndCustomConfig; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.when; + +import com.esaulpaugh.headlong.abi.Tuple; +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.ScheduleID; +import com.hedera.hapi.node.state.schedule.Schedule; +import com.hedera.hapi.node.transaction.TransactionBody; +import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; +import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; +import com.hedera.node.app.service.contract.impl.exec.scope.HederaNativeOperations; +import com.hedera.node.app.service.contract.impl.exec.scope.SystemContractOperations; +import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategies; +import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hss.DispatchForResponseCodeHssCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hss.HssCallAttempt; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hss.signschedule.SignScheduleTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.mint.MintTranslator; +import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; +import com.hedera.node.config.data.ContractsConfig; +import com.swirlds.config.api.Configuration; +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class SignScheduleTranslatorTest { + + @Mock + private HssCallAttempt attempt; + + @Mock + private Configuration configuration; + + @Mock + private ContractsConfig contractsConfig; + + @Mock + private HederaWorldUpdater.Enhancement enhancement; + + @Mock + private AddressIdConverter addressIdConverter; + + @Mock + private VerificationStrategies verificationStrategies; + + @Mock + private VerificationStrategy verificationStrategy; + + @Mock + private SystemContractGasCalculator gasCalculator; + + @Mock + private HederaNativeOperations nativeOperations; + + @Mock + private SystemContractOperations systemContractOperations; + + @Mock + private TransactionBody transactionBody; + + @Mock + private AccountID payerId; + + @Mock + private Schedule schedule; + + @Mock + private ScheduleID scheduleID; + + private SignScheduleTranslator subject; + + @BeforeEach + void setUp() { + subject = new SignScheduleTranslator(); + } + + @Test + void testMatchesWhenSignScheduleEnabled() { + // given: + given(configuration.getConfigData(ContractsConfig.class)).willReturn(contractsConfig); + given(contractsConfig.systemContractSignScheduleEnabled()).willReturn(true); + attempt = prepareHssAttemptWithSelectorAndCustomConfig( + SignScheduleTranslator.SIGN_SCHEDULE_PROXY, + subject, + enhancement, + addressIdConverter, + verificationStrategies, + gasCalculator, + configuration, + NON_SYSTEM_LONG_ZERO_ADDRESS); + + // when: + boolean matches = subject.matches(attempt); + + // then: + assertTrue(matches); + } + + @Test + void testFailsMatchesWhenSignScheduleEnabled() { + // given: + given(configuration.getConfigData(ContractsConfig.class)).willReturn(contractsConfig); + given(contractsConfig.systemContractSignScheduleEnabled()).willReturn(false); + attempt = prepareHssAttemptWithSelectorAndCustomConfig( + SignScheduleTranslator.SIGN_SCHEDULE_PROXY, + subject, + enhancement, + addressIdConverter, + verificationStrategies, + gasCalculator, + configuration, + NON_SYSTEM_LONG_ZERO_ADDRESS); + + // when: + boolean matches = subject.matches(attempt); + + // then: + assertFalse(matches); + } + + @Test + void testMatchesWhenAuthorizeScheduleEnabled() { + // given: + given(configuration.getConfigData(ContractsConfig.class)).willReturn(contractsConfig); + given(contractsConfig.systemContractAuthorizeScheduleEnabled()).willReturn(true); + attempt = prepareHssAttemptWithSelectorAndCustomConfig( + SignScheduleTranslator.AUTHORIZE_SCHEDULE, + subject, + enhancement, + addressIdConverter, + verificationStrategies, + gasCalculator, + configuration, + NON_SYSTEM_LONG_ZERO_ADDRESS); + + // when: + boolean matches = subject.matches(attempt); + + // then: + assertTrue(matches); + } + + @Test + void testFailsMatchesWhenAuthorizeScheduleEnabled() { + // given: + given(configuration.getConfigData(ContractsConfig.class)).willReturn(contractsConfig); + given(contractsConfig.systemContractAuthorizeScheduleEnabled()).willReturn(false); + attempt = prepareHssAttemptWithSelectorAndCustomConfig( + SignScheduleTranslator.AUTHORIZE_SCHEDULE, + subject, + enhancement, + addressIdConverter, + verificationStrategies, + gasCalculator, + configuration, + NON_SYSTEM_LONG_ZERO_ADDRESS); + + // when: + boolean matches = subject.matches(attempt); + + // then: + assertFalse(matches); + } + + @Test + void testMatchesFailsOnRandomSelector() { + // given: + given(configuration.getConfigData(ContractsConfig.class)).willReturn(contractsConfig); + given(contractsConfig.systemContractSignScheduleEnabled()).willReturn(true); + attempt = prepareHssAttemptWithSelectorAndCustomConfig( + MintTranslator.MINT, + subject, + enhancement, + addressIdConverter, + verificationStrategies, + gasCalculator, + configuration, + NON_SYSTEM_LONG_ZERO_ADDRESS); + + // when: + boolean matches = subject.matches(attempt); + + // then: + assertFalse(matches); + } + + @Test + void testScheduleIdForSignScheduleProxy() { + given(enhancement.nativeOperations()).willReturn(nativeOperations); + given(enhancement.systemOperations()).willReturn(systemContractOperations); + given(nativeOperations.getSchedule(anyLong())).willReturn(schedule); + given(schedule.scheduleId()).willReturn(scheduleID); + given(addressIdConverter.convertSender(OWNER_BESU_ADDRESS)).willReturn(payerId); + given(verificationStrategies.activatingOnlyContractKeysFor(OWNER_BESU_ADDRESS, false, nativeOperations)) + .willReturn(verificationStrategy); + + // when: + attempt = prepareHssAttemptWithBytesAndCustomConfig( + bytesForRedirectScheduleTxn( + SignScheduleTranslator.SIGN_SCHEDULE_PROXY.selector(), NON_SYSTEM_LONG_ZERO_ADDRESS), + subject, + enhancement, + addressIdConverter, + verificationStrategies, + gasCalculator, + configuration, + NON_SYSTEM_LONG_ZERO_ADDRESS); + + // then: + final var call = subject.callFrom(attempt); + + assertThat(call).isInstanceOf(DispatchForResponseCodeHssCall.class); + } + + @Test + void testScheduleIdForAuthorizeScheduleProxy() { + given(enhancement.nativeOperations()).willReturn(nativeOperations); + given(nativeOperations.getSchedule(anyLong())).willReturn(schedule); + given(schedule.scheduleId()).willReturn(scheduleID); + given(addressIdConverter.convertSender(OWNER_BESU_ADDRESS)).willReturn(payerId); + given(verificationStrategies.activatingOnlyContractKeysFor(OWNER_BESU_ADDRESS, false, nativeOperations)) + .willReturn(verificationStrategy); + + // when: + final var input = Bytes.wrapByteBuffer( + SignScheduleTranslator.AUTHORIZE_SCHEDULE.encodeCall(Tuple.of(APPROVED_HEADLONG_ADDRESS))); + attempt = prepareHssAttemptWithBytesAndCustomConfig( + input, + subject, + enhancement, + addressIdConverter, + verificationStrategies, + gasCalculator, + configuration, + NON_SYSTEM_LONG_ZERO_ADDRESS); + + // then: + final var call = subject.callFrom(attempt); + + assertThat(call).isInstanceOf(DispatchForResponseCodeHssCall.class); + } + + @Test + void testGasRequirement() { + long expectedGas = 1000L; + when(gasCalculator.gasRequirement(transactionBody, DispatchType.SCHEDULE_SIGN, payerId)) + .thenReturn(expectedGas); + + long gas = SignScheduleTranslator.gasRequirement(transactionBody, gasCalculator, enhancement, payerId); + + assertEquals(expectedGas, gas); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/schedule/HapiScheduleCreate.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/schedule/HapiScheduleCreate.java index 49daa5f46fa4..a45ccad14a9c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/schedule/HapiScheduleCreate.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/schedule/HapiScheduleCreate.java @@ -38,6 +38,7 @@ import com.hederahashgraph.api.proto.java.KeyList; import com.hederahashgraph.api.proto.java.SchedulableTransactionBody; import com.hederahashgraph.api.proto.java.ScheduleCreateTransactionBody; +import com.hederahashgraph.api.proto.java.ScheduleID; import com.hederahashgraph.api.proto.java.Timestamp; import com.hederahashgraph.api.proto.java.Transaction; import com.hederahashgraph.api.proto.java.TransactionBody; @@ -80,6 +81,7 @@ public class HapiScheduleCreate> extends HapiTxnOp waitForExpiry = Optional.empty(); private Optional> expirationTimeRelativeTo = Optional.empty(); private Optional> successCb = Optional.empty(); + private Optional> newScheduleIdObserver = Optional.empty(); private AtomicReference scheduledTxn = new AtomicReference<>(); private final String scheduleEntity; @@ -133,6 +135,11 @@ public HapiScheduleCreate exposingSuccessTo(BiConsumer cb) { return this; } + public HapiScheduleCreate exposingCreatedIdTo(final Consumer newScheduleIdObserver) { + this.newScheduleIdObserver = Optional.of(newScheduleIdObserver); + return this; + } + public HapiScheduleCreate designatingPayer(String s) { payerAccountID = Optional.of(s); return this; @@ -294,6 +301,9 @@ protected void updateStateOf(HapiSpec spec) throws Throwable { } var registry = spec.registry(); registry.saveScheduleId(scheduleEntity, lastReceipt.getScheduleID()); + + newScheduleIdObserver.ifPresent(obs -> obs.accept(lastReceipt.getScheduleID())); + adminKey.ifPresent( k -> registry.saveAdminKey(scheduleEntity, spec.registry().getKey(k))); if (saveExpectedScheduledTxnId) { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/schedule/ContractSignScheduleTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/schedule/ContractSignScheduleTest.java new file mode 100644 index 000000000000..44afd799cfa1 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/schedule/ContractSignScheduleTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.suites.contract.precompile.schedule; + +import static com.hedera.services.bdd.junit.TestTags.SMART_CONTRACT; +import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getScheduleInfo; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCallWithFunctionAbi; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; +import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; +import static com.hedera.services.bdd.suites.contract.Utils.mirrorAddrWith; +import static com.hedera.services.bdd.suites.leaky.LeakyContractTestsSuite.RECEIVER; +import static com.hedera.services.bdd.suites.leaky.LeakyContractTestsSuite.SENDER; + +import com.hedera.services.bdd.junit.HapiTest; +import com.hedera.services.bdd.junit.HapiTestLifecycle; +import com.hedera.services.bdd.junit.support.TestLifecycle; +import com.hedera.services.bdd.spec.transactions.token.TokenMovement; +import com.hederahashgraph.api.proto.java.ScheduleID; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Tag; + +@Tag(SMART_CONTRACT) +@DisplayName("Contract Sign Schedule") +@HapiTestLifecycle +public class ContractSignScheduleTest { + + private static final String A_SCHEDULE = "testSchedule"; + private static final String CONTRACT = "HRC755Contract"; + + @Nested + @DisplayName("Authorize Schedule") + class AuthorizeScheduleTest { + private static final AtomicReference scheduleID = new AtomicReference<>(); + + @BeforeAll + static void beforeAll(final TestLifecycle testLifecycle) { + testLifecycle.doAdhoc( + cryptoCreate(RECEIVER), + uploadInitCode(CONTRACT), + contractCreate(CONTRACT), + cryptoTransfer(TokenMovement.movingHbar(ONE_HUNDRED_HBARS).between(GENESIS, CONTRACT)), + scheduleCreate(A_SCHEDULE, cryptoTransfer(tinyBarsFromTo(CONTRACT, RECEIVER, 1))) + .exposingCreatedIdTo(scheduleID::set)); + } + + @HapiTest + @Disabled + final Stream authorizeScheduleWithContract() { + return hapiTest( + overriding("contracts.systemContract.scheduleService.enabled", "true"), + overriding("contracts.systemContract.scheduleService.authorizeSchedule.enabled", "true"), + contractCall( + CONTRACT, + "authorizeScheduleCall", + mirrorAddrWith(scheduleID.get().getScheduleNum()))); + } + } + + @Nested + @DisplayName("Sign Schedule From EOA") + class SignScheduleFromEOATest { + private static final AtomicReference scheduleID = new AtomicReference<>(); + private static final String SIGN_SCHEDULE = "signSchedule"; + private static final String IHRC755 = "IHRC755"; + + @BeforeAll + static void beforeAll(final TestLifecycle testLifecycle) { + testLifecycle.doAdhoc( + cryptoCreate(SENDER).balance(ONE_HUNDRED_HBARS), + cryptoCreate(RECEIVER).balance(ONE_HUNDRED_HBARS), + scheduleCreate(A_SCHEDULE, cryptoTransfer(tinyBarsFromTo(SENDER, RECEIVER, 1))) + .exposingCreatedIdTo(scheduleID::set)); + } + + @HapiTest + final Stream authorizeScheduleWithContract() { + var scheduleAddress = "0.0." + scheduleID.get().getScheduleNum(); + return hapiTest( + overriding("contracts.systemContract.scheduleService.enabled", "true"), + overriding("contracts.systemContract.scheduleService.signSchedule.enabled", "true"), + getScheduleInfo(A_SCHEDULE).isNotExecuted(), + contractCallWithFunctionAbi( + scheduleAddress, + getABIFor( + com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION, + SIGN_SCHEDULE, + IHRC755)) + .payingWith(SENDER) + .gas(1_000_000), + getScheduleInfo(A_SCHEDULE).isExecuted()); + } + } +} diff --git a/hedera-node/test-clients/src/main/resources/contract/contracts/HRC755Contract/HRC755Contract.bin b/hedera-node/test-clients/src/main/resources/contract/contracts/HRC755Contract/HRC755Contract.bin new file mode 100644 index 000000000000..50011afa6676 --- /dev/null +++ b/hedera-node/test-clients/src/main/resources/contract/contracts/HRC755Contract/HRC755Contract.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b50610455806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063092a105214610030575b600080fd5b61004a60048036038101906100459190610233565b610060565b604051610057919061027c565b60405180910390f35b600061006b826100bb565b9050601660030b8160070b146100b6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100ad906102f4565b60405180910390fd5b919050565b600080600061016b73ffffffffffffffffffffffffffffffffffffffff1663f063796160e01b856040516024016100f29190610323565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161015c91906103af565b6000604051808303816000865af19150503d8060008114610199576040519150601f19603f3d011682016040523d82523d6000602084013e61019e565b606091505b5091509150816101b257601560030b6101c7565b808060200190518101906101c691906103f2565b5b92505050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610200826101d5565b9050919050565b610210816101f5565b811461021b57600080fd5b50565b60008135905061022d81610207565b92915050565b600060208284031215610249576102486101d0565b5b60006102578482850161021e565b91505092915050565b60008160070b9050919050565b61027681610260565b82525050565b6000602082019050610291600083018461026d565b92915050565b600082825260208201905092915050565b7f417574686f72697a65207363686564756c65206661696c656400000000000000600082015250565b60006102de601983610297565b91506102e9826102a8565b602082019050919050565b6000602082019050818103600083015261030d816102d1565b9050919050565b61031d816101f5565b82525050565b60006020820190506103386000830184610314565b92915050565b600081519050919050565b600081905092915050565b60005b83811015610372578082015181840152602081019050610357565b60008484015250505050565b60006103898261033e565b6103938185610349565b93506103a3818560208601610354565b80840191505092915050565b60006103bb828461037e565b915081905092915050565b6103cf81610260565b81146103da57600080fd5b50565b6000815190506103ec816103c6565b92915050565b600060208284031215610408576104076101d0565b5b6000610416848285016103dd565b9150509291505056fea2646970667358221220c7e2b6924104383a1cd11bfcad6ea44b550e5ad6ec30e0a7668e3cbf15d5f17364736f6c63430008100033 \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resources/contract/contracts/HRC755Contract/HRC755Contract.json b/hedera-node/test-clients/src/main/resources/contract/contracts/HRC755Contract/HRC755Contract.json new file mode 100644 index 000000000000..477a164323eb --- /dev/null +++ b/hedera-node/test-clients/src/main/resources/contract/contracts/HRC755Contract/HRC755Contract.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"schedule","type":"address"}],"name":"authorizeScheduleCall","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resources/contract/contracts/HRC755Contract/HRC755Contract.sol b/hedera-node/test-clients/src/main/resources/contract/contracts/HRC755Contract/HRC755Contract.sol new file mode 100644 index 000000000000..4261e285d833 --- /dev/null +++ b/hedera-node/test-clients/src/main/resources/contract/contracts/HRC755Contract/HRC755Contract.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.4.9 <0.9.0; + +import "./HederaScheduleService.sol"; +import "./HederaResponseCodes.sol"; +pragma experimental ABIEncoderV2; + +contract HRC755Contract is HederaScheduleService { + function authorizeScheduleCall(address schedule) external returns (int64 responseCode) + { + (responseCode) = HederaScheduleService.authorizeSchedule(schedule); + require(responseCode == HederaResponseCodes.SUCCESS, "Authorize schedule failed"); + } +} diff --git a/hedera-node/test-clients/src/main/resources/contract/contracts/HRC755Contract/HederaResponseCodes.sol b/hedera-node/test-clients/src/main/resources/contract/contracts/HRC755Contract/HederaResponseCodes.sol new file mode 100644 index 000000000000..1c5cd6f44f67 --- /dev/null +++ b/hedera-node/test-clients/src/main/resources/contract/contracts/HRC755Contract/HederaResponseCodes.sol @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.4.9 <0.9.0; + +library HederaResponseCodes { + + // response codes + int32 public constant OK = 0; // The transaction passed the precheck validations. + int32 public constant INVALID_TRANSACTION = 1; // For any error not handled by specific error codes listed below. + int32 public constant PAYER_ACCOUNT_NOT_FOUND = 2; //Payer account does not exist. + int32 public constant INVALID_NODE_ACCOUNT = 3; //Node Account provided does not match the node account of the node the transaction was submitted to. + int32 public constant TRANSACTION_EXPIRED = 4; // Pre-Check error when TransactionValidStart + transactionValidDuration is less than current consensus time. + int32 public constant INVALID_TRANSACTION_START = 5; // Transaction start time is greater than current consensus time + int32 public constant INVALID_TRANSACTION_DURATION = 6; //valid transaction duration is a positive non zero number that does not exceed 120 seconds + int32 public constant INVALID_SIGNATURE = 7; // The transaction signature is not valid + int32 public constant MEMO_TOO_LONG = 8; //Transaction memo size exceeded 100 bytes + int32 public constant INSUFFICIENT_TX_FEE = 9; // The fee provided in the transaction is insufficient for this type of transaction + int32 public constant INSUFFICIENT_PAYER_BALANCE = 10; // The payer account has insufficient cryptocurrency to pay the transaction fee + int32 public constant DUPLICATE_TRANSACTION = 11; // This transaction ID is a duplicate of one that was submitted to this node or reached consensus in the last 180 seconds (receipt period) + int32 public constant BUSY = 12; //If API is throttled out + int32 public constant NOT_SUPPORTED = 13; //The API is not currently supported + + int32 public constant INVALID_FILE_ID = 14; //The file id is invalid or does not exist + int32 public constant INVALID_ACCOUNT_ID = 15; //The account id is invalid or does not exist + int32 public constant INVALID_CONTRACT_ID = 16; //The contract id is invalid or does not exist + int32 public constant INVALID_TRANSACTION_ID = 17; //Transaction id is not valid + int32 public constant RECEIPT_NOT_FOUND = 18; //Receipt for given transaction id does not exist + int32 public constant RECORD_NOT_FOUND = 19; //Record for given transaction id does not exist + int32 public constant INVALID_SOLIDITY_ID = 20; //The solidity id is invalid or entity with this solidity id does not exist + + int32 public constant UNKNOWN = 21; // The responding node has submitted the transaction to the network. Its final status is still unknown. + int32 public constant SUCCESS = 22; // The transaction succeeded + int32 public constant FAIL_INVALID = 23; // There was a system error and the transaction failed because of invalid request parameters. + int32 public constant FAIL_FEE = 24; // There was a system error while performing fee calculation, reserved for future. + int32 public constant FAIL_BALANCE = 25; // There was a system error while performing balance checks, reserved for future. + + int32 public constant KEY_REQUIRED = 26; //Key not provided in the transaction body + int32 public constant BAD_ENCODING = 27; //Unsupported algorithm/encoding used for keys in the transaction + int32 public constant INSUFFICIENT_ACCOUNT_BALANCE = 28; //When the account balance is not sufficient for the transfer + int32 public constant INVALID_SOLIDITY_ADDRESS = 29; //During an update transaction when the system is not able to find the Users Solidity address + + int32 public constant INSUFFICIENT_GAS = 30; //Not enough gas was supplied to execute transaction + int32 public constant CONTRACT_SIZE_LIMIT_EXCEEDED = 31; //contract byte code size is over the limit + int32 public constant LOCAL_CALL_MODIFICATION_EXCEPTION = 32; //local execution (query) is requested for a function which changes state + int32 public constant CONTRACT_REVERT_EXECUTED = 33; //Contract REVERT OPCODE executed + int32 public constant CONTRACT_EXECUTION_EXCEPTION = 34; //For any contract execution related error not handled by specific error codes listed above. + int32 public constant INVALID_RECEIVING_NODE_ACCOUNT = 35; //In Query validation, account with +ve(amount) value should be Receiving node account, the receiver account should be only one account in the list + int32 public constant MISSING_QUERY_HEADER = 36; // Header is missing in Query request + + int32 public constant ACCOUNT_UPDATE_FAILED = 37; // The update of the account failed + int32 public constant INVALID_KEY_ENCODING = 38; // Provided key encoding was not supported by the system + int32 public constant NULL_SOLIDITY_ADDRESS = 39; // null solidity address + + int32 public constant CONTRACT_UPDATE_FAILED = 40; // update of the contract failed + int32 public constant INVALID_QUERY_HEADER = 41; // the query header is invalid + + int32 public constant INVALID_FEE_SUBMITTED = 42; // Invalid fee submitted + int32 public constant INVALID_PAYER_SIGNATURE = 43; // Payer signature is invalid + + int32 public constant KEY_NOT_PROVIDED = 44; // The keys were not provided in the request. + int32 public constant INVALID_EXPIRATION_TIME = 45; // Expiration time provided in the transaction was invalid. + int32 public constant NO_WACL_KEY = 46; //WriteAccess Control Keys are not provided for the file + int32 public constant FILE_CONTENT_EMPTY = 47; //The contents of file are provided as empty. + int32 public constant INVALID_ACCOUNT_AMOUNTS = 48; // The crypto transfer credit and debit do not sum equal to 0 + int32 public constant EMPTY_TRANSACTION_BODY = 49; // Transaction body provided is empty + int32 public constant INVALID_TRANSACTION_BODY = 50; // Invalid transaction body provided + + int32 public constant INVALID_SIGNATURE_TYPE_MISMATCHING_KEY = 51; // the type of key (base ed25519 key, KeyList, or ThresholdKey) does not match the type of signature (base ed25519 signature, SignatureList, or ThresholdKeySignature) + int32 public constant INVALID_SIGNATURE_COUNT_MISMATCHING_KEY = 52; // the number of key (KeyList, or ThresholdKey) does not match that of signature (SignatureList, or ThresholdKeySignature). e.g. if a keyList has 3 base keys, then the corresponding signatureList should also have 3 base signatures. + + int32 public constant EMPTY_LIVE_HASH_BODY = 53; // the livehash body is empty + int32 public constant EMPTY_LIVE_HASH = 54; // the livehash data is missing + int32 public constant EMPTY_LIVE_HASH_KEYS = 55; // the keys for a livehash are missing + int32 public constant INVALID_LIVE_HASH_SIZE = 56; // the livehash data is not the output of a SHA-384 digest + + int32 public constant EMPTY_QUERY_BODY = 57; // the query body is empty + int32 public constant EMPTY_LIVE_HASH_QUERY = 58; // the crypto livehash query is empty + int32 public constant LIVE_HASH_NOT_FOUND = 59; // the livehash is not present + int32 public constant ACCOUNT_ID_DOES_NOT_EXIST = 60; // the account id passed has not yet been created. + int32 public constant LIVE_HASH_ALREADY_EXISTS = 61; // the livehash already exists for a given account + + int32 public constant INVALID_FILE_WACL = 62; // File WACL keys are invalid + int32 public constant SERIALIZATION_FAILED = 63; // Serialization failure + int32 public constant TRANSACTION_OVERSIZE = 64; // The size of the Transaction is greater than transactionMaxBytes + int32 public constant TRANSACTION_TOO_MANY_LAYERS = 65; // The Transaction has more than 50 levels + int32 public constant CONTRACT_DELETED = 66; //Contract is marked as deleted + + int32 public constant PLATFORM_NOT_ACTIVE = 67; // the platform node is either disconnected or lagging behind. + int32 public constant KEY_PREFIX_MISMATCH = 68; // one public key matches more than one prefixes on the signature map + int32 public constant PLATFORM_TRANSACTION_NOT_CREATED = 69; // transaction not created by platform due to large backlog + int32 public constant INVALID_RENEWAL_PERIOD = 70; // auto renewal period is not a positive number of seconds + int32 public constant INVALID_PAYER_ACCOUNT_ID = 71; // the response code when a smart contract id is passed for a crypto API request + int32 public constant ACCOUNT_DELETED = 72; // the account has been marked as deleted + int32 public constant FILE_DELETED = 73; // the file has been marked as deleted + int32 public constant ACCOUNT_REPEATED_IN_ACCOUNT_AMOUNTS = 74; // same accounts repeated in the transfer account list + int32 public constant SETTING_NEGATIVE_ACCOUNT_BALANCE = 75; // attempting to set negative balance value for crypto account + int32 public constant OBTAINER_REQUIRED = 76; // when deleting smart contract that has crypto balance either transfer account or transfer smart contract is required + int32 public constant OBTAINER_SAME_CONTRACT_ID = 77; //when deleting smart contract that has crypto balance you can not use the same contract id as transferContractId as the one being deleted + int32 public constant OBTAINER_DOES_NOT_EXIST = 78; //transferAccountId or transferContractId specified for contract delete does not exist + int32 public constant MODIFYING_IMMUTABLE_CONTRACT = 79; //attempting to modify (update or delete a immutable smart contract, i.e. one created without a admin key) + int32 public constant FILE_SYSTEM_EXCEPTION = 80; //Unexpected exception thrown by file system functions + int32 public constant AUTORENEW_DURATION_NOT_IN_RANGE = 81; // the duration is not a subset of [MINIMUM_AUTORENEW_DURATION,MAXIMUM_AUTORENEW_DURATION] + int32 public constant ERROR_DECODING_BYTESTRING = 82; // Decoding the smart contract binary to a byte array failed. Check that the input is a valid hex string. + int32 public constant CONTRACT_FILE_EMPTY = 83; // File to create a smart contract was of length zero + int32 public constant CONTRACT_BYTECODE_EMPTY = 84; // Bytecode for smart contract is of length zero + int32 public constant INVALID_INITIAL_BALANCE = 85; // Attempt to set negative initial balance + int32 public constant INVALID_RECEIVE_RECORD_THRESHOLD = 86; // [Deprecated]. attempt to set negative receive record threshold + int32 public constant INVALID_SEND_RECORD_THRESHOLD = 87; // [Deprecated]. attempt to set negative send record threshold + int32 public constant ACCOUNT_IS_NOT_GENESIS_ACCOUNT = 88; // Special Account Operations should be performed by only Genesis account, return this code if it is not Genesis Account + int32 public constant PAYER_ACCOUNT_UNAUTHORIZED = 89; // The fee payer account doesn't have permission to submit such Transaction + int32 public constant INVALID_FREEZE_TRANSACTION_BODY = 90; // FreezeTransactionBody is invalid + int32 public constant FREEZE_TRANSACTION_BODY_NOT_FOUND = 91; // FreezeTransactionBody does not exist + int32 public constant TRANSFER_LIST_SIZE_LIMIT_EXCEEDED = 92; //Exceeded the number of accounts (both from and to) allowed for crypto transfer list + int32 public constant RESULT_SIZE_LIMIT_EXCEEDED = 93; // Smart contract result size greater than specified maxResultSize + int32 public constant NOT_SPECIAL_ACCOUNT = 94; //The payer account is not a special account(account 0.0.55) + int32 public constant CONTRACT_NEGATIVE_GAS = 95; // Negative gas was offered in smart contract call + int32 public constant CONTRACT_NEGATIVE_VALUE = 96; // Negative value / initial balance was specified in a smart contract call / create + int32 public constant INVALID_FEE_FILE = 97; // Failed to update fee file + int32 public constant INVALID_EXCHANGE_RATE_FILE = 98; // Failed to update exchange rate file + int32 public constant INSUFFICIENT_LOCAL_CALL_GAS = 99; // Payment tendered for contract local call cannot cover both the fee and the gas + int32 public constant ENTITY_NOT_ALLOWED_TO_DELETE = 100; // Entities with Entity ID below 1000 are not allowed to be deleted + int32 public constant AUTHORIZATION_FAILED = 101; // Violating one of these rules: 1) treasury account can update all entities below 0.0.1000, 2) account 0.0.50 can update all entities from 0.0.51 - 0.0.80, 3) Network Function Master Account A/c 0.0.50 - Update all Network Function accounts & perform all the Network Functions listed below, 4) Network Function Accounts: i) A/c 0.0.55 - Update Address Book files (0.0.101/102), ii) A/c 0.0.56 - Update Fee schedule (0.0.111), iii) A/c 0.0.57 - Update Exchange Rate (0.0.112). + int32 public constant FILE_UPLOADED_PROTO_INVALID = 102; // Fee Schedule Proto uploaded but not valid (append or update is required) + int32 public constant FILE_UPLOADED_PROTO_NOT_SAVED_TO_DISK = 103; // Fee Schedule Proto uploaded but not valid (append or update is required) + int32 public constant FEE_SCHEDULE_FILE_PART_UPLOADED = 104; // Fee Schedule Proto File Part uploaded + int32 public constant EXCHANGE_RATE_CHANGE_LIMIT_EXCEEDED = 105; // The change on Exchange Rate exceeds Exchange_Rate_Allowed_Percentage + int32 public constant MAX_CONTRACT_STORAGE_EXCEEDED = 106; // Contract permanent storage exceeded the currently allowable limit + int32 public constant TRANSFER_ACCOUNT_SAME_AS_DELETE_ACCOUNT = 107; // Transfer Account should not be same as Account to be deleted + int32 public constant TOTAL_LEDGER_BALANCE_INVALID = 108; + int32 public constant EXPIRATION_REDUCTION_NOT_ALLOWED = 110; // The expiration date/time on a smart contract may not be reduced + int32 public constant MAX_GAS_LIMIT_EXCEEDED = 111; //Gas exceeded currently allowable gas limit per transaction + int32 public constant MAX_FILE_SIZE_EXCEEDED = 112; // File size exceeded the currently allowable limit + + int32 public constant INVALID_TOPIC_ID = 150; // The Topic ID specified is not in the system. + int32 public constant INVALID_ADMIN_KEY = 155; // A provided admin key was invalid. + int32 public constant INVALID_SUBMIT_KEY = 156; // A provided submit key was invalid. + int32 public constant UNAUTHORIZED = 157; // An attempted operation was not authorized (ie - a deleteTopic for a topic with no adminKey). + int32 public constant INVALID_TOPIC_MESSAGE = 158; // A ConsensusService message is empty. + int32 public constant INVALID_AUTORENEW_ACCOUNT = 159; // The autoRenewAccount specified is not a valid, active account. + int32 public constant AUTORENEW_ACCOUNT_NOT_ALLOWED = 160; // An adminKey was not specified on the topic, so there must not be an autoRenewAccount. + // The topic has expired, was not automatically renewed, and is in a 7 day grace period before the topic will be + // deleted unrecoverably. This error response code will not be returned until autoRenew functionality is supported + // by HAPI. + int32 public constant TOPIC_EXPIRED = 162; + int32 public constant INVALID_CHUNK_NUMBER = 163; // chunk number must be from 1 to total (chunks) inclusive. + int32 public constant INVALID_CHUNK_TRANSACTION_ID = 164; // For every chunk, the payer account that is part of initialTransactionID must match the Payer Account of this transaction. The entire initialTransactionID should match the transactionID of the first chunk, but this is not checked or enforced by Hedera except when the chunk number is 1. + int32 public constant ACCOUNT_FROZEN_FOR_TOKEN = 165; // Account is frozen and cannot transact with the token + int32 public constant TOKENS_PER_ACCOUNT_LIMIT_EXCEEDED = 166; // An involved account already has more than tokens.maxPerAccount associations with non-deleted tokens. + int32 public constant INVALID_TOKEN_ID = 167; // The token is invalid or does not exist + int32 public constant INVALID_TOKEN_DECIMALS = 168; // Invalid token decimals + int32 public constant INVALID_TOKEN_INITIAL_SUPPLY = 169; // Invalid token initial supply + int32 public constant INVALID_TREASURY_ACCOUNT_FOR_TOKEN = 170; // Treasury Account does not exist or is deleted + int32 public constant INVALID_TOKEN_SYMBOL = 171; // Token Symbol is not UTF-8 capitalized alphabetical string + int32 public constant TOKEN_HAS_NO_FREEZE_KEY = 172; // Freeze key is not set on token + int32 public constant TRANSFERS_NOT_ZERO_SUM_FOR_TOKEN = 173; // Amounts in transfer list are not net zero + int32 public constant MISSING_TOKEN_SYMBOL = 174; // A token symbol was not provided + int32 public constant TOKEN_SYMBOL_TOO_LONG = 175; // The provided token symbol was too long + int32 public constant ACCOUNT_KYC_NOT_GRANTED_FOR_TOKEN = 176; // KYC must be granted and account does not have KYC granted + int32 public constant TOKEN_HAS_NO_KYC_KEY = 177; // KYC key is not set on token + int32 public constant INSUFFICIENT_TOKEN_BALANCE = 178; // Token balance is not sufficient for the transaction + int32 public constant TOKEN_WAS_DELETED = 179; // Token transactions cannot be executed on deleted token + int32 public constant TOKEN_HAS_NO_SUPPLY_KEY = 180; // Supply key is not set on token + int32 public constant TOKEN_HAS_NO_WIPE_KEY = 181; // Wipe key is not set on token + int32 public constant INVALID_TOKEN_MINT_AMOUNT = 182; // The requested token mint amount would cause an invalid total supply + int32 public constant INVALID_TOKEN_BURN_AMOUNT = 183; // The requested token burn amount would cause an invalid total supply + int32 public constant TOKEN_NOT_ASSOCIATED_TO_ACCOUNT = 184; // A required token-account relationship is missing + int32 public constant CANNOT_WIPE_TOKEN_TREASURY_ACCOUNT = 185; // The target of a wipe operation was the token treasury account + int32 public constant INVALID_KYC_KEY = 186; // The provided KYC key was invalid. + int32 public constant INVALID_WIPE_KEY = 187; // The provided wipe key was invalid. + int32 public constant INVALID_FREEZE_KEY = 188; // The provided freeze key was invalid. + int32 public constant INVALID_SUPPLY_KEY = 189; // The provided supply key was invalid. + int32 public constant MISSING_TOKEN_NAME = 190; // Token Name is not provided + int32 public constant TOKEN_NAME_TOO_LONG = 191; // Token Name is too long + int32 public constant INVALID_WIPING_AMOUNT = 192; // The provided wipe amount must not be negative, zero or bigger than the token holder balance + int32 public constant TOKEN_IS_IMMUTABLE = 193; // Token does not have Admin key set, thus update/delete transactions cannot be performed + int32 public constant TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT = 194; // An associateToken operation specified a token already associated to the account + int32 public constant TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES = 195; // An attempted operation is invalid until all token balances for the target account are zero + int32 public constant ACCOUNT_IS_TREASURY = 196; // An attempted operation is invalid because the account is a treasury + int32 public constant TOKEN_ID_REPEATED_IN_TOKEN_LIST = 197; // Same TokenIDs present in the token list + int32 public constant TOKEN_TRANSFER_LIST_SIZE_LIMIT_EXCEEDED = 198; // Exceeded the number of token transfers (both from and to) allowed for token transfer list + int32 public constant EMPTY_TOKEN_TRANSFER_BODY = 199; // TokenTransfersTransactionBody has no TokenTransferList + int32 public constant EMPTY_TOKEN_TRANSFER_ACCOUNT_AMOUNTS = 200; // TokenTransfersTransactionBody has a TokenTransferList with no AccountAmounts + + int32 public constant INVALID_SCHEDULE_ID = 201; // The Scheduled entity does not exist; or has now expired, been deleted, or been executed + int32 public constant SCHEDULE_IS_IMMUTABLE = 202; // The Scheduled entity cannot be modified. Admin key not set + int32 public constant INVALID_SCHEDULE_PAYER_ID = 203; // The provided Scheduled Payer does not exist + int32 public constant INVALID_SCHEDULE_ACCOUNT_ID = 204; // The Schedule Create Transaction TransactionID account does not exist + int32 public constant NO_NEW_VALID_SIGNATURES = 205; // The provided sig map did not contain any new valid signatures from required signers of the scheduled transaction + int32 public constant UNRESOLVABLE_REQUIRED_SIGNERS = 206; // The required signers for a scheduled transaction cannot be resolved, for example because they do not exist or have been deleted + int32 public constant SCHEDULED_TRANSACTION_NOT_IN_WHITELIST = 207; // Only whitelisted transaction types may be scheduled + int32 public constant SOME_SIGNATURES_WERE_INVALID = 208; // At least one of the signatures in the provided sig map did not represent a valid signature for any required signer + int32 public constant TRANSACTION_ID_FIELD_NOT_ALLOWED = 209; // The scheduled field in the TransactionID may not be set to true + int32 public constant IDENTICAL_SCHEDULE_ALREADY_CREATED = 210; // A schedule already exists with the same identifying fields of an attempted ScheduleCreate (that is, all fields other than scheduledPayerAccountID) + int32 public constant INVALID_ZERO_BYTE_IN_STRING = 211; // A string field in the transaction has a UTF-8 encoding with the prohibited zero byte + int32 public constant SCHEDULE_ALREADY_DELETED = 212; // A schedule being signed or deleted has already been deleted + int32 public constant SCHEDULE_ALREADY_EXECUTED = 213; // A schedule being signed or deleted has already been executed + int32 public constant MESSAGE_SIZE_TOO_LARGE = 214; // ConsensusSubmitMessage request's message size is larger than allowed. +} diff --git a/hedera-node/test-clients/src/main/resources/contract/contracts/HRC755Contract/HederaScheduleService.sol b/hedera-node/test-clients/src/main/resources/contract/contracts/HRC755Contract/HederaScheduleService.sol new file mode 100644 index 000000000000..0ec05f63c7e7 --- /dev/null +++ b/hedera-node/test-clients/src/main/resources/contract/contracts/HRC755Contract/HederaScheduleService.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.5.0 <0.9.0; +pragma experimental ABIEncoderV2; + +import "./HederaResponseCodes.sol"; +import "./IHederaScheduleService.sol"; + +abstract contract HederaScheduleService { + address constant precompileAddress = address(0x16b); + + /// Authorizes the calling contract as a signer to the schedule transaction. + /// @param schedule the address of the schedule transaction. + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + function authorizeSchedule(address schedule) internal returns (int64 responseCode) { + (bool success, bytes memory result) = precompileAddress.call( + abi.encodeWithSelector(IHederaScheduleService.authorizeSchedule.selector, schedule)); + responseCode = success ? abi.decode(result, (int64)) : HederaResponseCodes.UNKNOWN; + } +} diff --git a/hedera-node/test-clients/src/main/resources/contract/contracts/HRC755Contract/IHederaScheduleService.sol b/hedera-node/test-clients/src/main/resources/contract/contracts/HRC755Contract/IHederaScheduleService.sol new file mode 100644 index 000000000000..6deb763b7a02 --- /dev/null +++ b/hedera-node/test-clients/src/main/resources/contract/contracts/HRC755Contract/IHederaScheduleService.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.4.9 <0.9.0; +pragma experimental ABIEncoderV2; + +interface IHederaScheduleService { + + /// Authorizes the calling contract as a signer to the schedule transaction. + /// @param schedule the address of the schedule transaction. + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + function authorizeSchedule(address schedule) external returns (int64 responseCode); +} diff --git a/hedera-node/test-clients/src/main/resources/contract/contracts/IHRC755/IHRC755.bin b/hedera-node/test-clients/src/main/resources/contract/contracts/IHRC755/IHRC755.bin new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/hedera-node/test-clients/src/main/resources/contract/contracts/IHRC755/IHRC755.json b/hedera-node/test-clients/src/main/resources/contract/contracts/IHRC755/IHRC755.json new file mode 100644 index 000000000000..f37ea448ea91 --- /dev/null +++ b/hedera-node/test-clients/src/main/resources/contract/contracts/IHRC755/IHRC755.json @@ -0,0 +1 @@ +[{"inputs":[],"name":"signSchedule","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resources/contract/contracts/IHRC755/IHRC755.sol b/hedera-node/test-clients/src/main/resources/contract/contracts/IHRC755/IHRC755.sol new file mode 100644 index 000000000000..be32ffb890b3 --- /dev/null +++ b/hedera-node/test-clients/src/main/resources/contract/contracts/IHRC755/IHRC755.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.4.9 <0.9.0; +pragma experimental ABIEncoderV2; + +interface IHRC755 { + // Sign the addressed schedule transaction with the keys of the calling EOA. + function signSchedule() external returns (int64 responseCode); +}