Skip to content

Commit 29d212b

Browse files
authored
fix: Add additional verification strategy for schedule delete facade calls (#22461)
Signed-off-by: Stanimir Stoyanov <stanimir.stoyanov@limechain.tech>
1 parent 429906c commit 29d212b

File tree

5 files changed

+164
-5
lines changed

5 files changed

+164
-5
lines changed

hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hss/DispatchForResponseCodeHssCall.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,31 @@ public DispatchForResponseCodeHssCall(
7171
e -> ReturnTypes.encodedRc(e.status()));
7272
}
7373

74+
/**
75+
* Convenience overload that slightly eases construction for the common case that would also need a custom verification strategy.
76+
*
77+
* @param attempt the attempt to translate to a dispatching
78+
* @param syntheticBody the synthetic body to dispatch
79+
* @param verificationStrategy the verification strategy to use
80+
* @param dispatchGasCalculator the dispatch gas calculator to use
81+
*/
82+
public DispatchForResponseCodeHssCall(
83+
@NonNull final HssCallAttempt attempt,
84+
@Nullable final TransactionBody syntheticBody,
85+
@NonNull final VerificationStrategy verificationStrategy,
86+
@NonNull final DispatchGasCalculator dispatchGasCalculator,
87+
@NonNull final Set<Key> authorizingKeys) {
88+
this(
89+
attempt.enhancement(),
90+
attempt.systemContractGasCalculator(),
91+
attempt.addressIdConverter().convertSender(attempt.senderAddress()),
92+
syntheticBody,
93+
verificationStrategy,
94+
dispatchGasCalculator,
95+
authorizingKeys,
96+
e -> ReturnTypes.encodedRc(e.status()));
97+
}
98+
7499
/**
75100
* More general constructor, for cases where perhaps a custom {@link VerificationStrategy} is needed.
76101
*

hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hss/deleteschedule/DeleteScheduleTranslator.java

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@
66
import com.esaulpaugh.headlong.abi.Address;
77
import com.google.common.annotations.VisibleForTesting;
88
import com.hedera.hapi.node.base.AccountID;
9+
import com.hedera.hapi.node.base.Key;
910
import com.hedera.hapi.node.base.ScheduleID;
1011
import com.hedera.hapi.node.scheduled.ScheduleDeleteTransactionBody;
1112
import com.hedera.hapi.node.transaction.TransactionBody;
1213
import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType;
1314
import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator;
1415
import com.hedera.node.app.service.contract.impl.exec.metrics.ContractMetrics;
16+
import com.hedera.node.app.service.contract.impl.exec.scope.EitherOrVerificationStrategy;
17+
import com.hedera.node.app.service.contract.impl.exec.scope.SpecificCryptoVerificationStrategy;
1518
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator;
1619
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call;
1720
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hss.DispatchForResponseCodeHssCall;
@@ -66,11 +69,22 @@ public Optional<SystemContractMethod> identifyMethod(@NonNull final HssCallAttem
6669

6770
@Override
6871
public Call callFrom(@NonNull final HssCallAttempt attempt) {
69-
return new DispatchForResponseCodeHssCall(
70-
attempt,
71-
transactionBodyFor(scheduleIdFor(attempt)),
72-
DeleteScheduleTranslator::gasRequirement,
73-
attempt.keySetFor());
72+
if (isRedirectWithNonContractAdminKey(attempt)) {
73+
// Use a custom verification strategy that checks the admin key of the redirected schedule
74+
// if the call is via the proxy and the schedule admin key is not a contract key
75+
return new DispatchForResponseCodeHssCall(
76+
attempt,
77+
transactionBodyFor(scheduleIdFor(attempt)),
78+
getCustomVerificationStrat(attempt),
79+
DeleteScheduleTranslator::gasRequirement,
80+
attempt.keySetFor());
81+
} else {
82+
return new DispatchForResponseCodeHssCall(
83+
attempt,
84+
transactionBodyFor(scheduleIdFor(attempt)),
85+
DeleteScheduleTranslator::gasRequirement,
86+
attempt.keySetFor());
87+
}
7488
}
7589

7690
/**
@@ -129,4 +143,27 @@ public static long gasRequirement(
129143
@NonNull final AccountID payerId) {
130144
return systemContractGasCalculator.gasRequirement(body, DispatchType.SCHEDULE_DELETE, payerId);
131145
}
146+
147+
private static boolean isRedirectWithNonContractAdminKey(@NonNull HssCallAttempt attempt) {
148+
return attempt.isSelector(DELETE_SCHEDULE_PROXY)
149+
&& attempt.isRedirect()
150+
&& attempt.redirectScheduleTxn() != null
151+
&& attempt.redirectScheduleTxn().adminKey() != null
152+
&& !attempt.redirectScheduleTxn().adminKey().hasContractID();
153+
}
154+
155+
/**
156+
* Gets a custom verification strategy that uses either the default contract verification strategy or a specific crypto
157+
* verification strategy based on the admin key of the redirected schedule transaction.
158+
*
159+
* @param attempt the HSS call attempt
160+
* @return the custom verification strategy
161+
*/
162+
@NonNull
163+
private static EitherOrVerificationStrategy getCustomVerificationStrat(@NonNull HssCallAttempt attempt) {
164+
return new EitherOrVerificationStrategy(
165+
attempt.defaultVerificationStrategy(),
166+
new SpecificCryptoVerificationStrategy(
167+
attempt.redirectScheduleTxn().adminKeyOrElse(Key.DEFAULT)));
168+
}
132169
}

hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/DispatchForResponseCodeHssCallTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ class DispatchForResponseCodeHssCallTest extends CallAttemptTestBase {
6464
private DispatchForResponseCodeHssCall subject;
6565
private DispatchForResponseCodeHssCall subjectScheduleCreateResultEncoder;
6666
private DispatchForResponseCodeHssCall subjectFromAttempt;
67+
private DispatchForResponseCodeHssCall subjectWithCustomVerification;
6768

6869
@BeforeEach
6970
void setUp() {
@@ -99,6 +100,8 @@ void setUp() {
99100
.willReturn(verificationStrategy);
100101
subjectFromAttempt =
101102
new DispatchForResponseCodeHssCall(attempt, TransactionBody.DEFAULT, dispatchGasCalculator, emptySet());
103+
subjectWithCustomVerification = new DispatchForResponseCodeHssCall(
104+
attempt, TransactionBody.DEFAULT, verificationStrategy, dispatchGasCalculator, emptySet());
102105
}
103106

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

146+
@Test
147+
void successResultFromAttemptWithCustomVerification() {
148+
assertArrayEquals(ReturnTypes.encodedRc(SUCCESS).array(), successResult(subjectWithCustomVerification));
149+
}
150+
143151
@Test
144152
void haltsImmediatelyWithNullDispatch() {
145153
given(frame.getMessageFrameStack()).willReturn(stack);
@@ -202,4 +210,10 @@ void failureResultFromScheduleCreateResultEncoder() {
202210
void failureResultFromAttemptConstructor() {
203211
assertArrayEquals(ReturnTypes.encodedRc(INVALID_SCHEDULE_ID).array(), failureResult(subjectFromAttempt));
204212
}
213+
214+
@Test
215+
void failureResultFromAttemptWithCustomVerification() {
216+
assertArrayEquals(
217+
ReturnTypes.encodedRc(INVALID_SCHEDULE_ID).array(), failureResult(subjectWithCustomVerification));
218+
}
205219
}

hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/deleteschedule/DeleteScheduleTranslatorTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import com.esaulpaugh.headlong.abi.Tuple;
1313
import com.hedera.hapi.node.base.AccountID;
14+
import com.hedera.hapi.node.base.Key;
1415
import com.hedera.hapi.node.base.ScheduleID;
1516
import com.hedera.hapi.node.state.schedule.Schedule;
1617
import com.hedera.hapi.node.transaction.TransactionBody;
@@ -63,6 +64,9 @@ class DeleteScheduleTranslatorTest extends CallAttemptTestBase {
6364
@Mock
6465
private ScheduleID scheduleID;
6566

67+
@Mock
68+
private Key adminKey;
69+
6670
private DeleteScheduleTranslator subject;
6771

6872
@BeforeEach
@@ -188,4 +192,26 @@ void testGasRequirement() {
188192
// then:
189193
assertEquals(expectedGas, gas);
190194
}
195+
196+
@Test
197+
void testScheduleDeleteProxyWithNonContractAdminKey() {
198+
given(nativeOperations.getAccount(payerId)).willReturn(B_CONTRACT);
199+
given(addressIdConverter.convertSender(OWNER_BESU_ADDRESS)).willReturn(payerId);
200+
given(verificationStrategies.activatingOnlyContractKeysFor(OWNER_BESU_ADDRESS, false, nativeOperations))
201+
.willReturn(verificationStrategy);
202+
given(nativeOperations.entityIdFactory()).willReturn(entityIdFactory);
203+
given(nativeOperations.configuration()).willReturn(HederaTestConfigBuilder.createConfig());
204+
given(nativeOperations.getSchedule(any(ScheduleID.class))).willReturn(schedule);
205+
given(schedule.adminKeyOrElse(Key.DEFAULT)).willReturn(adminKey);
206+
given(schedule.adminKey()).willReturn(adminKey);
207+
given(adminKey.hasContractID()).willReturn(false);
208+
given(adminKey.hasEcdsaSecp256k1()).willReturn(true);
209+
// when:
210+
var input = bytesForRedirectScheduleTxn(
211+
DeleteScheduleTranslator.DELETE_SCHEDULE_PROXY.selector(), NON_SYSTEM_LONG_ZERO_ADDRESS);
212+
attempt = createHssCallAttempt(input, false, configuration, List.of(subject));
213+
final var call = subject.callFrom(attempt);
214+
// then:
215+
assertThat(call).isInstanceOf(DispatchForResponseCodeHssCall.class);
216+
}
191217
}

hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/schedule/ScheduleDeleteTest.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,30 @@
55
import static com.hedera.services.bdd.junit.TestTags.SMART_CONTRACT;
66
import static com.hedera.services.bdd.spec.HapiSpec.hapiTest;
77
import static com.hedera.services.bdd.spec.queries.QueryVerbs.getScheduleInfo;
8+
import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord;
9+
import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate;
10+
import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer;
11+
import static com.hedera.services.bdd.spec.transactions.TxnVerbs.ethereumCallWithFunctionAbi;
12+
import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleCreate;
13+
import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromAccountToAlias;
814
import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor;
15+
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.exposeSpecSecondTo;
16+
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed;
17+
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing;
18+
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext;
19+
import static com.hedera.services.bdd.suites.HapiSuite.GENESIS;
20+
import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS;
21+
import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS;
22+
import static com.hedera.services.bdd.suites.HapiSuite.RELAYER;
23+
import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE;
24+
import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SOURCE_KEY;
25+
import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION;
926
import static com.hedera.services.bdd.suites.contract.Utils.asScheduleId;
27+
import static com.hedera.services.bdd.suites.contract.Utils.getABIFor;
28+
import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.RECEIVER;
1029

1130
import com.esaulpaugh.headlong.abi.Address;
31+
import com.hedera.services.bdd.junit.HapiTest;
1232
import com.hedera.services.bdd.junit.HapiTestLifecycle;
1333
import com.hedera.services.bdd.junit.LeakyHapiTest;
1434
import com.hedera.services.bdd.junit.support.TestLifecycle;
@@ -19,6 +39,7 @@
1939
import com.hedera.services.bdd.spec.utilops.UtilVerbs;
2040
import com.hedera.services.bdd.suites.HapiSuite;
2141
import com.hederahashgraph.api.proto.java.ResponseCodeEnum;
42+
import com.hederahashgraph.api.proto.java.ScheduleID;
2243
import edu.umd.cs.findbugs.annotations.NonNull;
2344
import java.math.BigInteger;
2445
import java.util.concurrent.atomic.AtomicInteger;
@@ -135,4 +156,40 @@ private Stream<DynamicTest> deleteScheduleTest(
135156
.isDeleted());
136157
}));
137158
}
159+
160+
@HapiTest
161+
final Stream<DynamicTest> tryScheduleDeleteViaFacade() {
162+
var deleteFacade = "deleteFacade";
163+
var lastSecond = new AtomicReference<Long>();
164+
var schedule = "scheduledTransfer";
165+
var scheduleId = new AtomicReference<ScheduleID>();
166+
return hapiTest(withOpContext((spec, opLog) -> {
167+
allRunFor(
168+
spec,
169+
exposeSpecSecondTo(lastSecond::set),
170+
newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE),
171+
cryptoCreate(RECEIVER).balance(0L),
172+
cryptoCreate(RELAYER).balance(6 * ONE_MILLION_HBARS),
173+
cryptoTransfer(tinyBarsFromAccountToAlias(GENESIS, SECP_256K1_SOURCE_KEY, ONE_HUNDRED_HBARS)),
174+
sourcing(() -> scheduleCreate(
175+
schedule, cryptoTransfer(tinyBarsFromAccountToAlias(RELAYER, RECEIVER, 10L)))
176+
.adminKey(SECP_256K1_SOURCE_KEY)
177+
.waitForExpiry(true)
178+
.hasKnownStatus(ResponseCodeEnum.SUCCESS)
179+
.expiringAt(lastSecond.get() + 60)
180+
.exposingCreatedIdTo(scheduleId::set)));
181+
allRunFor(
182+
spec,
183+
getScheduleInfo(schedule).isNotDeleted(),
184+
ethereumCallWithFunctionAbi(
185+
false,
186+
String.valueOf(scheduleId.get().getScheduleNum()),
187+
getABIFor(FUNCTION, "deleteSchedule", "IHRC1215ScheduleFacade"))
188+
.signingWith(SECP_256K1_SOURCE_KEY)
189+
.payingWith(RELAYER)
190+
.via(deleteFacade),
191+
getTxnRecord(deleteFacade).andAllChildRecords().logged(),
192+
getScheduleInfo(schedule).isDeleted());
193+
}));
194+
}
138195
}

0 commit comments

Comments
 (0)