Skip to content

Commit

Permalink
fix: tokenClaimAirdrop throws NPE on null sender or receiver (#17096)
Browse files Browse the repository at this point in the history
Signed-off-by: Kim Rader <[email protected]>
  • Loading branch information
kimbor authored Dec 18, 2024
1 parent abb296b commit 608f91f
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import static com.hedera.hapi.node.base.ResponseCodeEnum.EMPTY_PENDING_AIRDROP_ID_LIST;
import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID;
import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_PENDING_AIRDROP_ID;
import static com.hedera.hapi.node.base.ResponseCodeEnum.PENDING_AIRDROP_ID_LIST_TOO_LONG;
import static com.hedera.hapi.node.base.ResponseCodeEnum.PENDING_AIRDROP_ID_REPEATED;
import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_AIRDROP_WITH_FALLBACK_ROYALTY;
Expand Down Expand Up @@ -114,6 +115,11 @@ public void pureChecks(@NonNull TransactionBody txn) throws PreCheckException {

final var uniqueAirdrops = Set.copyOf(pendingAirdrops);
validateTruePreCheck(pendingAirdrops.size() == uniqueAirdrops.size(), PENDING_AIRDROP_ID_REPEATED);

validateTruePreCheck(
pendingAirdrops.stream().allMatch(PendingAirdropId::hasSenderId), INVALID_PENDING_AIRDROP_ID);
validateTruePreCheck(
pendingAirdrops.stream().allMatch(PendingAirdropId::hasReceiverId), INVALID_PENDING_AIRDROP_ID);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,34 @@ void pureChecksHasValidPath() {
.doesNotThrowAnyException();
}

@Test
void pureChecksEmptySenderThrows() {
final List<PendingAirdropId> pendingAirdropIds = new ArrayList<>();
pendingAirdropIds.add(PendingAirdropId.newBuilder()
.receiverId(ACCOUNT_ID_3333)
.fungibleTokenType(TOKEN_2468)
.build());
final var txn = newTokenClaimAirdrop(TokenClaimAirdropTransactionBody.newBuilder()
.pendingAirdrops(pendingAirdropIds)
.build());
Assertions.assertThatThrownBy(() -> tokenClaimAirdropHandler.pureChecks(txn))
.isInstanceOf(PreCheckException.class);
}

@Test
void pureChecksEmptyReceiverThrows() {
final List<PendingAirdropId> pendingAirdropIds = new ArrayList<>();
pendingAirdropIds.add(PendingAirdropId.newBuilder()
.senderId(ACCOUNT_ID_4444)
.fungibleTokenType(TOKEN_2468)
.build());
final var txn = newTokenClaimAirdrop(TokenClaimAirdropTransactionBody.newBuilder()
.pendingAirdrops(pendingAirdropIds)
.build());
Assertions.assertThatThrownBy(() -> tokenClaimAirdropHandler.pureChecks(txn))
.isInstanceOf(PreCheckException.class);
}

@Test
void preHandleAccountNotExistPath() throws PreCheckException {
final List<PendingAirdropId> pendingAirdropIds = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ETHEREUM_TRANSACTION;
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_FILE_ID;
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_NODE_ACCOUNT;
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_PENDING_AIRDROP_ID;
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SCHEDULE_ID;
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE;
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOKEN_ID;
Expand Down Expand Up @@ -141,7 +142,12 @@ public class BodyIdClearingStrategy extends IdClearingStrategy<TxnModification>
entry(
"proto.EthereumTransactionBody.call_data",
ExpectedResponse.atConsensus(INVALID_ETHEREUM_TRANSACTION)),
entry("proto.TokenUpdateNftsTransactionBody.token", ExpectedResponse.atIngest(INVALID_TOKEN_ID)));
entry("proto.TokenUpdateNftsTransactionBody.token", ExpectedResponse.atIngest(INVALID_TOKEN_ID)),
entry("proto.PendingAirdropId.receiver_id", ExpectedResponse.atIngest(INVALID_PENDING_AIRDROP_ID)),
entry(
"proto.PendingAirdropId.fungible_token_type",
ExpectedResponse.atConsensus(INVALID_PENDING_AIRDROP_ID)),
entry("proto.PendingAirdropId.sender_id", ExpectedResponse.atIngest(INVALID_PENDING_AIRDROP_ID)));

private static final Map<String, ExpectedResponse> SCHEDULED_CLEARED_ID_RESPONSES = Map.ofEntries(
entry("proto.AccountAmount.accountID", ExpectedResponse.atConsensusOneOf(INVALID_ACCOUNT_ID)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt;
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed;
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding;
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified;
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsd;
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext;
import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds;
import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER;
import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR;
import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS;
Expand Down Expand Up @@ -222,6 +224,23 @@ final Stream<DynamicTest> claimFungibleTokenAirdrop() {
getAccountInfo(RECEIVER).hasToken(relationshipWith(NON_FUNGIBLE_TOKEN)));
}

@HapiTest
@DisplayName("fails gracefully with null parameters")
final Stream<DynamicTest> idVariantsTreatedAsExpected() {
return hapiTest(
cryptoCreate(OWNER).balance(ONE_HUNDRED_HBARS),
cryptoCreate(RECEIVER_WITH_0_AUTO_ASSOCIATIONS)
.balance(ONE_HUNDRED_HBARS)
.maxAutomaticTokenAssociations(0),
createFT(FUNGIBLE_TOKEN_1, OWNER, 1000L),
tokenAirdrop(moving(1, FUNGIBLE_TOKEN_1).between(OWNER, RECEIVER_WITH_0_AUTO_ASSOCIATIONS))
.payingWith(OWNER),
submitModified(withSuccessivelyVariedBodyIds(), () -> tokenClaimAirdrop(
pendingAirdrop(OWNER, RECEIVER_WITH_0_AUTO_ASSOCIATIONS, FUNGIBLE_TOKEN_1))
.signedBy(DEFAULT_PAYER, RECEIVER_WITH_0_AUTO_ASSOCIATIONS)
.payingWith(RECEIVER_WITH_0_AUTO_ASSOCIATIONS)));
}

@HapiTest
@DisplayName("single token claim success that receiver paying for it")
final Stream<DynamicTest> singleTokenClaimSuccessThatReceiverPayingForIt() {
Expand Down

0 comments on commit 608f91f

Please sign in to comment.