From aba3492891c8099412f366e60607856c0b5f3de7 Mon Sep 17 00:00:00 2001 From: Rob Walworth Date: Thu, 6 Jul 2023 16:21:15 -0500 Subject: [PATCH 1/5] Add TokenPauseTransaction Signed-off-by: Rob Walworth --- sdk/main/CMakeLists.txt | 1 + sdk/main/include/TokenPauseTransaction.h | 133 +++++++++++++++++++++++ sdk/main/src/TokenPauseTransaction.cc | 88 +++++++++++++++ 3 files changed, 222 insertions(+) create mode 100644 sdk/main/include/TokenPauseTransaction.h create mode 100644 sdk/main/src/TokenPauseTransaction.cc diff --git a/sdk/main/CMakeLists.txt b/sdk/main/CMakeLists.txt index e799434f..e160a0dd 100644 --- a/sdk/main/CMakeLists.txt +++ b/sdk/main/CMakeLists.txt @@ -84,6 +84,7 @@ add_library(${PROJECT_NAME} STATIC src/TokenNftAllowance.cc src/TokenNftRemoveAllowance.cc src/TokenNftTransfer.cc + src/TokenPauseTransaction.cc src/TokenSupplyType.cc src/TokenTransfer.cc src/TokenType.cc diff --git a/sdk/main/include/TokenPauseTransaction.h b/sdk/main/include/TokenPauseTransaction.h new file mode 100644 index 00000000..54084e0c --- /dev/null +++ b/sdk/main/include/TokenPauseTransaction.h @@ -0,0 +1,133 @@ +/*- + * + * Hedera C++ SDK + * + * Copyright (C) 2020 - 2022 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. + * + */ +#ifndef HEDERA_SDK_CPP_TOKEN_PAUSE_TRANSACTION_H_ +#define HEDERA_SDK_CPP_TOKEN_PAUSE_TRANSACTION_H_ + +#include "TokenId.h" +#include "Transaction.h" + +namespace proto +{ +class TokenPauseTransactionBody; +class TransactionBody; +} + +namespace Hedera +{ +/** + * A token pause transaction prevents the token from being involved in any kind of operation. The token's pause key is + * required to sign the transaction. This is a key that is specified during the creation of a token. If a token has no + * pause key, you will not be able to pause the token. If the pause key was not set during the creation of a token, you + * will not be able to update the token to add this key. + * + * The following operations cannot be performed when a token is paused and will result in a TOKEN_IS_PAUSED status: + * - Updating the token + * - Transferring the token + * - Transferring any other token where it has its paused key in a custom fee schedule + * - Deleting the token + * - Minting or burning a token + * - Freezing or unfreezing an account that holds the token + * - Enabling or disabling KYC + * - Associating or disassociating a token + * - Wiping a token + * + * Once a token is paused, token status will update to paused. To verify if the token's status has been updated to + * paused, you can request the token info via the SDK or use the token info mirror node query. If the token is not + * paused the token status will be unpaused. The token status for tokens that do not have an assigned pause key will + * state PauseNotApplicable. + * + * Transaction Signing Requirements: + * - The pause key of the token. + * - Transaction fee payer account key. + */ +class TokenPauseTransaction : public Transaction +{ +public: + TokenPauseTransaction() = default; + + /** + * Construct from a TransactionBody protobuf object. + * + * @param transactionBody The TransactionBody protobuf object from which to construct. + * @throws std::invalid_argument If the input TransactionBody does not represent a TokenPause transaction. + */ + explicit TokenPauseTransaction(const proto::TransactionBody& transactionBody); + + /** + * Set the ID of the tokens to pause. + * + * @param tokenId The ID of the tokens to pause. + * @return A reference to this TokenPauseTransaction object with the newly-set token ID. + * @throws IllegalStateException If this TokenPauseTransaction is frozen. + */ + TokenPauseTransaction& setTokenId(const TokenId& tokenId); + + /** + * Get the ID of the tokens to pause. + * + * @return The ID of the tokens to pause. + */ + [[nodiscard]] inline TokenId getTokenId() const { return mTokenId; } + +private: + /** + * Derived from Executable. Construct a Transaction protobuf object from this TokenPauseTransaction object. + * + * @param client The Client trying to construct this TokenPauseTransaction. + * @param node The Node to which this TokenPauseTransaction will be sent. This is unused. + * @return A Transaction protobuf object filled with this TokenPauseTransaction object's data. + * @throws UninitializedException If the input client has no operator with which to sign this + * TokenPauseTransaction. + */ + [[nodiscard]] proto::Transaction makeRequest(const Client& client, + const std::shared_ptr& /*node*/) const override; + + /** + * Derived from Executable. Submit this TokenPauseTransaction to a Node. + * + * @param client The Client submitting this TokenPauseTransaction. + * @param deadline The deadline for submitting this TokenPauseTransaction. + * @param node Pointer to the Node to which this TokenPauseTransaction should be submitted. + * @param response Pointer to the TransactionResponse protobuf object that gRPC should populate with the response + * information from the gRPC server. + * @return The gRPC status of the submission. + */ + [[nodiscard]] grpc::Status submitRequest(const Client& client, + const std::chrono::system_clock::time_point& deadline, + const std::shared_ptr& node, + proto::TransactionResponse* response) const override; + + /** + * Build a TokenPauseTransactionBody protobuf object from this TokenPauseTransaction object. + * + * @return A pointer to a TokenPauseTransactionBody protobuf object filled with this TokenPauseTransaction object's + * data. + */ + [[nodiscard]] proto::TokenPauseTransactionBody* build() const; + + /** + * The ID of the token to pause. + */ + TokenId mTokenId; +}; + +} // namespace Hedera + +#endif // HEDERA_SDK_CPP_TOKEN_PAUSE_TRANSACTION_H_ diff --git a/sdk/main/src/TokenPauseTransaction.cc b/sdk/main/src/TokenPauseTransaction.cc new file mode 100644 index 00000000..b4d08bb0 --- /dev/null +++ b/sdk/main/src/TokenPauseTransaction.cc @@ -0,0 +1,88 @@ +/*- + * + * Hedera C++ SDK + * + * Copyright (C) 2020 - 2022 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. + * + */ +#include "TokenPauseTransaction.h" +#include "impl/Node.h" + +#include +#include +#include +#include + +namespace Hedera +{ +//----- +TokenPauseTransaction::TokenPauseTransaction(const proto::TransactionBody& transactionBody) + : Transaction(transactionBody) +{ + if (!transactionBody.has_token_pause()) + { + throw std::invalid_argument("Transaction body doesn't contain TokenPause data"); + } + + const proto::TokenPauseTransactionBody& body = transactionBody.token_pause(); + + if (body.has_token()) + { + mTokenId = TokenId::fromProtobuf(body.token()); + } +} + +//----- +TokenPauseTransaction& TokenPauseTransaction::setTokenId(const TokenId& tokenId) +{ + requireNotFrozen(); + mTokenId = tokenId; + return *this; +} + +//----- +proto::Transaction TokenPauseTransaction::makeRequest(const Client& client, + const std::shared_ptr&) const +{ + proto::TransactionBody transactionBody = generateTransactionBody(client); + transactionBody.set_allocated_token_pause(build()); + + return signTransaction(transactionBody, client); +} + +//----- +grpc::Status TokenPauseTransaction::submitRequest(const Client& client, + const std::chrono::system_clock::time_point& deadline, + const std::shared_ptr& node, + proto::TransactionResponse* response) const +{ + return node->submitTransaction( + proto::TransactionBody::DataCase::kTokenPause, makeRequest(client, node), deadline, response); +} + +//----- +proto::TokenPauseTransactionBody* TokenPauseTransaction::build() const +{ + auto body = std::make_unique(); + + if (!(mTokenId == TokenId())) + { + body->set_allocated_token(mTokenId.toProtobuf().release()); + } + + return body.release(); +} + +} // namespace Hedera From 42a8e1ce0befdd0fcbf118077ce0bcada710c452 Mon Sep 17 00:00:00 2001 From: Rob Walworth Date: Thu, 6 Jul 2023 16:47:18 -0500 Subject: [PATCH 2/5] Add TokenPauseTransaction unit tests Signed-off-by: Rob Walworth --- sdk/main/include/Transaction.h | 4 +- sdk/main/src/Executable.cc | 2 + sdk/main/src/Transaction.cc | 7 +- sdk/tests/unit/CMakeLists.txt | 1 + .../unit/TokenPauseTransactionUnitTests.cc | 82 +++++++++++++++++++ sdk/tests/unit/TransactionTest.cc | 60 ++++++++++++++ 6 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 sdk/tests/unit/TokenPauseTransactionUnitTests.cc diff --git a/sdk/main/include/Transaction.h b/sdk/main/include/Transaction.h index d9b650ae..3c82ab53 100644 --- a/sdk/main/include/Transaction.h +++ b/sdk/main/include/Transaction.h @@ -57,6 +57,7 @@ class TokenDeleteTransaction; class TokenDissociateTransaction; class TokenFeeScheduleUpdateTransaction; class TokenMintTransaction; +class TokenPauseTransaction; class TokenUpdateTransaction; class TokenWipeTransaction; class TransactionResponse; @@ -138,7 +139,8 @@ class Transaction TokenWipeTransaction, TokenBurnTransaction, TokenDissociateTransaction, - TokenFeeScheduleUpdateTransaction>> + TokenFeeScheduleUpdateTransaction, + TokenPauseTransaction>> fromBytes(const std::vector& bytes); /** diff --git a/sdk/main/src/Executable.cc b/sdk/main/src/Executable.cc index 0a464e7b..7e6ac6e4 100644 --- a/sdk/main/src/Executable.cc +++ b/sdk/main/src/Executable.cc @@ -56,6 +56,7 @@ #include "TokenInfo.h" #include "TokenInfoQuery.h" #include "TokenMintTransaction.h" +#include "TokenPauseTransaction.h" #include "TokenUpdateTransaction.h" #include "TokenWipeTransaction.h" #include "TransactionReceipt.h" @@ -373,6 +374,7 @@ template class Executable; template class Executable; template class Executable; +template class Executable; template class Executable; template class Executable; template class Executable; diff --git a/sdk/main/src/Transaction.cc b/sdk/main/src/Transaction.cc index 9fdee2f3..7a12bd38 100644 --- a/sdk/main/src/Transaction.cc +++ b/sdk/main/src/Transaction.cc @@ -43,6 +43,7 @@ #include "TokenDissociateTransaction.h" #include "TokenFeeScheduleUpdateTransaction.h" #include "TokenMintTransaction.h" +#include "TokenPauseTransaction.h" #include "TokenUpdateTransaction.h" #include "TokenWipeTransaction.h" #include "TransactionId.h" @@ -89,7 +90,8 @@ std::pair> + TokenFeeScheduleUpdateTransaction, + TokenPauseTransaction>> Transaction::fromBytes(const std::vector& bytes) { proto::TransactionBody txBody; @@ -179,6 +181,8 @@ Transaction::fromBytes(const std::vector& bytes) return { 22, TokenDissociateTransaction(txBody) }; case proto::TransactionBody::kTokenFeeScheduleUpdate: return { 23, TokenFeeScheduleUpdateTransaction(txBody) }; + case proto::TransactionBody::kTokenPause: + return { 24, TokenPauseTransaction(txBody) }; default: throw std::invalid_argument("Type of transaction cannot be determined from input bytes"); } @@ -501,6 +505,7 @@ template class Transaction; template class Transaction; template class Transaction; template class Transaction; +template class Transaction; template class Transaction; template class Transaction; template class Transaction; diff --git a/sdk/tests/unit/CMakeLists.txt b/sdk/tests/unit/CMakeLists.txt index 466c0311..e72a147a 100644 --- a/sdk/tests/unit/CMakeLists.txt +++ b/sdk/tests/unit/CMakeLists.txt @@ -75,6 +75,7 @@ add_executable(${TEST_PROJECT_NAME} TokenNftAllowanceTest.cc TokenNftRemoveAllowanceTest.cc TokenNftTransferTest.cc + TokenPauseTransactionUnitTests.cc TokenSupplyTypeTest.cc TokenTransferTest.cc TokenTypeTest.cc diff --git a/sdk/tests/unit/TokenPauseTransactionUnitTests.cc b/sdk/tests/unit/TokenPauseTransactionUnitTests.cc new file mode 100644 index 00000000..2ee6ef91 --- /dev/null +++ b/sdk/tests/unit/TokenPauseTransactionUnitTests.cc @@ -0,0 +1,82 @@ +/*- + * + * Hedera C++ SDK + * + * Copyright (C) 2020 - 2022 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. + * + */ +#include "Client.h" +#include "ECDSAsecp256k1PrivateKey.h" +#include "TokenPauseTransaction.h" +#include "exceptions/IllegalStateException.h" + +#include +#include + +using namespace Hedera; + +class TokenPauseTransactionTest : public ::testing::Test +{ +protected: + void SetUp() override { mClient.setOperator(AccountId(), ECDSAsecp256k1PrivateKey::generatePrivateKey().get()); } + + [[nodiscard]] inline const Client& getTestClient() const { return mClient; } + [[nodiscard]] inline const TokenId& getTestTokenId() const { return mTestTokenId; } + +private: + Client mClient; + const TokenId mTestTokenId = TokenId(1ULL); +}; + +//----- +TEST_F(TokenPauseTransactionTest, ConstructTokenPauseTransactionFromTransactionBodyProtobuf) +{ + // Given + auto body = std::make_unique(); + body->set_allocated_token(getTestTokenId().toProtobuf().release()); + + proto::TransactionBody txBody; + txBody.set_allocated_token_pause(body.release()); + + // When + const TokenPauseTransaction tokenPauseTransaction(txBody); + + // Then + EXPECT_EQ(tokenPauseTransaction.getTokenId(), getTestTokenId()); +} + +//----- +TEST_F(TokenPauseTransactionTest, GetSetTokenId) +{ + // Given + TokenPauseTransaction transaction; + + // When + EXPECT_NO_THROW(transaction.setTokenId(getTestTokenId())); + + // Then + EXPECT_EQ(transaction.getTokenId(), getTestTokenId()); +} + +//----- +TEST_F(TokenPauseTransactionTest, GetSetTokenIdFrozen) +{ + // Given + TokenPauseTransaction transaction; + ASSERT_NO_THROW(transaction.freezeWith(getTestClient())); + + // When / Then + EXPECT_THROW(transaction.setTokenId(getTestTokenId()), IllegalStateException); +} \ No newline at end of file diff --git a/sdk/tests/unit/TransactionTest.cc b/sdk/tests/unit/TransactionTest.cc index 4acbde86..d0f97505 100644 --- a/sdk/tests/unit/TransactionTest.cc +++ b/sdk/tests/unit/TransactionTest.cc @@ -39,6 +39,7 @@ #include "TokenDissociateTransaction.h" #include "TokenFeeScheduleUpdateTransaction.h" #include "TokenMintTransaction.h" +#include "TokenPauseTransaction.h" #include "TokenUpdateTransaction.h" #include "TokenWipeTransaction.h" #include "TransferTransaction.h" @@ -1469,3 +1470,62 @@ TEST_F(TransactionTest, TokenFeeScheduleUpdateTransactionFromTransactionBytes) ASSERT_EQ(index, 23); EXPECT_NO_THROW(const TokenFeeScheduleUpdateTransaction tokenFeeScheduleUpdateTransaction = std::get<23>(txVariant)); } + +//----- +TEST_F(TransactionTest, TokenPauseTransactionFromTransactionBodyBytes) +{ + // Given + proto::TransactionBody txBody; + txBody.set_allocated_token_pause(new proto::TokenPauseTransactionBody); + + // When + const auto [index, txVariant] = + Transaction::fromBytes(internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + + // Then + ASSERT_EQ(index, 24); + EXPECT_NO_THROW(const TokenPauseTransaction tokenPauseTransaction = std::get<24>(txVariant)); +} + +//----- +TEST_F(TransactionTest, TokenPauseTransactionFromSignedTransactionBytes) +{ + // Given + proto::TransactionBody txBody; + txBody.set_allocated_token_pause(new proto::TokenPauseTransactionBody); + + proto::SignedTransaction signedTx; + signedTx.set_bodybytes(txBody.SerializeAsString()); + // SignatureMap not required + + // When + const auto [index, txVariant] = + Transaction::fromBytes(internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + + // Then + ASSERT_EQ(index, 24); + EXPECT_NO_THROW(const TokenPauseTransaction tokenPauseTransaction = std::get<24>(txVariant)); +} + +//----- +TEST_F(TransactionTest, TokenPauseTransactionFromTransactionBytes) +{ + // Given + proto::TransactionBody txBody; + txBody.set_allocated_token_pause(new proto::TokenPauseTransactionBody); + + proto::SignedTransaction signedTx; + signedTx.set_bodybytes(txBody.SerializeAsString()); + // SignatureMap not required + + proto::Transaction tx; + tx.set_signedtransactionbytes(signedTx.SerializeAsString()); + + // When + const auto [index, txVariant] = + Transaction::fromBytes(internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + + // Then + ASSERT_EQ(index, 24); + EXPECT_NO_THROW(const TokenPauseTransaction tokenPauseTransaction = std::get<24>(txVariant)); +} From c890ce71ea52d31ea5c215e9dac52a47f36097f5 Mon Sep 17 00:00:00 2001 From: Rob Walworth Date: Fri, 7 Jul 2023 10:41:32 -0500 Subject: [PATCH 3/5] Add TokenPauseTransaction integration tests Signed-off-by: Rob Walworth --- sdk/main/src/impl/Node.cc | 2 + sdk/tests/integration/CMakeLists.txt | 1 + .../TokenPauseTransactionIntegrationTests.cc | 115 ++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 sdk/tests/integration/TokenPauseTransactionIntegrationTests.cc diff --git a/sdk/main/src/impl/Node.cc b/sdk/main/src/impl/Node.cc index 2f6ee587..63f6b17c 100644 --- a/sdk/main/src/impl/Node.cc +++ b/sdk/main/src/impl/Node.cc @@ -206,6 +206,8 @@ grpc::Status Node::submitTransaction(proto::TransactionBody::DataCase funcEnum, return mTokenStub->updateTokenFeeSchedule(&context, transaction, response); case proto::TransactionBody::DataCase::kTokenMint: return mTokenStub->mintToken(&context, transaction, response); + case proto::TransactionBody::DataCase::kTokenPause: + return mTokenStub->pauseToken(&context, transaction, response); case proto::TransactionBody::DataCase::kTokenUpdate: return mTokenStub->updateToken(&context, transaction, response); default: diff --git a/sdk/tests/integration/CMakeLists.txt b/sdk/tests/integration/CMakeLists.txt index dd445c19..9fb25cf9 100644 --- a/sdk/tests/integration/CMakeLists.txt +++ b/sdk/tests/integration/CMakeLists.txt @@ -32,6 +32,7 @@ add_executable(${TEST_PROJECT_NAME} TokenFeeScheduleUpdateTransactionIntegrationTests.cc TokenInfoQueryIntegrationTests.cc TokenMintTransactionIntegrationTests.cc + TokenPauseTransactionIntegrationTests.cc TokenUpdateTransactionIntegrationTests.cc TransactionIntegrationTest.cc TransactionReceiptIntegrationTest.cc diff --git a/sdk/tests/integration/TokenPauseTransactionIntegrationTests.cc b/sdk/tests/integration/TokenPauseTransactionIntegrationTests.cc new file mode 100644 index 00000000..ee9b8df6 --- /dev/null +++ b/sdk/tests/integration/TokenPauseTransactionIntegrationTests.cc @@ -0,0 +1,115 @@ +/*- + * + * Hedera C++ SDK + * + * Copyright (C) 2020 - 2022 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. + * + */ +#include "AccountCreateTransaction.h" +#include "AccountDeleteTransaction.h" +#include "AccountId.h" +#include "BaseIntegrationTest.h" +#include "Client.h" +#include "ED25519PrivateKey.h" +#include "PrivateKey.h" +#include "TokenAssociateTransaction.h" +#include "TokenCreateTransaction.h" +#include "TokenDeleteTransaction.h" +#include "TokenPauseTransaction.h" +#include "TransactionReceipt.h" +#include "TransactionResponse.h" +#include "TransferTransaction.h" +#include "exceptions/PrecheckStatusException.h" +#include "exceptions/ReceiptStatusException.h" + +#include + +using namespace Hedera; + +class TokenPauseTransactionIntegrationTest : public BaseIntegrationTest +{ +}; + +//----- +TEST_F(TokenPauseTransactionIntegrationTest, ExecuteTokenPauseTransaction) +{ + // Given + const int64_t amount = 10LL; + + std::shared_ptr operatorKey; + std::shared_ptr accountKey; + + ASSERT_NO_THROW( + operatorKey = ED25519PrivateKey::fromString( + "302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137")); + ASSERT_NO_THROW(accountKey = ED25519PrivateKey::generatePrivateKey()); + + AccountId accountId; + ASSERT_NO_THROW(accountId = AccountCreateTransaction() + .setInitialBalance(Hbar(1LL)) + .setKey(accountKey.get()) + .execute(getTestClient()) + .getReceipt(getTestClient()) + .mAccountId.value()); + + TokenId tokenId; + ASSERT_NO_THROW(tokenId = TokenCreateTransaction() + .setTokenName("ffff") + .setTokenSymbol("F") + .setInitialSupply(100000ULL) + .setTreasuryAccountId(AccountId(2ULL)) + .setAdminKey(operatorKey) + .setPauseKey(operatorKey) + .execute(getTestClient()) + .getReceipt(getTestClient()) + .mTokenId.value()); + + ASSERT_NO_THROW(const TransactionReceipt txReceipt = TokenAssociateTransaction() + .setAccountId(accountId) + .setTokenIds({ tokenId }) + .freezeWith(getTestClient()) + .sign(accountKey.get()) + .execute(getTestClient()) + .getReceipt(getTestClient())); + + ASSERT_NO_THROW(const TransactionReceipt txReceipt = TransferTransaction() + .addTokenTransfer(tokenId, accountId, amount) + .addTokenTransfer(tokenId, AccountId(2ULL), -amount) + .execute(getTestClient()) + .getReceipt(getTestClient())); + + // When + EXPECT_NO_THROW(const TransactionReceipt txReceipt = + TokenPauseTransaction().setTokenId(tokenId).execute(getTestClient()).getReceipt(getTestClient())); + + // Then + EXPECT_THROW(const TransactionReceipt txReceipt = TransferTransaction() + .addTokenTransfer(tokenId, accountId, -amount) + .addTokenTransfer(tokenId, AccountId(2ULL), amount) + .freezeWith(getTestClient()) + .sign(accountKey.get()) + .execute(getTestClient()) + .getReceipt(getTestClient()), + ReceiptStatusException); // TOKEN_IS_PAUSED +} + +//----- +TEST_F(TokenPauseTransactionIntegrationTest, CannotPauseWithNoTokenId) +{ + // Given / When / Then + EXPECT_THROW(const TransactionReceipt txReceipt = + TokenPauseTransaction().execute(getTestClient()).getReceipt(getTestClient()), + PrecheckStatusException); // INVALID_TOKEN_ID +} From 3f4b6234c4d23701effb3e75bced507385c3f30a Mon Sep 17 00:00:00 2001 From: Rob Walworth Date: Fri, 7 Jul 2023 11:05:56 -0500 Subject: [PATCH 4/5] Add TokenUnpauseTransaction and unit tests Signed-off-by: Rob Walworth --- sdk/main/CMakeLists.txt | 1 + sdk/main/include/TokenUnpauseTransaction.h | 116 ++++++++++++++++++ sdk/main/include/Transaction.h | 4 +- sdk/main/src/Executable.cc | 2 + sdk/main/src/TokenUnpauseTransaction.cc | 88 +++++++++++++ sdk/main/src/Transaction.cc | 7 +- sdk/main/src/impl/Node.cc | 2 + sdk/tests/unit/CMakeLists.txt | 1 + .../unit/TokenUnpauseTransactionUnitTests.cc | 82 +++++++++++++ sdk/tests/unit/TransactionTest.cc | 60 +++++++++ 10 files changed, 361 insertions(+), 2 deletions(-) create mode 100644 sdk/main/include/TokenUnpauseTransaction.h create mode 100644 sdk/main/src/TokenUnpauseTransaction.cc create mode 100644 sdk/tests/unit/TokenUnpauseTransactionUnitTests.cc diff --git a/sdk/main/CMakeLists.txt b/sdk/main/CMakeLists.txt index e799434f..0cfc3353 100644 --- a/sdk/main/CMakeLists.txt +++ b/sdk/main/CMakeLists.txt @@ -87,6 +87,7 @@ add_library(${PROJECT_NAME} STATIC src/TokenSupplyType.cc src/TokenTransfer.cc src/TokenType.cc + src/TokenUnpauseTransaction.cc src/TokenUpdateTransaction.cc src/TokenWipeTransaction.cc src/Transaction.cc diff --git a/sdk/main/include/TokenUnpauseTransaction.h b/sdk/main/include/TokenUnpauseTransaction.h new file mode 100644 index 00000000..36d9d24a --- /dev/null +++ b/sdk/main/include/TokenUnpauseTransaction.h @@ -0,0 +1,116 @@ +/*- + * + * Hedera C++ SDK + * + * Copyright (C) 2020 - 2022 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. + * + */ +#ifndef HEDERA_SDK_CPP_TOKEN_UNPAUSE_TRANSACTION_H_ +#define HEDERA_SDK_CPP_TOKEN_UNPAUSE_TRANSACTION_H_ + +#include "TokenId.h" +#include "Transaction.h" + +namespace proto +{ +class TokenUnpauseTransactionBody; +class TransactionBody; +} + +namespace Hedera +{ +/** + * A token unpause transaction is a transaction that unpauses the token that was previously disabled from participating + * in transactions. The token's pause key is required to sign the transaction. Once the unpause transaction is submitted + * the token pause status is updated to unpause. + * + * Transaction Signing Requirements: + * - The pause key of the token. + * - Transaction fee payer account key. + */ +class TokenUnpauseTransaction : public Transaction +{ +public: + TokenUnpauseTransaction() = default; + + /** + * Construct from a TransactionBody protobuf object. + * + * @param transactionBody The TransactionBody protobuf object from which to construct. + * @throws std::invalid_argument If the input TransactionBody does not represent a TokenUnpause transaction. + */ + explicit TokenUnpauseTransaction(const proto::TransactionBody& transactionBody); + + /** + * Set the ID of the token to unpause. + * + * @param tokenId The ID of the token to unpause. + * @return A reference to this TokenUnpauseTransaction object with the newly-set token ID. + * @throws IllegalStateException If this TokenUnpauseTransaction is frozen. + */ + TokenUnpauseTransaction& setTokenId(const TokenId& tokenId); + + /** + * Get the ID of the token to unpause. + * + * @return The ID of the token to unpause. + */ + [[nodiscard]] inline TokenId getTokenId() const { return mTokenId; } + +private: + /** + * Derived from Executable. Construct a Transaction protobuf object from this TokenUnpauseTransaction object. + * + * @param client The Client trying to construct this TokenUnpauseTransaction. + * @param node The Node to which this TokenUnpauseTransaction will be sent. This is unused. + * @return A Transaction protobuf object filled with this TokenUnpauseTransaction object's data. + * @throws UninitializedException If the input client has no operator with which to sign this + * TokenUnpauseTransaction. + */ + [[nodiscard]] proto::Transaction makeRequest(const Client& client, + const std::shared_ptr& /*node*/) const override; + + /** + * Derived from Executable. Submit this TokenUnpauseTransaction to a Node. + * + * @param client The Client submitting this TokenUnpauseTransaction. + * @param deadline The deadline for submitting this TokenUnpauseTransaction. + * @param node Pointer to the Node to which this TokenUnpauseTransaction should be submitted. + * @param response Pointer to the TransactionResponse protobuf object that gRPC should populate with the response + * information from the gRPC server. + * @return The gRPC status of the submission. + */ + [[nodiscard]] grpc::Status submitRequest(const Client& client, + const std::chrono::system_clock::time_point& deadline, + const std::shared_ptr& node, + proto::TransactionResponse* response) const override; + + /** + * Build a TokenUnpauseTransactionBody protobuf object from this TokenUnpauseTransaction object. + * + * @return A pointer to a TokenUnpauseTransactionBody protobuf object filled with this TokenUnpauseTransaction + * object's data. + */ + [[nodiscard]] proto::TokenUnpauseTransactionBody* build() const; + + /** + * The ID of the token to unpause. + */ + TokenId mTokenId; +}; + +} // namespace Hedera + +#endif // HEDERA_SDK_CPP_TOKEN_UNPAUSE_TRANSACTION_H_ diff --git a/sdk/main/include/Transaction.h b/sdk/main/include/Transaction.h index d9b650ae..352d1c70 100644 --- a/sdk/main/include/Transaction.h +++ b/sdk/main/include/Transaction.h @@ -57,6 +57,7 @@ class TokenDeleteTransaction; class TokenDissociateTransaction; class TokenFeeScheduleUpdateTransaction; class TokenMintTransaction; +class TokenUnpauseTransaction; class TokenUpdateTransaction; class TokenWipeTransaction; class TransactionResponse; @@ -138,7 +139,8 @@ class Transaction TokenWipeTransaction, TokenBurnTransaction, TokenDissociateTransaction, - TokenFeeScheduleUpdateTransaction>> + TokenFeeScheduleUpdateTransaction, + TokenUnpauseTransaction>> fromBytes(const std::vector& bytes); /** diff --git a/sdk/main/src/Executable.cc b/sdk/main/src/Executable.cc index 0a464e7b..19e888f2 100644 --- a/sdk/main/src/Executable.cc +++ b/sdk/main/src/Executable.cc @@ -56,6 +56,7 @@ #include "TokenInfo.h" #include "TokenInfoQuery.h" #include "TokenMintTransaction.h" +#include "TokenUnpauseTransaction.h" #include "TokenUpdateTransaction.h" #include "TokenWipeTransaction.h" #include "TransactionReceipt.h" @@ -373,6 +374,7 @@ template class Executable; template class Executable; template class Executable; +template class Executable; template class Executable; template class Executable; template class Executable; diff --git a/sdk/main/src/TokenUnpauseTransaction.cc b/sdk/main/src/TokenUnpauseTransaction.cc new file mode 100644 index 00000000..12644749 --- /dev/null +++ b/sdk/main/src/TokenUnpauseTransaction.cc @@ -0,0 +1,88 @@ +/*- + * + * Hedera C++ SDK + * + * Copyright (C) 2020 - 2022 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. + * + */ +#include "TokenUnpauseTransaction.h" +#include "impl/Node.h" + +#include +#include +#include +#include + +namespace Hedera +{ +//----- +TokenUnpauseTransaction::TokenUnpauseTransaction(const proto::TransactionBody& transactionBody) + : Transaction(transactionBody) +{ + if (!transactionBody.has_token_unpause()) + { + throw std::invalid_argument("Transaction body doesn't contain TokenUnpause data"); + } + + const proto::TokenUnpauseTransactionBody& body = transactionBody.token_unpause(); + + if (body.has_token()) + { + mTokenId = TokenId::fromProtobuf(body.token()); + } +} + +//----- +TokenUnpauseTransaction& TokenUnpauseTransaction::setTokenId(const TokenId& tokenId) +{ + requireNotFrozen(); + mTokenId = tokenId; + return *this; +} + +//----- +proto::Transaction TokenUnpauseTransaction::makeRequest(const Client& client, + const std::shared_ptr&) const +{ + proto::TransactionBody transactionBody = generateTransactionBody(client); + transactionBody.set_allocated_token_unpause(build()); + + return signTransaction(transactionBody, client); +} + +//----- +grpc::Status TokenUnpauseTransaction::submitRequest(const Client& client, + const std::chrono::system_clock::time_point& deadline, + const std::shared_ptr& node, + proto::TransactionResponse* response) const +{ + return node->submitTransaction( + proto::TransactionBody::DataCase::kTokenUnpause, makeRequest(client, node), deadline, response); +} + +//----- +proto::TokenUnpauseTransactionBody* TokenUnpauseTransaction::build() const +{ + auto body = std::make_unique(); + + if (!(mTokenId == TokenId())) + { + body->set_allocated_token(mTokenId.toProtobuf().release()); + } + + return body.release(); +} + +} // namespace Hedera diff --git a/sdk/main/src/Transaction.cc b/sdk/main/src/Transaction.cc index 9fdee2f3..4f43662f 100644 --- a/sdk/main/src/Transaction.cc +++ b/sdk/main/src/Transaction.cc @@ -43,6 +43,7 @@ #include "TokenDissociateTransaction.h" #include "TokenFeeScheduleUpdateTransaction.h" #include "TokenMintTransaction.h" +#include "TokenUnpauseTransaction.h" #include "TokenUpdateTransaction.h" #include "TokenWipeTransaction.h" #include "TransactionId.h" @@ -89,7 +90,8 @@ std::pair> + TokenFeeScheduleUpdateTransaction, + TokenUnpauseTransaction>> Transaction::fromBytes(const std::vector& bytes) { proto::TransactionBody txBody; @@ -179,6 +181,8 @@ Transaction::fromBytes(const std::vector& bytes) return { 22, TokenDissociateTransaction(txBody) }; case proto::TransactionBody::kTokenFeeScheduleUpdate: return { 23, TokenFeeScheduleUpdateTransaction(txBody) }; + case proto::TransactionBody::kTokenUnpause: + return { 24, TokenUnpauseTransaction(txBody) }; default: throw std::invalid_argument("Type of transaction cannot be determined from input bytes"); } @@ -501,6 +505,7 @@ template class Transaction; template class Transaction; template class Transaction; template class Transaction; +template class Transaction; template class Transaction; template class Transaction; template class Transaction; diff --git a/sdk/main/src/impl/Node.cc b/sdk/main/src/impl/Node.cc index 2f6ee587..7dd2c0e0 100644 --- a/sdk/main/src/impl/Node.cc +++ b/sdk/main/src/impl/Node.cc @@ -206,6 +206,8 @@ grpc::Status Node::submitTransaction(proto::TransactionBody::DataCase funcEnum, return mTokenStub->updateTokenFeeSchedule(&context, transaction, response); case proto::TransactionBody::DataCase::kTokenMint: return mTokenStub->mintToken(&context, transaction, response); + case proto::TransactionBody::DataCase::kTokenUnpause: + return mTokenStub->unpauseToken(&context, transaction, response); case proto::TransactionBody::DataCase::kTokenUpdate: return mTokenStub->updateToken(&context, transaction, response); default: diff --git a/sdk/tests/unit/CMakeLists.txt b/sdk/tests/unit/CMakeLists.txt index 466c0311..a7ac7a06 100644 --- a/sdk/tests/unit/CMakeLists.txt +++ b/sdk/tests/unit/CMakeLists.txt @@ -78,6 +78,7 @@ add_executable(${TEST_PROJECT_NAME} TokenSupplyTypeTest.cc TokenTransferTest.cc TokenTypeTest.cc + TokenUnpauseTransactionUnitTests.cc TokenUpdateTransactionUnitTests.cc TokenWipeTransactionUnitTests.cc TransactionIdTest.cc diff --git a/sdk/tests/unit/TokenUnpauseTransactionUnitTests.cc b/sdk/tests/unit/TokenUnpauseTransactionUnitTests.cc new file mode 100644 index 00000000..98fe2de3 --- /dev/null +++ b/sdk/tests/unit/TokenUnpauseTransactionUnitTests.cc @@ -0,0 +1,82 @@ +/*- + * + * Hedera C++ SDK + * + * Copyright (C) 2020 - 2022 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. + * + */ +#include "Client.h" +#include "ECDSAsecp256k1PrivateKey.h" +#include "TokenUnpauseTransaction.h" +#include "exceptions/IllegalStateException.h" + +#include +#include + +using namespace Hedera; + +class TokenUnpauseTransactionTest : public ::testing::Test +{ +protected: + void SetUp() override { mClient.setOperator(AccountId(), ECDSAsecp256k1PrivateKey::generatePrivateKey().get()); } + + [[nodiscard]] inline const Client& getTestClient() const { return mClient; } + [[nodiscard]] inline const TokenId& getTestTokenId() const { return mTestTokenId; } + +private: + Client mClient; + const TokenId mTestTokenId = TokenId(1ULL); +}; + +//----- +TEST_F(TokenUnpauseTransactionTest, ConstructTokenUnpauseTransactionFromTransactionBodyProtobuf) +{ + // Given + auto body = std::make_unique(); + body->set_allocated_token(getTestTokenId().toProtobuf().release()); + + proto::TransactionBody txBody; + txBody.set_allocated_token_unpause(body.release()); + + // When + const TokenUnpauseTransaction tokenUnpauseTransaction(txBody); + + // Then + EXPECT_EQ(tokenUnpauseTransaction.getTokenId(), getTestTokenId()); +} + +//----- +TEST_F(TokenUnpauseTransactionTest, GetSetTokenId) +{ + // Given + TokenUnpauseTransaction transaction; + + // When + EXPECT_NO_THROW(transaction.setTokenId(getTestTokenId())); + + // Then + EXPECT_EQ(transaction.getTokenId(), getTestTokenId()); +} + +//----- +TEST_F(TokenUnpauseTransactionTest, GetSetTokenIdFrozen) +{ + // Given + TokenUnpauseTransaction transaction; + ASSERT_NO_THROW(transaction.freezeWith(getTestClient())); + + // When / Then + EXPECT_THROW(transaction.setTokenId(getTestTokenId()), IllegalStateException); +} diff --git a/sdk/tests/unit/TransactionTest.cc b/sdk/tests/unit/TransactionTest.cc index 4acbde86..830a9723 100644 --- a/sdk/tests/unit/TransactionTest.cc +++ b/sdk/tests/unit/TransactionTest.cc @@ -39,6 +39,7 @@ #include "TokenDissociateTransaction.h" #include "TokenFeeScheduleUpdateTransaction.h" #include "TokenMintTransaction.h" +#include "TokenUnpauseTransaction.h" #include "TokenUpdateTransaction.h" #include "TokenWipeTransaction.h" #include "TransferTransaction.h" @@ -1469,3 +1470,62 @@ TEST_F(TransactionTest, TokenFeeScheduleUpdateTransactionFromTransactionBytes) ASSERT_EQ(index, 23); EXPECT_NO_THROW(const TokenFeeScheduleUpdateTransaction tokenFeeScheduleUpdateTransaction = std::get<23>(txVariant)); } + +//----- +TEST_F(TransactionTest, TokenUnpauseTransactionFromTransactionBodyBytes) +{ + // Given + proto::TransactionBody txBody; + txBody.set_allocated_token_unpause(new proto::TokenUnpauseTransactionBody); + + // When + const auto [index, txVariant] = Transaction::fromBytes( + internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + + // Then + ASSERT_EQ(index, 24); + EXPECT_NO_THROW(const TokenUnpauseTransaction tokenUnpauseTransaction = std::get<24>(txVariant)); +} + +//----- +TEST_F(TransactionTest, TokenUnpauseTransactionFromSignedTransactionBytes) +{ + // Given + proto::TransactionBody txBody; + txBody.set_allocated_token_unpause(new proto::TokenUnpauseTransactionBody); + + proto::SignedTransaction signedTx; + signedTx.set_bodybytes(txBody.SerializeAsString()); + // SignatureMap not required + + // When + const auto [index, txVariant] = Transaction::fromBytes( + internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + + // Then + ASSERT_EQ(index, 24); + EXPECT_NO_THROW(const TokenUnpauseTransaction tokenUnpauseTransaction = std::get<24>(txVariant)); +} + +//----- +TEST_F(TransactionTest, TokenUnpauseTransactionFromTransactionBytes) +{ + // Given + proto::TransactionBody txBody; + txBody.set_allocated_token_unpause(new proto::TokenUnpauseTransactionBody); + + proto::SignedTransaction signedTx; + signedTx.set_bodybytes(txBody.SerializeAsString()); + // SignatureMap not required + + proto::Transaction tx; + tx.set_signedtransactionbytes(signedTx.SerializeAsString()); + + // When + const auto [index, txVariant] = Transaction::fromBytes( + internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + + // Then + ASSERT_EQ(index, 24); + EXPECT_NO_THROW(const TokenUnpauseTransaction tokenUnpauseTransaction = std::get<24>(txVariant)); +} From 7f7874cab0701c3f9a8d0b42f63fd6b3a2679b21 Mon Sep 17 00:00:00 2001 From: Rob Walworth Date: Fri, 7 Jul 2023 13:34:39 -0500 Subject: [PATCH 5/5] Add TokenUnpauseTransaction integration tests Signed-off-by: Rob Walworth --- sdk/main/src/impl/Node.cc | 2 + sdk/tests/integration/CMakeLists.txt | 1 + ...TokenUnpauseTransactionIntegrationTests.cc | 126 ++++++++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 sdk/tests/integration/TokenUnpauseTransactionIntegrationTests.cc diff --git a/sdk/main/src/impl/Node.cc b/sdk/main/src/impl/Node.cc index 91cd9622..3de1e172 100644 --- a/sdk/main/src/impl/Node.cc +++ b/sdk/main/src/impl/Node.cc @@ -212,6 +212,8 @@ grpc::Status Node::submitTransaction(proto::TransactionBody::DataCase funcEnum, return mTokenStub->unpauseToken(&context, transaction, response); case proto::TransactionBody::DataCase::kTokenUpdate: return mTokenStub->updateToken(&context, transaction, response); + case proto::TransactionBody::DataCase::kTokenWipe: + return mTokenStub->wipeTokenAccount(&context, transaction, response); default: // This should never happen throw std::invalid_argument("Unrecognized gRPC transaction method case"); diff --git a/sdk/tests/integration/CMakeLists.txt b/sdk/tests/integration/CMakeLists.txt index 9fb25cf9..117924d5 100644 --- a/sdk/tests/integration/CMakeLists.txt +++ b/sdk/tests/integration/CMakeLists.txt @@ -33,6 +33,7 @@ add_executable(${TEST_PROJECT_NAME} TokenInfoQueryIntegrationTests.cc TokenMintTransactionIntegrationTests.cc TokenPauseTransactionIntegrationTests.cc + TokenUnpauseTransactionIntegrationTests.cc TokenUpdateTransactionIntegrationTests.cc TransactionIntegrationTest.cc TransactionReceiptIntegrationTest.cc diff --git a/sdk/tests/integration/TokenUnpauseTransactionIntegrationTests.cc b/sdk/tests/integration/TokenUnpauseTransactionIntegrationTests.cc new file mode 100644 index 00000000..1a09a35f --- /dev/null +++ b/sdk/tests/integration/TokenUnpauseTransactionIntegrationTests.cc @@ -0,0 +1,126 @@ +/*- + * + * Hedera C++ SDK + * + * Copyright (C) 2020 - 2022 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. + * + */ +#include "AccountCreateTransaction.h" +#include "AccountDeleteTransaction.h" +#include "AccountId.h" +#include "BaseIntegrationTest.h" +#include "Client.h" +#include "ED25519PrivateKey.h" +#include "PrivateKey.h" +#include "TokenAssociateTransaction.h" +#include "TokenCreateTransaction.h" +#include "TokenDeleteTransaction.h" +#include "TokenUnpauseTransaction.h" +#include "TokenWipeTransaction.h" +#include "TransactionReceipt.h" +#include "TransactionResponse.h" +#include "TransferTransaction.h" +#include "exceptions/PrecheckStatusException.h" + +#include + +using namespace Hedera; + +class TokenUnpauseTransactionIntegrationTest : public BaseIntegrationTest +{ +}; + +//----- +TEST_F(TokenUnpauseTransactionIntegrationTest, ExecuteTokenUnpauseTransaction) +{ + // Given + const int64_t amount = 10LL; + + std::shared_ptr operatorKey; + std::shared_ptr accountKey; + + ASSERT_NO_THROW( + operatorKey = ED25519PrivateKey::fromString( + "302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137")); + ASSERT_NO_THROW(accountKey = ED25519PrivateKey::generatePrivateKey()); + + AccountId accountId; + ASSERT_NO_THROW(accountId = AccountCreateTransaction() + .setInitialBalance(Hbar(2LL)) + .setKey(accountKey.get()) + .execute(getTestClient()) + .getReceipt(getTestClient()) + .mAccountId.value()); + + TokenId tokenId; + ASSERT_NO_THROW(tokenId = TokenCreateTransaction() + .setTokenName("ffff") + .setTokenSymbol("F") + .setInitialSupply(100000ULL) + .setTreasuryAccountId(AccountId(2ULL)) + .setAdminKey(operatorKey) + .setPauseKey(operatorKey) + .setWipeKey(operatorKey) + .execute(getTestClient()) + .getReceipt(getTestClient()) + .mTokenId.value()); + + ASSERT_NO_THROW(const TransactionReceipt txReceipt = TokenAssociateTransaction() + .setAccountId(accountId) + .setTokenIds({ tokenId }) + .freezeWith(getTestClient()) + .sign(accountKey.get()) + .execute(getTestClient()) + .getReceipt(getTestClient())); + + // When + EXPECT_NO_THROW(const TransactionReceipt txReceipt = + TokenUnpauseTransaction().setTokenId(tokenId).execute(getTestClient()).getReceipt(getTestClient())); + + // Then + EXPECT_NO_THROW(const TransactionReceipt txReceipt = TransferTransaction() + .addTokenTransfer(tokenId, accountId, amount) + .addTokenTransfer(tokenId, AccountId(2ULL), -amount) + .execute(getTestClient()) + .getReceipt(getTestClient())); + + // Clean up + ASSERT_NO_THROW(const TransactionReceipt txReceipt = TokenWipeTransaction() + .setTokenId(tokenId) + .setAccountId(accountId) + .setAmount(amount) + .execute(getTestClient()) + .getReceipt(getTestClient())); + + ASSERT_NO_THROW(const TransactionReceipt txReceipt = + TokenDeleteTransaction().setTokenId(tokenId).execute(getTestClient()).getReceipt(getTestClient())); + + ASSERT_NO_THROW(const TransactionReceipt txReceipt = AccountDeleteTransaction() + .setTransferAccountId(AccountId(2ULL)) + .setDeleteAccountId(accountId) + .freezeWith(getTestClient()) + .sign(accountKey.get()) + .execute(getTestClient()) + .getReceipt(getTestClient())); +} + +//----- +TEST_F(TokenUnpauseTransactionIntegrationTest, CannotUnpauseWithNoTokenId) +{ + // Given / When / Then + EXPECT_THROW(const TransactionReceipt txReceipt = + TokenUnpauseTransaction().execute(getTestClient()).getReceipt(getTestClient()), + PrecheckStatusException); // INVALID_TOKEN_ID +}