Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,31 @@ public DispatchForResponseCodeHssCall(
e -> ReturnTypes.encodedRc(e.status()));
}

/**
* Convenience overload that slightly eases construction for the common case that would also need a custom verification strategy.
*
* @param attempt the attempt to translate to a dispatching
* @param syntheticBody the synthetic body to dispatch
* @param verificationStrategy the verification strategy to use
* @param dispatchGasCalculator the dispatch gas calculator to use
*/
public DispatchForResponseCodeHssCall(
@NonNull final HssCallAttempt attempt,
@Nullable final TransactionBody syntheticBody,
@NonNull final VerificationStrategy verificationStrategy,
@NonNull final DispatchGasCalculator dispatchGasCalculator,
@NonNull final Set<Key> authorizingKeys) {
this(
attempt.enhancement(),
attempt.systemContractGasCalculator(),
attempt.addressIdConverter().convertSender(attempt.senderAddress()),
syntheticBody,
verificationStrategy,
dispatchGasCalculator,
authorizingKeys,
e -> ReturnTypes.encodedRc(e.status()));
}

/**
* More general constructor, for cases where perhaps a custom {@link VerificationStrategy} is needed.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
import com.esaulpaugh.headlong.abi.Address;
import com.google.common.annotations.VisibleForTesting;
import com.hedera.hapi.node.base.AccountID;
import com.hedera.hapi.node.base.Key;
import com.hedera.hapi.node.base.ScheduleID;
import com.hedera.hapi.node.scheduled.ScheduleDeleteTransactionBody;
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.metrics.ContractMetrics;
import com.hedera.node.app.service.contract.impl.exec.scope.EitherOrVerificationStrategy;
import com.hedera.node.app.service.contract.impl.exec.scope.SpecificCryptoVerificationStrategy;
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;
Expand Down Expand Up @@ -66,11 +69,22 @@ public Optional<SystemContractMethod> identifyMethod(@NonNull final HssCallAttem

@Override
public Call callFrom(@NonNull final HssCallAttempt attempt) {
return new DispatchForResponseCodeHssCall(
attempt,
transactionBodyFor(scheduleIdFor(attempt)),
DeleteScheduleTranslator::gasRequirement,
attempt.keySetFor());
if (isRedirectWithNonContractAdminKey(attempt)) {
// Use a custom verification strategy that checks the admin key of the redirected schedule
// if the call is via the proxy and the schedule admin key is not a contract key
return new DispatchForResponseCodeHssCall(
attempt,
transactionBodyFor(scheduleIdFor(attempt)),
getCustomVerificationStrat(attempt),
DeleteScheduleTranslator::gasRequirement,
attempt.keySetFor());
} else {
return new DispatchForResponseCodeHssCall(
attempt,
transactionBodyFor(scheduleIdFor(attempt)),
DeleteScheduleTranslator::gasRequirement,
attempt.keySetFor());
}
}

/**
Expand Down Expand Up @@ -129,4 +143,27 @@ public static long gasRequirement(
@NonNull final AccountID payerId) {
return systemContractGasCalculator.gasRequirement(body, DispatchType.SCHEDULE_DELETE, payerId);
}

private static boolean isRedirectWithNonContractAdminKey(@NonNull HssCallAttempt attempt) {
return attempt.isSelector(DELETE_SCHEDULE_PROXY)
&& attempt.isRedirect()
&& attempt.redirectScheduleTxn() != null
&& attempt.redirectScheduleTxn().adminKey() != null
&& !attempt.redirectScheduleTxn().adminKey().hasContractID();
}

/**
* Gets a custom verification strategy that uses either the default contract verification strategy or a specific crypto
* verification strategy based on the admin key of the redirected schedule transaction.
*
* @param attempt the HSS call attempt
* @return the custom verification strategy
*/
@NonNull
private static EitherOrVerificationStrategy getCustomVerificationStrat(@NonNull HssCallAttempt attempt) {
return new EitherOrVerificationStrategy(
attempt.defaultVerificationStrategy(),
new SpecificCryptoVerificationStrategy(
attempt.redirectScheduleTxn().adminKeyOrElse(Key.DEFAULT)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class DispatchForResponseCodeHssCallTest extends CallAttemptTestBase {
private DispatchForResponseCodeHssCall subject;
private DispatchForResponseCodeHssCall subjectScheduleCreateResultEncoder;
private DispatchForResponseCodeHssCall subjectFromAttempt;
private DispatchForResponseCodeHssCall subjectWithCustomVerification;

@BeforeEach
void setUp() {
Expand Down Expand Up @@ -99,6 +100,8 @@ void setUp() {
.willReturn(verificationStrategy);
subjectFromAttempt =
new DispatchForResponseCodeHssCall(attempt, TransactionBody.DEFAULT, dispatchGasCalculator, emptySet());
subjectWithCustomVerification = new DispatchForResponseCodeHssCall(
attempt, TransactionBody.DEFAULT, verificationStrategy, dispatchGasCalculator, emptySet());
}

private byte[] successResult(final DispatchForResponseCodeHssCall call) {
Expand Down Expand Up @@ -140,6 +143,11 @@ void successResultFromAttemptConstructor() {
assertArrayEquals(ReturnTypes.encodedRc(SUCCESS).array(), successResult(subjectFromAttempt));
}

@Test
void successResultFromAttemptWithCustomVerification() {
assertArrayEquals(ReturnTypes.encodedRc(SUCCESS).array(), successResult(subjectWithCustomVerification));
}

@Test
void haltsImmediatelyWithNullDispatch() {
given(frame.getMessageFrameStack()).willReturn(stack);
Expand Down Expand Up @@ -202,4 +210,10 @@ void failureResultFromScheduleCreateResultEncoder() {
void failureResultFromAttemptConstructor() {
assertArrayEquals(ReturnTypes.encodedRc(INVALID_SCHEDULE_ID).array(), failureResult(subjectFromAttempt));
}

@Test
void failureResultFromAttemptWithCustomVerification() {
assertArrayEquals(
ReturnTypes.encodedRc(INVALID_SCHEDULE_ID).array(), failureResult(subjectWithCustomVerification));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import com.esaulpaugh.headlong.abi.Tuple;
import com.hedera.hapi.node.base.AccountID;
import com.hedera.hapi.node.base.Key;
import com.hedera.hapi.node.base.ScheduleID;
import com.hedera.hapi.node.state.schedule.Schedule;
import com.hedera.hapi.node.transaction.TransactionBody;
Expand Down Expand Up @@ -63,6 +64,9 @@ class DeleteScheduleTranslatorTest extends CallAttemptTestBase {
@Mock
private ScheduleID scheduleID;

@Mock
private Key adminKey;

private DeleteScheduleTranslator subject;

@BeforeEach
Expand Down Expand Up @@ -188,4 +192,26 @@ void testGasRequirement() {
// then:
assertEquals(expectedGas, gas);
}

@Test
void testScheduleDeleteProxyWithNonContractAdminKey() {
given(nativeOperations.getAccount(payerId)).willReturn(B_CONTRACT);
given(addressIdConverter.convertSender(OWNER_BESU_ADDRESS)).willReturn(payerId);
given(verificationStrategies.activatingOnlyContractKeysFor(OWNER_BESU_ADDRESS, false, nativeOperations))
.willReturn(verificationStrategy);
given(nativeOperations.entityIdFactory()).willReturn(entityIdFactory);
given(nativeOperations.configuration()).willReturn(HederaTestConfigBuilder.createConfig());
given(nativeOperations.getSchedule(any(ScheduleID.class))).willReturn(schedule);
given(schedule.adminKeyOrElse(Key.DEFAULT)).willReturn(adminKey);
given(schedule.adminKey()).willReturn(adminKey);
given(adminKey.hasContractID()).willReturn(false);
given(adminKey.hasEcdsaSecp256k1()).willReturn(true);
// when:
var input = bytesForRedirectScheduleTxn(
DeleteScheduleTranslator.DELETE_SCHEDULE_PROXY.selector(), NON_SYSTEM_LONG_ZERO_ADDRESS);
attempt = createHssCallAttempt(input, false, configuration, List.of(subject));
final var call = subject.callFrom(attempt);
// then:
assertThat(call).isInstanceOf(DispatchForResponseCodeHssCall.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,30 @@
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.queries.QueryVerbs.getTxnRecord;
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.ethereumCallWithFunctionAbi;
import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleCreate;
import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromAccountToAlias;
import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor;
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.exposeSpecSecondTo;
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed;
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing;
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext;
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.HapiSuite.ONE_MILLION_HBARS;
import static com.hedera.services.bdd.suites.HapiSuite.RELAYER;
import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE;
import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SOURCE_KEY;
import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION;
import static com.hedera.services.bdd.suites.contract.Utils.asScheduleId;
import static com.hedera.services.bdd.suites.contract.Utils.getABIFor;
import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.RECEIVER;

import com.esaulpaugh.headlong.abi.Address;
import com.hedera.services.bdd.junit.HapiTest;
import com.hedera.services.bdd.junit.HapiTestLifecycle;
import com.hedera.services.bdd.junit.LeakyHapiTest;
import com.hedera.services.bdd.junit.support.TestLifecycle;
Expand All @@ -19,6 +39,7 @@
import com.hedera.services.bdd.spec.utilops.UtilVerbs;
import com.hedera.services.bdd.suites.HapiSuite;
import com.hederahashgraph.api.proto.java.ResponseCodeEnum;
import com.hederahashgraph.api.proto.java.ScheduleID;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.math.BigInteger;
import java.util.concurrent.atomic.AtomicInteger;
Expand Down Expand Up @@ -135,4 +156,40 @@ private Stream<DynamicTest> deleteScheduleTest(
.isDeleted());
}));
}

@HapiTest
final Stream<DynamicTest> tryScheduleDeleteViaFacade() {
var deleteFacade = "deleteFacade";
var lastSecond = new AtomicReference<Long>();
var schedule = "scheduledTransfer";
var scheduleId = new AtomicReference<ScheduleID>();
return hapiTest(withOpContext((spec, opLog) -> {
allRunFor(
spec,
exposeSpecSecondTo(lastSecond::set),
newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE),
cryptoCreate(RECEIVER).balance(0L),
cryptoCreate(RELAYER).balance(6 * ONE_MILLION_HBARS),
cryptoTransfer(tinyBarsFromAccountToAlias(GENESIS, SECP_256K1_SOURCE_KEY, ONE_HUNDRED_HBARS)),
sourcing(() -> scheduleCreate(
schedule, cryptoTransfer(tinyBarsFromAccountToAlias(RELAYER, RECEIVER, 10L)))
.adminKey(SECP_256K1_SOURCE_KEY)
.waitForExpiry(true)
.hasKnownStatus(ResponseCodeEnum.SUCCESS)
.expiringAt(lastSecond.get() + 60)
.exposingCreatedIdTo(scheduleId::set)));
allRunFor(
spec,
getScheduleInfo(schedule).isNotDeleted(),
ethereumCallWithFunctionAbi(
false,
String.valueOf(scheduleId.get().getScheduleNum()),
getABIFor(FUNCTION, "deleteSchedule", "IHRC1215ScheduleFacade"))
.signingWith(SECP_256K1_SOURCE_KEY)
.payingWith(RELAYER)
.via(deleteFacade),
getTxnRecord(deleteFacade).andAllChildRecords().logged(),
getScheduleInfo(schedule).isDeleted());
}));
}
}
Loading