com.hedera.node.app.service.token.impl.util.TokenHandlerHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of app-service-token-impl Show documentation
Show all versions of app-service-token-impl Show documentation
Default Hedera Token Service Implementation
/*
* Copyright (C) 2023-2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hedera.node.app.service.token.impl.util;
/*
* Copyright (C) 2023 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import static com.hedera.hapi.node.base.ResponseCodeEnum.ACCOUNT_DELETED;
import static com.hedera.hapi.node.base.ResponseCodeEnum.ACCOUNT_FROZEN_FOR_TOKEN;
import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_AUTORENEW_ACCOUNT;
import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID;
import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TREASURY_ACCOUNT_FOR_TOKEN;
import static com.hedera.hapi.node.base.ResponseCodeEnum.OK;
import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_IS_PAUSED;
import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_NOT_ASSOCIATED_TO_ACCOUNT;
import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_WAS_DELETED;
import static com.hedera.hapi.util.HapiUtils.EMPTY_KEY_LIST;
import static com.hedera.node.app.service.token.impl.util.TokenHandlerHelper.TokenValidations.REQUIRE_NOT_PAUSED;
import static com.hedera.node.app.spi.workflows.HandleException.validateFalse;
import static com.hedera.node.app.spi.workflows.HandleException.validateTrue;
import static java.util.Objects.requireNonNull;
import com.hedera.hapi.node.base.AccountID;
import com.hedera.hapi.node.base.Key;
import com.hedera.hapi.node.base.ResponseCodeEnum;
import com.hedera.hapi.node.base.TokenID;
import com.hedera.hapi.node.state.token.Account;
import com.hedera.hapi.node.state.token.Token;
import com.hedera.hapi.node.state.token.TokenRelation;
import com.hedera.node.app.service.token.ReadableAccountStore;
import com.hedera.node.app.service.token.ReadableTokenRelationStore;
import com.hedera.node.app.service.token.ReadableTokenStore;
import com.hedera.node.app.spi.validation.EntityType;
import com.hedera.node.app.spi.validation.ExpiryValidator;
import com.hedera.node.app.spi.workflows.HandleException;
import com.hedera.node.app.spi.workflows.PreCheckException;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
/**
* Class for retrieving objects in a certain context. For example, during a {@code handler.handle(...)} call.
* This allows compartmentalizing common validation logic without requiring store implementations to
* throw inappropriately-contextual exceptions, and also abstracts duplicated business logic out of
* multiple handlers.
*/
public class TokenHandlerHelper {
private TokenHandlerHelper() {
throw new UnsupportedOperationException("Utility class only");
}
/**
* Enum to determine the type of account ID, aliased or not aliased
*/
public enum AccountIDType {
/**
* Account ID is aliased
*/
ALIASED_ID,
/**
* Account ID is not aliased
*/
NOT_ALIASED_ID
}
/**
* Returns the account if it exists and is usable. A {@link HandleException} is thrown if the account is invalid.
* Note that this method should also work with account ID's that represent smart contracts
* If the account is deleted the return error code is ACCOUNT_DELETED
*
* @param accountId the ID of the account to get
* @param accountStore the {@link ReadableTokenStore} to use for account retrieval
* @param expiryValidator the {@link ExpiryValidator} to determine if the account is expired
* @param errorIfNotUsable the {@link ResponseCodeEnum} to use if the account is not found/usable
* @throws HandleException if any of the account conditions are not met
* @return the account if it exists and is usable
*/
@NonNull
public static Account getIfUsable(
@NonNull final AccountID accountId,
@NonNull final ReadableAccountStore accountStore,
@NonNull final ExpiryValidator expiryValidator,
@NonNull final ResponseCodeEnum errorIfNotUsable) {
return getIfUsable(
accountId,
accountStore,
expiryValidator,
errorIfNotUsable,
ACCOUNT_DELETED,
AccountIDType.NOT_ALIASED_ID);
}
/**
* Returns the account if it exists and is usable. A {@link HandleException} is thrown if the account is invalid.
* Note that this method should also work with account ID's that represent smart contracts
* If the account is deleted the return error code is ACCOUNT_DELETED
*
* @param accountId the ID of the account to get
* @param accountStore the {@link ReadableTokenStore} to use for account retrieval
* @param expiryValidator the {@link ExpiryValidator} to determine if the account is expired
* @param errorIfNotUsable the {@link ResponseCodeEnum} to use if the account is not found/usable
* @throws HandleException if any of the account conditions are not met
* @return the account if it exists and is usable
*/
@NonNull
public static Account getIfUsableForAliasedId(
@NonNull final AccountID accountId,
@NonNull final ReadableAccountStore accountStore,
@NonNull final ExpiryValidator expiryValidator,
@NonNull final ResponseCodeEnum errorIfNotUsable) {
return getIfUsable(
accountId, accountStore, expiryValidator, errorIfNotUsable, ACCOUNT_DELETED, AccountIDType.ALIASED_ID);
}
/**
* Returns the account if it exists and is usable. A {@link HandleException} is thrown if the account is invalid.
* Note that this method should also work with account ID's that represent smart contracts
* If the account is deleted the return error code is INVALID_AUTORENEW_ACCOUNT
*
* @param accountId the ID of the account to get
* @param accountStore the {@link ReadableTokenStore} to use for account retrieval
* @param expiryValidator the {@link ExpiryValidator} to determine if the account is expired
* @param errorIfNotUsable the {@link ResponseCodeEnum} to use if the account is not found/usable
* @throws HandleException if any of the account conditions are not met
* @return the account if it exists and is usable
*/
@NonNull
public static Account getIfUsableForAutoRenew(
@NonNull final AccountID accountId,
@NonNull final ReadableAccountStore accountStore,
@NonNull final ExpiryValidator expiryValidator,
@NonNull final ResponseCodeEnum errorIfNotUsable) {
return getIfUsable(
accountId,
accountStore,
expiryValidator,
errorIfNotUsable,
INVALID_AUTORENEW_ACCOUNT,
AccountIDType.NOT_ALIASED_ID);
}
/**
* Returns the account if it exists and is usable. A {@link HandleException} is thrown if the account is invalid.
* Note that this method should also work with account ID's that represent smart contracts.
* If the account is deleted the return error code is INVALID_TREASURY_ACCOUNT_FOR_TOKEN
*
* @param accountId the ID of the account to get
* @param accountStore the {@link ReadableTokenStore} to use for account retrieval
* @param expiryValidator the {@link ExpiryValidator} to determine if the account is expired
* @param errorIfNotUsable the {@link ResponseCodeEnum} to use if the account is not found/usable
* @throws HandleException if any of the account conditions are not met
* @return the account if it exists and is usable
*/
@NonNull
public static Account getIfUsableWithTreasury(
@NonNull final AccountID accountId,
@NonNull final ReadableAccountStore accountStore,
@NonNull final ExpiryValidator expiryValidator,
@NonNull final ResponseCodeEnum errorIfNotUsable) {
return getIfUsable(
accountId,
accountStore,
expiryValidator,
errorIfNotUsable,
INVALID_TREASURY_ACCOUNT_FOR_TOKEN,
AccountIDType.NOT_ALIASED_ID);
}
/**
* Returns the account if it exists and is usable. A {@link HandleException} is thrown if the account is invalid.
* @param accountId the ID of the account to get
* @param accountStore the {@link ReadableTokenStore} to use for account retrieval
* @param expiryValidator the {@link ExpiryValidator} to determine if the account is expired
* @param errorIfNotUsable the {@link ResponseCodeEnum} to use if the account is not found/usable
* @param errorOnAccountDeleted the {@link ResponseCodeEnum} to use if the account is deleted
* @param accountIDType the type of account ID
* @return the account if it exists and is usable
*/
@NonNull
public static Account getIfUsable(
@NonNull final AccountID accountId,
@NonNull final ReadableAccountStore accountStore,
@NonNull final ExpiryValidator expiryValidator,
@NonNull final ResponseCodeEnum errorIfNotUsable,
@NonNull final ResponseCodeEnum errorOnAccountDeleted,
@NonNull final AccountIDType accountIDType) {
requireNonNull(accountId);
requireNonNull(accountStore);
requireNonNull(expiryValidator);
requireNonNull(errorIfNotUsable);
requireNonNull(errorOnAccountDeleted);
final Account acct;
if (accountIDType == AccountIDType.ALIASED_ID) {
acct = accountStore.getAliasedAccountById(accountId);
} else {
acct = accountStore.getAccountById(accountId);
}
validateTrue(acct != null, errorIfNotUsable);
final var isContract = acct.smartContract();
validateFalse(acct.deleted(), errorOnAccountDeleted);
final var type = isContract ? EntityType.CONTRACT : EntityType.ACCOUNT;
final var expiryStatus =
expiryValidator.expirationStatus(type, acct.expiredAndPendingRemoval(), acct.tinybarBalance());
validateTrue(expiryStatus == OK, expiryStatus);
return acct;
}
/**
* Enum to determine the type of validations to be performed on the token. If the token is allowed to be paused
* or not
*/
public enum TokenValidations {
/**
* Token should not be paused
*/
REQUIRE_NOT_PAUSED,
/**
* Token can be paused
*/
PERMIT_PAUSED
}
/**
* Returns the token if it exists and is usable. A {@link HandleException} is thrown if the token is invalid
* @param tokenId the ID of the token to get
* @param tokenStore the {@link ReadableTokenStore} to use for token retrieval
* @return the token if it exists and is usable
*/
public static Token getIfUsable(@NonNull final TokenID tokenId, @NonNull final ReadableTokenStore tokenStore) {
return getIfUsable(tokenId, tokenStore, REQUIRE_NOT_PAUSED);
}
/**
* Returns the token if it exists and is usable. A {@link HandleException} is thrown if the token is invalid
*
* @param tokenId the ID of the token to get
* @param tokenStore the {@link ReadableTokenStore} to use for token retrieval
* @param tokenValidations whether validate paused token status
* @throws HandleException if any of the token conditions are not met
* @return the token if it exists and is usable
*/
@NonNull
public static Token getIfUsable(
@NonNull final TokenID tokenId,
@NonNull final ReadableTokenStore tokenStore,
@NonNull final TokenValidations tokenValidations) {
requireNonNull(tokenId);
requireNonNull(tokenStore);
requireNonNull(tokenValidations);
final var token = tokenStore.get(tokenId);
validateTrue(token != null, INVALID_TOKEN_ID);
validateFalse(token.deleted(), TOKEN_WAS_DELETED);
if (tokenValidations == REQUIRE_NOT_PAUSED) {
validateFalse(token.paused(), TOKEN_IS_PAUSED);
}
return token;
}
/**
* Returns the token relation if it exists and is usable
*
* @param accountId the ID of the account
* @param tokenId the ID of the token
* @param tokenRelStore the {@link ReadableTokenRelationStore} to use for token relation retrieval
* @throws HandleException if any of the token relation conditions are not met
* @return the token relation if it exists and is usable
*/
@NonNull
public static TokenRelation getIfUsable(
@NonNull final AccountID accountId,
@NonNull final TokenID tokenId,
@NonNull final ReadableTokenRelationStore tokenRelStore) {
requireNonNull(accountId);
requireNonNull(tokenId);
requireNonNull(tokenRelStore);
final var tokenRel = tokenRelStore.get(accountId, tokenId);
validateTrue(tokenRel != null, TOKEN_NOT_ASSOCIATED_TO_ACCOUNT);
validateTrue(!tokenRel.frozen(), ACCOUNT_FROZEN_FOR_TOKEN);
return tokenRel;
}
/**
* Returns the token relation if it exists and is usable
* @param key the key to check
* @param responseCode the response code to throw if the key is empty
* @throws PreCheckException if the key is empty
*/
public static void verifyNotEmptyKey(@Nullable final Key key, @NonNull final ResponseCodeEnum responseCode)
throws PreCheckException {
if (EMPTY_KEY_LIST.equals(key)) {
throw new PreCheckException(responseCode);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy