Skip to content

Commit

Permalink
chore: 15111 cherry pick (#15112)
Browse files Browse the repository at this point in the history
Signed-off-by: lukelee-sl <[email protected]>
  • Loading branch information
lukelee-sl authored Aug 26, 2024
1 parent 414b518 commit 9436208
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,13 @@ public void pureChecks(@NonNull TransactionBody txn) throws PreCheckException {
}

private boolean isAdminSigRequired(final ContractUpdateTransactionBody op) {
return !op.hasExpirationTime()
|| hasCryptoAdminKey(op)
|| op.hasProxyAccountID()
|| op.hasAutoRenewPeriod()
|| op.hasFileID()
|| !op.memoOrElse("").isEmpty();
// Consider the update attempt with both expiration time and id reset to default fields
final var withDefaultExpirationTimeAndTarget = op.copyBuilder()
.contractID(ContractUpdateTransactionBody.DEFAULT.contractID())
.expirationTime(ContractUpdateTransactionBody.DEFAULT.expirationTime())
.build();
// If anything else was touched, then admin sig is required
return !withDefaultExpirationTimeAndTarget.equals(ContractUpdateTransactionBody.DEFAULT);
}

private boolean hasCryptoAdminKey(final ContractUpdateTransactionBody op) {
Expand Down Expand Up @@ -168,7 +169,7 @@ private void validateSemantics(
}
}

validateFalse(!onlyAffectsExpiry(op) && !isMutable(contract), MODIFYING_IMMUTABLE_CONTRACT);
validateFalse(nonExpiryFieldUpdated(op) && !isMutable(contract), MODIFYING_IMMUTABLE_CONTRACT);
validateFalse(reducesExpiry(op, contract.expirationSecond()), EXPIRATION_REDUCTION_NOT_ALLOWED);

if (op.hasMaxAutomaticTokenAssociations()) {
Expand Down Expand Up @@ -231,17 +232,12 @@ private boolean keyIfAcceptable(Key candidate) {
return keyIsNotValid || candidate.contractID() != null;
}

private boolean onlyAffectsExpiry(ContractUpdateTransactionBody op) {
return !(op.hasProxyAccountID()
|| op.hasFileID()
|| affectsMemo(op)
|| op.hasAutoRenewPeriod()
|| op.hasAdminKey())
|| op.hasMaxAutomaticTokenAssociations();
private boolean nonExpiryFieldUpdated(ContractUpdateTransactionBody op) {
return isAdminSigRequired(op);
}

private boolean affectsMemo(@NonNull final ContractUpdateTransactionBody op) {
return op.hasMemoWrapper() || (op.hasMemo() && !op.memoOrThrow().isEmpty());
return op.hasMemoWrapper() || (!op.memoOrElse("").isEmpty());
}

private boolean isMutable(final Account contract) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,43 @@ void invalidAutoRenewAccountIdFails() throws PreCheckException {
assertThrowsPreCheck(() -> subject.preHandle(context), INVALID_AUTORENEW_ACCOUNT);
}

@Test
void callsKey2xIfAdminKeyRequired() throws PreCheckException {
when(payerAccount.keyOrThrow()).thenReturn(AN_ED25519_KEY);
when(accountStore.getContractById(targetContract)).thenReturn(payerAccount);

final var txn = TransactionBody.newBuilder()
.contractUpdateInstance(
ContractUpdateTransactionBody.newBuilder()
.contractID(targetContract)
.memo("new memo") // invalid account
)
.transactionID(transactionID)
.build();
final var context = new FakePreHandleContext(accountStore, txn);

subject.preHandle(context);

verify(payerAccount, times(2)).key();
}

@Test
void callsKey1xIfAdminKeyNotRequired() throws PreCheckException {
final var txn = TransactionBody.newBuilder()
.contractUpdateInstance(
ContractUpdateTransactionBody.newBuilder()
.contractID(targetContract)
.expirationTime(Timestamp.newBuilder().seconds(1L)) // invalid account
)
.transactionID(transactionID)
.build();
final var context = new FakePreHandleContext(accountStore, txn);

subject.preHandle(context);

verify(payerAccount, times(1)).key();
}

@Test
void handleWithNullContextFails() {
final HandleContext context = null;
Expand Down Expand Up @@ -322,6 +359,7 @@ void maxAutomaticTokenAssociationsBiggerThenAllowedFails() {
when(context.configuration()).thenReturn(configuration);

when(accountStore.getContractById(targetContract)).thenReturn(contract);
when(contract.key()).thenReturn(Key.newBuilder().build());

given(context.storeFactory()).willReturn(storeFactory);
given(storeFactory.readableStore(ReadableAccountStore.class)).willReturn(accountStore);
Expand Down Expand Up @@ -351,6 +389,7 @@ void maxAutomaticTokenAssociationsSmallerThenContractLimitFails() {

when(accountStore.getContractById(targetContract)).thenReturn(contract);
when(contract.maxAutoAssociations()).thenReturn(maxAutomaticTokenAssociations + 1);
when(contract.key()).thenReturn(Key.newBuilder().build());

given(context.storeFactory()).willReturn(storeFactory);
given(storeFactory.readableStore(ReadableAccountStore.class)).willReturn(accountStore);
Expand Down Expand Up @@ -381,6 +420,7 @@ void maxAutomaticTokenAssociationsBiggerThenMaxConfigFails() {
when(context.configuration()).thenReturn(configuration);

when(contract.maxAutoAssociations()).thenReturn(maxAutomaticTokenAssociations - 1);
when(contract.key()).thenReturn(Key.newBuilder().build());
when(accountStore.getContractById(targetContract)).thenReturn(contract);
given(context.storeFactory()).willReturn(storeFactory);
given(storeFactory.readableStore(ReadableAccountStore.class)).willReturn(accountStore);
Expand Down Expand Up @@ -413,6 +453,7 @@ void maxAutomaticTokenAssociationsWhenItIsNotAllowedFails() {
when(context.configuration()).thenReturn(configuration);

when(contract.maxAutoAssociations()).thenReturn(maxAutomaticTokenAssociations - 1);
when(contract.key()).thenReturn(Key.newBuilder().build());
when(accountStore.getContractById(targetContract)).thenReturn(contract);
given(context.storeFactory()).willReturn(storeFactory);
given(storeFactory.readableStore(ReadableAccountStore.class)).willReturn(accountStore);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static com.hedera.services.bdd.junit.ContextRequirement.PROPERTY_OVERRIDES;
import static com.hedera.services.bdd.junit.TestTags.SMART_CONTRACT;
import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec;
import static com.hedera.services.bdd.spec.HapiSpec.hapiTest;
import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec;
import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.isLiteralResult;
import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.resultWith;
Expand Down Expand Up @@ -49,6 +50,7 @@
import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.FULLY_NONDETERMINISTIC;
import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONTRACT_CALL_RESULTS;
import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES;
import static com.hedera.services.bdd.suites.HapiSuite.CIVILIAN_PAYER;
import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_CONTRACT_SENDER;
import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER;
import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PROPS;
Expand Down Expand Up @@ -103,6 +105,8 @@ public class ContractUpdateSuite {
public static final String NEW_ADMIN_KEY = "newAdminKey";
private static final String CONTRACT = "Multipurpose";
public static final String INITIAL_ADMIN_KEY = "initialAdminKey";
private static final String AUTO_RENEW_ACCOUNT = "autoRenewAccount";
private static final String STAKED_ACCOUNT = "stakedAccount";

@HapiTest
final Stream<DynamicTest> idVariantsTreatedAsExpected() {
Expand Down Expand Up @@ -469,10 +473,6 @@ final Stream<DynamicTest> fridayThe13thSpec() {
.signedBy(payer)
.newMemo(NEW_MEMO)
.hasKnownStatus(INVALID_SIGNATURE),
contractUpdate(contract + suffix)
.payingWith(payer)
.signedBy(payer, INITIAL_ADMIN_KEY)
.hasKnownStatus(INVALID_SIGNATURE),
contractUpdate(contract)
.payingWith(payer)
.newMemo(BETTER_MEMO)
Expand Down Expand Up @@ -619,4 +619,92 @@ final Stream<DynamicTest> playGame() {
.when(when.toArray(HapiSpecOperation[]::new))
.then(then.toArray(HapiSpecOperation[]::new));
}

@HapiTest
final Stream<DynamicTest> cannotUpdateImmutableContractExceptExpiry() {
final var someValidExpiry = new AtomicLong(Instant.now().getEpochSecond() + THREE_MONTHS_IN_SECONDS + 1234L);
return hapiTest(
newKeyNamed(ADMIN_KEY),
cryptoCreate(AUTO_RENEW_ACCOUNT),
cryptoCreate(STAKED_ACCOUNT),
uploadInitCode(CONTRACT),
contractCreate(CONTRACT).immutable(),
contractUpdate(CONTRACT).newAutoRenew(1).hasKnownStatus(MODIFYING_IMMUTABLE_CONTRACT),
contractUpdate(CONTRACT)
.newAutoRenewAccount(AUTO_RENEW_ACCOUNT)
.hasKnownStatus(MODIFYING_IMMUTABLE_CONTRACT),
contractUpdate(CONTRACT).newDeclinedReward(true).hasKnownStatus(MODIFYING_IMMUTABLE_CONTRACT),
contractUpdate(CONTRACT).newExpirySecs(someValidExpiry.get()).hasKnownStatus(SUCCESS),
contractUpdate(CONTRACT).newKey(ADMIN_KEY).hasKnownStatus(MODIFYING_IMMUTABLE_CONTRACT),
contractUpdate(CONTRACT).newMaxAutomaticAssociations(100).hasKnownStatus(MODIFYING_IMMUTABLE_CONTRACT),
contractUpdate(CONTRACT).newMemo("The new memo").hasKnownStatus(MODIFYING_IMMUTABLE_CONTRACT),
contractUpdate(CONTRACT).newProxy(CONTRACT).hasKnownStatus(MODIFYING_IMMUTABLE_CONTRACT),
contractUpdate(CONTRACT)
.newStakedAccountId(STAKED_ACCOUNT)
.hasKnownStatus(MODIFYING_IMMUTABLE_CONTRACT),
contractUpdate(CONTRACT).newStakedNodeId(1).hasKnownStatus(MODIFYING_IMMUTABLE_CONTRACT));
}

@HapiTest
final Stream<DynamicTest> cannotUpdateContractExceptExpiryWithWrongKey() {
final var someValidExpiry = new AtomicLong(Instant.now().getEpochSecond() + THREE_MONTHS_IN_SECONDS + 1234L);
return hapiTest(
newKeyNamed(ADMIN_KEY),
newKeyNamed(NEW_ADMIN_KEY),
cryptoCreate(AUTO_RENEW_ACCOUNT),
cryptoCreate(STAKED_ACCOUNT),
cryptoCreate(CIVILIAN_PAYER).balance(10 * ONE_HUNDRED_HBARS),
uploadInitCode(CONTRACT),
contractCreate(CONTRACT).adminKey(ADMIN_KEY),
contractUpdate(CONTRACT)
.payingWith(CIVILIAN_PAYER)
.signedBy(CIVILIAN_PAYER, NEW_ADMIN_KEY)
.newAutoRenew(1)
.hasKnownStatus(INVALID_SIGNATURE),
contractUpdate(CONTRACT)
.payingWith(CIVILIAN_PAYER)
.signedBy(CIVILIAN_PAYER, NEW_ADMIN_KEY)
.newAutoRenewAccount(AUTO_RENEW_ACCOUNT)
.hasKnownStatus(INVALID_SIGNATURE),
contractUpdate(CONTRACT)
.payingWith(CIVILIAN_PAYER)
.signedBy(CIVILIAN_PAYER, NEW_ADMIN_KEY)
.newDeclinedReward(true)
.hasKnownStatus(INVALID_SIGNATURE),
contractUpdate(CONTRACT)
.payingWith(CIVILIAN_PAYER)
.signedBy(CIVILIAN_PAYER, NEW_ADMIN_KEY)
.newExpirySecs(someValidExpiry.get())
.hasKnownStatus(SUCCESS),
contractUpdate(CONTRACT)
.payingWith(CIVILIAN_PAYER)
.signedBy(CIVILIAN_PAYER, NEW_ADMIN_KEY)
.newKey(ADMIN_KEY)
.hasKnownStatus(INVALID_SIGNATURE),
contractUpdate(CONTRACT)
.payingWith(CIVILIAN_PAYER)
.signedBy(CIVILIAN_PAYER, NEW_ADMIN_KEY)
.newMaxAutomaticAssociations(100)
.hasKnownStatus(INVALID_SIGNATURE),
contractUpdate(CONTRACT)
.payingWith(CIVILIAN_PAYER)
.signedBy(CIVILIAN_PAYER, NEW_ADMIN_KEY)
.newMemo("The new memo")
.hasKnownStatus(INVALID_SIGNATURE),
contractUpdate(CONTRACT)
.payingWith(CIVILIAN_PAYER)
.signedBy(CIVILIAN_PAYER, NEW_ADMIN_KEY)
.newProxy(CONTRACT)
.hasKnownStatus(INVALID_SIGNATURE),
contractUpdate(CONTRACT)
.payingWith(CIVILIAN_PAYER)
.signedBy(CIVILIAN_PAYER, NEW_ADMIN_KEY)
.newStakedAccountId(STAKED_ACCOUNT)
.hasKnownStatus(INVALID_SIGNATURE),
contractUpdate(CONTRACT)
.payingWith(CIVILIAN_PAYER)
.signedBy(CIVILIAN_PAYER, NEW_ADMIN_KEY)
.newStakedNodeId(1)
.hasKnownStatus(INVALID_SIGNATURE));
}
}

0 comments on commit 9436208

Please sign in to comment.