From fb37a0089302efa5187e8db046db6bd7aa978244 Mon Sep 17 00:00:00 2001 From: gsstoykov Date: Wed, 13 Mar 2024 18:12:25 +0200 Subject: [PATCH] Added Account Balance/Info contract Info Mirror Node Queries (#641) Signed-off-by: gsstoykov --- src/sdk/main/CMakeLists.txt | 3 + src/sdk/main/include/AccountBalance.h | 12 ++ src/sdk/main/include/AccountBalanceQuery.h | 10 ++ src/sdk/main/include/AccountId.h | 2 +- src/sdk/main/include/AccountInfo.h | 8 ++ src/sdk/main/include/AccountInfoQuery.h | 10 ++ src/sdk/main/include/ContractInfo.h | 7 + src/sdk/main/include/ContractInfoQuery.h | 10 ++ src/sdk/main/include/Executable.h | 15 +++ src/sdk/main/include/TokenRelationship.h | 122 ++++++++++++++++++ .../main/include/exceptions/CURLException.h | 60 +++++++++ src/sdk/main/include/impl/ASN1ECKey.h | 12 +- src/sdk/main/include/impl/ASN1Object.h | 10 +- src/sdk/main/include/impl/HexConverter.h | 2 +- src/sdk/main/include/impl/HttpClient.h | 12 +- src/sdk/main/include/impl/MirrorNodeGateway.h | 78 +++++++++++ src/sdk/main/include/impl/MirrorNodeRouter.h | 68 ++++++++++ src/sdk/main/include/impl/Utilities.h | 15 +++ src/sdk/main/src/AccountBalanceQuery.cc | 48 ++++++- src/sdk/main/src/AccountInfoQuery.cc | 39 +++++- src/sdk/main/src/ContractInfoQuery.cc | 37 +++++- src/sdk/main/src/Executable.cc | 2 +- src/sdk/main/src/TokenRelationship.cc | 97 ++++++++++++++ src/sdk/main/src/impl/HttpClient.cc | 12 +- src/sdk/main/src/impl/MirrorNodeGateway.cc | 98 ++++++++++++++ src/sdk/main/src/impl/MirrorNodeRouter.cc | 44 +++++++ src/sdk/main/src/impl/Utilities.cc | 26 ++++ .../AccountInfoQueryIntegrationTests.cc | 80 ++++++++++++ src/sdk/tests/integration/CMakeLists.txt | 1 + .../integration/ClientIntegrationTests.cc | 8 +- .../integration/HttpClientIntegrationTests.cc | 2 - .../MirrorNodeGatewayIntegrationTests.cc | 107 +++++++++++++++ 32 files changed, 1026 insertions(+), 31 deletions(-) create mode 100644 src/sdk/main/include/TokenRelationship.h create mode 100644 src/sdk/main/include/exceptions/CURLException.h create mode 100644 src/sdk/main/include/impl/MirrorNodeGateway.h create mode 100644 src/sdk/main/include/impl/MirrorNodeRouter.h create mode 100644 src/sdk/main/src/TokenRelationship.cc create mode 100644 src/sdk/main/src/impl/MirrorNodeGateway.cc create mode 100644 src/sdk/main/src/impl/MirrorNodeRouter.cc create mode 100644 src/sdk/tests/integration/MirrorNodeGatewayIntegrationTests.cc diff --git a/src/sdk/main/CMakeLists.txt b/src/sdk/main/CMakeLists.txt index 9ae74a76e..5474bb153 100644 --- a/src/sdk/main/CMakeLists.txt +++ b/src/sdk/main/CMakeLists.txt @@ -138,6 +138,7 @@ add_library(${PROJECT_NAME} STATIC src/TokenNftInfoQuery.cc src/TokenNftTransfer.cc src/TokenPauseTransaction.cc + src/TokenRelationship.cc src/TokenRevokeKycTransaction.cc src/TokenSupplyType.cc src/TokenTransfer.cc @@ -183,6 +184,8 @@ add_library(${PROJECT_NAME} STATIC src/impl/HttpClient.cc src/impl/MirrorNetwork.cc src/impl/MirrorNode.cc + src/impl/MirrorNodeGateway.cc + src/impl/MirrorNodeRouter.cc src/impl/Network.cc src/impl/Node.cc src/impl/OpenSSLUtils.cc diff --git a/src/sdk/main/include/AccountBalance.h b/src/sdk/main/include/AccountBalance.h index ea2d46477..292e1306a 100644 --- a/src/sdk/main/include/AccountBalance.h +++ b/src/sdk/main/include/AccountBalance.h @@ -21,11 +21,13 @@ #define HEDERA_SDK_CPP_ACCOUNT_BALANCE_H_ #include "Hbar.h" +#include "TokenId.h" #include #include #include #include +#include #include namespace proto @@ -91,6 +93,16 @@ class AccountBalance * The account or contract balance. */ Hbar mBalance; + + /** + * Map of tokens with associated values. + */ + std::unordered_map mTokens; + + /** + * Map of token decimals with associated values. + */ + std::unordered_map mTokenDecimals; }; } // namespace Hedera diff --git a/src/sdk/main/include/AccountBalanceQuery.h b/src/sdk/main/include/AccountBalanceQuery.h index 6da920004..77a2b17a9 100644 --- a/src/sdk/main/include/AccountBalanceQuery.h +++ b/src/sdk/main/include/AccountBalanceQuery.h @@ -108,6 +108,16 @@ class AccountBalanceQuery : public Query */ void validateChecksums(const Client& client) const override; + /** + * Fetches token relationship data from Mirror Node and updates the queried data. + * + * This function retrieves token relationship information. It then populates missing + * object fields with token information obtained from the Mirror Node response. + * + * @throws IllegalStateException on bad Mirror Node response state + */ + void fetchTokenInformation(AccountBalance& accountBalance) const; + /** * Derived from Query. Build a Query protobuf object with this AccountBalanceQuery's data, with the input QueryHeader * protobuf object. diff --git a/src/sdk/main/include/AccountId.h b/src/sdk/main/include/AccountId.h index 3f2fe33e1..2339b103a 100644 --- a/src/sdk/main/include/AccountId.h +++ b/src/sdk/main/include/AccountId.h @@ -194,7 +194,7 @@ class AccountId [[nodiscard]] std::string toSolidityAddress() const; /** - * @brief Populates the EVM address for an Account using the Mirror Node. This function fetches the EVM address for an + * Populates the EVM address for an Account using the Mirror Node. This function fetches the EVM address for an * Account from the Mirror Node. * * User Note: This Function requires a 3 second sleep if running on testnet environment as the MirrorNode does not diff --git a/src/sdk/main/include/AccountInfo.h b/src/sdk/main/include/AccountInfo.h index a6b1c163c..af591fcc5 100644 --- a/src/sdk/main/include/AccountInfo.h +++ b/src/sdk/main/include/AccountInfo.h @@ -26,12 +26,15 @@ #include "Key.h" #include "LedgerId.h" #include "StakingInfo.h" +#include "TokenId.h" +#include "TokenRelationship.h" #include #include #include #include #include +#include #include namespace proto @@ -177,6 +180,11 @@ class AccountInfo * The staking metadata for the queried account. */ StakingInfo mStakingInfo; + + /** + * The token relationships mappings for the queried account. + */ + std::unordered_map mTokenRelationships; }; } // namespace Hedera diff --git a/src/sdk/main/include/AccountInfoQuery.h b/src/sdk/main/include/AccountInfoQuery.h index 4a276e02d..e40b97a5d 100644 --- a/src/sdk/main/include/AccountInfoQuery.h +++ b/src/sdk/main/include/AccountInfoQuery.h @@ -86,6 +86,16 @@ class AccountInfoQuery : public Query */ void validateChecksums(const Client& client) const override; + /** + * Fetches token relationship data from Mirror Node and updates the queried data. + * + * This function retrieves token relationship information. It then populates missing + * object fields with token information obtained from the Mirror Node response. + * + * @throws IllegalStateException on bad Mirror Node response state + */ + void fetchTokenInformation(AccountInfo& accountInfo) const; + /** * Derived from Query. Build a Query protobuf object with this AccountInfoQuery's data, with the input QueryHeader * protobuf object. diff --git a/src/sdk/main/include/ContractInfo.h b/src/sdk/main/include/ContractInfo.h index 01a316e99..4aa5d836f 100644 --- a/src/sdk/main/include/ContractInfo.h +++ b/src/sdk/main/include/ContractInfo.h @@ -26,6 +26,8 @@ #include "Key.h" #include "LedgerId.h" #include "StakingInfo.h" +#include "TokenId.h" +#include "TokenRelationship.h" #include #include @@ -165,6 +167,11 @@ class ContractInfo * The staking metadata for this contract. */ StakingInfo mStakingInfo; + + /** + * The token relationships mappings for the queried account. + */ + std::unordered_map mTokenRelationships; }; } // namespace Hedera diff --git a/src/sdk/main/include/ContractInfoQuery.h b/src/sdk/main/include/ContractInfoQuery.h index 5aea148b3..0444be98b 100644 --- a/src/sdk/main/include/ContractInfoQuery.h +++ b/src/sdk/main/include/ContractInfoQuery.h @@ -88,6 +88,16 @@ class ContractInfoQuery : public Query */ void validateChecksums(const Client& client) const override; + /** + * Fetches token relationship data from Mirror Node and updates the queried data. + * + * This function retrieves token relationship information. It then populates missing + * object fields with token information obtained from the Mirror Node response. + * + * @throws IllegalStateException on bad Mirror Node response state + */ + void fetchTokenInformation(ContractInfo& contractInfo) const; + /** * Derived from Query. Build a Query protobuf object with this ContractInfoQuery's data, with the input QueryHeader * protobuf object. diff --git a/src/sdk/main/include/Executable.h b/src/sdk/main/include/Executable.h index 8d8b2b722..2c4e0c759 100644 --- a/src/sdk/main/include/Executable.h +++ b/src/sdk/main/include/Executable.h @@ -325,6 +325,16 @@ class Executable [[maybe_unused]] const Client& client, [[maybe_unused]] const ProtoResponseType& response); + /** + * Gets the mirror node resolution for the query. + * + * This function returns the mirror node resolution by accessing the first element + * of the vector mMirrorNodeIds. + * + * @return The mirror node resolution as a std::string. + */ + [[nodiscard]] std::string getMirrorNodeResolution() const { return mMirrorNodeIds[0]; } + private: /** * Construct a ProtoRequestType object from this Executable, based on the node account ID at the given index. @@ -423,6 +433,11 @@ class Executable */ std::vector mNodeAccountIds; + /** + * The list of node IDs of the mirror nodes with which query should be attempted. + */ + std::vector mMirrorNodeIds; + /** * The callback to be called before a request is sent. The callback will receive the protobuf of the request and * return the protobuf of the request. This allows the callback to read, copy, and/or modify the request that will be diff --git a/src/sdk/main/include/TokenRelationship.h b/src/sdk/main/include/TokenRelationship.h new file mode 100644 index 000000000..2cb0e6fc9 --- /dev/null +++ b/src/sdk/main/include/TokenRelationship.h @@ -0,0 +1,122 @@ +/*- + * + * Hedera C++ SDK + * + * Copyright (C) 2020 - 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. + * + */ +#ifndef HEDERA_SDK_CPP_TOKEN_RELATIONSHIP_H_ +#define HEDERA_SDK_CPP_TOKEN_RELATIONSHIP_H_ + +#include "TokenId.h" + +#include +#include +#include + +namespace Hedera +{ +/** + * Represents the relationship between an account and a token. + * + * This class encapsulates information about the account's relationship with a specific token, + * including the token ID, symbol, balance, KYC status, freeze status, and whether the relationship is created + * implicitly. + */ +class TokenRelationship +{ +public: + /** + * Constructor for TokenRelationship. + * + * @param tokenId The unique token ID. + * @param symbol The symbol of the token. + * @param balance The balance of the account in the smallest denomination for fungible common tokens, + * or the number of NFTs held by the account for non-fungible unique tokens. + * @param decimals The token decimals + * @param kycStatus The KYC status of the account (optional). + * @param freezeStatus The freeze status of the account (optional). + * @param automaticAssociation Specifies if the relationship is created implicitly. + */ + TokenRelationship(const TokenId& tokenId, + const std::string& symbol, + uint64_t balance, + uint32_t decimals, + std::string_view kycStatus, + std::string_view freezeStatus, + bool automaticAssociation); + + /** + * Converts the TokenRelationship object to a string representation. + * + * @return A string representation of the TokenRelationship. + */ + std::string toString() const; + + /** + * The unique token ID. + */ + TokenId mTokenId; + + /** + * The symbol of the token. + */ + std::string mSymbol; + + /** + * The balance of the account. + */ + uint64_t mBalance; + + /** + * The token decimals. + */ + uint32_t mDecimals; + + /** + * The KYC status of the account (optional). + */ + std::optional mKycStatus; + + /** + * The freeze status of the account (optional). + */ + std::optional mFreezeStatus; + + /** + * Specifies if the relationship is created implicitly. + */ + bool mAutomaticAssociation; + +private: + /** + * Sets the KYC status based on the provided string. + * + * @param kycStatusString The string representation of KYC status ("GRANTED", "REVOKED", or "NOT_APPLICABLE"). + * @throws std::invalid_argument if the provided string is not a valid KYC status. + */ + void setKycStatus(std::string_view kycStatusString); + + /** + * Sets the freeze status based on the provided string. + * + * @param freezeStatus The string representation of freeze status ("FROZEN", "UNFROZEN", or "NOT_APPLICABLE"). + * @throws std::invalid_argument if the provided string is not a valid freeze status. + */ + void setFreezeStatus(std::string_view freezeStatus); +}; +} // namespace Hedera + +#endif // HEDERA_SDK_CPP_TOKEN_RELATIONSHIP_H_ diff --git a/src/sdk/main/include/exceptions/CURLException.h b/src/sdk/main/include/exceptions/CURLException.h new file mode 100644 index 000000000..957223e4e --- /dev/null +++ b/src/sdk/main/include/exceptions/CURLException.h @@ -0,0 +1,60 @@ +/*- + * + * Hedera C++ SDK + * + * Copyright (C) 2020 - 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. + * + */ +#ifndef HEDERA_SDK_CPP_CURL_EXCEPTION_H_ +#define HEDERA_SDK_CPP_CURL_EXCEPTION_H_ + +#include +#include + +namespace Hedera +{ +/** + * Exception that encompasses all unrecoverable CURL errors. + */ +class CURLException : public std::exception +{ +public: + /** + * Construct with a message. + * + * @param msg The error message to further describe this exception. + */ + explicit CURLException(std::string_view msg) + : mError(msg) + { + } + + /** + * Get the descriptor message for this error. + * + * @return The descriptor message for this error. + */ + [[nodiscard]] const char* what() const noexcept override { return mError.data(); }; + +private: + /** + * Descriptive error message. + */ + std::string_view mError; +}; + +} // namespace Hedera + +#endif // HEDERA_SDK_CPP_CURL_EXCEPTION_H_ diff --git a/src/sdk/main/include/impl/ASN1ECKey.h b/src/sdk/main/include/impl/ASN1ECKey.h index b3bbec8e0..0ced48d28 100644 --- a/src/sdk/main/include/impl/ASN1ECKey.h +++ b/src/sdk/main/include/impl/ASN1ECKey.h @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * @brief This file defines the Hedera C++ SDK's ASN1Key class, derived from ASN1Object. + * This file defines the Hedera C++ SDK's ASN1Key class, derived from ASN1Object. */ #ifndef HEDERA_SDK_CPP_IMPL_ASN1_EC_KEY_H_ @@ -32,13 +32,13 @@ constexpr size_t MAX_ENCRYPTED_KEY_LENGHT = 160; // bytes ~ 320 characters /** * @class ASN1Key - * @brief ASN.1 key object. + * ASN.1 key object. */ class ASN1ECKey : public ASN1Object { public: /** - * @brief Get the key value associated with the ASN.1 key. + * Get the key value associated with the ASN.1 key. * * @return The key as a vector of bytes. */ @@ -46,7 +46,7 @@ class ASN1ECKey : public ASN1Object protected: /** - * @brief Decode ASN.1 data representing an Elliptic Curve Key. + * Decode ASN.1 data representing an Elliptic Curve Key. * * This method decodes basic ASN.1 data, extracting key data and storing it in the `asn1KeyData` map. * EC Keys in ASN1 format always follow a common structure: @@ -63,7 +63,7 @@ class ASN1ECKey : public ASN1Object virtual void decode(const std::vector& bytes); /** - * @brief Get the value associated with the given ASN.1 tag. + * Get the value associated with the given ASN.1 tag. * * @param tag The ASN.1 tag. * @return The value associated with the tag as a vector of bytes. @@ -71,7 +71,7 @@ class ASN1ECKey : public ASN1Object virtual const std::vector get(const std::byte tag) const; /** - * @brief A map to store ASN.1 key data with their associated tags. + * A map to store ASN.1 key data with their associated tags. */ std::unordered_map> asn1KeyData; }; diff --git a/src/sdk/main/include/impl/ASN1Object.h b/src/sdk/main/include/impl/ASN1Object.h index a96b2f349..151c3c060 100644 --- a/src/sdk/main/include/impl/ASN1Object.h +++ b/src/sdk/main/include/impl/ASN1Object.h @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * @brief This file defines the Hedera C++ SDK's ASN1Object class. + * This file defines the Hedera C++ SDK's ASN1Object class. */ #ifndef HEDERA_SDK_CPP_IMPL_ASN1_OBJECT_H_ @@ -30,7 +30,7 @@ namespace Hedera::internal::asn1 { /** - * @brief Constants for ASN.1 standard. + * Constants for ASN.1 standard. */ constexpr std::byte INTEGER = std::byte(0x02); constexpr std::byte BIT_STRING = std::byte(0x03); @@ -40,13 +40,13 @@ constexpr std::byte SEQUENCE = std::byte(0x30); /** * @class ASN1Object - * @brief Abstract base class for ASN.1 objects. + * Abstract base class for ASN.1 objects. */ class ASN1Object { protected: /** - * @brief Get the ASN.1 object's value in bytes. + * Get the ASN.1 object's value in bytes. * * @param tag The ASN.1 tag of the object. * @return The object's value in bytes. @@ -54,7 +54,7 @@ class ASN1Object virtual const std::vector get(const std::byte tag) const = 0; /** - * @brief Decode the ASN.1 object from a vector of bytes. + * Decode the ASN.1 object from a vector of bytes. * * @param data The vector of bytes containing the ASN.1 object's data. */ diff --git a/src/sdk/main/include/impl/HexConverter.h b/src/sdk/main/include/impl/HexConverter.h index 0887c718c..b019b0383 100644 --- a/src/sdk/main/include/impl/HexConverter.h +++ b/src/sdk/main/include/impl/HexConverter.h @@ -45,7 +45,7 @@ std::string bytesToHex(const std::vector& bytes); std::vector hexToBytes(std::string_view hex); /** - * @brief Convert a Base64-encoded string to a hexadecimal string. + * Convert a Base64-encoded string to a hexadecimal string. * * This function takes a Base64-encoded string as input and returns a hexadecimal * string. diff --git a/src/sdk/main/include/impl/HttpClient.h b/src/sdk/main/include/impl/HttpClient.h index c86c55abf..583fbada5 100644 --- a/src/sdk/main/include/impl/HttpClient.h +++ b/src/sdk/main/include/impl/HttpClient.h @@ -31,7 +31,7 @@ class HttpClient { public: /** - * @brief Constructor for HttpClient. + * Constructor for HttpClient. * * This constructor initializes the HttpClient and performs global libcurl initialization * using CURL_GLOBAL_DEFAULT. @@ -44,21 +44,21 @@ class HttpClient HttpClient& operator=(const HttpClient&) = delete; // no use case for httpClient copy /** - * @brief Destructor for HttpClient. + * Destructor for HttpClient. * * This destructor cleans up global libcurl resources using curl_global_cleanup. * It should be called when an HttpClient instance is no longer needed to release libcurl resources. */ ~HttpClient(); /** - * @brief Fetches data from the specified URL using the provided RPC method. + * Fetches data from the specified URL using the provided RPC method. * @param url The URL to fetch data from. * @param rpcMethod The RPC method. * @return The fetched data as a string. */ [[nodiscard]] std::string invokeRPC(const std::string& url, const std::string& rpcMethod); /** - * @brief This invokeREST function creates GET and POST requests. + * This invokeREST function creates GET and POST requests. * Can be further extended for supporting other HTTP * methods or handle more advanced scenarios as needed. * @param url The URL to fetch data from. @@ -67,10 +67,10 @@ class HttpClient * @return The fetched data as a string. */ [[nodiscard]] std::string invokeREST(const std::string& url, - const std::string& httpMethod, + const std::string& httpMethod = "GET", const std::string& requestBody = ""); /** - * @brief The callback function used for writing fetched data. + * The callback function used for writing fetched data. * @param contents A pointer to the fetched data. * @param size The size of each element. * @param nmemb The number of elements. diff --git a/src/sdk/main/include/impl/MirrorNodeGateway.h b/src/sdk/main/include/impl/MirrorNodeGateway.h new file mode 100644 index 000000000..83026db44 --- /dev/null +++ b/src/sdk/main/include/impl/MirrorNodeGateway.h @@ -0,0 +1,78 @@ +/*- + * + * Hedera C++ SDK + * + * Copyright (C) 2020 - 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. + * + */ +#ifndef HEDERA_SDK_CPP_MIRRORNODEGATEWAY_H +#define HEDERA_SDK_CPP_MIRRORNODEGATEWAY_H + +#include "impl/HttpClient.h" +#include "impl/MirrorNodeRouter.h" + +#include + +#include + +namespace Hedera::internal::MirrorNodeGateway +{ +/** + * Perform a mirror node query. + * + * This function constructs a URL based on the provided mirror node URL, query type, + * and parameters. It then sends a REST request to the constructed URL using an + * HttpClient, retrieves the response, and parses it into a JSON object. + * + * @param mirrorNodeUrl The mirror node URL. + * @param params A vector of strings representing parameters for the query. + * @param queryType The type of the query. + * @return A JSON object representing the response of the mirror node query. + * @throws IllegalStateException If an error occurs during the mirror node query or JSON parsing. + */ +nlohmann::json MirrorNodeQuery(std::string_view mirrorNodeUrl, + const std::vector& params, + std::string_view queryType); + +/** + * Replaces all occurrences of a substring in a string. + * + * This function replaces all occurrences of the substring specified by 'search' + * with the string specified by 'replace' in the original string. + * + * @param original The original string to be modified. + * @param search The substring to search for. + * @param replace The string to replace the occurrences of 'search'. + */ +void replaceParameters(std::string& original, std::string_view search, std::string_view replace); + +/** + * Builds a URL based on a mirror node URL, query type, and parameters. + * + * This function constructs a URL by combining a mirror node URL, a query type, + * and a list of parameters. It replaces occurrences of "$" in the route with + * the provided parameters. + * + * @param mirrorNodeUrl The mirror node URL. + * @param queryType The type of the query. + * @param params A vector of strings representing parameters. + * @return The constructed URL. + */ +std::string buildUrlForNetwork(std::string_view mirrorNodeUrl, + std::string_view queryType, + const std::vector& params, + bool& isLocalNetwork); +} // namespace Hedera::internal::MirrorNodeGateway +#endif // HEDERA_SDK_CPP_MIRRORNODEGATEWAY_H \ No newline at end of file diff --git a/src/sdk/main/include/impl/MirrorNodeRouter.h b/src/sdk/main/include/impl/MirrorNodeRouter.h new file mode 100644 index 000000000..d38b72fc2 --- /dev/null +++ b/src/sdk/main/include/impl/MirrorNodeRouter.h @@ -0,0 +1,68 @@ +/*- + * + * Hedera C++ SDK + * + * Copyright (C) 2020 - 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. + */ + +#ifndef HEDERA_SDK_CPP_MIRRORNODEROUTER_H +#define HEDERA_SDK_CPP_MIRRORNODEROUTER_H + +#include +#include +#include + +/** + * Namespace for the internal classes related to the Mirror Node Gateway. + */ +namespace Hedera::internal::MirrorNodeGateway +{ +/** + * Represents different mirror node query types. + */ +static const std::string& ACCOUNT_INFO_QUERY = "accountInfoQuery"; +static const std::string& CONTRACT_INFO_QUERY = "contractInfoQuery"; +static const std::string& TOKEN_RELATIONSHIPS_QUERY = "tokenRelationshipsQuery"; +static const std::string& TOKEN_BALANCES_QUERY = "tokenBalancesQuery"; + +/** + * Class responsible for routing requests to different mirror node routes. + */ +class MirrorNodeRouter +{ +public: + /** + * Retrieves the route for a specific mirror node query type. + * + * @param queryType The type of the mirror node query (e.g., "accountInfoQuery"). + * @return A string view representing the route for the specified mirror node query. + */ + [[nodiscard]] std::string getRoute(std::string_view queryType) const; + +private: + /** + * Internal mapping of mirror node query types to their respective routes. + */ + const std::unordered_map routes = { + {ACCOUNT_INFO_QUERY, "/api/v1/accounts/$" }, + { CONTRACT_INFO_QUERY, "/api/v1/contracts/$" }, + { TOKEN_RELATIONSHIPS_QUERY, "/api/v1/accounts/$/tokens"}, + { TOKEN_BALANCES_QUERY, "/api/v1/tokens/$/balances"} + }; +}; + +} // namespace Hedera::internal::MirrorNodeGateway + +#endif // HEDERA_SDK_CPP_MIRRORNODEROUTER_H diff --git a/src/sdk/main/include/impl/Utilities.h b/src/sdk/main/include/impl/Utilities.h index 26ef75c2f..4eb9d72ac 100644 --- a/src/sdk/main/include/impl/Utilities.h +++ b/src/sdk/main/include/impl/Utilities.h @@ -26,6 +26,8 @@ #include #include +#include + namespace Hedera::internal::Utilities { /** @@ -160,6 +162,19 @@ template */ [[nodiscard]] unsigned int getRandomNumber(unsigned int lowerBound, unsigned int upperBound); +/** + * Reads and parses a JSON file. + * + * This function reads the contents of a JSON file located at the specified path, + * parses it, and returns a JSON object. + * + * @param path The path to the JSON file. + * @return A JSON object representing the contents of the file. + * @throws std::invalid_argument if the file cannot be found or if the contents + * are not valid JSON. + */ +[[nodiscard]] nlohmann::json fromConfigFile(std::string_view path); + } // namespace Hedera::internal::Utilities #endif // HEDERA_SDK_CPP_IMPL_UTILITIES_H_ diff --git a/src/sdk/main/src/AccountBalanceQuery.cc b/src/sdk/main/src/AccountBalanceQuery.cc index f68bb0c66..dc38edc71 100644 --- a/src/sdk/main/src/AccountBalanceQuery.cc +++ b/src/sdk/main/src/AccountBalanceQuery.cc @@ -19,6 +19,9 @@ */ #include "AccountBalanceQuery.h" #include "AccountBalance.h" +#include "TokenId.h" +#include "exceptions/UninitializedException.h" +#include "impl/MirrorNodeGateway.h" #include "impl/Node.h" #include @@ -26,6 +29,13 @@ #include #include +#include +#include + +#include + +using json = nlohmann::json; + namespace Hedera { //----- @@ -49,7 +59,11 @@ AccountBalanceQuery& AccountBalanceQuery::setContractId(const ContractId& contra //----- AccountBalance AccountBalanceQuery::mapResponse(const proto::Response& response) const { - return AccountBalance::fromProtobuf(response.cryptogetaccountbalance()); + AccountBalance accountBalance = AccountBalance::fromProtobuf(response.cryptogetaccountbalance()); + + fetchTokenInformation(accountBalance); + + return accountBalance; } //----- @@ -75,6 +89,38 @@ void AccountBalanceQuery::validateChecksums(const Client& client) const } } +//----- +void AccountBalanceQuery::fetchTokenInformation(AccountBalance& accountBalance) const +{ + std::string param; + if (mAccountId.has_value()) + { + param = mAccountId.value().toString(); + } + else if (mContractId.has_value()) + { + param = mContractId.value().toString(); + } + else + { + throw UninitializedException("Missing both accountId and contractId"); + } + + json tokens = internal::MirrorNodeGateway::MirrorNodeQuery( + getMirrorNodeResolution(), { param }, internal::MirrorNodeGateway::TOKEN_RELATIONSHIPS_QUERY.data()); + + if (!tokens["tokens"].empty()) + { + for (const auto& token : tokens["tokens"]) + { + std::string tokenIdStr = token["token_id"].dump(); + uint64_t tokenBalance = token["balance"]; + TokenId tokenId = TokenId::fromString(tokenIdStr.substr(1, tokenIdStr.length() - 2)); + accountBalance.mTokens.insert({ tokenId, tokenBalance }); + } + } +} + //----- proto::Query AccountBalanceQuery::buildRequest(proto::QueryHeader* header) const { diff --git a/src/sdk/main/src/AccountInfoQuery.cc b/src/sdk/main/src/AccountInfoQuery.cc index a4ab662f2..3cb174175 100644 --- a/src/sdk/main/src/AccountInfoQuery.cc +++ b/src/sdk/main/src/AccountInfoQuery.cc @@ -19,6 +19,9 @@ */ #include "AccountInfoQuery.h" #include "AccountInfo.h" +#include "TokenId.h" +#include "TokenRelationship.h" +#include "impl/MirrorNodeGateway.h" #include "impl/Node.h" #include @@ -26,6 +29,10 @@ #include #include +#include + +using json = nlohmann::json; + namespace Hedera { //----- @@ -38,7 +45,11 @@ AccountInfoQuery& AccountInfoQuery::setAccountId(const AccountId& accountId) //----- AccountInfo AccountInfoQuery::mapResponse(const proto::Response& response) const { - return AccountInfo::fromProtobuf(response.cryptogetinfo().accountinfo()); + AccountInfo accountInfo = AccountInfo::fromProtobuf(response.cryptogetinfo().accountinfo()); + + fetchTokenInformation(accountInfo); + + return accountInfo; } //----- @@ -56,6 +67,32 @@ void AccountInfoQuery::validateChecksums(const Client& client) const mAccountId.validateChecksum(client); } +//----- +void AccountInfoQuery::fetchTokenInformation(AccountInfo& accountInfo) const +{ + json tokens = + internal::MirrorNodeGateway::MirrorNodeQuery(getMirrorNodeResolution(), + { mAccountId.toString() }, + internal::MirrorNodeGateway::TOKEN_RELATIONSHIPS_QUERY.data()); + if (!tokens["tokens"].empty()) + { + for (const auto& token : tokens["tokens"]) + { + bool automaticAssociation = token["automatic_association"]; + uint64_t balance = token["balance"]; + uint32_t decimals = token["decimals"]; + std::string kycStatus = token["kyc_status"].dump().substr(1, token["kyc_status"].dump().length() - 2); + std::string freezeStatus = token["freeze_status"].dump().substr(1, token["freeze_status"].dump().length() - 2); + TokenId tokenId = TokenId::fromString(token["token_id"].dump().substr(1, token["token_id"].dump().length() - 2)); + + TokenRelationship tokenRelationship( + tokenId, "", balance, decimals, kycStatus, freezeStatus, automaticAssociation); + + accountInfo.mTokenRelationships.insert({ tokenId, tokenRelationship }); + } + } +} + //----- proto::Query AccountInfoQuery::buildRequest(proto::QueryHeader* header) const { diff --git a/src/sdk/main/src/ContractInfoQuery.cc b/src/sdk/main/src/ContractInfoQuery.cc index e05596c01..735ede2bb 100644 --- a/src/sdk/main/src/ContractInfoQuery.cc +++ b/src/sdk/main/src/ContractInfoQuery.cc @@ -19,6 +19,7 @@ */ #include "ContractInfoQuery.h" #include "ContractInfo.h" +#include "impl/MirrorNodeGateway.h" #include "impl/Node.h" #include @@ -26,6 +27,10 @@ #include #include +#include + +using json = nlohmann::json; + namespace Hedera { //----- @@ -38,7 +43,11 @@ ContractInfoQuery& ContractInfoQuery::setContractId(const ContractId& contractId //----- ContractInfo ContractInfoQuery::mapResponse(const proto::Response& response) const { - return ContractInfo::fromProtobuf(response.contractgetinfo().contractinfo()); + ContractInfo contractInfo = ContractInfo::fromProtobuf(response.contractgetinfo().contractinfo()); + + fetchTokenInformation(contractInfo); + + return contractInfo; } //----- @@ -56,6 +65,32 @@ void ContractInfoQuery::validateChecksums(const Client& client) const mContractId.validateChecksum(client); } +//----- +void ContractInfoQuery::fetchTokenInformation(ContractInfo& contractInfo) const +{ + json tokens = + internal::MirrorNodeGateway::MirrorNodeQuery(getMirrorNodeResolution(), + { mContractId.toString() }, + internal::MirrorNodeGateway::TOKEN_RELATIONSHIPS_QUERY.data()); + if (!tokens["tokens"].empty()) + { + for (const auto& token : tokens["tokens"]) + { + bool automaticAssociation = token["automatic_association"]; + uint64_t balance = token["balance"]; + uint32_t decimals = token["decimals"]; + std::string kycStatus = token["kyc_status"].dump().substr(1, token["kyc_status"].dump().length() - 2); + std::string freezeStatus = token["freeze_status"].dump().substr(1, token["freeze_status"].dump().length() - 2); + TokenId tokenId = TokenId::fromString(token["token_id"].dump().substr(1, token["token_id"].dump().length() - 2)); + + TokenRelationship tokenRelationship( + tokenId, "", balance, decimals, kycStatus, freezeStatus, automaticAssociation); + + contractInfo.mTokenRelationships.insert({ tokenId, tokenRelationship }); + } + } +} + //----- proto::Query ContractInfoQuery::buildRequest(proto::QueryHeader* header) const { diff --git a/src/sdk/main/src/Executable.cc b/src/sdk/main/src/Executable.cc index 936a6923b..f2886240e 100644 --- a/src/sdk/main/src/Executable.cc +++ b/src/sdk/main/src/Executable.cc @@ -116,7 +116,6 @@ SdkResponseType Executable SdkResponseType Executable::execute( @@ -472,6 +471,7 @@ void Executable curl(curl_easy_init(), curl_easy_cleanup); if (!curl) { - throw std::runtime_error("Failed to initialize libcurl"); + throw CURLException("Failed to initialize libcurl"); } curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str()); @@ -101,7 +103,7 @@ std::string HttpClient::invokeRPC(const std::string& url, const std::string& rpc CURLcode res = curl_easy_perform(curl.get()); if (res != CURLE_OK) { - throw std::runtime_error(std::string("Error getting curl result! ") + curl_easy_strerror(res)); + throw CURLException(std::string("Error getting curl result! ") + curl_easy_strerror(res)); } return response; diff --git a/src/sdk/main/src/impl/MirrorNodeGateway.cc b/src/sdk/main/src/impl/MirrorNodeGateway.cc new file mode 100644 index 000000000..9629e8311 --- /dev/null +++ b/src/sdk/main/src/impl/MirrorNodeGateway.cc @@ -0,0 +1,98 @@ +/*- + * + * Hedera C++ SDK + * + * Copyright (C) 2020 - 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. + * + */ + +#include "impl/MirrorNodeGateway.h" +#include "exceptions/CURLException.h" +#include "exceptions/IllegalStateException.h" + +#include +#include +#include +#include + +using json = nlohmann::json; + +namespace Hedera::internal::MirrorNodeGateway +{ +//----- +json MirrorNodeQuery(std::string_view mirrorNodeUrl, const std::vector& params, std::string_view queryType) +{ + std::string response; + try + { + bool isLocalNetwork = true; + const std::string url = buildUrlForNetwork(mirrorNodeUrl, queryType, params, isLocalNetwork); + HttpClient httpClient; + + // this is needed because of Mirror Node update delay time + // agreed to be handled by the user not a local network + if (isLocalNetwork) + { + std::this_thread::sleep_for(std::chrono::seconds(3)); + } + + response = httpClient.invokeREST(url); + } + catch (const std::exception& e) + { + throw IllegalStateException(std::string(e.what() + std::string("Illegal json state!"))); + } + return json::parse(response); +} + +//----- +void replaceParameters(std::string& original, std::string_view search, std::string_view replace) +{ + size_t startPos = 0; + + while ((startPos = original.find(search, startPos)) != std::string::npos) + { + original.replace(startPos, search.length(), replace); + startPos += replace.length(); + } +} + +//----- +std::string buildUrlForNetwork(std::string_view mirrorNodeUrl, + std::string_view queryType, + const std::vector& params, + bool& isLocalNetwork) +{ + std::string httpPrefix = "http://"; + std::string localPrefix = "127.0.0.1:5600"; + std::string url = mirrorNodeUrl.data(); + if (url.compare(0, httpPrefix.length(), httpPrefix) != 0 && url.compare(0, localPrefix.length(), localPrefix) != 0) + { + isLocalNetwork = false; + url = "https://" + url; + } + if (url == localPrefix) + { + url = httpPrefix + url; + url.replace(url.length() - 4, 4, "5551"); + } + MirrorNodeRouter router; + std::string route = router.getRoute(queryType.data()).data(); + for_each( + params.begin(), params.end(), [&route](const std::string& replace) { replaceParameters(route, "$", replace); }); + url += route; + return url; +} +} \ No newline at end of file diff --git a/src/sdk/main/src/impl/MirrorNodeRouter.cc b/src/sdk/main/src/impl/MirrorNodeRouter.cc new file mode 100644 index 000000000..75777a088 --- /dev/null +++ b/src/sdk/main/src/impl/MirrorNodeRouter.cc @@ -0,0 +1,44 @@ +/*- + * + * Hedera C++ SDK + * + * Copyright (C) 2020 - 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. + * + */ + +#include "impl/MirrorNodeRouter.h" +#include "impl/Utilities.h" + +#include +#include + +namespace Hedera::internal::MirrorNodeGateway +{ +//----- +std::string MirrorNodeRouter::getRoute(std::string_view queryType) const +{ + std::string queryRoute; + auto route = routes.find(queryType.data()); + if (route != routes.end()) + { + queryRoute = route->second; + } + else + { + throw std::runtime_error("Route not found in the routes map"); + } + return queryRoute; +} +} // namespace Hedera::internal::MirrorNodeGateway \ No newline at end of file diff --git a/src/sdk/main/src/impl/Utilities.cc b/src/sdk/main/src/impl/Utilities.cc index 30cdf1d4a..6d8d8b697 100644 --- a/src/sdk/main/src/impl/Utilities.cc +++ b/src/sdk/main/src/impl/Utilities.cc @@ -21,9 +21,12 @@ #include #include +#include #include #include +using json = nlohmann::json; + namespace Hedera::internal::Utilities { //----- @@ -103,4 +106,27 @@ unsigned int getRandomNumber(unsigned int lowerBound, unsigned int upperBound) return dis(eng); } +//----- +json fromConfigFile(std::string_view path) +{ + std::ifstream infile(path.data()); + if (!infile.is_open()) + { + throw std::invalid_argument(std::string("File cannot be found at ") + path.data()); + } + + // Make sure the input file is valid JSON. + nlohmann::json jsonObj; + try + { + jsonObj = nlohmann::json::parse(infile); + } + catch (const std::exception& ex) + { + throw std::invalid_argument(std::string("Cannot parse JSON: ") + ex.what()); + } + + return jsonObj; +} + } // namespace Hedera::internal::Utilities diff --git a/src/sdk/tests/integration/AccountInfoQueryIntegrationTests.cc b/src/sdk/tests/integration/AccountInfoQueryIntegrationTests.cc index ba7562916..5c8ded4e7 100644 --- a/src/sdk/tests/integration/AccountInfoQueryIntegrationTests.cc +++ b/src/sdk/tests/integration/AccountInfoQueryIntegrationTests.cc @@ -27,8 +27,12 @@ #include "Defaults.h" #include "ED25519PrivateKey.h" #include "PrivateKey.h" +#include "TokenAssociateTransaction.h" +#include "TokenCreateTransaction.h" +#include "TokenGrantKycTransaction.h" #include "TransactionReceipt.h" #include "TransactionResponse.h" +#include "TransferTransaction.h" #include "exceptions/PrecheckStatusException.h" #include @@ -92,6 +96,7 @@ TEST_F(AccountInfoQueryIntegrationTests, CannotQueryDeletedAccount) .execute(getTestClient()) .getReceipt(getTestClient()) .mAccountId.value()); + ASSERT_NO_THROW(const TransactionReceipt txReceipt = AccountDeleteTransaction() .setDeleteAccountId(accountId) .setTransferAccountId(AccountId(2ULL)) @@ -104,3 +109,78 @@ TEST_F(AccountInfoQueryIntegrationTests, CannotQueryDeletedAccount) EXPECT_THROW(AccountInfoQuery().setAccountId(accountId).execute(getTestClient()), PrecheckStatusException); // ACCOUNT_DELETED } + +//----- +TEST_F(AccountInfoQueryIntegrationTests, GetTokenRelationshipsAfterTokenTransfer) +{ + // Given + const std::shared_ptr privateKey = ED25519PrivateKey::generatePrivateKey(); + AccountId accountId; + ASSERT_NO_THROW(accountId = AccountCreateTransaction() + .setKey(privateKey->getPublicKey()) + .execute(getTestClient()) + .getReceipt(getTestClient()) + .mAccountId.value()); + + uint64_t expectedBalance = 10; + uint32_t expectedDecimals = 0; + std::optional expectedKycStatus = true; + std::optional expectedFreezeStatus = false; + bool expectedAutomaticAssociation = false; + + // When + TransactionReceipt txReceipt; + EXPECT_NO_THROW(txReceipt = TokenCreateTransaction() + .setTokenName("ffff") + .setTokenSymbol("F") + .setDecimals(0) + .setInitialSupply(100000) + .setTreasuryAccountId(AccountId(2ULL)) + .setAdminKey(getTestClient().getOperatorPublicKey()) + .setFreezeKey(getTestClient().getOperatorPublicKey()) + .setWipeKey(getTestClient().getOperatorPublicKey()) + .setKycKey(getTestClient().getOperatorPublicKey()) + .setSupplyKey(getTestClient().getOperatorPublicKey()) + .setFeeScheduleKey(getTestClient().getOperatorPublicKey()) + .execute(getTestClient()) + .getReceipt(getTestClient())); + + // Then + TokenId tokenId; + EXPECT_NO_THROW(tokenId = txReceipt.mTokenId.value()); + + EXPECT_NO_THROW(txReceipt = TokenAssociateTransaction() + .setAccountId(accountId) + .setTokenIds({ tokenId }) + .freezeWith(&getTestClient()) + .sign(privateKey) + .execute(getTestClient()) + .getReceipt(getTestClient())); + + EXPECT_NO_THROW(txReceipt = TokenGrantKycTransaction() + .setAccountId(accountId) + .setTokenId(tokenId) + .freezeWith(&getTestClient()) + .sign(privateKey) + .execute(getTestClient()) + .getReceipt(getTestClient())); + + EXPECT_NO_THROW(txReceipt = TransferTransaction() + .addTokenTransfer(tokenId, getTestClient().getOperatorAccountId().value(), -10LL) + .addTokenTransfer(tokenId, accountId, 10LL) + .execute(getTestClient()) + .getReceipt(getTestClient())); + + // Then + AccountInfo accountInfo; + EXPECT_NO_THROW(accountInfo = AccountInfoQuery().setAccountId(accountId).execute(getTestClient())); + EXPECT_EQ(accountInfo.mTokenRelationships.empty(), false); + auto entry = *(accountInfo.mTokenRelationships.begin()); + EXPECT_EQ(entry.first.toString(), tokenId.toString()); + auto relationship = entry.second; + EXPECT_EQ(relationship.mBalance, expectedBalance); + EXPECT_EQ(relationship.mDecimals, expectedDecimals); + EXPECT_EQ(relationship.mKycStatus, expectedKycStatus); + EXPECT_EQ(relationship.mFreezeStatus, expectedFreezeStatus); + EXPECT_EQ(relationship.mAutomaticAssociation, expectedAutomaticAssociation); +} diff --git a/src/sdk/tests/integration/CMakeLists.txt b/src/sdk/tests/integration/CMakeLists.txt index 7bed29d90..c74295919 100644 --- a/src/sdk/tests/integration/CMakeLists.txt +++ b/src/sdk/tests/integration/CMakeLists.txt @@ -32,6 +32,7 @@ add_executable(${TEST_PROJECT_NAME} FreezeTransactionIntegrationTests.cc HttpClientIntegrationTests.cc JSONIntegrationTests.cc + MirrorNodeGatewayIntegrationTests.cc NetworkVersionInfoQueryIntegrationTests.cc PrngTransactionIntegrationTests.cc QueryIntegrationTests.cc diff --git a/src/sdk/tests/integration/ClientIntegrationTests.cc b/src/sdk/tests/integration/ClientIntegrationTests.cc index 991cc95ce..83c5d8a5a 100644 --- a/src/sdk/tests/integration/ClientIntegrationTests.cc +++ b/src/sdk/tests/integration/ClientIntegrationTests.cc @@ -127,7 +127,13 @@ TEST_F(ClientIntegrationTests, ConnectToLocalNode) } //----- -TEST_F(ClientIntegrationTests, SetNetworkIsWorkingCorrectly) +// Disabled as certain queries would need to query the Mirror Node. +// Mirror Node url's are built from the client network so they can +// resolve to the correct local/testnet/mainnet/previewnet networks. +// When networkMap is built there is no guarantee that queries would +// resolve to an appropriate mirror node url. This specific case +// should be further taken into consideration for HIP-367. +TEST_F(ClientIntegrationTests, DISABLED_SetNetworkIsWorkingCorrectly) { // Given const AccountId accountId_3 = AccountId::fromString("0.0.3"); diff --git a/src/sdk/tests/integration/HttpClientIntegrationTests.cc b/src/sdk/tests/integration/HttpClientIntegrationTests.cc index 4e17053e8..4c004fbd0 100644 --- a/src/sdk/tests/integration/HttpClientIntegrationTests.cc +++ b/src/sdk/tests/integration/HttpClientIntegrationTests.cc @@ -21,8 +21,6 @@ #include "BaseIntegrationTest.h" #include "impl/HttpClient.h" -#include -#include #include #include #include diff --git a/src/sdk/tests/integration/MirrorNodeGatewayIntegrationTests.cc b/src/sdk/tests/integration/MirrorNodeGatewayIntegrationTests.cc new file mode 100644 index 000000000..de9dfaee3 --- /dev/null +++ b/src/sdk/tests/integration/MirrorNodeGatewayIntegrationTests.cc @@ -0,0 +1,107 @@ +/*- + * + * Hedera C++ SDK + * + * Copyright (C) 2020 - 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. + * + */ + +#include "BaseIntegrationTest.h" +#include "impl/MirrorNodeGateway.h" + +#include +#include +#include + +using json = nlohmann::json; +using namespace Hedera; + +class MirrorNodeGatewayIntegrationTests : public BaseIntegrationTest +{ +protected: + [[nodiscard]] inline const std::string& getAccountIdStr() const { return mAccountIdStr; } + [[nodiscard]] inline const std::string& getMirrorNetworkUrl() const { return mMirrorNetworkUrl; } + +private: + const std::string mAccountIdStr = "0.0.3"; + const std::string mMirrorNetworkUrl = "http://127.0.0.1:5551"; +}; + +//----- +TEST_F(MirrorNodeGatewayIntegrationTests, AccountInfoQuery) +{ + // Given + const std::string& accountIdStr = getAccountIdStr(); + + // When + json response; + ASSERT_NO_THROW(response = internal::MirrorNodeGateway::MirrorNodeQuery( + getMirrorNetworkUrl(), { accountIdStr }, internal::MirrorNodeGateway::ACCOUNT_INFO_QUERY.data());); + + // Then + ASSERT_FALSE(response.empty()); // checks if any data + EXPECT_TRUE(response["_status"].empty()); // if status is in json then not found +} + +//----- +TEST_F(MirrorNodeGatewayIntegrationTests, ContractInfoQuery) +{ + // Given + const std::string& contractIdStr = getAccountIdStr(); + + // When + json response; + ASSERT_NO_THROW( + response = internal::MirrorNodeGateway::MirrorNodeQuery( + getMirrorNetworkUrl(), { contractIdStr }, internal::MirrorNodeGateway::CONTRACT_INFO_QUERY.data());); + + // Then + ASSERT_FALSE(response.empty()); // checks if any data + EXPECT_FALSE(response["_status"].empty()); // no such contract exists then should have _status not found +} + +//----- +TEST_F(MirrorNodeGatewayIntegrationTests, TokenRelationshipQuery) +{ + // Given + const std::string& accountIdStr = getAccountIdStr(); + + // When + json response; + ASSERT_NO_THROW( + response = internal::MirrorNodeGateway::MirrorNodeQuery( + getMirrorNetworkUrl(), { accountIdStr }, internal::MirrorNodeGateway::TOKEN_RELATIONSHIPS_QUERY.data());); + + // Then + ASSERT_FALSE(response.empty()); // checks if any data + EXPECT_TRUE(response["_status"].empty()); // if status is in json then not found +} + +//----- +TEST_F(MirrorNodeGatewayIntegrationTests, TokensBalancesQuery) +{ + // Given + const std::string& accountIdStr = getAccountIdStr(); + + // When + json response; + ASSERT_NO_THROW( + response = internal::MirrorNodeGateway::MirrorNodeQuery( + getMirrorNetworkUrl(), { accountIdStr }, internal::MirrorNodeGateway::TOKEN_BALANCES_QUERY.data());); + + // Then + ASSERT_FALSE(response.empty()); // checks if any data + EXPECT_TRUE(response["_status"].empty()); // no such contract exists then should have _status not found +}