diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/BaseTokenHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/BaseTokenHandler.java index d346996d1ccc..8e477646ee35 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/BaseTokenHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/BaseTokenHandler.java @@ -42,7 +42,9 @@ import com.hedera.node.app.service.token.impl.WritableAccountStore; import com.hedera.node.app.service.token.impl.WritableTokenRelationStore; import com.hedera.node.app.service.token.impl.WritableTokenStore; +import com.hedera.node.app.service.token.impl.util.TokenHandlerHelper; import com.hedera.node.app.service.token.impl.util.TokenKey; +import com.hedera.node.app.spi.validation.ExpiryValidator; import com.hedera.node.config.data.EntitiesConfig; import com.hedera.node.config.data.TokensConfig; import com.swirlds.config.api.Configuration; @@ -77,12 +79,14 @@ public class BaseTokenHandler { /** * Mints fungible tokens. This method is called in both token create and mint. - * @param token the new or existing token to mint - * @param treasuryRel the treasury relation for the token - * @param amount the amount to mint - * @param accountStore the account store - * @param tokenStore the token store + * + * @param token the new or existing token to mint + * @param treasuryRel the treasury relation for the token + * @param amount the amount to mint + * @param accountStore the account store + * @param tokenStore the token store * @param tokenRelationStore the token relation store + * @param expiryValidator the expiry validator */ protected long mintFungible( @NonNull final Token token, @@ -90,12 +94,20 @@ protected long mintFungible( final long amount, @NonNull final WritableAccountStore accountStore, @NonNull final WritableTokenStore tokenStore, - @NonNull final WritableTokenRelationStore tokenRelationStore) { + @NonNull final WritableTokenRelationStore tokenRelationStore, + @NonNull final ExpiryValidator expiryValidator) { requireNonNull(token); requireNonNull(treasuryRel); return changeSupply( - token, treasuryRel, +amount, INVALID_TOKEN_MINT_AMOUNT, accountStore, tokenStore, tokenRelationStore); + token, + treasuryRel, + +amount, + INVALID_TOKEN_MINT_AMOUNT, + accountStore, + tokenStore, + tokenRelationStore, + expiryValidator); } /** @@ -105,13 +117,14 @@ protected long mintFungible( *

* Note: This method assumes the given token has a non-null supply key! * - * @param token the token that is minted or burned - * @param treasuryRel the treasury relation for the token - * @param amount the amount to mint or burn - * @param invalidSupplyCode the invalid supply code to use if the supply is invalid - * @param accountStore the account store - * @param tokenStore the token store + * @param token the token that is minted or burned + * @param treasuryRel the treasury relation for the token + * @param amount the amount to mint or burn + * @param invalidSupplyCode the invalid supply code to use if the supply is invalid + * @param accountStore the account store + * @param tokenStore the token store * @param tokenRelationStore the token relation store + * @param expiryValidator the expiry validator */ protected long changeSupply( @NonNull final Token token, @@ -120,7 +133,8 @@ protected long changeSupply( @NonNull final ResponseCodeEnum invalidSupplyCode, @NonNull final WritableAccountStore accountStore, @NonNull final WritableTokenStore tokenStore, - @NonNull final WritableTokenRelationStore tokenRelationStore) { + @NonNull final WritableTokenRelationStore tokenRelationStore, + @NonNull final ExpiryValidator expiryValidator) { requireNonNull(token); requireNonNull(treasuryRel); requireNonNull(invalidSupplyCode); @@ -140,8 +154,8 @@ protected long changeSupply( validateTrue(token.maxSupply() >= newTotalSupply, TOKEN_MAX_SUPPLY_REACHED); } - final var treasuryAccount = accountStore.get(treasuryRel.accountId()); - validateTrue(treasuryAccount != null, INVALID_TREASURY_ACCOUNT_FOR_TOKEN); + final var treasuryAccount = TokenHandlerHelper.getIfUsable( + treasuryRel.accountId(), accountStore, expiryValidator, INVALID_TREASURY_ACCOUNT_FOR_TOKEN); final long newTreasuryBalance = treasuryRel.balance() + amount; validateTrue(newTreasuryBalance >= 0, INSUFFICIENT_TOKEN_BALANCE); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoApproveAllowanceHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoApproveAllowanceHandler.java index c93d986638e3..51a843f38b09 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoApproveAllowanceHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoApproveAllowanceHandler.java @@ -41,6 +41,7 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.HederaFunctionality; +import com.hedera.hapi.node.base.NftID; import com.hedera.hapi.node.base.SubType; import com.hedera.hapi.node.base.TokenID; import com.hedera.hapi.node.state.token.Account; @@ -56,9 +57,11 @@ import com.hedera.node.app.service.token.impl.WritableAccountStore; import com.hedera.node.app.service.token.impl.WritableNftStore; import com.hedera.node.app.service.token.impl.WritableTokenStore; +import com.hedera.node.app.service.token.impl.util.TokenHandlerHelper; import com.hedera.node.app.service.token.impl.validators.ApproveAllowanceValidator; import com.hedera.node.app.spi.fees.FeeContext; import com.hedera.node.app.spi.fees.Fees; +import com.hedera.node.app.spi.validation.ExpiryValidator; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.PreCheckException; @@ -186,8 +189,8 @@ public void handle(@NonNull final HandleContext context) throws HandleException final var accountStore = context.storeFactory().writableStore(WritableAccountStore.class); // Validate payer account exists - final var payerAccount = accountStore.getAccountById(payer); - validateTrue(payerAccount != null, INVALID_PAYER_ACCOUNT_ID); + final var payerAccount = TokenHandlerHelper.getIfUsable( + payer, accountStore, context.expiryValidator(), INVALID_PAYER_ACCOUNT_ID); // Validate the transaction body fields that include state or configuration. validateSemantics(context, payerAccount, accountStore); @@ -241,26 +244,36 @@ private void approveAllowance( /* --- Apply changes to state --- */ final var allowanceMaxAccountLimit = hederaConfig.allowancesMaxAccountLimit(); - applyCryptoAllowances(cryptoAllowances, payerId, accountStore, allowanceMaxAccountLimit); - applyFungibleTokenAllowances(tokenAllowances, payerId, accountStore, allowanceMaxAccountLimit); + final var expiryValidator = context.expiryValidator(); + applyCryptoAllowances(cryptoAllowances, payerId, accountStore, allowanceMaxAccountLimit, expiryValidator); + applyFungibleTokenAllowances(tokenAllowances, payerId, accountStore, allowanceMaxAccountLimit, expiryValidator); applyNftAllowances( - nftAllowances, payerId, accountStore, tokenStore, uniqueTokenStore, allowanceMaxAccountLimit); + nftAllowances, + payerId, + accountStore, + tokenStore, + uniqueTokenStore, + allowanceMaxAccountLimit, + expiryValidator); } /** * Applies all changes needed for Crypto allowances from the transaction. * If the spender already has an allowance, the allowance value will be replaced with values * from transaction. If the amount specified is 0, the allowance will be removed. - * @param cryptoAllowances the list of crypto allowances - * @param payerId the payer account id - * @param accountStore the account store + * + * @param cryptoAllowances the list of crypto allowances + * @param payerId the payer account id + * @param accountStore the account store * @param allowanceMaxAccountLimit the {@link HederaConfig} + * @param expiryValidator the expiry validator */ private void applyCryptoAllowances( @NonNull final List cryptoAllowances, @NonNull final AccountID payerId, @NonNull final WritableAccountStore accountStore, - final int allowanceMaxAccountLimit) { + final int allowanceMaxAccountLimit, + @NonNull final ExpiryValidator expiryValidator) { requireNonNull(cryptoAllowances); requireNonNull(payerId); requireNonNull(accountStore); @@ -268,7 +281,7 @@ private void applyCryptoAllowances( for (final var allowance : cryptoAllowances) { final var owner = allowance.owner(); final var spender = allowance.spenderOrThrow(); - final var effectiveOwner = getEffectiveOwnerAccount(owner, payerId, accountStore); + final var effectiveOwner = getEffectiveOwnerAccount(owner, payerId, accountStore, expiryValidator); final var mutableAllowances = new ArrayList<>(effectiveOwner.cryptoAllowances()); final var amount = allowance.amount(); @@ -318,23 +331,25 @@ private void updateCryptoAllowance( * Applies all changes needed for fungible token allowances from the transaction. If the key * {token, spender} already has an allowance, the allowance value will be replaced with values * from transaction. - * @param tokenAllowances the list of token allowances - * @param payerId the payer account id - * @param accountStore the account store + * @param tokenAllowances the list of token allowances + * @param payerId the payer account id + * @param accountStore the account store * @param allowanceMaxAccountLimit the {@link HederaConfig} allowance max account limit + * @param expiryValidator the expiry validator */ private void applyFungibleTokenAllowances( @NonNull final List tokenAllowances, @NonNull final AccountID payerId, @NonNull final WritableAccountStore accountStore, - final int allowanceMaxAccountLimit) { + final int allowanceMaxAccountLimit, + @NonNull final ExpiryValidator expiryValidator) { for (final var allowance : tokenAllowances) { final var owner = allowance.owner(); final var amount = allowance.amount(); final var tokenId = allowance.tokenIdOrThrow(); final var spender = allowance.spenderOrThrow(); - final var effectiveOwner = getEffectiveOwnerAccount(owner, payerId, accountStore); + final var effectiveOwner = getEffectiveOwnerAccount(owner, payerId, accountStore, expiryValidator); final var mutableTokenAllowances = new ArrayList<>(effectiveOwner.tokenAllowances()); updateTokenAllowance(mutableTokenAllowances, amount, spender, tokenId); @@ -387,12 +402,13 @@ private void updateTokenAllowance( * spenderNum} doesn't exist in the map the allowance will be inserted. If the key exists, * existing allowance values will be replaced with new allowances given in operation * - * @param nftAllowances the list of nft allowances - * @param payerId the payer account id - * @param accountStore the account store - * @param tokenStore the token store - * @param uniqueTokenStore the unique token store + * @param nftAllowances the list of nft allowances + * @param payerId the payer account id + * @param accountStore the account store + * @param tokenStore the token store + * @param uniqueTokenStore the unique token store * @param allowanceMaxAccountLimit the {@link HederaConfig} config allowance max account limit + * @param expiryValidator the expiry validator */ protected void applyNftAllowances( final List nftAllowances, @@ -400,13 +416,14 @@ protected void applyNftAllowances( @NonNull final WritableAccountStore accountStore, @NonNull final WritableTokenStore tokenStore, @NonNull final WritableNftStore uniqueTokenStore, - final int allowanceMaxAccountLimit) { + final int allowanceMaxAccountLimit, + @NonNull final ExpiryValidator expiryValidator) { for (final var allowance : nftAllowances) { final var owner = allowance.owner(); final var tokenId = allowance.tokenIdOrThrow(); final var spender = allowance.spenderOrThrow(); - final var effectiveOwner = getEffectiveOwnerAccount(owner, payerId, accountStore); + final var effectiveOwner = getEffectiveOwnerAccount(owner, payerId, accountStore, expiryValidator); final var mutableNftAllowances = new ArrayList<>(effectiveOwner.approveForAllNftAllowances()); if (allowance.hasApprovedForAll()) { @@ -456,8 +473,11 @@ public void updateSpender( final var serialsSet = new HashSet<>(serialNums); for (final var serialNum : serialsSet) { - final var nft = uniqueTokenStore.get(tokenId, serialNum); - final var token = tokenStore.get(tokenId); + final var nftId = + NftID.newBuilder().serialNumber(serialNum).tokenId(tokenId).build(); + final var nft = TokenHandlerHelper.getIfUsable(nftId, uniqueTokenStore); + final var token = TokenHandlerHelper.getIfUsable( + tokenId, tokenStore, TokenHandlerHelper.TokenValidations.PERMIT_PAUSED); final AccountID accountOwner = owner.accountId(); validateTrue(isValidOwner(nft, accountOwner, token), SENDER_DOES_NOT_OWN_NFT_SERIAL_NO); @@ -508,15 +528,18 @@ private int lookupSpenderAndToken( * Returns the effective owner account. If the owner is not present or owner is same as payer. * Since we are modifying the payer account in the same transaction for each allowance if owner is not specified, * we need to get the payer account each time from the modifications map. - * @param owner owner of the allowance - * @param payerId payer of the transaction - * @param accountStore account store + * + * @param owner owner of the allowance + * @param payerId payer of the transaction + * @param accountStore account store + * @param expiryValidator the expiry validator * @return effective owner account */ private static Account getEffectiveOwnerAccount( @Nullable final AccountID owner, @NonNull final AccountID payerId, - @NonNull final ReadableAccountStore accountStore) { + @NonNull final ReadableAccountStore accountStore, + @NonNull final ExpiryValidator expiryValidator) { final var ownerNum = owner != null ? owner.accountNumOrElse(0L) : 0L; if (ownerNum == 0 || ownerNum == payerId.accountNumOrThrow()) { // The payer would have been modified in the same transaction for previous allowances @@ -524,8 +547,8 @@ private static Account getEffectiveOwnerAccount( return accountStore.getAccountById(payerId); } else { // If owner is in modifications get the modified account from state - final var ownerAccount = accountStore.getAccountById(owner); - validateTrue(ownerAccount != null, INVALID_ALLOWANCE_OWNER_ID); + final var ownerAccount = + TokenHandlerHelper.getIfUsable(owner, accountStore, expiryValidator, INVALID_ALLOWANCE_OWNER_ID); return ownerAccount; } } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteAllowanceHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteAllowanceHandler.java index 14684156873c..78812d9b856c 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteAllowanceHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteAllowanceHandler.java @@ -18,7 +18,6 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.EMPTY_ALLOWANCES; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ALLOWANCE_OWNER_ID; -import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_NFT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_PAYER_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.SENDER_DOES_NOT_OWN_NFT_SERIAL_NO; @@ -33,6 +32,7 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.HederaFunctionality; +import com.hedera.hapi.node.base.NftID; import com.hedera.hapi.node.base.SubType; import com.hedera.hapi.node.base.TokenID; import com.hedera.hapi.node.state.token.Account; @@ -42,6 +42,7 @@ import com.hedera.node.app.service.token.impl.WritableAccountStore; import com.hedera.node.app.service.token.impl.WritableNftStore; import com.hedera.node.app.service.token.impl.WritableTokenStore; +import com.hedera.node.app.service.token.impl.util.TokenHandlerHelper; import com.hedera.node.app.service.token.impl.validators.DeleteAllowanceValidator; import com.hedera.node.app.spi.fees.FeeContext; import com.hedera.node.app.spi.fees.Fees; @@ -113,8 +114,8 @@ public void handle(@NonNull final HandleContext context) throws HandleException final var accountStore = context.storeFactory().writableStore(WritableAccountStore.class); // validate payer account exists - final var payerAccount = accountStore.getAccountById(payer); - validateTrue(payerAccount != null, INVALID_PAYER_ACCOUNT_ID); + final var payerAccount = TokenHandlerHelper.getIfUsable( + payer, accountStore, context.expiryValidator(), INVALID_PAYER_ACCOUNT_ID); // validate the transaction body fields that include state or configuration // We can use payerAccount for validations since it's not mutated in validateSemantics @@ -180,9 +181,10 @@ private void deleteNftSerials( final var owner = getEffectiveOwner(allowance.owner(), payerAccount, accountStore, expiryValidator); final var token = tokenStore.get(tokenId); for (final var serial : serialNums) { - final var nft = nftStore.get(tokenId, serial); + final var nftId = + NftID.newBuilder().serialNumber(serial).tokenId(tokenId).build(); + final var nft = TokenHandlerHelper.getIfUsable(nftId, nftStore); - validateTrue(nft != null, INVALID_NFT_ID); final AccountID accountOwner = owner.accountId(); validateTrue(isValidOwner(nft, accountOwner, token), SENDER_DOES_NOT_OWN_NFT_SERIAL_NO); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoUpdateHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoUpdateHandler.java index ea67624613a8..4cd8e94db1f7 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoUpdateHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoUpdateHandler.java @@ -55,6 +55,7 @@ import com.hedera.node.app.service.token.CryptoSignatureWaivers; import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.service.token.impl.WritableAccountStore; +import com.hedera.node.app.service.token.impl.util.TokenHandlerHelper; import com.hedera.node.app.service.token.impl.validators.StakingValidator; import com.hedera.node.app.service.token.records.CryptoUpdateStreamBuilder; import com.hedera.node.app.spi.fees.FeeCalculator; @@ -147,8 +148,8 @@ public void handle(@NonNull final HandleContext context) { // validate update account exists final var accountStore = context.storeFactory().writableStore(WritableAccountStore.class); - final var targetAccount = accountStore.get(target); - validateTrue(targetAccount != null, INVALID_ACCOUNT_ID); + final var targetAccount = + TokenHandlerHelper.getIfUsable(target, accountStore, context.expiryValidator(), INVALID_ACCOUNT_ID); context.attributeValidator().validateMemo(op.memo()); // Customize the account based on fields set in transaction body diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAccountWipeHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAccountWipeHandler.java index eca905121322..63c9074cff55 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAccountWipeHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAccountWipeHandler.java @@ -19,7 +19,6 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.ACCOUNT_DOES_NOT_OWN_WIPED_NFT; import static com.hedera.hapi.node.base.ResponseCodeEnum.ACCOUNT_KYC_NOT_GRANTED_FOR_TOKEN; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; -import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_NFT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_WIPING_AMOUNT; import static com.hedera.node.app.hapi.fees.usage.SingletonUsageProperties.USAGE_PROPERTIES; @@ -169,8 +168,11 @@ public void handle(@NonNull final HandleContext context) throws HandleException // Load and validate the nfts for (final Long nftSerial : nftSerialNums) { - final var nft = nftStore.get(tokenId, nftSerial); - validateTrue(nft != null, INVALID_NFT_ID); + final var nftId = NftID.newBuilder() + .serialNumber(nftSerial) + .tokenId(tokenId) + .build(); + final var nft = TokenHandlerHelper.getIfUsable(nftId, nftStore); final var nftOwner = nft.ownerId(); validateTrue(Objects.equals(nftOwner, unaliasedId), ACCOUNT_DOES_NOT_OWN_WIPED_NFT); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAssociateToAccountHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAssociateToAccountHandler.java index 1a51c5521298..288a1577a1b2 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAssociateToAccountHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAssociateToAccountHandler.java @@ -16,7 +16,6 @@ package com.hedera.node.app.service.token.impl.handlers; -import static com.hedera.hapi.node.base.ResponseCodeEnum.ACCOUNT_DELETED; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_ENTITIES_IN_PRICE_REGIME_HAVE_BEEN_CREATED; @@ -29,7 +28,6 @@ import static com.hedera.node.app.service.token.impl.handlers.BaseCryptoHandler.hasAccountNumOrAlias; import static com.hedera.node.app.service.token.impl.util.TokenHandlerHelper.getIfUsable; import static com.hedera.node.app.spi.fees.Fees.CONSTANT_FEE_DATA; -import static com.hedera.node.app.spi.workflows.HandleException.validateFalse; import static com.hedera.node.app.spi.workflows.HandleException.validateTrue; import static com.hedera.node.app.spi.workflows.PreCheckException.validateFalsePreCheck; import static com.hedera.node.app.spi.workflows.PreCheckException.validateTruePreCheck; @@ -50,9 +48,11 @@ import com.hedera.node.app.service.token.ReadableTokenStore; import com.hedera.node.app.service.token.impl.WritableAccountStore; import com.hedera.node.app.service.token.impl.WritableTokenRelationStore; +import com.hedera.node.app.service.token.impl.util.TokenHandlerHelper; import com.hedera.node.app.service.token.impl.validators.TokenListChecks; import com.hedera.node.app.spi.fees.FeeContext; import com.hedera.node.app.spi.fees.Fees; +import com.hedera.node.app.spi.validation.ExpiryValidator; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.PreCheckException; @@ -102,7 +102,14 @@ public void handle(@NonNull final HandleContext context) throws HandleException final var accountStore = storeFactory.writableStore(WritableAccountStore.class); final var tokenRelStore = storeFactory.writableStore(WritableTokenRelationStore.class); final var validated = validateSemantics( - tokenIds, op.accountOrThrow(), tokensConfig, entitiesConfig, accountStore, tokenStore, tokenRelStore); + tokenIds, + op.accountOrThrow(), + tokensConfig, + entitiesConfig, + accountStore, + tokenStore, + tokenRelStore, + context.expiryValidator()); // Now that we've validated we can link all the new token IDs to the account, // create the corresponding token relations and update the account @@ -133,7 +140,8 @@ private Validated validateSemantics( @NonNull final EntitiesConfig entitiesConfig, @NonNull final WritableAccountStore accountStore, @NonNull final ReadableTokenStore tokenStore, - @NonNull final WritableTokenRelationStore tokenRelStore) { + @NonNull final WritableTokenRelationStore tokenRelStore, + @NonNull final ExpiryValidator expiryValidator) { requireNonNull(tokenConfig); requireNonNull(entitiesConfig); @@ -142,10 +150,9 @@ private Validated validateSemantics( isTotalNumTokenRelsWithinMax(tokenIds.size(), tokenRelStore, tokenConfig.maxAggregateRels()), MAX_ENTITIES_IN_PRICE_REGIME_HAVE_BEEN_CREATED); - // Check that the account exists - final var account = accountStore.get(accountId); - validateTrue(account != null, INVALID_ACCOUNT_ID); - validateFalse(account.deleted(), ACCOUNT_DELETED); + // Check that the account is usable + final var account = + TokenHandlerHelper.getIfUsable(accountId, accountStore, expiryValidator, INVALID_ACCOUNT_ID); // Check that the given tokens exist and are usable final var tokens = new ArrayList(); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenBurnHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenBurnHandler.java index 4942d0fce953..2c8ad612ef74 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenBurnHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenBurnHandler.java @@ -136,7 +136,8 @@ public void handle(@NonNull final HandleContext context) throws HandleException INVALID_TOKEN_BURN_AMOUNT, accountStore, tokenStore, - tokenRelStore); + tokenRelStore, + context.expiryValidator()); record.newTotalSupply(newTotalSupply); } else { validateTrue(!nftSerialNums.isEmpty(), INVALID_TOKEN_BURN_METADATA); @@ -152,7 +153,14 @@ public void handle(@NonNull final HandleContext context) throws HandleException // Update counts for accounts and token rels final var newTotalSupply = changeSupply( - token, treasuryRel, -nftSerialNums.size(), FAIL_INVALID, accountStore, tokenStore, tokenRelStore); + token, + treasuryRel, + -nftSerialNums.size(), + FAIL_INVALID, + accountStore, + tokenStore, + tokenRelStore, + context.expiryValidator()); // Update treasury's NFT count final var treasuryAcct = accountStore.get(token.treasuryAccountIdOrThrow()); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenCreateHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenCreateHandler.java index 280cee2a2fc8..7f464a7c8cc1 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenCreateHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenCreateHandler.java @@ -163,7 +163,14 @@ public void handle(@NonNull final HandleContext context) { final var treasuryRel = requireNonNull(tokenRelationStore.get(op.treasuryOrThrow(), newTokenId)); if (op.initialSupply() > 0) { // This keeps modified token with minted balance into modifications in token store - mintFungible(newToken, treasuryRel, op.initialSupply(), accountStore, tokenStore, tokenRelationStore); + mintFungible( + newToken, + treasuryRel, + op.initialSupply(), + accountStore, + tokenStore, + tokenRelationStore, + context.expiryValidator()); } // Increment treasury's title count final var treasuryAccount = requireNonNull(accountStore.get(treasuryRel.accountIdOrThrow())); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenFreezeAccountHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenFreezeAccountHandler.java index dc83931d7d05..cf0f278d802d 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenFreezeAccountHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenFreezeAccountHandler.java @@ -19,7 +19,6 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_HAS_NO_FREEZE_KEY; -import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_NOT_ASSOCIATED_TO_ACCOUNT; import static com.hedera.node.app.hapi.fees.usage.token.TokenOpsUsageUtils.TOKEN_OPS_USAGE_UTILS; import static com.hedera.node.app.spi.workflows.HandleException.validateTrue; import static java.util.Objects.requireNonNull; @@ -143,12 +142,8 @@ private TokenRelation validateSemantics( // Check that token exists TokenHandlerHelper.getIfUsable(tokenId, tokenStore); - // Check that the token is associated to the account - final var tokenRel = tokenRelStore.getForModify(accountId, tokenId); - validateTrue(tokenRel != null, TOKEN_NOT_ASSOCIATED_TO_ACCOUNT); - - // Return the token relation - return tokenRel; + // Check that the token is associated to the account, and return it + return TokenHandlerHelper.getIfUsable(accountId, tokenId, tokenRelStore); } @NonNull diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenMintHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenMintHandler.java index 43f6f7d83b04..75c467ff1324 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenMintHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenMintHandler.java @@ -57,6 +57,7 @@ import com.hedera.node.app.service.token.records.TokenMintStreamBuilder; import com.hedera.node.app.spi.fees.FeeContext; import com.hedera.node.app.spi.fees.Fees; +import com.hedera.node.app.spi.validation.ExpiryValidator; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.PreCheckException; @@ -138,8 +139,14 @@ public void handle(@NonNull final HandleContext context) throws HandleException if (token.tokenType() == TokenType.FUNGIBLE_COMMON) { validateTrue(op.amount() >= 0, INVALID_TOKEN_MINT_AMOUNT); // we need to know if treasury mint while creation to ignore supply key exist or not. - long newTotalSupply = - mintFungible(token, treasuryRel, op.amount(), accountStore, tokenStore, tokenRelStore); + long newTotalSupply = mintFungible( + token, + treasuryRel, + op.amount(), + accountStore, + tokenStore, + tokenRelStore, + context.expiryValidator()); recordBuilder.newTotalSupply(newTotalSupply); } else { // get the config needed for validation @@ -159,7 +166,8 @@ public void handle(@NonNull final HandleContext context) throws HandleException accountStore, tokenStore, tokenRelStore, - nftStore); + nftStore, + context.expiryValidator()); recordBuilder.newTotalSupply(tokenStore.get(tokenId).totalSupply()); recordBuilder.serialNumbers(mintedSerials); } @@ -182,14 +190,15 @@ private void validateSemantics(final HandleContext context) { * serial number of the given base unique token, and increments total owned nfts of the * non-fungible token. * - * @param token - the token to mint nfts for - * @param treasuryRel - the treasury relation of the token - * @param metadata - the metadata of the nft to be minted - * @param consensusTime - the consensus time of the transaction - * @param accountStore - the account store - * @param tokenStore - the token store - * @param tokenRelStore - the token relation store - * @param nftStore - the nft store + * @param token - the token to mint nfts for + * @param treasuryRel - the treasury relation of the token + * @param metadata - the metadata of the nft to be minted + * @param consensusTime - the consensus time of the transaction + * @param accountStore - the account store + * @param tokenStore - the token store + * @param tokenRelStore - the token relation store + * @param nftStore - the nft store + * @param expiryValidator - the expiry validator */ private List mintNonFungible( final Token token, @@ -199,7 +208,8 @@ private List mintNonFungible( @NonNull final WritableAccountStore accountStore, @NonNull final WritableTokenStore tokenStore, @NonNull final WritableTokenRelationStore tokenRelStore, - @NonNull final WritableNftStore nftStore) { + @NonNull final WritableNftStore nftStore, + @NonNull final ExpiryValidator expiryValidator) { final var metadataCount = metadata.size(); validateFalse(metadata.isEmpty(), INVALID_TOKEN_MINT_METADATA); @@ -207,15 +217,23 @@ private List mintNonFungible( final var tokenId = treasuryRel.tokenId(); // get the treasury account - var treasuryAccount = accountStore.get(treasuryRel.accountIdOrThrow()); - validateTrue(treasuryAccount != null, INVALID_TREASURY_ACCOUNT_FOR_TOKEN); + var treasuryAccount = TokenHandlerHelper.getIfUsable( + treasuryRel.accountIdOrThrow(), accountStore, expiryValidator, INVALID_TREASURY_ACCOUNT_FOR_TOKEN); // get the latest serial number minted for the token var currentSerialNumber = token.lastUsedSerialNumber(); validateTrue((currentSerialNumber + metadataCount) <= MAX_SERIAL_NO_ALLOWED, SERIAL_NUMBER_LIMIT_REACHED); // Change the supply on token - changeSupply(token, treasuryRel, metadataCount, FAIL_INVALID, accountStore, tokenStore, tokenRelStore); + changeSupply( + token, + treasuryRel, + metadataCount, + FAIL_INVALID, + accountStore, + tokenStore, + tokenRelStore, + expiryValidator); // Since changeSupply call above modifies the treasuryAccount, we need to get the modified treasuryAccount treasuryAccount = accountStore.get(treasuryRel.accountIdOrThrow()); // The token is modified in previous step, so we need to get the modified token diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenUpdateHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenUpdateHandler.java index 2752d16a73ed..8977c733730a 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenUpdateHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenUpdateHandler.java @@ -58,6 +58,7 @@ import com.hedera.node.app.service.token.impl.WritableAccountStore; import com.hedera.node.app.service.token.impl.WritableTokenRelationStore; import com.hedera.node.app.service.token.impl.WritableTokenStore; +import com.hedera.node.app.service.token.impl.util.TokenHandlerHelper; import com.hedera.node.app.service.token.impl.util.TokenKey; import com.hedera.node.app.service.token.impl.validators.TokenUpdateValidator; import com.hedera.node.app.service.token.records.TokenUpdateStreamBuilder; @@ -170,8 +171,8 @@ public void handle(@NonNull final HandleContext context) throws HandleException // Treasury can be modified when it owns NFTs when the property "tokens.nfts.useTreasuryWildcards" // is enabled. if (!tokensConfig.nftsUseTreasuryWildcards() && token.tokenType().equals(NON_FUNGIBLE_UNIQUE)) { - final var existingTreasuryRel = tokenRelStore.get(existingTreasury, tokenId); - validateTrue(existingTreasuryRel != null, INVALID_TREASURY_ACCOUNT_FOR_TOKEN); + final var existingTreasuryRel = + TokenHandlerHelper.getIfUsable(existingTreasury, tokenId, tokenRelStore); final var tokenRelBalance = existingTreasuryRel.balance(); validateTrue(tokenRelBalance == 0, CURRENT_TREASURY_STILL_OWNS_NFTS); } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/module-info.java b/hedera-node/hedera-token-service-impl/src/main/java/module-info.java index 8b2c1e55c8b1..e5ba7bd85c1e 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/module-info.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/module-info.java @@ -28,7 +28,8 @@ exports com.hedera.node.app.service.token.impl.handlers to com.hedera.node.app, - com.hedera.node.app.service.token.impl.test; + com.hedera.node.app.service.token.impl.test, + com.hedera.node.test.clients; exports com.hedera.node.app.service.token.impl; exports com.hedera.node.app.service.token.impl.api to com.hedera.node.app, diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoDeleteAllowanceHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoDeleteAllowanceHandlerTest.java index 5536c049a51d..fe206a575965 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoDeleteAllowanceHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoDeleteAllowanceHandlerTest.java @@ -71,6 +71,7 @@ public void setUp() { given(handleContext.configuration()).willReturn(configuration); given(handleContext.expiryValidator()).willReturn(expiryValidator); + given(expiryValidator.expirationStatus(any(), anyBoolean(), anyLong())).willReturn(OK); } @Test diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoUpdateHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoUpdateHandlerTest.java index fff176cb6b2a..f29e8e781472 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoUpdateHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoUpdateHandlerTest.java @@ -27,6 +27,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_EXPIRATION_TIME; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_STAKING_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.MEMO_TOO_LONG; +import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; import static com.hedera.hapi.node.base.ResponseCodeEnum.PROXY_ACCOUNT_ID_FIELD_IS_DEPRECATED; import static com.hedera.hapi.node.base.ResponseCodeEnum.REQUESTED_NUM_AUTOMATIC_ASSOCIATIONS_EXCEEDS_ASSOCIATION_LIMIT; import static com.hedera.hapi.node.base.ResponseCodeEnum.STAKING_NOT_ENABLED; @@ -146,6 +147,9 @@ public void setUp() { updateReadableAccountStore(Map.of(updateAccountId.accountNum(), updateAccount, accountNum, account)); lenient().when(handleContext.savepointStack()).thenReturn(stack); lenient().when(stack.getBaseBuilder(CryptoUpdateStreamBuilder.class)).thenReturn(streamBuilder); + lenient() + .when(expiryValidator.expirationStatus(any(), anyBoolean(), anyLong())) + .thenReturn(OK); subject = new CryptoUpdateHandler(waivers); } diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenAssociateToAccountHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenAssociateToAccountHandlerTest.java index a13e38f7a1fa..22bcb3d20355 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenAssociateToAccountHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenAssociateToAccountHandlerTest.java @@ -18,6 +18,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID; +import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKENS_PER_ACCOUNT_LIMIT_EXCEEDED; import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_ID_REPEATED_IN_TOKEN_LIST; import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_IS_PAUSED; @@ -33,7 +34,11 @@ import static com.hedera.node.app.service.token.impl.test.keys.KeysAndIds.MISC_ACCOUNT; import static com.hedera.node.app.spi.fixtures.workflows.ExceptionConditions.responseCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; +import static org.mockito.Mock.Strictness.LENIENT; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; @@ -53,6 +58,7 @@ import com.hedera.node.app.service.token.impl.test.handlers.util.ParityTestBase; import com.hedera.node.app.spi.fixtures.workflows.FakePreHandleContext; import com.hedera.node.app.spi.store.StoreFactory; +import com.hedera.node.app.spi.validation.ExpiryValidator; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.PreCheckException; @@ -86,8 +92,14 @@ class TokenAssociateToAccountHandlerTest { @Mock private StoreFactory storeFactory; + @Mock(strictness = LENIENT) + private ExpiryValidator expiryValidator; + @BeforeEach void setUp() { + lenient().when(context.expiryValidator()).thenReturn(expiryValidator); + given(expiryValidator.expirationStatus(any(), anyBoolean(), anyLong())).willReturn(OK); + subject = new TokenAssociateToAccountHandler(); } diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenBurnHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenBurnHandlerTest.java index 8ea4083b7937..42e7614403c9 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenBurnHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenBurnHandlerTest.java @@ -26,6 +26,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TRANSACTION_BODY; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TREASURY_ACCOUNT_FOR_TOKEN; import static com.hedera.hapi.node.base.ResponseCodeEnum.NOT_SUPPORTED; +import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_HAS_NO_SUPPLY_KEY; import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_IS_PAUSED; import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_NOT_ASSOCIATED_TO_ACCOUNT; @@ -42,6 +43,9 @@ import static com.hedera.node.app.spi.workflows.record.StreamBuilder.ReversingBehavior.REVERSIBLE; import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; @@ -72,6 +76,7 @@ import com.hedera.node.app.spi.fixtures.state.MapWritableStates; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.store.StoreFactory; +import com.hedera.node.app.spi.validation.ExpiryValidator; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.PreCheckException; @@ -868,6 +873,11 @@ private HandleContext mockContext(TransactionBody txn) { given(storeFactory.writableStore(WritableNftStore.class)).willReturn(writableNftStore); given(context.configuration()).willReturn(configuration); lenient().when(context.savepointStack()).thenReturn(stack); + final var expiryValidator = mock(ExpiryValidator.class); + lenient().when(context.expiryValidator()).thenReturn(expiryValidator); + lenient() + .when(expiryValidator.expirationStatus(any(), anyBoolean(), anyLong())) + .thenReturn(OK); return context; } diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenFreezeAccountHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenFreezeAccountHandlerTest.java index ec9a9964c522..17eaed27a331 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenFreezeAccountHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenFreezeAccountHandlerTest.java @@ -253,7 +253,7 @@ void tokenRelNotFound() throws HandleException { given(readableTokenStore.getTokenMeta(token)).willReturn(tokenMetaWithFreezeKey()); given(readableAccountStore.getAccountById(ACCOUNT_13257)) .willReturn(Account.newBuilder().accountId(ACCOUNT_13257).build()); - given(tokenRelStore.getForModify(ACCOUNT_13257, token)).willReturn(null); + given(tokenRelStore.get(ACCOUNT_13257, token)).willReturn(null); given(expiryValidator.expirationStatus(EntityType.ACCOUNT, false, 0)) .willReturn(OK); final var txn = newFreezeTxn(token); @@ -273,7 +273,7 @@ void tokenRelFreezeSuccessful() { given(readableTokenStore.getTokenMeta(token)).willReturn(tokenMetaWithFreezeKey()); given(readableAccountStore.getAccountById(ACCOUNT_13257)) .willReturn(Account.newBuilder().accountId(ACCOUNT_13257).build()); - given(tokenRelStore.getForModify(ACCOUNT_13257, token)) + given(tokenRelStore.get(ACCOUNT_13257, token)) .willReturn(TokenRelation.newBuilder() .tokenId(token) .accountId(ACCOUNT_13257) diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/AssociateTokenRecipientsStepTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/AssociateTokenRecipientsStepTest.java index 213b502b6bec..a27935c60d8a 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/AssociateTokenRecipientsStepTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/AssociateTokenRecipientsStepTest.java @@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.lenient; import com.hedera.hapi.node.base.AccountAmount; import com.hedera.hapi.node.base.AccountID; @@ -115,7 +116,9 @@ void givenValidTxn() { .build(); given(handleContext.configuration()).willReturn(configuration); given(handleContext.expiryValidator()).willReturn(expiryValidator); - given(expiryValidator.expirationStatus(any(), anyBoolean(), anyLong())).willReturn(ResponseCodeEnum.OK); + lenient() + .when(expiryValidator.expirationStatus(any(), anyBoolean(), anyLong())) + .thenReturn(ResponseCodeEnum.OK); given(handleContext.savepointStack()).willReturn(stack); } diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/CryptoTokenHandlerTestBase.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/CryptoTokenHandlerTestBase.java index f98ce345e12d..f573577db529 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/CryptoTokenHandlerTestBase.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/CryptoTokenHandlerTestBase.java @@ -26,8 +26,12 @@ import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.ContractID; @@ -37,6 +41,7 @@ import com.hedera.hapi.node.base.NftID; import com.hedera.hapi.node.base.PendingAirdropId; import com.hedera.hapi.node.base.PendingAirdropValue; +import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.base.Timestamp; import com.hedera.hapi.node.base.TokenID; import com.hedera.hapi.node.base.TokenSupplyType; @@ -86,6 +91,7 @@ import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.store.StoreFactory; +import com.hedera.node.app.spi.validation.ExpiryValidator; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.PreHandleContext; import com.hedera.node.config.VersionedConfigImpl; @@ -1173,7 +1179,13 @@ protected void givenStoresAndConfig(final HandleContext context) { .willReturn(readableRewardsStore); given(storeFactory.writableStore(WritableNetworkStakingRewardsStore.class)) .willReturn(writableRewardsStore); - given(context.dispatchComputeFees(any(), any(), any())).willReturn(new Fees(1l, 2l, 3l)); + given(context.dispatchComputeFees(any(), any(), any())).willReturn(new Fees(1L, 2L, 3L)); + + final var expiryValidator = mock(ExpiryValidator.class); + lenient().when(context.expiryValidator()).thenReturn(expiryValidator); + lenient() + .when(expiryValidator.expirationStatus(any(), anyBoolean(), anyLong())) + .thenReturn(ResponseCodeEnum.OK); } protected void givenStoresAndConfig(final FinalizeContext context) { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoUpdateSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoUpdateSuite.java index 04c5f8d0271e..4b038e755376 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoUpdateSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoUpdateSuite.java @@ -35,6 +35,7 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractUpdate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoDelete; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoUpdate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; @@ -50,6 +51,7 @@ import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.EXPECT_STREAMLINED_INGEST_RECORDS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; @@ -57,6 +59,7 @@ import static com.hedera.services.bdd.suites.HapiSuite.ZERO_BYTE_MEMO; import static com.hedera.services.bdd.suites.contract.hapi.ContractUpdateSuite.ADMIN_KEY; import static com.hederahashgraph.api.proto.java.HederaFunctionality.CryptoUpdate; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_DELETED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.EXISTING_AUTOMATIC_ASSOCIATIONS_EXCEED_GIVEN_LIMIT; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ADMIN_KEY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_EXPIRATION_TIME; @@ -549,4 +552,16 @@ final Stream updateMaxAutoAssociationsWorks() { contractUpdate(CONTRACT).newMaxAutomaticAssociations(-1).hasKnownStatus(SUCCESS), getContractInfo(CONTRACT).has(contractWith().maxAutoAssociations(-1))); } + + @HapiTest + final Stream deletedAccountCannotBeUpdated() { + final var accountToDelete = "accountToDelete"; + return hapiTest( + cryptoCreate(accountToDelete).declinedReward(false), + cryptoDelete(accountToDelete), + cryptoUpdate(accountToDelete) + .payingWith(DEFAULT_PAYER) + .newDeclinedReward(true) + .hasKnownStatus(ACCOUNT_DELETED)); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenAssociationSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenAssociationSpecs.java index 5a7201250925..135dcb515a0c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenAssociationSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenAssociationSpecs.java @@ -71,6 +71,7 @@ import static com.hederahashgraph.api.proto.java.TokenFreezeStatus.Unfrozen; import static com.hederahashgraph.api.proto.java.TokenKycStatus.Granted; import static com.hederahashgraph.api.proto.java.TokenKycStatus.KycNotApplicable; +import static com.hederahashgraph.api.proto.java.TokenType.FUNGIBLE_COMMON; import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; import com.google.protobuf.ByteString; @@ -583,4 +584,22 @@ public static HapiSpecOperation[] basicKeysAndTokens() { tokenCreate(VANILLA_TOKEN).treasury(TOKEN_TREASURY) }; } + + @HapiTest + final Stream deletedAccountCannotBeAssociatedToToken() { + final var accountToDelete = "accountToDelete"; + final var token = "anyToken"; + final var supplyKey = "supplyKey"; + return hapiTest( + newKeyNamed(supplyKey), + cryptoCreate(accountToDelete), + cryptoDelete(accountToDelete), + tokenCreate(token) + .treasury(DEFAULT_PAYER) + .tokenType(FUNGIBLE_COMMON) + .initialSupply(1000L) + .supplyKey(supplyKey) + .hasKnownStatus(SUCCESS), + tokenAssociate(accountToDelete, token).hasKnownStatus(ACCOUNT_DELETED)); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenTransactSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenTransactSpecs.java index 08e6edd9fd18..d304c11f5383 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenTransactSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenTransactSpecs.java @@ -20,6 +20,7 @@ import static com.hedera.services.bdd.junit.TestTags.ADHOC; import static com.hedera.services.bdd.junit.TestTags.TOKEN; 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.assertions.AccountInfoAsserts.accountWith; import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.changeFromSnapshot; import static com.hedera.services.bdd.spec.assertions.AutoAssocAsserts.accountTokenPairs; @@ -44,6 +45,7 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenDelete; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenDissociate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenFreeze; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUpdate; import static com.hedera.services.bdd.spec.transactions.token.CustomFeeSpecs.fixedHbarFee; import static com.hedera.services.bdd.spec.transactions.token.CustomFeeSpecs.fixedHbarFeeInheritingRoyaltyCollector; @@ -2038,6 +2040,25 @@ final Stream collectorIsChargedRoyaltyFallbackFeeUnlessExempt() { .logged()); } + @HapiTest + final Stream tokenFrozenOnTreasuryCannotBeFrozenAgain() { + final var alice = "alice"; + final var token = "token"; + final var freezeKey = "freezeKey"; + return hapiTest( + newKeyNamed(freezeKey), + cryptoCreate(alice), + tokenCreate(token) + .treasury(DEFAULT_PAYER) + .tokenType(FUNGIBLE_COMMON) + .initialSupply(1000L) + .freezeKey(freezeKey) + .hasKnownStatus(SUCCESS), + tokenAssociate(alice, token), + tokenFreeze(token, alice).hasKnownStatus(SUCCESS), + tokenFreeze(token, alice).hasKnownStatus(ACCOUNT_FROZEN_FOR_TOKEN)); + } + private static final String TXN_TRIGGERING_COLLECTOR_EXEMPT_FEE = "collectorExempt"; private static final String TXN_TRIGGERING_COLLECTOR_NON_EXEMPT_FEE = "collectorNonExempt"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenUpdateSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenUpdateSpecs.java index 45335e53072d..0d00678b0d0c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenUpdateSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenUpdateSpecs.java @@ -34,6 +34,7 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenDelete; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenFeeScheduleUpdate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenFreeze; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUnfreeze; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUpdate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.wipeTokenAccount; @@ -47,6 +48,7 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.doSeveralWithStartupConfigNow; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.doWithStartupConfigNow; 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.specOps; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.suites.HapiSuite.ADDRESS_BOOK_CONTROL; @@ -59,6 +61,7 @@ import static com.hedera.services.bdd.suites.HapiSuite.ZERO_BYTE_MEMO; import static com.hedera.services.bdd.suites.HapiSuite.salted; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_DELETED; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_FROZEN_FOR_TOKEN; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ADMIN_KEY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_AUTORENEW_ACCOUNT; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_CUSTOM_FEE_SCHEDULE_KEY; @@ -68,6 +71,7 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOKEN_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ZERO_BYTE_IN_STRING; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NO_REMAINING_AUTOMATIC_ASSOCIATIONS; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_FEE_SCHEDULE_KEY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_FREEZE_KEY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_KYC_KEY; @@ -85,6 +89,7 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; +import com.hedera.services.bdd.junit.LeakyHapiTest; import com.hedera.services.bdd.spec.queries.crypto.ExpectedTokenRel; import com.hedera.services.bdd.spec.transactions.TxnUtils; import com.hederahashgraph.api.proto.java.TokenFreezeStatus; @@ -1014,4 +1019,29 @@ final Stream updateUniqueTreasuryWithNfts() { getAccountInfo("newTreasury").logged(), getTokenInfo("tbu").hasTreasury("newTreasury")); } + + @LeakyHapiTest(overrides = {"tokens.nfts.useTreasuryWildcards"}) + final Stream tokenFrozenOnTreasuryCannotBeUpdated() { + final var accountToFreeze = "account"; + final var adminKey = "adminKey"; + final var tokenToFreeze = "token"; + return hapiTest( + overriding("tokens.nfts.useTreasuryWildcards", "false"), + newKeyNamed(adminKey), + cryptoCreate(accountToFreeze), + tokenCreate(tokenToFreeze) + .treasury(accountToFreeze) + .tokenType(NON_FUNGIBLE_UNIQUE) + .initialSupply(0) + .supplyKey(adminKey) + .freezeKey(adminKey) + .adminKey(adminKey) + .hasKnownStatus(SUCCESS), + tokenFreeze(tokenToFreeze, accountToFreeze), + tokenAssociate(DEFAULT_PAYER, tokenToFreeze), + tokenUpdate(tokenToFreeze) + .treasury(DEFAULT_PAYER) + .signedBy(DEFAULT_PAYER, adminKey) + .hasKnownStatus(ACCOUNT_FROZEN_FOR_TOKEN)); + } }