Skip to content

Commit f13f857

Browse files
authored
chore: Cherry-pick: Implement un-HAPI tests for TokenAssociateToAccountHandler (#12730)
Signed-off-by: Michael Heinrichs <[email protected]>
1 parent 95ea39f commit f13f857

File tree

13 files changed

+190
-17
lines changed

13 files changed

+190
-17
lines changed

hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileUpdateHandler.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,10 @@ public void handle(@NonNull final HandleContext handleContext) throws HandleExce
147147

148148
// First validate this file is mutable; and the pending mutations are allowed
149149
if (wantsToMutateNonExpiryField(fileUpdate)) {
150-
validateFalse(file.keys() == null, UNAUTHORIZED);
150+
if (handleContext.hasPrivilegedAuthorization() != SystemPrivilege.AUTHORIZED) {
151+
validateTrue(file.hasKeys() && !file.keys().keys().isEmpty(), UNAUTHORIZED);
152+
}
153+
151154
validateMaybeNewMemo(handleContext.attributeValidator(), fileUpdate);
152155
}
153156

hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractCallHandler.java

+10
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@
1717
package com.hedera.node.app.service.contract.impl.handlers;
1818

1919
import static com.hedera.hapi.node.base.HederaFunctionality.CONTRACT_CALL;
20+
import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_CONTRACT_ID;
2021
import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.throwIfUnsuccessful;
2122
import static com.hedera.node.app.service.mono.pbj.PbjConverter.fromPbj;
23+
import static com.hedera.node.app.spi.validation.Validations.mustExist;
2224
import static java.util.Objects.requireNonNull;
2325

2426
import com.hedera.hapi.node.base.HederaFunctionality;
2527
import com.hedera.hapi.node.base.SubType;
28+
import com.hedera.hapi.node.transaction.TransactionBody;
2629
import com.hedera.node.app.hapi.utils.fee.SmartContractFeeBuilder;
2730
import com.hedera.node.app.service.contract.impl.exec.CallOutcome.ExternalizeAbortResult;
2831
import com.hedera.node.app.service.contract.impl.exec.TransactionComponent;
@@ -32,6 +35,7 @@
3235
import com.hedera.node.app.spi.fees.Fees;
3336
import com.hedera.node.app.spi.workflows.HandleContext;
3437
import com.hedera.node.app.spi.workflows.HandleException;
38+
import com.hedera.node.app.spi.workflows.PreCheckException;
3539
import com.hedera.node.app.spi.workflows.PreHandleContext;
3640
import com.hedera.node.app.spi.workflows.TransactionHandler;
3741
import edu.umd.cs.findbugs.annotations.NonNull;
@@ -72,6 +76,12 @@ public void preHandle(@NonNull final PreHandleContext context) {
7276
// No non-payer signatures to verify
7377
}
7478

79+
@Override
80+
public void pureChecks(@NonNull TransactionBody txn) throws PreCheckException {
81+
final var op = txn.contractCallOrThrow();
82+
mustExist(op.contractID(), INVALID_CONTRACT_ID);
83+
}
84+
7585
@NonNull
7686
@Override
7787
public Fees calculateFees(@NonNull final FeeContext feeContext) {

hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAssociateToAccountHandler.java

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.hedera.node.app.service.token.impl.handlers;
1818

19+
import static com.hedera.hapi.node.base.ResponseCodeEnum.ACCOUNT_DELETED;
1920
import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID;
2021
import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID;
2122
import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_ENTITIES_IN_PRICE_REGIME_HAVE_BEEN_CREATED;
@@ -27,6 +28,7 @@
2728
import static com.hedera.node.app.service.token.impl.comparator.TokenComparators.TOKEN_ID_COMPARATOR;
2829
import static com.hedera.node.app.service.token.impl.handlers.BaseCryptoHandler.hasAccountNumOrAlias;
2930
import static com.hedera.node.app.service.token.impl.util.TokenHandlerHelper.getIfUsable;
31+
import static com.hedera.node.app.spi.workflows.HandleException.validateFalse;
3032
import static com.hedera.node.app.spi.workflows.HandleException.validateTrue;
3133
import static com.hedera.node.app.spi.workflows.PreCheckException.validateFalsePreCheck;
3234
import static com.hedera.node.app.spi.workflows.PreCheckException.validateTruePreCheck;
@@ -133,6 +135,7 @@ private Validated validateSemantics(
133135
// Check that the account exists
134136
final var account = accountStore.get(accountId);
135137
validateTrue(account != null, INVALID_ACCOUNT_ID);
138+
validateFalse(account.deleted(), ACCOUNT_DELETED);
136139

137140
// Check that the given tokens exist and are usable
138141
final var tokens = new ArrayList<Token>();

hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/HapiSpecOperation.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,8 @@ public Optional<Throwable> execFor(final HapiSpec spec) {
256256
return Optional.empty();
257257
}
258258
if (verboseLoggingOn) {
259-
String message = MessageFormat.format("{0}{1} failed - {2}", spec.logPrefix(), this, t);
260-
log.warn(message);
259+
String message = MessageFormat.format("{0}{1} failed", spec.logPrefix(), this);
260+
log.warn(message, t);
261261
} else if (!loggingOff) {
262262
String message = MessageFormat.format("{0}{1} failed - {2}!", spec.logPrefix(), this, t.getMessage());
263263
log.warn(message);

hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/keys/SigControl.java

+4
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ public static SigControl threshSigs(int M, SigControl... childControls) {
140140
return new SigControl(M, childControls);
141141
}
142142

143+
public static SigControl emptyList() {
144+
return new SigControl(LIST);
145+
}
146+
143147
protected SigControl(Nature nature) {
144148
this.nature = nature;
145149
}

hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiContractCall.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -291,10 +291,12 @@ protected Consumer<TransactionBody.Builder> opBodyDef(HapiSpec spec) throws Thro
291291
ContractCallTransactionBody opBody = spec.txns()
292292
.<ContractCallTransactionBody, ContractCallTransactionBody.Builder>body(
293293
ContractCallTransactionBody.class, builder -> {
294-
if (!tryAsHexedAddressIfLenMatches) {
295-
builder.setContractID(spec.registry().getContractId(contract));
296-
} else {
297-
builder.setContractID(TxnUtils.asContractId(contract, spec));
294+
if (contract != null) {
295+
if (!tryAsHexedAddressIfLenMatches) {
296+
builder.setContractID(spec.registry().getContractId(contract));
297+
} else {
298+
builder.setContractID(TxnUtils.asContractId(contract, spec));
299+
}
298300
}
299301
builder.setFunctionParameters(ByteString.copyFrom(callData));
300302
valueSent.ifPresent(builder::setAmount);

hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/file/HapiFileCreate.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public HapiFileCreate entityMemo(String s) {
124124
}
125125

126126
public HapiFileCreate key(String keyName) {
127-
this.keyName = Optional.of(keyName);
127+
this.keyName = Optional.ofNullable(keyName);
128128
return this;
129129
}
130130

hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/token/HapiTokenAssociate.java

+14-6
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import static com.hedera.services.bdd.spec.queries.QueryVerbs.getContractInfo;
2222
import static com.hedera.services.bdd.spec.transactions.TxnUtils.suFrom;
2323
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS;
24-
import static java.util.stream.Collectors.toList;
2524

2625
import com.google.common.base.MoreObjects;
2726
import com.hedera.node.app.hapi.fees.usage.TxnUsageEstimator;
@@ -33,7 +32,13 @@
3332
import com.hedera.services.bdd.spec.queries.crypto.HapiGetAccountInfo;
3433
import com.hedera.services.bdd.spec.transactions.HapiTxnOp;
3534
import com.hedera.services.bdd.spec.transactions.TxnUtils;
36-
import com.hederahashgraph.api.proto.java.*;
35+
import com.hederahashgraph.api.proto.java.HederaFunctionality;
36+
import com.hederahashgraph.api.proto.java.Key;
37+
import com.hederahashgraph.api.proto.java.ResponseCodeEnum;
38+
import com.hederahashgraph.api.proto.java.TokenAssociateTransactionBody;
39+
import com.hederahashgraph.api.proto.java.Transaction;
40+
import com.hederahashgraph.api.proto.java.TransactionBody;
41+
import com.hederahashgraph.api.proto.java.TransactionResponse;
3742
import java.util.ArrayList;
3843
import java.util.List;
3944
import java.util.Optional;
@@ -45,6 +50,8 @@
4550
public class HapiTokenAssociate extends HapiTxnOp<HapiTokenAssociate> {
4651
static final Logger log = LogManager.getLogger(HapiTokenAssociate.class);
4752

53+
public static final long DEFAULT_FEE = 100_000_000L;
54+
4855
private String account;
4956
private List<String> tokens = new ArrayList<>();
5057
private Optional<ResponseCodeEnum[]> permissibleCostAnswerPrechecks = Optional.empty();
@@ -82,7 +89,7 @@ _txn, new TxnUsageEstimator(suFrom(svo), _txn, ESTIMATOR_UTILS))
8289
return spec.fees()
8390
.forActivityBasedOp(HederaFunctionality.TokenAssociateToAccount, metricsCalc, txn, numPayerKeys);
8491
} catch (Throwable ignore) {
85-
return 100_000_000L;
92+
return DEFAULT_FEE;
8693
}
8794
}
8895

@@ -138,14 +145,15 @@ private long lookupExpiry(HapiSpec spec) throws Throwable {
138145

139146
@Override
140147
protected Consumer<TransactionBody.Builder> opBodyDef(HapiSpec spec) throws Throwable {
141-
var aId = TxnUtils.asId(account, spec);
142148
TokenAssociateTransactionBody opBody = spec.txns()
143149
.<TokenAssociateTransactionBody, TokenAssociateTransactionBody.Builder>body(
144150
TokenAssociateTransactionBody.class, b -> {
145-
b.setAccount(aId);
151+
if (account != null) {
152+
b.setAccount(TxnUtils.asId(account, spec));
153+
}
146154
b.addAllTokens(tokens.stream()
147155
.map(lit -> TxnUtils.asTokenId(lit, spec))
148-
.collect(toList()));
156+
.toList());
149157
});
150158
return b -> b.setTokenAssociate(opBody);
151159
}

hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/token/HapiTokenCreate.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ public HapiTokenCreate symbol(final Function<HapiSpec, String> symbolFn) {
242242
}
243243

244244
public HapiTokenCreate name(final String name) {
245-
this.name = Optional.of(name);
245+
this.name = Optional.ofNullable(name);
246246
return this;
247247
}
248248

hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallSuite.java

+9
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE;
9292
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_TX_FEE;
9393
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ACCOUNT_ID;
94+
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_CONTRACT_ID;
9495
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE;
9596
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SOLIDITY_ADDRESS;
9697
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NOT_SUPPORTED;
@@ -262,6 +263,14 @@ public List<HapiSpec> getSpecsInSuite() {
262263
callStaticCallToLargeAddress());
263264
}
264265

266+
@HapiTest
267+
public HapiSpec canHandleInvalidContractCallTransactions() {
268+
return defaultHapiSpec("canHandleInvalidContractCallTransactions")
269+
.given()
270+
.when()
271+
.then(contractCall(null).hasPrecheck(INVALID_CONTRACT_ID));
272+
}
273+
265274
@HapiTest
266275
final HapiSpec repeatedCreate2FailsWithInterpretableActionSidecars() {
267276
final var contract = "Create2PrecompileUser";

hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileUpdateSuite.java

+21
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
import com.hedera.services.bdd.junit.HapiTestSuite;
9494
import com.hedera.services.bdd.spec.HapiSpec;
9595
import com.hedera.services.bdd.spec.HapiSpecSetup;
96+
import com.hedera.services.bdd.spec.keys.SigControl;
9697
import com.hedera.services.bdd.spec.transactions.TxnUtils;
9798
import com.hedera.services.bdd.spec.transactions.TxnVerbs;
9899
import com.hedera.services.bdd.spec.utilops.UtilVerbs;
@@ -352,6 +353,26 @@ final HapiSpec vanillaUpdateSucceeds() {
352353
getFileInfo("test").hasMemo(secondMemo));
353354
}
354355

356+
@HapiTest
357+
public HapiSpec cannotUpdateImmutableFile() {
358+
final String file1 = "FILE_1";
359+
final String file2 = "FILE_2";
360+
return defaultHapiSpec("CannotUpdateImmutableFile")
361+
.given(
362+
fileCreate(file1).contents("Hello World").unmodifiable(),
363+
fileCreate(file2).contents("Hello World").waclShape(SigControl.emptyList()))
364+
.when()
365+
.then(
366+
fileUpdate(file1)
367+
.contents("Goodbye World")
368+
.signedBy(DEFAULT_PAYER)
369+
.hasKnownStatus(UNAUTHORIZED),
370+
fileUpdate(file2)
371+
.contents("Goodbye World")
372+
.signedBy(DEFAULT_PAYER)
373+
.hasKnownStatus(UNAUTHORIZED));
374+
}
375+
355376
@HapiTest
356377
final HapiSpec cannotUpdateExpirationPastMaxLifetime() {
357378
return defaultHapiSpec("CannotUpdateExpirationPastMaxLifetime")

hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenAssociationSpecs.java

+62
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static com.hedera.services.bdd.junit.TestTags.TOKEN;
2020
import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec;
21+
import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec;
2122
import static com.hedera.services.bdd.spec.assertions.NoTokenTransfers.emptyTokenTransfers;
2223
import static com.hedera.services.bdd.spec.assertions.SomeFungibleTransfers.changingFungibleBalances;
2324
import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith;
@@ -41,18 +42,26 @@
4142
import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUnfreeze;
4243
import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUpdate;
4344
import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode;
45+
import static com.hedera.services.bdd.spec.transactions.token.HapiTokenAssociate.DEFAULT_FEE;
4446
import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving;
4547
import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor;
4648
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed;
49+
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingTwo;
4750
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor;
4851
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing;
4952
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext;
53+
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_DELETED;
5054
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_FROZEN_FOR_TOKEN;
5155
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_IS_TREASURY;
5256
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ACCOUNT_ID;
5357
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE;
5458
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOKEN_ID;
59+
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS;
60+
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKENS_PER_ACCOUNT_LIMIT_EXCEEDED;
61+
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT;
62+
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_ID_REPEATED_IN_TOKEN_LIST;
5563
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_NOT_ASSOCIATED_TO_ACCOUNT;
64+
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_WAS_DELETED;
5665
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES;
5766
import static com.hederahashgraph.api.proto.java.TokenFreezeStatus.FreezeNotApplicable;
5867
import static com.hederahashgraph.api.proto.java.TokenFreezeStatus.Unfrozen;
@@ -121,6 +130,59 @@ public boolean canRunConcurrent() {
121130
return true;
122131
}
123132

133+
@HapiTest
134+
public HapiSpec canHandleInvalidAssociateTransactions() {
135+
final String alice = "ALICE";
136+
final String bob = "BOB";
137+
final String unknownID = "0.0." + Long.MAX_VALUE;
138+
return defaultHapiSpec("CanHandleInvalidAssociateTransactions")
139+
.given(
140+
newKeyNamed(MULTI_KEY),
141+
cryptoCreate(alice),
142+
cryptoCreate(bob),
143+
cryptoDelete(bob),
144+
tokenCreate(VANILLA_TOKEN),
145+
tokenCreate(KNOWABLE_TOKEN),
146+
tokenAssociate(alice, KNOWABLE_TOKEN),
147+
tokenCreate(TBD_TOKEN).adminKey(MULTI_KEY),
148+
tokenDelete(TBD_TOKEN))
149+
.when()
150+
.then(
151+
tokenAssociate(null, VANILLA_TOKEN)
152+
.fee(DEFAULT_FEE)
153+
.signedBy(DEFAULT_PAYER)
154+
.hasPrecheck(INVALID_ACCOUNT_ID),
155+
tokenAssociate(unknownID, VANILLA_TOKEN)
156+
.fee(DEFAULT_FEE)
157+
.signedBy(DEFAULT_PAYER)
158+
.hasPrecheck(INVALID_ACCOUNT_ID),
159+
tokenAssociate(bob, VANILLA_TOKEN).fee(DEFAULT_FEE).hasKnownStatus(ACCOUNT_DELETED),
160+
tokenAssociate(alice, List.of()).hasKnownStatus(SUCCESS),
161+
tokenAssociate(alice, VANILLA_TOKEN, VANILLA_TOKEN)
162+
.hasPrecheck(TOKEN_ID_REPEATED_IN_TOKEN_LIST),
163+
tokenAssociate(alice, unknownID).hasKnownStatus(INVALID_TOKEN_ID),
164+
tokenAssociate(alice, TBD_TOKEN).hasKnownStatus(TOKEN_WAS_DELETED),
165+
tokenAssociate(alice, KNOWABLE_TOKEN).hasKnownStatus(TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT));
166+
}
167+
168+
@HapiTest
169+
public HapiSpec canLimitMaxTokensPerAccountTransactions() {
170+
final String alice = "ALICE";
171+
final String treasury2 = "TREASURY_2";
172+
return propertyPreservingHapiSpec("CanHandleInvalidAssociateTransactions")
173+
.preserving("tokens.maxPerAccount", "entities.limitTokenAssociations")
174+
.given(
175+
overridingTwo("tokens.maxPerAccount", "1", "entities.limitTokenAssociations", "true"),
176+
cryptoCreate(alice),
177+
cryptoCreate(TOKEN_TREASURY),
178+
cryptoCreate(treasury2),
179+
tokenCreate(VANILLA_TOKEN).treasury(TOKEN_TREASURY),
180+
tokenCreate(KNOWABLE_TOKEN).treasury(treasury2),
181+
tokenAssociate(alice, KNOWABLE_TOKEN))
182+
.when()
183+
.then(tokenAssociate(alice, VANILLA_TOKEN).hasKnownStatus(TOKENS_PER_ACCOUNT_LIMIT_EXCEEDED));
184+
}
185+
124186
@HapiTest
125187
public HapiSpec handlesUseOfDefaultTokenId() {
126188
return defaultHapiSpec("HandlesUseOfDefaultTokenId", SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES)

0 commit comments

Comments
 (0)