From 52e6ae7139e2cf9c5aac315b735820f74c00009c Mon Sep 17 00:00:00 2001 From: gsstoykov Date: Tue, 14 Jan 2025 18:28:47 +0200 Subject: [PATCH] feat: POC refactor for TokenRejectFlow (#868) Signed-off-by: gsstoykov --- src/sdk/main/include/TokenRejectFlow.h | 158 ++---------------- src/sdk/main/include/Transaction.h | 1 + src/sdk/main/src/TokenRejectFlow.cc | 118 ++----------- src/sdk/main/src/Transaction.cc | 5 + .../TokenRejectTransactionIntegrationTests.cc | 60 +++++++ 5 files changed, 93 insertions(+), 249 deletions(-) diff --git a/src/sdk/main/include/TokenRejectFlow.h b/src/sdk/main/include/TokenRejectFlow.h index ce8b699a0..5f924deda 100644 --- a/src/sdk/main/include/TokenRejectFlow.h +++ b/src/sdk/main/include/TokenRejectFlow.h @@ -4,7 +4,9 @@ #include "AccountId.h" #include "NftId.h" +#include "TokenDissociateTransaction.h" #include "TokenId.h" +#include "TokenRejectTransaction.h" #include "TransactionResponse.h" namespace Hiero @@ -21,6 +23,16 @@ namespace Hiero class TokenRejectFlow { public: + /** + * The transaction used to reject tokens. + */ + TokenRejectTransaction tokenRejectTransaction; + + /** + * The transaction used to dissociate tokens. + */ + TokenDissociateTransaction tokenDissociateTransaction; + /** * Execute the Transactions in this flow (TokenRejectTransaction and TokenDissociateTransaction). * @@ -45,153 +57,17 @@ class TokenRejectFlow TransactionResponse execute(const Client& client, const std::chrono::system_clock::duration& timeout); /** - * Freeze the TokenRejectTransaction with a Client. The Client's operator will be used to generate a transaction - * ID, and the client's network will be used to generate a list of node account IDs. - * - * NOTE: Since Client's can't be copied, this TokenRejectFlow will store a pointer to the input Client. It is the - * responsibility of the user to make sure that the Client does not go out of scope or get destroyed until this - * TokenRejectFlow is done executing, otherwise this will crash upon execution. - * - * @param client The Client with which to freeze the TokenRejectTransaction. - * @return A reference to this TokenRejectFlow object with the newly-set freeze Client. - * @throws UninitializedException If Client operator has not been initialized. - */ - TokenRejectFlow& freezeWith(const Client* client); - - /** - * Set the PrivateKey with which the TokenRejectTransaction will be signed. - * - * @param key The PrivateKey with which to sign the TokenRejectTransaction. - * @return A reference to this TokenRejectFlow object with the newly-set signing PrivateKey. - */ - TokenRejectFlow& sign(const std::shared_ptr& key); - - /** - * Set the PublicKey and signer function with which the TokenRejectTransaction will be signed. - * - * @param key The PublicKey with which to sign the TokenRejectTransaction. - * @param signer The callback function to use to sign the TokenRejectTransaction. - * @return A reference to this TokenRejectFlow object with the newly-set public key and signer function. - */ - TokenRejectFlow& signWith(const std::shared_ptr& key, - const std::function(const std::vector&)>& signer); - - /** - * Set the Client operator with which the TokenRejectTransaction will be signed. + * Set the PrivateKey to be used for signing the transactions. * - * @param client The Client operator to sign the TokenRejectTransaction. - * @return A reference to this TokenRejectFlow object with the newly-set signing operator. - * @throws UninitializedException If the Client operator has not yet been set. + * @param privateKey A shared pointer to the `PrivateKey` object. */ - TokenRejectFlow& signWithOperator(const Client& client); - - /** - * Get the list of account IDs for nodes with which execution will be attempted. - * - * @return The list of account IDs of nodes this TokenRejectFlow would attempt request submission. - */ - [[nodiscard]] inline std::vector getNodeAccountIds() const { return mNodeAccountIds; } - - /** - * Get the account holding tokens to be rejected. - * - * @return Optional containing the account Id of the owner. - */ - [[nodiscard]] std::optional getOwner() const { return mOwner; }; - - /** - * Get the list of fungible tokens to be rejected. - * - * @return A vector of TokenId objects. - */ - [[nodiscard]] const std::vector& getFts() const { return mFts; }; - - /** - * Get the list of non-fungible tokens to be rejected. - * - * @return A vector of NftId objects. - */ - [[nodiscard]] const std::vector& getNfts() const { return mNfts; }; - - /** - * Set the desired account IDs of nodes to which this transaction will be submitted. - * - * @param nodeAccountIds The desired list of account IDs of nodes to submit this request. - * @return A reference to this TokenRejectFlow object with the newly-set node account IDs. - */ - TokenRejectFlow& setNodeAccountIds(const std::vector& nodeAccountIds); - - /** - * Set a new account holding tokens to be rejected. - * - * @param owner Account Id of the account. - * @return A reference to this TokenRejectFlow with the newly-set owner. - */ - TokenRejectFlow& setOwner(const AccountId& owner); - - /** - * Set a new fungible tokens list of tokens to be rejected. - * - * @param fts List of token ids. - * @return A reference to this TokenRejectFlow with the newly-set tokens list. - */ - TokenRejectFlow& setFts(const std::vector& fts); - - /** - * Set a new non-fungible tokens list of tokens to be rejected. - * - * @param nfts List of nft ids. - * @return A reference to this TokenRejectFlow with the newly-set nfts list. - */ - TokenRejectFlow& setNfts(const std::vector& nfts); + void setReceiverPrivateKey(const std::shared_ptr& privateKey) { mReceiverPrivateKey = privateKey; } private: /** - * The Client with which to freeze the TokenRejectTransaction. - */ - const Client* mFreezeWithClient = nullptr; - - /** - * The PrivateKey with which to sign the TokenRejectTransaction. - */ - std::shared_ptr mPrivateKey = nullptr; - - /** - * The PublicKey associated with the signer function to sign the TokenRejectTransaction. - */ - std::shared_ptr mPublicKey = nullptr; - - /** - * The signer function to use to sign the TokenRejectTransaction. - */ - std::optional(const std::vector&)>> mSigner; - - /** - * The list of account IDs of the nodes with which execution should be attempted. - */ - std::vector mNodeAccountIds; - - /** - * An account holding the tokens to be rejected. - * If set, this account MUST sign this transaction. - * If not set, the payer for this transaction SHALL be the account rejecting tokens. - */ - std::optional mOwner; - - /** - * On success each rejected token serial number or balance SHALL be transferred from - * the requesting account to the treasury account for that token type. - * After rejection the requesting account SHALL continue to be associated with the token. - * if dissociation is desired then a separate TokenDissociate transaction MUST be submitted to remove the association. - * - * A list of one or more fungible token rejections. - */ - std::vector mFts; - - /** - * A list of one or more non-fungible token rejections. + * The PrivateKey with which to sign the transactions. */ - std::vector mNfts; + std::shared_ptr mReceiverPrivateKey = nullptr; }; } // namespace Hiero diff --git a/src/sdk/main/include/Transaction.h b/src/sdk/main/include/Transaction.h index 643ddb885..4dae1444e 100644 --- a/src/sdk/main/include/Transaction.h +++ b/src/sdk/main/include/Transaction.h @@ -93,6 +93,7 @@ class Transaction * @param key The PrivateKey with which to sign this Transaction. * @return A reference to this derived Transaction object with the signature. * @throws IllegalStateException If this Transaction object is not frozen. + * @throws std::invalid_argument If the signing key is not set. */ SdkRequestType& sign(const std::shared_ptr& key); diff --git a/src/sdk/main/src/TokenRejectFlow.cc b/src/sdk/main/src/TokenRejectFlow.cc index c1ab489c1..0cb4ee39d 100644 --- a/src/sdk/main/src/TokenRejectFlow.cc +++ b/src/sdk/main/src/TokenRejectFlow.cc @@ -20,125 +20,27 @@ TransactionResponse TokenRejectFlow::execute(const Client& client) //----- TransactionResponse TokenRejectFlow::execute(const Client& client, const std::chrono::system_clock::duration& timeout) { - TokenRejectTransaction tokenRejectTransaction; - - if (mOwner.has_value()) - { - tokenRejectTransaction.setOwner(mOwner.value()); - } - - tokenRejectTransaction.setFts(mFts).setNfts(mNfts); - - if (!mNodeAccountIds.empty()) - { - tokenRejectTransaction.setNodeAccountIds(mNodeAccountIds); - } - - if (mFreezeWithClient != nullptr) - { - tokenRejectTransaction.freezeWith(mFreezeWithClient); - } - - if (mPrivateKey) - { - tokenRejectTransaction.sign(mPrivateKey); - } - else if (mPublicKey && mSigner.has_value()) - { - tokenRejectTransaction.signWith(mPublicKey, mSigner.value()); - } - // Submit the TokenRejectTransaction. - TransactionResponse txResponse = tokenRejectTransaction.execute(client, timeout); + TransactionResponse txResponse = + tokenRejectTransaction.freezeWith(&client).sign(mReceiverPrivateKey).execute(client, timeout); // Make sure the transaction reaches consensus. TransactionReceipt txReceipt = txResponse.getReceipt(client, timeout); - TokenDissociateTransaction tokenDissociateTransaction; - - if (mOwner.has_value()) - { - tokenDissociateTransaction.setAccountId(mOwner.value()); - } - // build dissociate vector from rejected nfts std::vector toDissociate; - std::for_each( - mNfts.cbegin(), mNfts.cend(), [&toDissociate](const NftId& nftId) { toDissociate.push_back(nftId.mTokenId); }); + std::for_each(tokenRejectTransaction.getNfts().cbegin(), + tokenRejectTransaction.getNfts().cend(), + [&toDissociate](const NftId& nftId) { toDissociate.push_back(nftId.mTokenId); }); // Make sure the transaction reaches consensus. - txReceipt = tokenDissociateTransaction.setTokenIds(toDissociate).execute(client, timeout).getReceipt(client, timeout); + txReceipt = tokenDissociateTransaction.setTokenIds(toDissociate) + .freezeWith(&client) + .sign(mReceiverPrivateKey) + .execute(client, timeout) + .getReceipt(client, timeout); return txResponse; } -//----- -TokenRejectFlow& TokenRejectFlow::freezeWith(const Client* client) -{ - mFreezeWithClient = client; - return *this; -} - -//----- -TokenRejectFlow& TokenRejectFlow::sign(const std::shared_ptr& key) -{ - mPrivateKey = key; - mPublicKey = nullptr; - mSigner.reset(); - return *this; -} - -//----- -TokenRejectFlow& TokenRejectFlow::signWith( - const std::shared_ptr& key, - const std::function(const std::vector&)>& signer) -{ - mPrivateKey = nullptr; - mPublicKey = key; - mSigner = signer; - return *this; -} - -//----- -TokenRejectFlow& TokenRejectFlow::signWithOperator(const Client& client) -{ - if (!client.getOperatorPublicKey()) - { - throw UninitializedException("Client operator has not yet been set"); - } - - mPrivateKey = nullptr; - mPublicKey = client.getOperatorPublicKey(); - mSigner = client.getOperatorSigner(); - return *this; -} - -//----- -TokenRejectFlow& TokenRejectFlow::setNodeAccountIds(const std::vector& nodeAccountIds) -{ - mNodeAccountIds = nodeAccountIds; - return *this; -} - -//----- -TokenRejectFlow& TokenRejectFlow::setOwner(const AccountId& owner) -{ - mOwner = owner; - return *this; -} - -//----- -TokenRejectFlow& TokenRejectFlow::setFts(const std::vector& fts) -{ - mFts = fts; - return *this; -} - -//----- -TokenRejectFlow& TokenRejectFlow::setNfts(const std::vector& nfts) -{ - mNfts = nfts; - return *this; -} - } // namespace Hiero \ No newline at end of file diff --git a/src/sdk/main/src/Transaction.cc b/src/sdk/main/src/Transaction.cc index 5fda4e472..3a47b8da3 100644 --- a/src/sdk/main/src/Transaction.cc +++ b/src/sdk/main/src/Transaction.cc @@ -356,6 +356,11 @@ template SdkRequestType& Transaction::sign(const std::shared_ptr& key) { // clang-format off + if (key == nullptr) + { + throw std::invalid_argument("Signing key must be set."); + } + return signInternal(key->getPublicKey(), [key](const std::vector& vec) { return key->sign(vec); }, key); // clang-format on } diff --git a/src/sdk/tests/integration/TokenRejectTransactionIntegrationTests.cc b/src/sdk/tests/integration/TokenRejectTransactionIntegrationTests.cc index 7b2ace3f4..761faa378 100644 --- a/src/sdk/tests/integration/TokenRejectTransactionIntegrationTests.cc +++ b/src/sdk/tests/integration/TokenRejectTransactionIntegrationTests.cc @@ -10,6 +10,7 @@ #include "TokenFreezeTransaction.h" #include "TokenMintTransaction.h" #include "TokenPauseTransaction.h" +#include "TokenRejectFlow.h" #include "TokenRejectTransaction.h" #include "TransactionReceipt.h" #include "TransactionResponse.h" @@ -893,3 +894,62 @@ TEST_F(TokenRejectTransactionIntegrationTests, FailsWithInvalidSignature) .getReceipt(getTestClient()), ReceiptStatusException); } + +TEST_F(TokenRejectTransactionIntegrationTests, CanExecuteFlowForFT) +{ + // Given + std::shared_ptr operatorKey; + ASSERT_NO_THROW( + operatorKey = ED25519PrivateKey::fromString( + "302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137")); + + // Create two fts + TokenId tokenId1; + ASSERT_NO_THROW(tokenId1 = createFt(operatorKey)); + + // Create receiver account with auto associations + AccountId receiver; + std::shared_ptr receiverKey = ED25519PrivateKey::generatePrivateKey(); + ASSERT_NO_THROW(receiver = createAccount(receiverKey)); + + // When + // manually associate ft + TransactionReceipt txReceipt; + ASSERT_NO_THROW(txReceipt = TokenAssociateTransaction() + .setAccountId(receiver) + .setTokenIds({ tokenId1 }) + .freezeWith(&getTestClient()) + .sign(receiverKey) + .execute(getTestClient()) + .getReceipt(getTestClient())); + + // Transfer fts to the receiver + ASSERT_NO_THROW(txReceipt = TransferTransaction() + .addTokenTransfer(tokenId1, getTestClient().getOperatorAccountId().value(), -10) + .addTokenTransfer(tokenId1, receiver, 10) + .execute(getTestClient()) + .getReceipt(getTestClient())); + + // Then + // Initialize a TokenRejectFlow + TokenRejectFlow tokenRejectFlow; + tokenRejectFlow.tokenRejectTransaction.setOwner(receiver).setFts({ tokenId1 }); + tokenRejectFlow.tokenDissociateTransaction.setAccountId(receiver); + tokenRejectFlow.setReceiverPrivateKey(receiverKey); + + // execute the token reject flow + EXPECT_NO_THROW(txReceipt = tokenRejectFlow.execute(getTestClient()).getReceipt(getTestClient());); + + // Verify the balance of the receiver is 0 + AccountBalance balance; + EXPECT_NO_THROW(balance = AccountBalanceQuery().setAccountId(receiver).execute(getTestClient())); + + EXPECT_EQ(0, balance.mTokens[tokenId1]); + + // Verify the tokens are transferred back to the treasury + EXPECT_NO_THROW( + balance = + AccountBalanceQuery().setAccountId(getTestClient().getOperatorAccountId().value()).execute(getTestClient())); + + EXPECT_EQ(100000, balance.mTokens[tokenId1]); +} \ No newline at end of file