Skip to content

Commit

Permalink
fix: Check for usability in various ops (#15390)
Browse files Browse the repository at this point in the history
Signed-off-by: Matt Hess <[email protected]>
  • Loading branch information
mhess-swl authored Sep 11, 2024
1 parent ab8b709 commit 1c641ab
Show file tree
Hide file tree
Showing 23 changed files with 302 additions and 96 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -77,25 +79,35 @@ 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,
@NonNull final TokenRelation treasuryRel,
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);
}

/**
Expand All @@ -105,13 +117,14 @@ protected long mintFungible(
* <p>
* <b>Note:</b> 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,
Expand All @@ -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);
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -241,34 +244,44 @@ 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<CryptoAllowance> 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);

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();
Expand Down Expand Up @@ -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<TokenAllowance> 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);
Expand Down Expand Up @@ -387,26 +402,28 @@ 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<NftAllowance> nftAllowances,
@NonNull final AccountID payerId,
@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()) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -508,24 +528,27 @@ 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
// So, get it from modifications map.
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;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 1c641ab

Please sign in to comment.