diff --git a/CMakeLists.txt b/CMakeLists.txt index 77b81f68..4dfdb1cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,7 @@ SET(PROJECT_NAME "cardano-c") # Option that the user can optionally select OPTION (TESTING_ENABLED "Enables unit test build." ON) OPTION (DOXYGEN_ENABLED "Build documentation" OFF) +OPTION (EXAMPLES_ENABLED "Build examples" OFF) # Find external dependencies LIST (APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) @@ -67,6 +68,7 @@ MESSAGE ( STATUS "CMAKE_PREFIX_PATH = ${CMAKE_PREFIX_PATH}" ) MESSAGE ( STATUS "ARCHITECTURE = ${ARCHITECTURE}" ) MESSAGE ( STATUS "TESTING ENABLED = ${TESTING_ENABLED}") MESSAGE ( STATUS "DOXYGEN ENABLED = ${DOXYGEN_ENABLED}") +MESSAGE ( STATUS "EXAMPLES ENABLED = ${EXAMPLES_ENABLED}") MESSAGE ( STATUS "CMAKE_C_CLANG_TIDY = ${CMAKE_C_CLANG_TIDY}") MESSAGE ( STATUS ) MESSAGE ( STATUS "change a configuration variable with: cmake -D=" ) @@ -159,6 +161,10 @@ IF (CMAKE_BUILD_TYPE STREQUAL "Fuzz") ADD_SUBDIRECTORY (fuzz ${BUILD_DIR}/fuzz) ENDIF () +# Build examples +IF (EXAMPLES_ENABLED) + ADD_SUBDIRECTORY (examples ${BUILD_DIR}/examples) +ENDIF () # Packaging. SET (CPACK_SOURCE_IGNORE_FILES ".git,.swp") diff --git a/doc/src/sections/api/providers/index.rst b/doc/src/sections/api/providers/index.rst new file mode 100644 index 00000000..8c6731a7 --- /dev/null +++ b/doc/src/sections/api/providers/index.rst @@ -0,0 +1,10 @@ +Providers +=================== + +This section of the documentation introduces the data providers for interacting with the Cardano blockchain: + +.. toctree:: + :maxdepth: 1 + + provider + provider_impl \ No newline at end of file diff --git a/doc/src/sections/api/providers/provider.rst b/doc/src/sections/api/providers/provider.rst new file mode 100644 index 00000000..c801b7f7 --- /dev/null +++ b/doc/src/sections/api/providers/provider.rst @@ -0,0 +1,72 @@ +Provider +========================== + +.. doxygentypedef:: cardano_provider_t + +------------ + +.. doxygenfunction:: cardano_provider_new + +------------ + +.. doxygenfunction:: cardano_provider_get_name + +------------ + +.. doxygenfunction:: cardano_provider_get_parameters + +------------ + +.. doxygenfunction:: cardano_provider_get_unspent_outputs + +------------ + +.. doxygenfunction:: cardano_provider_get_rewards_available + +------------ + +.. doxygenfunction:: cardano_provider_get_unspent_outputs_with_asset + +------------ + +.. doxygenfunction:: cardano_provider_get_unspent_output_by_nft + +------------ + +.. doxygenfunction:: cardano_provider_resolve_unspent_outputs + +------------ + +.. doxygenfunction:: cardano_provider_resolve_datum_outputs + +------------ + +.. doxygenfunction:: cardano_provider_confirm_transaction_outputs + +------------ + +.. doxygenfunction:: cardano_provider_submit_transaction + +------------ + +.. doxygenfunction:: cardano_provider_evaluate_transaction + +------------ + +.. doxygenfunction:: cardano_provider_unref + +------------ + +.. doxygenfunction:: cardano_provider_ref + +------------ + +.. doxygenfunction:: cardano_provider_refcount + +------------ + +.. doxygenfunction:: cardano_provider_set_last_error + +------------ + +.. doxygenfunction:: cardano_provider_get_last_error diff --git a/doc/src/sections/api/providers/provider_impl.rst b/doc/src/sections/api/providers/provider_impl.rst new file mode 100644 index 00000000..ad098f81 --- /dev/null +++ b/doc/src/sections/api/providers/provider_impl.rst @@ -0,0 +1,49 @@ +Provider Implementation +========================== + +.. doxygentypedef:: cardano_provider_impl_t + +------------ + +.. doxygentypedef:: cardano_get_parameters_func_t + + +------------ + +.. doxygentypedef:: cardano_get_unspent_outputs_func_t + +------------ + +.. doxygentypedef:: cardano_get_rewards_balance_func_t + +------------ + +.. doxygentypedef:: cardano_get_unspent_outputs_with_asset_func_t + +------------ + +.. doxygentypedef:: cardano_get_unspent_output_by_nft_func_t + +------------ + +.. doxygentypedef:: cardano_resolve_unspent_outputs_func_t + + +------------ + +.. doxygentypedef:: cardano_resolve_datum_func_t + +------------ + +.. doxygentypedef:: cardano_confirm_transaction_func_t + + +------------ + +.. doxygentypedef:: cardano_submit_transaction_func_t + + +------------ + +.. doxygentypedef:: cardano_evaluate_transaction_func_t + diff --git a/doc/src/sections/api/transaction_body/value.rst b/doc/src/sections/api/transaction_body/value.rst index 957b112d..5528ecdb 100644 --- a/doc/src/sections/api/transaction_body/value.rst +++ b/doc/src/sections/api/transaction_body/value.rst @@ -9,6 +9,10 @@ Value ------------ +.. doxygenfunction:: cardano_value_from_asset_map + +------------ + .. doxygenfunction:: cardano_value_from_cbor ------------ diff --git a/doc/src/sections/api/witness_set/redeemer_list.rst b/doc/src/sections/api/witness_set/redeemer_list.rst index d1c9179d..f649fcb2 100644 --- a/doc/src/sections/api/witness_set/redeemer_list.rst +++ b/doc/src/sections/api/witness_set/redeemer_list.rst @@ -29,6 +29,14 @@ Redeemer List ------------ +.. doxygenfunction:: cardano_redeemer_list_set_ex_units + +------------ + +.. doxygenfunction:: cardano_redeemer_list_clone + +------------ + .. doxygenfunction:: cardano_redeemer_list_clear_cbor_cache ------------ diff --git a/doc/src/sections/index.rst b/doc/src/sections/index.rst index c4b1c01b..476a3018 100644 --- a/doc/src/sections/index.rst +++ b/doc/src/sections/index.rst @@ -83,6 +83,7 @@ See `APACHE LICENSE, VERSION 2.0`_ api/pool_params/index api/proposal_procedures/index api/protocol_params/index + api/providers/index api/scripts/index api/transaction/index api/transaction_body/index diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 00000000..95c953d9 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,20 @@ +FILE(GLOB EXAMPLE_SOURCES "*_example.c") +MESSAGE(STATUS "Example sources: ${EXAMPLE_SOURCES}") + +FILE(GLOB_RECURSE PROVIDERS_HEADER_DIRS RELATIVE ${CMAKE_SOURCE_DIR} utils/ providers/) + +FILE(GLOB_RECURSE PROVIDERS_SOURCE_FILES utils/*.c providers/*.c) + +FIND_PACKAGE(CURL REQUIRED) + +FOREACH(EXAMPLE_SOURCE ${EXAMPLE_SOURCES}) + GET_FILENAME_COMPONENT(EXAMPLE_TARGET_NAME ${EXAMPLE_SOURCE} NAME_WE) # Extract the filename without extension + + ADD_EXECUTABLE(${EXAMPLE_TARGET_NAME} ${EXAMPLE_SOURCE} ${PROVIDERS_SOURCE_FILES}) + MESSAGE(STATUS "Example source: ${EXAMPLE_TARGET_NAME}") + + TARGET_INCLUDE_DIRECTORIES(${EXAMPLE_TARGET_NAME} PRIVATE ${CARDANO_C_INCLUDE_DIR}) + TARGET_INCLUDE_DIRECTORIES(${EXAMPLE_TARGET_NAME} PRIVATE utils providers) + + TARGET_LINK_LIBRARIES(${EXAMPLE_TARGET_NAME} PRIVATE cardano-c ${CURL_LIBRARIES}) +ENDFOREACH() \ No newline at end of file diff --git a/examples/provider_example.c b/examples/provider_example.c new file mode 100644 index 00000000..0e539a2a --- /dev/null +++ b/examples/provider_example.c @@ -0,0 +1,311 @@ +/** + * \file provider_example.c + * + * \author angel.castillo + * \date Sep 28, 2024 + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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. + */ + +/* INCLUDES ******************************************************************/ + +#include "providers/provider_factory.h" + +#include "utils/console.h" +#include + +#include +#include +#include + +/* DECLARATIONS **************************************************************/ + +/** + * \brief Creates a Cardano address object from a string representation. + * + * This function creates a \ref cardano_address_t object from the provided address string. The address string can represent a + * Bech32 or hexadecimal Cardano address. The function validates the address format and initializes the corresponding + * address object. + * + * \param[in] address A pointer to the address string. This parameter must not be NULL. + * \param[in] address_length The length of the address string in bytes. + * + * \return A pointer to the newly created \ref cardano_address_t object if the address is valid. Returns NULL if the + * address creation fails due to an invalid address format or any internal error. + */ +static cardano_address_t* +create_address(const char* address, size_t address_length); + +/** + * \brief Creates a Cardano reward address object from a string representation. + * + * This function creates a \ref cardano_reward_address_t object from the provided address string. The address string can represent + * a valid Bech32-encoded Cardano reward address. The function validates the address format and initializes the corresponding + * reward address object. + * + * \param[in] address_str A pointer to the reward address string. This parameter must not be NULL. + * \param[in] address_str_length The length of the reward address string in bytes. + * + * \return A pointer to the newly created \ref cardano_reward_address_t object if the address is valid. Returns NULL if the + * address creation fails due to an invalid address format or any internal error. + */ +static cardano_reward_address_t* +create_reward_address(const char* address_str, size_t address_str_length); + +/** + * \brief Displays a summary of the total balance, including UTXO and reward balances. + * + * This function aggregates the ADA from all UTXOs in the provided \ref cardano_utxo_list_t and combines it with the reward balance. + * The resulting balances are displayed, including: + * - The total amount of ADA in the UTXOs. + * - The reward balance. + * - The total of UTXO balance + reward balance. + * + * \param[in] utxos A pointer to the UTXO list (\ref cardano_utxo_list_t) from which the ADA amounts will be summed. + * This parameter must not be NULL. + * \param[in] reward_balance The total amount of rewards as a \c uint64_t. + * + * \return \ref cardano_error_t indicating success or an error if the UTXO list cannot be processed. + */ +static cardano_error_t +display_balance(cardano_utxo_list_t* utxos, uint64_t reward_balance); + +/* MAIN **********************************************************************/ + +/** + * \brief Entry point of the program. + * + * \return Returns `0` on successful execution, or a non-zero value if there is an error. + */ +int +main(void) +{ + // Preprod addresses. + static const char* address = "addr_test1qqnqfr70emn3kyywffxja44znvdw0y4aeyh0vdc3s3rky48vlp50u6nrq5s7k6h89uqrjnmr538y6e50crvz6jdv3vqqxah5fk"; + static const char* stake_address = "stake_test1urk0s687df3s2g0tdtnj7qpefa36gnjdv68upkpdfxkgkqq8kq6ly"; + + const char* api_key = getenv("BLOCKFROST_API_KEY"); + + if (api_key == NULL) + { + console_error("BLOCKFROST_API_KEY environment variable is not set.\n"); + + return EXIT_FAILURE; + } + + cardano_provider_t* provider = NULL; + + cardano_error_t result = create_blockfrost_provider(CARDANO_NETWORK_MAGIC_PREPROD, api_key, strlen(api_key), &provider); + + if (result != CARDANO_SUCCESS) + { + console_error("Failed to create blockfrost provider: %s\n", cardano_provider_get_last_error(provider)); + cardano_provider_unref(&provider); + + return EXIT_FAILURE; + } + + console_info("Cardano Provider Example"); + console_debug("libcardano-c: V-%s", cardano_get_lib_version()); + console_debug("Provider name: %s\n", cardano_provider_get_name(provider)); + + cardano_address_t* payment_address = create_address(address, strlen(address)); + + if (payment_address == NULL) + { + cardano_provider_unref(&provider); + + return EXIT_FAILURE; + } + + cardano_reward_address_t* reward_address = create_reward_address(stake_address, strlen(stake_address)); + + if (reward_address == NULL) + { + cardano_address_unref(&payment_address); + cardano_provider_unref(&provider); + + return EXIT_FAILURE; + } + + uint64_t rewards_available = 0U; + result = cardano_provider_get_rewards_available(provider, reward_address, &rewards_available); + + if (result != CARDANO_SUCCESS) + { + console_error("Failed to get rewards available: %s", cardano_provider_get_last_error(provider)); + + cardano_provider_unref(&provider); + cardano_address_unref(&payment_address); + cardano_reward_address_unref(&reward_address); + + return EXIT_FAILURE; + } + + cardano_utxo_list_t* utxo_list = NULL; + result = cardano_provider_get_unspent_outputs(provider, payment_address, &utxo_list); + + if (result != CARDANO_SUCCESS) + { + console_error("Failed to get unspent outputs: %s", cardano_provider_get_last_error(provider)); + + cardano_provider_unref(&provider); + cardano_address_unref(&payment_address); + cardano_reward_address_unref(&reward_address); + + return EXIT_FAILURE; + } + + result = display_balance(utxo_list, rewards_available); + + cardano_provider_unref(&provider); + cardano_address_unref(&payment_address); + cardano_reward_address_unref(&reward_address); + cardano_utxo_list_unref(&utxo_list); + + if (result != CARDANO_SUCCESS) + { + console_error("Failed to display balance: %s", cardano_provider_get_last_error(provider)); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +/* IMPLEMENTATIONS **********************************************************/ + +static cardano_address_t* +create_address(const char* address, const size_t address_length) +{ + cardano_address_t* payment_address = NULL; + + cardano_error_t result = cardano_address_from_string(address, address_length, &payment_address); + + if (result != CARDANO_SUCCESS) + { + console_error("Failed to create payment address: %s", cardano_error_to_string(result)); + return NULL; + } + + return payment_address; +} + +static cardano_reward_address_t* +create_reward_address(const char* address_str, const size_t address_str_length) +{ + cardano_reward_address_t* reward_address = NULL; + cardano_error_t result = cardano_reward_address_from_bech32(address_str, address_str_length, &reward_address); + + if (result != CARDANO_SUCCESS) + { + console_error("Failed to create reward address: %s", cardano_error_to_string(result)); + return NULL; + } + + return reward_address; +} + +static cardano_error_t +display_balance(cardano_utxo_list_t* utxos, const uint64_t reward_balance) +{ + cardano_value_t* total = NULL; + + cardano_error_t result = cardano_value_new(0, NULL, &total); + + if (result != CARDANO_SUCCESS) + { + console_error("Failed to create value"); + return result; + } + + for (size_t i = 0U; i < cardano_utxo_list_get_length(utxos); ++i) + { + cardano_utxo_t* utxo = NULL; + + result = cardano_utxo_list_get(utxos, i, &utxo); + + if (result != CARDANO_SUCCESS) + { + cardano_value_unref(&total); + + console_error("Failed to get utxo"); + return result; + } + + cardano_transaction_output_t* output = cardano_utxo_get_output(utxo); + cardano_value_t* output_value = cardano_transaction_output_get_value(output); + + if (output_value == NULL) + { + // *_unref functions are safe to use with NULL pointers. + cardano_utxo_unref(&utxo); + cardano_value_unref(&output_value); + cardano_transaction_output_unref(&output); + cardano_value_unref(&total); + + console_error("Failed to get output value"); + + return result; + } + + cardano_value_t* tmp = NULL; + + result = cardano_value_add(total, output_value, &tmp); + + if (result != CARDANO_SUCCESS) + { + cardano_utxo_unref(&utxo); + cardano_value_unref(&output_value); + cardano_transaction_output_unref(&output); + cardano_value_unref(&total); + + console_error("Failed to add value"); + + return result; + } + + cardano_value_unref(&total); + total = tmp; + + cardano_utxo_unref(&utxo); + cardano_value_unref(&output_value); + cardano_transaction_output_unref(&output); + } + + uint64_t total_coin = cardano_value_get_coin(total); + uint64_t total_lovelace = total_coin + reward_balance; + + console_info("Balance Summary"); + console_info("==================================="); + + console_write("Available lovelace: "); + console_set_foreground_color(CONSOLE_COLOR_GREEN); + console_write("%lu\n", total_coin); + console_reset_color(); + + console_write("Withdrawable rewards: "); + console_set_foreground_color(CONSOLE_COLOR_GREEN); + console_write("%lu\n", reward_balance); + console_reset_color(); + + console_write("Total lovelace: "); + console_set_foreground_color(CONSOLE_COLOR_GREEN); + console_write("%lu\n", total_lovelace); + console_reset_color(); + + cardano_value_unref(&total); + + return result; +} \ No newline at end of file diff --git a/examples/providers/blockfrost/blockfrost_provider.c b/examples/providers/blockfrost/blockfrost_provider.c new file mode 100644 index 00000000..a7d09945 --- /dev/null +++ b/examples/providers/blockfrost/blockfrost_provider.c @@ -0,0 +1,1091 @@ +/** + * \file blockfrost_provider.c + * + * \author angel.castillo + * \date Sep 28, 2024 + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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. + */ + +/* INCLUDES ******************************************************************/ + +#include "../provider_factory.h" + +#include "../../utils/utils.h" +#include "blockfrost/common/blockfrost_common.h" +#include "blockfrost/common/blockfrost_url_builders.h" +#include "blockfrost/parsers/blockfrost_parsers.h" + +#include +#include +#include +#include + +/* STATIC FUNCTIONS **********************************************************/ + +/** + * \brief Creates a new blockfrost context object. + * + * \return A pointer to the newly created blockfrost context object, or `NULL` if the operation failed. + */ +static blockfrost_context_t* +cardano_blockfrost_context_new(void) +{ + blockfrost_context_t* data = malloc(sizeof(blockfrost_context_t)); + + if (data == NULL) + { + return NULL; + } + + data->base.ref_count = 1; + data->base.last_error[0] = '\0'; + data->base.deallocator = free; + + data->network = CARDANO_NETWORK_MAGIC_PREPROD; + + CARDANO_UNUSED(memset(data->project_id, 0, sizeof(data->project_id))); + + return data; +} + +/** + * \brief Retrieves protocol parameters from the provider and stores them in a `cardano_protocol_parameters_t` structure. + * + * This function queries the provider (such as Blockfrost) for the current protocol parameters and parses + * the response into the `cardano_protocol_parameters_t` structure. The function is responsible for handling + * the request, processing the response, and properly allocating memory for the parameters structure. + * + * \param[in] provider_impl A pointer to the `cardano_provider_impl_t` structure, which contains provider-specific data and logic. + * \param[out] parameters A double pointer to a `cardano_protocol_parameters_t` structure. The function will allocate memory + * for the parameters and populate them with the retrieved protocol parameters. The caller is responsible + * for deallocating the memory using the appropriate function once done. + * + * \return `CARDANO_SUCCESS` if the parameters were successfully retrieved and parsed; otherwise, an appropriate `cardano_error_t` + * error code is returned, indicating the failure reason (e.g., network errors, parsing errors). + */ +static cardano_error_t +get_parameters(cardano_provider_impl_t* provider_impl, cardano_protocol_parameters_t** parameters) +{ + blockfrost_context_t* context = (blockfrost_context_t*)provider_impl->context; + char* url = cardano_blockfrost_get_endpoint_url(context->network, "epochs/latest/parameters"); + cardano_buffer_t* response_buffer = NULL; + uint64_t response_code = 0; + + cardano_error_t result = cardano_blockfrost_http_get(provider_impl, url, strlen(url), &response_code, &response_buffer); + free(url); + + if ((response_code != 200U) || (result != CARDANO_SUCCESS)) + { + cardano_blockfrost_parse_error(provider_impl, response_buffer); + + cardano_buffer_unref(&response_buffer); + + return CARDANO_ERROR_INVALID_HTTP_REQUEST; + } + + result = cardano_blockfrost_parse_protocol_parameters(provider_impl, (char*)cardano_buffer_get_data(response_buffer), cardano_buffer_get_size(response_buffer), parameters); + + cardano_buffer_unref(&response_buffer); + + if (result != CARDANO_SUCCESS) + { + cardano_protocol_parameters_unref(parameters); + } + + return result; +} + +/** + * \brief Retrieves the unspent transaction outputs (UTXOs) for a given address. + * + * This function fetches the unspent transaction outputs (UTXOs) associated with the specified Cardano address using the provided provider implementation. + * The UTXOs are returned as a \ref cardano_utxo_list_t object. + * + * \param[in] provider_impl A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context for querying the blockchain. + * This parameter must not be NULL. + * \param[in] address A pointer to an initialized \ref cardano_address_t object representing the Cardano address for which UTXOs are being retrieved. + * This parameter must not be NULL. + * \param[out] utxo_list On successful execution, this will point to a newly created \ref cardano_utxo_list_t object containing the list of UTXOs for the specified address. + * The caller is responsible for managing the lifecycle of this object and must call \ref cardano_utxo_list_unref when it is no longer needed. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the UTXOs were successfully retrieved, + * or an appropriate error code if an error occurred (e.g., if the query failed or the address is invalid). + */ +static cardano_error_t +get_unspent_outputs( + cardano_provider_impl_t* provider_impl, + cardano_address_t* address, + cardano_utxo_list_t** utxo_list) +{ + size_t page = 1; + size_t max_results = 100; + size_t last_results = 0; + + cardano_error_t result = CARDANO_SUCCESS; + + do + { + char* url = cardano_blockfrost_build_utxo_url(provider_impl, cardano_address_get_string(address), page, max_results); + cardano_buffer_t* response_buffer = NULL; + uint64_t response_code = 0; + + result = cardano_blockfrost_http_get(provider_impl, url, strlen(url), &response_code, &response_buffer); + free(url); + + if ((response_code != 200U) || (result != CARDANO_SUCCESS)) + { + cardano_blockfrost_parse_error(provider_impl, response_buffer); + + cardano_buffer_unref(&response_buffer); + + return CARDANO_ERROR_INVALID_HTTP_REQUEST; + } + + cardano_utxo_list_t* current_list = NULL; + result = cardano_blockfrost_parse_unspent_outputs(provider_impl, (char*)cardano_buffer_get_data(response_buffer), cardano_buffer_get_size(response_buffer), ¤t_list); + + cardano_buffer_unref(&response_buffer); + + if (result != CARDANO_SUCCESS) + { + cardano_utxo_list_unref(utxo_list); + } + + last_results = cardano_utxo_list_get_length(current_list); + + if (*utxo_list == NULL) + { + *utxo_list = current_list; + } + else + { + cardano_utxo_list_t* tmp_list = *utxo_list; + + *utxo_list = cardano_utxo_list_concat(tmp_list, current_list); + + cardano_utxo_list_unref(&tmp_list); + cardano_utxo_list_unref(¤t_list); + } + + ++page; + } + while (last_results == max_results); + + return result; +} + +/** + * \brief Retrieves the staking rewards balance for a given reward address. + * + * This function queries the staking rewards associated with a given reward address using the provided \ref cardano_provider_impl_t. + * The rewards balance is returned as a 64-bit unsigned integer. + * + * \param[in] provider_impl A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context for querying the rewards. + * This parameter must not be NULL. + * \param[in] address A pointer to an initialized \ref cardano_reward_address_t object representing the reward address for which to retrieve the rewards. + * This parameter must not be NULL. + * \param[out] rewards On successful execution, this will hold the total rewards balance as a 64-bit unsigned integer. The caller must ensure this pointer is valid. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the rewards were successfully retrieved, + * or an appropriate error code if an error occurred (e.g., if the query failed or if the address is invalid). + */ +static cardano_error_t +get_rewards_balance(cardano_provider_impl_t* provider_impl, cardano_reward_address_t* address, uint64_t* rewards) +{ + char* url = cardano_blockfrost_build_rewards_url(provider_impl, cardano_reward_address_get_string(address)); + cardano_buffer_t* response_buffer = NULL; + uint64_t response_code = 0; + + cardano_error_t result = cardano_blockfrost_http_get(provider_impl, url, strlen(url), &response_code, &response_buffer); + free(url); + + if ((response_code != 200U) || (result != CARDANO_SUCCESS)) + { + cardano_blockfrost_parse_error(provider_impl, response_buffer); + + cardano_buffer_unref(&response_buffer); + + return CARDANO_ERROR_INVALID_HTTP_REQUEST; + } + + result = cardano_blockfrost_parse_rewards(provider_impl, (char*)cardano_buffer_get_data(response_buffer), cardano_buffer_get_size(response_buffer), rewards); + + cardano_buffer_unref(&response_buffer); + + return result; +} + +/** + * \brief Retrieves the unspent transaction outputs (UTXOs) associated with a given address and asset ID. + * + * This function queries the UTXOs associated with a specified address and filters them by the provided asset ID. The results are stored in a UTXO list. + * + * \param[in] provider_impl A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context for querying the UTXOs. + * This parameter must not be NULL. + * \param[in] address A pointer to an initialized \ref cardano_address_t object representing the address to query for UTXOs. This parameter must not be NULL. + * \param[in] asset_id A pointer to an initialized \ref cardano_asset_id_t object representing the asset ID used to filter the UTXOs. This parameter must not be NULL. + * \param[out] utxo_list A pointer to a pointer of \ref cardano_utxo_list_t that will be populated with the filtered UTXOs. + * The caller must manage the lifecycle of this list and free it when no longer needed. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the UTXOs were successfully retrieved, + * or an appropriate error code if an error occurred (e.g., if the query failed or the address/asset ID is invalid). + */ +static cardano_error_t +get_unspent_outputs_with_asset( + cardano_provider_impl_t* provider_impl, + cardano_address_t* address, + cardano_asset_id_t* asset_id, + cardano_utxo_list_t** utxo_list) +{ + size_t page = 1; + size_t max_results = 100; + size_t last_results = 0; + + cardano_error_t result = CARDANO_SUCCESS; + + do + { + char* url = cardano_blockfrost_build_utxo_with_asset_url(provider_impl, cardano_address_get_string(address), cardano_asset_id_get_hex(asset_id), page, max_results); + cardano_buffer_t* response_buffer = NULL; + uint64_t response_code = 0; + + result = cardano_blockfrost_http_get(provider_impl, url, strlen(url), &response_code, &response_buffer); + free(url); + + if ((response_code != 200U) || (result != CARDANO_SUCCESS)) + { + cardano_blockfrost_parse_error(provider_impl, response_buffer); + + cardano_buffer_unref(&response_buffer); + + return CARDANO_ERROR_INVALID_HTTP_REQUEST; + } + + cardano_utxo_list_t* current_list = NULL; + result = cardano_blockfrost_parse_unspent_outputs(provider_impl, (char*)cardano_buffer_get_data(response_buffer), cardano_buffer_get_size(response_buffer), ¤t_list); + + cardano_buffer_unref(&response_buffer); + + if (result != CARDANO_SUCCESS) + { + cardano_utxo_list_unref(utxo_list); + } + + last_results = cardano_utxo_list_get_length(current_list); + + if (*utxo_list == NULL) + { + *utxo_list = current_list; + } + else + { + cardano_utxo_list_t* tmp_list = *utxo_list; + + *utxo_list = cardano_utxo_list_concat(tmp_list, current_list); + + cardano_utxo_list_unref(&tmp_list); + cardano_utxo_list_unref(¤t_list); + } + + ++page; + } + while (last_results == max_results); + + return result; +} + +/** + * \brief Retrieves the unspent transaction output (UTXO) associated with a specific NFT (Non-Fungible Token). + * + * This function queries the provider implementation to retrieve the UTXO corresponding to the provided NFT asset ID. + * + * \param[in] provider_impl A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context for querying the UTXO. + * This parameter must not be NULL. + * \param[in] asset_id A pointer to an initialized \ref cardano_asset_id_t object representing the asset ID of the NFT. This parameter must not be NULL. + * \param[out] utxo A pointer to a pointer of \ref cardano_utxo_t that will be populated with the UTXO containing the specified NFT. + * The caller is responsible for managing the lifecycle of the returned UTXO and must free it by calling \ref cardano_utxo_unref when no longer needed. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the UTXO was successfully retrieved, + * or an appropriate error code if an error occurred (e.g., failure to find the UTXO or invalid asset ID). + */ +static cardano_error_t +get_unspent_output_by_nft( + cardano_provider_impl_t* provider_impl, + cardano_asset_id_t* asset_id, + cardano_utxo_t** utxo) +{ + char* url = cardano_blockfrost_build_addresses_with_asset_url(provider_impl, cardano_asset_id_get_hex(asset_id)); + cardano_buffer_t* response_buffer = NULL; + uint64_t response_code = 0; + + cardano_error_t result = cardano_blockfrost_http_get(provider_impl, url, strlen(url), &response_code, &response_buffer); + free(url); + + if ((response_code != 200U) || (result != CARDANO_SUCCESS)) + { + cardano_blockfrost_parse_error(provider_impl, response_buffer); + cardano_buffer_unref(&response_buffer); + return CARDANO_ERROR_INVALID_HTTP_REQUEST; + } + + struct json_tokener* tok = json_tokener_new(); + struct json_object* parsed_json = json_tokener_parse_ex(tok, (const char*)cardano_buffer_get_data(response_buffer), (int32_t)cardano_buffer_get_size(response_buffer)); + + if (parsed_json == NULL) + { + cardano_buffer_unref(&response_buffer); + json_tokener_free(tok); + + return CARDANO_ERROR_INVALID_JSON; + } + + size_t array_len = json_object_array_length(parsed_json); + + if (array_len == 0U) + { + cardano_buffer_unref(&response_buffer); + json_object_put(parsed_json); + json_tokener_free(tok); + + cardano_utils_set_error_message(provider_impl, "No asset found for the specified asset ID"); + + return CARDANO_ERROR_ELEMENT_NOT_FOUND; + } + + if (array_len != 1U) + { + cardano_buffer_unref(&response_buffer); + json_object_put(parsed_json); + json_tokener_free(tok); + + cardano_utils_set_error_message(provider_impl, "Asset is not an NFT. Multiple assets found for the specified asset ID"); + + return CARDANO_ERROR_INVALID_ARGUMENT; + } + + struct json_object* address_with_count_obj = json_object_array_get_idx(parsed_json, 0); + + if (address_with_count_obj == NULL) + { + cardano_buffer_unref(&response_buffer); + json_object_put(parsed_json); + json_tokener_free(tok); + + return CARDANO_ERROR_INVALID_JSON; + } + + struct json_object* address_obj = NULL; + cardano_address_t* address = NULL; + + if (json_object_object_get_ex(address_with_count_obj, "address", &address_obj)) + { + result = cardano_address_from_string(json_object_get_string(address_obj), json_object_get_string_len(address_obj), &address); + + if (result != CARDANO_SUCCESS) + { + cardano_buffer_unref(&response_buffer); + json_object_put(parsed_json); + json_tokener_free(tok); + return result; + } + } + + if (address == NULL) + { + cardano_buffer_unref(&response_buffer); + json_object_put(parsed_json); + json_tokener_free(tok); + + return CARDANO_ERROR_INVALID_JSON; + } + + cardano_utxo_list_t* utxo_list = NULL; + result = get_unspent_outputs_with_asset(provider_impl, address, asset_id, &utxo_list); + + json_object_put(parsed_json); + json_tokener_free(tok); + + if (result != CARDANO_SUCCESS) + { + cardano_address_unref(&address); + cardano_buffer_unref(&response_buffer); + return result; + } + + if (cardano_utxo_list_get_length(utxo_list) == 0) + { + cardano_address_unref(&address); + cardano_utxo_list_unref(&utxo_list); + cardano_buffer_unref(&response_buffer); + + cardano_utils_set_error_message(provider_impl, "No unspent outputs found for the specified asset ID"); + + return CARDANO_ERROR_ELEMENT_NOT_FOUND; + } + + if (cardano_utxo_list_get_length(utxo_list) > 1) + { + cardano_address_unref(&address); + cardano_utxo_list_unref(&utxo_list); + cardano_buffer_unref(&response_buffer); + + cardano_utils_set_error_message(provider_impl, "Asset is not an NFT. Multiple unspent outputs found for the specified asset ID"); + + return CARDANO_ERROR_INVALID_ARGUMENT; + } + + result = cardano_utxo_list_get(utxo_list, 0, utxo); + + cardano_address_unref(&address); + cardano_utxo_list_unref(&utxo_list); + cardano_buffer_unref(&response_buffer); + + return result; +} + +/** + * \brief Resolves the unspent transaction outputs (UTXOs) for a given set of transaction inputs. + * + * This function retrieves the UTXOs corresponding to the provided set of transaction inputs by querying the underlying provider implementation. + * The results are stored in a UTXO list. + * + * \param[in] provider_impl A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context for resolving UTXOs. + * This parameter must not be NULL. + * \param[in] tx_ins A pointer to an initialized \ref cardano_transaction_input_set_t object representing the set of transaction inputs to resolve. + * This parameter must not be NULL. + * \param[out] utxo_list A pointer to a pointer of \ref cardano_utxo_list_t that will be populated with the resolved UTXOs. + * The caller is responsible for managing the lifecycle of this list and freeing it when no longer needed. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the UTXOs were successfully resolved, + * or an appropriate error code if an error occurred (e.g., failure to resolve the UTXOs or invalid input data). + */ +static cardano_error_t +resolve_unspent_outputs( + cardano_provider_impl_t* provider_impl, + cardano_transaction_input_set_t* tx_ins, + cardano_utxo_list_t** utxo_list) +{ + cardano_error_t result = cardano_utxo_list_new(utxo_list); + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider_impl, "Failed to create UTXO list"); + return result; + } + + for (size_t i = 0; i < cardano_transaction_input_set_get_length(tx_ins); ++i) + { + cardano_transaction_input_t* tx_in = NULL; + result = cardano_transaction_input_set_get(tx_ins, i, &tx_in); + cardano_transaction_input_unref(&tx_in); + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider_impl, "Failed to get transaction input"); + cardano_utxo_list_unref(utxo_list); + return result; + } + + const uint64_t index = cardano_transaction_input_get_index(tx_in); + cardano_blake2b_hash_t* tx_hash = cardano_transaction_input_get_id(tx_in); + cardano_blake2b_hash_unref(&tx_hash); + + const size_t tx_id_hex_size = cardano_blake2b_hash_get_hex_size(tx_hash); + char* tx_id_hex = malloc(tx_id_hex_size); + + if (tx_id_hex == NULL) + { + cardano_utils_set_error_message(provider_impl, "Failed to allocate memory for transaction ID"); + cardano_utxo_list_unref(utxo_list); + + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; + } + + result = cardano_blake2b_hash_to_hex(tx_hash, tx_id_hex, tx_id_hex_size); + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider_impl, "Failed to convert transaction ID to hex"); + cardano_utxo_list_unref(utxo_list); + free(tx_id_hex); + + return result; + } + + char* url = cardano_blockfrost_build_transaction_utxos_url(provider_impl, tx_id_hex); + + cardano_buffer_t* response_buffer = NULL; + + uint64_t response_code = 0; + + result = cardano_blockfrost_http_get(provider_impl, url, strlen(url), &response_code, &response_buffer); + + free(url); + + if ((response_code != 200U) || (result != CARDANO_SUCCESS)) + { + cardano_blockfrost_parse_error(provider_impl, response_buffer); + cardano_buffer_unref(&response_buffer); + cardano_utxo_list_unref(utxo_list); + free(tx_id_hex); + + return CARDANO_ERROR_INVALID_HTTP_REQUEST; + } + + struct json_tokener* tok = json_tokener_new(); + struct json_object* parsed_json = json_tokener_parse_ex(tok, (const char*)cardano_buffer_get_data(response_buffer), (int32_t)cardano_buffer_get_size(response_buffer)); + + if (parsed_json == NULL) + { + cardano_buffer_unref(&response_buffer); + json_tokener_free(tok); + cardano_utxo_list_unref(utxo_list); + free(tx_id_hex); + + return CARDANO_ERROR_INVALID_JSON; + } + + struct json_object* tx_outputs = NULL; + + if (!json_object_object_get_ex(parsed_json, "outputs", &tx_outputs)) + { + cardano_buffer_unref(&response_buffer); + json_object_put(parsed_json); + json_tokener_free(tok); + cardano_utxo_list_unref(utxo_list); + free(tx_id_hex); + + return CARDANO_ERROR_INVALID_JSON; + } + + size_t json_len = 0; + const char* json_string = json_object_to_json_string_length(tx_outputs, JSON_C_TO_STRING_PLAIN, &json_len); + + cardano_utxo_list_t* tmp_utxo_list = NULL; + result = cardano_blockfrost_parse_tx_unspent_outputs(provider_impl, json_string, json_len, tx_id_hex, tx_id_hex_size - 1, &tmp_utxo_list); + + if (result != CARDANO_SUCCESS) + { + cardano_buffer_unref(&response_buffer); + json_object_put(parsed_json); + json_tokener_free(tok); + cardano_utxo_list_unref(utxo_list); + cardano_utxo_list_unref(&tmp_utxo_list); + free(tx_id_hex); + + return result; + } + + for (size_t j = 0; j < cardano_utxo_list_get_length(tmp_utxo_list); ++j) + { + cardano_utxo_t* utxo = NULL; + + result = cardano_utxo_list_get(tmp_utxo_list, j, &utxo); + cardano_utxo_unref(&utxo); + + if (result != CARDANO_SUCCESS) + { + cardano_buffer_unref(&response_buffer); + json_object_put(parsed_json); + json_tokener_free(tok); + cardano_utxo_list_unref(utxo_list); + cardano_utxo_list_unref(&tmp_utxo_list); + free(tx_id_hex); + + return result; + } + + cardano_transaction_input_t* out_tx_in = cardano_utxo_get_input(utxo); + cardano_transaction_input_unref(&out_tx_in); + + if (out_tx_in == NULL) + { + cardano_buffer_unref(&response_buffer); + json_object_put(parsed_json); + json_tokener_free(tok); + cardano_utxo_list_unref(utxo_list); + cardano_utxo_list_unref(&tmp_utxo_list); + free(tx_id_hex); + + return CARDANO_ERROR_INVALID_JSON; + } + + if (index == cardano_transaction_input_get_index(out_tx_in)) + { + result = cardano_utxo_list_add(*utxo_list, utxo); + + if (result != CARDANO_SUCCESS) + { + cardano_buffer_unref(&response_buffer); + json_object_put(parsed_json); + json_tokener_free(tok); + cardano_utxo_list_unref(utxo_list); + cardano_utxo_list_unref(&tmp_utxo_list); + free(tx_id_hex); + + return result; + } + + break; + } + } + + cardano_buffer_unref(&response_buffer); + cardano_utxo_list_unref(&tmp_utxo_list); + free(tx_id_hex); + json_object_put(parsed_json); + json_tokener_free(tok); + } + + return result; +} + +/** + * \brief Resolves a datum from the blockchain using its datum hash. + * + * This function retrieves the Plutus datum associated with a given datum hash from the blockchain. It queries the blockchain provider + * to resolve the datum and returns the result in the provided \ref cardano_plutus_data_t pointer. + * + * \param[in] provider_impl A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context and functions for blockchain interaction. + * This parameter must not be NULL. + * \param[in] datum_hash A pointer to a \ref cardano_blake2b_hash_t object representing the datum hash to be resolved. + * This parameter must not be NULL. + * \param[out] datum On success, this will point to the resolved \ref cardano_plutus_data_t object representing the datum. + * The caller is responsible for managing the lifecycle of this object, and must call \ref cardano_plutus_data_unref to release it when no longer needed. + * This parameter must not be NULL. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the datum was successfully resolved, + * or an appropriate error code if the datum could not be found or an error occurred during the operation. + */ +static cardano_error_t +resolve_datum( + cardano_provider_impl_t* provider_impl, + cardano_blake2b_hash_t* datum_hash, + cardano_plutus_data_t** datum) +{ + size_t hash_size = cardano_blake2b_hash_get_hex_size(datum_hash); + char* hash = malloc(hash_size); + + if (hash == NULL) + { + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; + } + + cardano_error_t result = cardano_blake2b_hash_to_hex(datum_hash, hash, hash_size); + + if (result != CARDANO_SUCCESS) + { + free(hash); + return result; + } + + char* url = cardano_blockfrost_build_datum_url(provider_impl, hash); + cardano_buffer_t* response_buffer = NULL; + uint64_t response_code = 0U; + + free(hash); + + result = cardano_blockfrost_http_get(provider_impl, url, strlen(url), &response_code, &response_buffer); + free(url); + + if ((response_code != 200U) || (result != CARDANO_SUCCESS)) + { + cardano_blockfrost_parse_error(provider_impl, response_buffer); + + cardano_buffer_unref(&response_buffer); + + return CARDANO_ERROR_INVALID_HTTP_REQUEST; + } + + result = cardano_blockfrost_parse_datum(provider_impl, (char*)cardano_buffer_get_data(response_buffer), cardano_buffer_get_size(response_buffer), datum); + + cardano_buffer_unref(&response_buffer); + + return result; +} + +/** + * \brief Awaits confirmation of a transaction on the blockchain within a specified timeout. + * + * This function waits for the specified transaction to be confirmed on the blockchain. It periodically checks the transaction's status + * until either the transaction is confirmed or the specified timeout is reached. + * + * \param[in] provider_impl A pointer to an initialized \ref cardano_provider_impl_t object that contains the necessary context for querying the blockchain. + * This parameter must not be NULL. + * \param[in] tx_id A pointer to a \ref cardano_blake2b_hash_t object representing the transaction ID to be checked for confirmation. + * This parameter must not be NULL. + * \param[in] timeout_ms The maximum amount of time, in milliseconds, to wait for the transaction to be confirmed. + * If the timeout is reached without confirmation, the function will return. + * \param[out] confirmed A pointer to a boolean that will be set to \c true if the transaction is confirmed before the timeout, or \c false if the timeout is reached without confirmation. + * This parameter must not be NULL. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the transaction was successfully confirmed, + * \ref CARDANO_ERROR_TIMEOUT if the timeout was reached without confirmation, or an appropriate error code if another failure occurred. + */ +static cardano_error_t +await_transaction_confirmation( + cardano_provider_impl_t* provider_impl, + cardano_blake2b_hash_t* tx_id, + const uint64_t timeout_ms, + bool* confirmed) +{ + uint64_t remaining_time_ms = timeout_ms; + uint64_t start_time_sec = cardano_utils_get_time(); + cardano_error_t result = CARDANO_SUCCESS; + + size_t hash_size = cardano_blake2b_hash_get_hex_size(tx_id); + char* hash = malloc(hash_size); + + result = cardano_blake2b_hash_to_hex(tx_id, hash, hash_size); + + if (result != CARDANO_SUCCESS) + { + *confirmed = false; + free(hash); + + return result; + } + + char* url = cardano_blockfrost_build_tx_metadata_cbor_url(provider_impl, hash); + + if ((hash == NULL) || (url == NULL)) + { + free(hash); + free(url); + + *confirmed = false; + + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; + } + + do + { + uint64_t elapsed_time_sec = cardano_utils_get_elapsed_time_since(start_time_sec); + uint64_t elapsed_time_ms = elapsed_time_sec * 1000U; + + if (elapsed_time_ms >= remaining_time_ms) + { + remaining_time_ms = 0U; + } + else + { + remaining_time_ms = remaining_time_ms - elapsed_time_ms; + } + + cardano_buffer_t* response_buffer = NULL; + uint64_t response_code = 0U; + + result = cardano_blockfrost_http_get(provider_impl, url, strlen(url), &response_code, &response_buffer); + + if (result != CARDANO_SUCCESS) + { + *confirmed = false; + cardano_blockfrost_parse_error(provider_impl, response_buffer); + + cardano_buffer_unref(&response_buffer); + free(hash); + free(url); + + return CARDANO_ERROR_INVALID_HTTP_REQUEST; + } + + cardano_buffer_unref(&response_buffer); + + *confirmed = (response_code == 200U); + + if (!(*confirmed) && (remaining_time_ms > 0U)) + { + uint64_t sleep_time_ms = (remaining_time_ms > 20000U) ? 20000U : remaining_time_ms; + cardano_utils_sleep(sleep_time_ms); + } + } + while (!(*confirmed) && (remaining_time_ms > 0U)); + + free(hash); + free(url); + + return result; +} + +/** + * \brief Posts a transaction to the Cardano blockchain using the provider implementation. + * + * This function submits a signed transaction to the Cardano blockchain using the specified \ref cardano_provider_impl_t provider. + * Upon successful submission, it returns the transaction ID (hash). + * + * \param[in] provider_impl A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context for + * interacting with the Cardano blockchain. This parameter must not be NULL. + * \param[in] tx A pointer to an initialized \ref cardano_transaction_t object representing the signed transaction to be posted to the blockchain. + * This parameter must not be NULL. + * \param[out] tx_id A pointer to a pointer of \ref cardano_blake2b_hash_t that will be populated with the transaction ID (hash) after the transaction is posted. + * The caller is responsible for managing the lifecycle of the returned \ref cardano_blake2b_hash_t object and must release it + * by calling \ref cardano_blake2b_hash_unref when it is no longer needed. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the transaction was successfully posted, + * or an appropriate error code if an error occurred during submission. + */ +static cardano_error_t +post_transaction_to_chain( + cardano_provider_impl_t* provider_impl, + cardano_transaction_t* tx, + cardano_blake2b_hash_t** tx_id) +{ + blockfrost_context_t* context = (blockfrost_context_t*)provider_impl->context; + char* base_path = cardano_blockfrost_get_endpoint_url(context->network, "tx/submit"); + cardano_buffer_t* response_buffer = NULL; + uint64_t response_code = 0U; + + cardano_cbor_writer_t* writer = cardano_cbor_writer_new(); + + if (writer == NULL) + { + free(base_path); + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; + } + + cardano_error_t result = cardano_transaction_to_cbor(tx, writer); + + if (result != CARDANO_SUCCESS) + { + cardano_cbor_writer_unref(&writer); + + free(base_path); + return result; + } + + const size_t cbor_size = cardano_cbor_writer_get_encode_size(writer); + byte_t* cbor_data = malloc(cbor_size); + + if (cbor_data == NULL) + { + cardano_cbor_writer_unref(&writer); + + free(base_path); + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; + } + + result = cardano_cbor_writer_encode(writer, cbor_data, cbor_size); + + cardano_cbor_writer_unref(&writer); + + if (result != CARDANO_SUCCESS) + { + free(cbor_data); + free(base_path); + + return result; + } + + result = cardano_blockfrost_http_post( + provider_impl, + base_path, + strlen(base_path), + cbor_data, + cbor_size, + CARDANO_BLOCKFROST_CONTENT_TYPE_CBOR, + &response_code, + &response_buffer); + + free(base_path); + free(cbor_data); + + if ((response_code != 200U) || (result != CARDANO_SUCCESS)) + { + cardano_blockfrost_parse_error(provider_impl, response_buffer); + + cardano_buffer_unref(&response_buffer); + + return CARDANO_ERROR_INVALID_HTTP_REQUEST; + } + + struct json_tokener* tok = json_tokener_new(); + struct json_object* parsed_json = json_tokener_parse_ex(tok, (char*)cardano_buffer_get_data(response_buffer), (int32_t)cardano_buffer_get_size(response_buffer)); + + if (parsed_json == NULL) + { + json_tokener_free(tok); + cardano_buffer_unref(&response_buffer); + + cardano_utils_set_error_message(provider_impl, "Failed to parse JSON response"); + + return CARDANO_ERROR_INVALID_JSON; + } + + if (!json_object_is_type(parsed_json, json_type_string)) + { + json_object_put(parsed_json); + json_tokener_free(tok); + cardano_buffer_unref(&response_buffer); + + cardano_utils_set_error_message(provider_impl, "Invalid JSON response"); + + return CARDANO_ERROR_INVALID_JSON; + } + + const char* hex_string = json_object_get_string(parsed_json); + size_t hex_len = strlen(hex_string); + + result = cardano_blake2b_hash_from_hex(hex_string, hex_len, tx_id); + + cardano_buffer_unref(&response_buffer); + + json_object_put(parsed_json); + json_tokener_free(tok); + + return result; +} + +/** + * \brief Evaluates a transaction using the specified provider and updates redeemer execution units. + * + * This function evaluates a transaction in the context of a set of additional UTXOs. It communicates with the provider + * to evaluate the transaction and updates the redeemer list with execution units based on the result. + * + * \param[in] provider_impl A pointer to an initialized \ref cardano_provider_impl_t object representing the provider. This must not be NULL. + * \param[in] tx A pointer to an initialized \ref cardano_transaction_t object representing the transaction to be evaluated. This must not be NULL. + * \param[in] additional_utxos A pointer to an initialized \ref cardano_utxo_list_t object representing the additional UTXOs. This parameter can be NULL + * if no additional UTXOs are needed for the evaluation. + * \param[out] redeemers On success, this will point to a newly created \ref cardano_redeemer_list_t object containing the redeemers + * with updated execution units based on the evaluation result. The caller is responsible for managing the lifecycle + * of this object, including calling \ref cardano_redeemer_list_unref when it is no longer needed. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the transaction was successfully evaluated, + * or an appropriate error code such as \ref CARDANO_POINTER_IS_NULL if any required input pointer is NULL, or a specific error from the provider. + * + * \note The caller is responsible for releasing the memory of the \p redeemers object when it is no longer needed. + */ +static cardano_error_t +evaluate_transaction( + cardano_provider_impl_t* provider_impl, + cardano_transaction_t* tx, + cardano_utxo_list_t* additional_utxos, + cardano_redeemer_list_t** redeemers) +{ + blockfrost_context_t* context = (blockfrost_context_t*)provider_impl->context; + char* base_path = cardano_blockfrost_get_endpoint_url(context->network, "/utils/txs/evaluate/utxos"); + cardano_buffer_t* response_buffer = NULL; + uint64_t response_code = 0; + + char* json_payload = NULL; + size_t json_size = 0; + cardano_error_t result = cardano_evaluate_params_to_json(tx, additional_utxos, &json_payload, &json_size); + + if (result != CARDANO_SUCCESS) + { + free(base_path); + + return result; + } + + result = cardano_blockfrost_http_post( + provider_impl, + base_path, + strlen(base_path), + (byte_t*)json_payload, + json_size, + CARDANO_BLOCKFROST_CONTENT_TYPE_JSON, + &response_code, + &response_buffer); + + free(base_path); + free(json_payload); + + if ((response_code != 200U) || (result != CARDANO_SUCCESS)) + { + cardano_blockfrost_parse_error(provider_impl, response_buffer); + + cardano_buffer_unref(&response_buffer); + + return CARDANO_ERROR_INVALID_HTTP_REQUEST; + } + + cardano_witness_set_t* witness_set = cardano_transaction_get_witness_set(tx); + cardano_witness_set_unref(&witness_set); + + if (witness_set == NULL) + { + cardano_buffer_unref(&response_buffer); + + return CARDANO_ERROR_INVALID_ARGUMENT; + } + + cardano_redeemer_list_t* original_redeemers = cardano_witness_set_get_redeemers(witness_set); + cardano_redeemer_list_unref(&original_redeemers); + + if (original_redeemers == NULL) + { + cardano_buffer_unref(&response_buffer); + + return CARDANO_ERROR_INVALID_ARGUMENT; + } + + result = cardano_blockfrost_parse_tx_eval_response(provider_impl, (char*)cardano_buffer_get_data(response_buffer), cardano_buffer_get_size(response_buffer), original_redeemers, redeemers); + + cardano_buffer_unref(&response_buffer); + + return result; +} + +/* DEFINITIONS ****************************************************************/ + +cardano_error_t +create_blockfrost_provider( + const cardano_network_magic_t network, + const char* project_id, + const size_t project_id_size, + cardano_provider_t** provider) +{ + cardano_provider_impl_t impl = { 0 }; + + CARDANO_UNUSED(sprintf(impl.name, "blockfrost-%s", cardano_network_magic_to_string(network))); + + if ((provider == NULL) || (project_id == NULL)) + { + return CARDANO_ERROR_POINTER_IS_NULL; + } + + if ((project_id_size > 64U) || (project_id_size == 0U)) + { + return CARDANO_ERROR_INVALID_ARGUMENT; + } + + blockfrost_context_t* context = cardano_blockfrost_context_new(); + + if (context == NULL) + { + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; + } + + cardano_utils_safe_memcpy(context->project_id, 64U, project_id, project_id_size); + + context->project_id_size = project_id_size; + context->network = network; + + impl.get_parameters = get_parameters; + impl.get_unspent_outputs = get_unspent_outputs; + impl.get_rewards_balance = get_rewards_balance; + impl.get_unspent_outputs_with_asset = get_unspent_outputs_with_asset; + impl.get_unspent_output_by_nft = get_unspent_output_by_nft; + impl.resolve_unspent_outputs = resolve_unspent_outputs; + impl.resolve_datum = resolve_datum; + impl.await_transaction_confirmation = await_transaction_confirmation; + impl.post_transaction_to_chain = post_transaction_to_chain; + impl.evaluate_transaction = evaluate_transaction; + + impl.context = (cardano_object_t*)context; + + return cardano_provider_new(impl, provider); +} diff --git a/examples/providers/blockfrost/common/blockfrost_common.c b/examples/providers/blockfrost/common/blockfrost_common.c new file mode 100644 index 00000000..848673a3 --- /dev/null +++ b/examples/providers/blockfrost/common/blockfrost_common.c @@ -0,0 +1,228 @@ +/** + * \file blockfrost_common.c + * + * \author angel.castillo + * \date Sep 30, 2024 + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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. + */ + +/* INCLUDES ******************************************************************/ + +#include "blockfrost_common.h" +#include "../utils/console.h" +#include "utils.h" + +#include + +#include +#include +#include +#include +#include + +/* DEFINITIONS ***************************************************************/ + +struct curl_slist* +cardano_blockfrost_get_headers( + const char* project_id, + const size_t project_id_size, + const cardano_blockfrost_content_type_t content_type) +{ + struct curl_slist* headers = NULL; + + char* project_id_header = malloc(project_id_size + 13U); + CARDANO_UNUSED(memset(project_id_header, 0, project_id_size + 13U)); + + CARDANO_UNUSED(snprintf(project_id_header, project_id_size + 13U, "project_id: %s", project_id)); + + if (content_type == CARDANO_BLOCKFROST_CONTENT_TYPE_JSON) + { + headers = curl_slist_append(headers, "Content-Type: application/json"); + } + else if (content_type == CARDANO_BLOCKFROST_CONTENT_TYPE_CBOR) + { + headers = curl_slist_append(headers, "Content-Type: application/cbor"); + } + + headers = curl_slist_append(headers, project_id_header); + + free(project_id_header); + + return headers; +} + +void +cardano_blockfrost_parse_error(cardano_provider_impl_t* provider, cardano_buffer_t* buffer) +{ + if (buffer == NULL) + { + return; + } + + struct json_tokener* tok = json_tokener_new(); + struct json_object* parsed_json = NULL; + struct json_object* status_code = NULL; + struct json_object* error = NULL; + struct json_object* message = NULL; + + parsed_json = json_tokener_parse_ex(tok, (char*)cardano_buffer_get_data(buffer), (int32_t)cardano_buffer_get_size(buffer)); + + if (parsed_json == NULL) + { + cardano_utils_set_error_message(provider, "Failed to parse JSON response"); + json_tokener_free(tok); + return; + } + + CARDANO_UNUSED(json_object_object_get_ex(parsed_json, "status_code", &status_code)); + CARDANO_UNUSED(json_object_object_get_ex(parsed_json, "error", &error)); + CARDANO_UNUSED(json_object_object_get_ex(parsed_json, "message", &message)); + + CARDANO_UNUSED(sprintf(provider->error_message, "%lu - %s - %s", json_object_get_uint64(status_code), json_object_get_string(error), json_object_get_string(message))); + + json_object_put(parsed_json); + json_tokener_free(tok); +} + +size_t +cardano_blockfrost_handle_response(void* contents, const size_t size, const size_t count, void* user_provided) +{ + const size_t total_size = size * count; + cardano_buffer_t* buffer = (cardano_buffer_t*)user_provided; + + cardano_error_t result = cardano_buffer_write(buffer, contents, total_size); + + console_debug("Received response of %lu bytes", total_size); + console_debug("%.*s", (int)total_size, (char*)contents); + + CARDANO_UNUSED(result); + + return total_size; +} + +cardano_error_t +cardano_blockfrost_http_get(cardano_provider_impl_t* provider_impl, const char* url, const size_t url_size, uint64_t* response_code, cardano_buffer_t** response_buffer) +{ + CARDANO_UNUSED(url_size); + + blockfrost_context_t* context = (blockfrost_context_t*)provider_impl->context; + *response_buffer = cardano_buffer_new(1024); + *response_code = 0; + + curl_global_init(CURL_GLOBAL_DEFAULT); + CURL* curl = curl_easy_init(); + + if (!curl) + { + cardano_buffer_unref(response_buffer); + cardano_utils_set_error_message(provider_impl, "Failed to initialize libcurl"); + + return CARDANO_ERROR_GENERIC; + } + + curl_easy_setopt(curl, CURLOPT_URL, url); + + struct curl_slist* headers = cardano_blockfrost_get_headers(context->project_id, context->project_id_size, CARDANO_BLOCKFROST_CONTENT_TYPE_JSON); + + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cardano_blockfrost_handle_response); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)(*response_buffer)); + + console_debug("Sending GET request to endpoint: %s", url); + + CURLcode res = curl_easy_perform(curl); + + if (res != CURLE_OK) + { + cardano_utils_set_error_message(provider_impl, curl_easy_strerror(res)); + + cardano_buffer_unref(response_buffer); + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + + return CARDANO_ERROR_GENERIC; + } + + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, response_code); + + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + + return CARDANO_SUCCESS; +} + +cardano_error_t +cardano_blockfrost_http_post( + cardano_provider_impl_t* provider_impl, + const char* url, + const size_t url_size, + const byte_t* body, + const size_t body_size, + cardano_blockfrost_content_type_t content_type, + uint64_t* response_code, + cardano_buffer_t** response_buffer) +{ + CARDANO_UNUSED(url_size); + + blockfrost_context_t* context = (blockfrost_context_t*)provider_impl->context; + *response_buffer = cardano_buffer_new(1024); + *response_code = 0; + + curl_global_init(CURL_GLOBAL_DEFAULT); + CURL* curl = curl_easy_init(); + + if (!curl) + { + cardano_buffer_unref(response_buffer); + cardano_utils_set_error_message(provider_impl, "Failed to initialize libcurl"); + + return CARDANO_ERROR_GENERIC; + } + + curl_easy_setopt(curl, CURLOPT_URL, url); + + struct curl_slist* headers = cardano_blockfrost_get_headers(context->project_id, context->project_id_size, content_type); + + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cardano_blockfrost_handle_response); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)(*response_buffer)); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)body_size); + + console_debug("Sending POST request to endpoint: %s", url); + console_debug("Sending POST request payload: %.*s", body_size, (char*)body); + + CURLcode res = curl_easy_perform(curl); + + if (res != CURLE_OK) + { + cardano_utils_set_error_message(provider_impl, curl_easy_strerror(res)); + + cardano_buffer_unref(response_buffer); + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + + return CARDANO_ERROR_GENERIC; + } + + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, response_code); + + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + + return CARDANO_SUCCESS; +} diff --git a/examples/providers/blockfrost/common/blockfrost_common.h b/examples/providers/blockfrost/common/blockfrost_common.h new file mode 100644 index 00000000..7c86e3e7 --- /dev/null +++ b/examples/providers/blockfrost/common/blockfrost_common.h @@ -0,0 +1,206 @@ +/** + * \file blockfrost_common.h + * + * \author angel.castillo + * \date Sep 30, 2024 + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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 BIGLUP_LABS_INCLUDE_CARDANO_BLOCKFROST_COMMON_H +#define BIGLUP_LABS_INCLUDE_CARDANO_BLOCKFROST_COMMON_H + +/* INCLUDES ******************************************************************/ + +#include +#include +#include + +/* DECLARATIONS **************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* STRUCTURES ***************************************************************/ + +/** + * \brief A context structure for managing Blockfrost provider details. + * + * This structure holds the necessary information for interacting with the Blockfrost API, + * including the network magic for the Cardano network and the project ID used for authentication. + * + * \var network + * The Cardano network magic number used to identify the specific network (mainnet, testnet, etc.). + * + * \var project_id + * The project ID associated with a Blockfrost project. This is used for authenticating API requests. + * + * \var project_id_size + * The size (in bytes) of the project ID stored in `project_id`. It represents the actual length of the + * project ID string, not the full size of the array. + */ +typedef struct blockfrost_context_t +{ + cardano_object_t base; + cardano_network_magic_t network; + char project_id[64]; + size_t project_id_size; +} blockfrost_context_t; + +/* ENUMERATIONS *************************************************************/ + +/** + * \brief Enumerates the content types supported by the Blockfrost API. + */ +typedef enum +{ + /** + * JSON content type. + */ + CARDANO_BLOCKFROST_CONTENT_TYPE_JSON = 0, + + /** + * CBOR content type. + */ + CARDANO_BLOCKFROST_CONTENT_TYPE_CBOR = 1, +} cardano_blockfrost_content_type_t; + +/* DECLARATIONS **************************************************************/ + +/** + * \brief Constructs the HTTP headers required for Blockfrost API requests. + * + * This function creates a list of headers to be used in HTTP requests to the Blockfrost API. + * Specifically, it includes the `project_id` for authentication in the `project_id` header. + * + * \param[in] project_id A pointer to the project ID string used for authentication with the Blockfrost API. + * It should be a valid, non-null C-string. + * \param[in] project_id_size The length of the project ID string. This helps ensure the correct number + * of bytes is included in the request header. + * \param[in] content_type The content type to be used in the request. This determines the format of the + * payload (e.g., JSON or CBOR). + * + * \return A pointer to a `curl_slist` structure containing the headers. This list must be freed + * after usage with `curl_slist_free_all()`. Returns `NULL` if memory allocation fails. + */ +struct curl_slist* +cardano_blockfrost_get_headers(const char* project_id, size_t project_id_size, cardano_blockfrost_content_type_t content_type); + +/** + * \brief Parses the error response from a Cardano provider and extracts the error message. + * + * This function takes the error response stored in the `buffer` and extracts relevant error + * information. It then stores the extracted error message in the provider’s internal error state. + * + * \param[in] provider A pointer to the Cardano provider implementation where the error message will be stored. + * This must be a valid pointer to a `cardano_provider_impl_t` structure. + * \param[in] buffer A pointer to a `cardano_buffer_t` structure containing the error response data + * received from the provider. It should be a valid buffer containing error details. + * + * \note This function does not return any value, but it modifies the internal state of the + * `provider` by setting the error message for further handling. + */ +void +cardano_blockfrost_parse_error(cardano_provider_impl_t* provider, cardano_buffer_t* buffer); + +/** + * \brief Callback function to handle the response data from a libcurl request. + * + * This function is called by libcurl as the data is received from the server. It appends the received data to a + * buffer provided by the user. The buffer must be managed by the caller, and the total size of the data + * is calculated as `size * count`. + * + * \param[in] contents A pointer to the raw data received from the server. + * \param[in] size The size of each data chunk received. + * \param[in] count The number of data chunks received. + * \param[in] user_provided A pointer to a buffer (e.g., a `cardano_buffer_t` structure) provided by the user + * where the response data will be appended. + * + * \return The total size of the data that was processed, calculated as `size * count`. This value tells libcurl + * how much data was processed, and if it differs from the input size, libcurl will stop the request. + */ +size_t +cardano_blockfrost_handle_response(void* contents, size_t size, size_t count, void* user_provided); + +/** + * \brief Performs an HTTP GET request using the Blockfrost provider. + * + * This function sends an HTTP GET request to the specified URL using the Blockfrost API provider. + * It retrieves the response code and the response body, which are returned to the caller via + * the provided output parameters. + * + * \param[in] provider_impl Pointer to the \ref cardano_provider_impl_t instance, representing + * the Blockfrost provider implementation. Must not be `NULL`. + * \param[in] url Pointer to a null-terminated string containing the URL to which the + * GET request should be sent. Must not be `NULL`. + * \param[in] url_size The size of the URL string in bytes (excluding the null terminator). + * \param[in] content_type The content type to be used in the request. This determines the format of the + * payload (e.g., JSON or CBOR). + * \param[out] response_buffer Pointer to a \ref cardano_buffer_t* where the function will store the + * response body. The buffer will be allocated within the function, and the caller + * is responsible for freeing it using the appropriate deallocation function. + * + * \returns A \ref cardano_error_t indicating success or failure of the operation. + * - Other error codes for provider-specific failures. + */ +cardano_error_t +cardano_blockfrost_http_get( + cardano_provider_impl_t* provider_impl, + const char* url, + size_t url_size, + uint64_t* response_code, + cardano_buffer_t** response_buffer); + +/** + * \brief Sends an HTTP POST request to the Blockfrost API. + * + * This function posts data to the specified URL using the Blockfrost API. The request body is included in the HTTP POST request, + * and the response code and response content are returned. + * + * \param[in] provider_impl A pointer to an initialized \ref cardano_provider_impl_t object that contains the necessary context for making the HTTP request. + * This parameter must not be NULL. + * \param[in] url A pointer to a null-terminated string representing the target URL for the HTTP POST request. + * This parameter must not be NULL. + * \param[in] url_size The size of the URL string, including the null terminator. + * \param[in] body A pointer to the data to be posted as the body of the HTTP request. + * This parameter must not be NULL if body_size is greater than 0. + * \param[in] body_size The size of the body data in bytes. If set to 0, the request will have no body content. + * \param[in] content_type A \ref cardano_blockfrost_content_type_t value specifying the content type of the request body (e.g., JSON or CBOR). + * \param[out] response_code A pointer to a \c uint64_t that will be set to the HTTP response code from the server (e.g., 200 for success). + * This parameter must not be NULL. + * \param[out] response_buffer A pointer to a pointer of \ref cardano_buffer_t that will be populated with the response body content from the server. + * The caller is responsible for managing the lifecycle of the returned \ref cardano_buffer_t object and must release it + * by calling \ref cardano_buffer_unref when it is no longer needed. If no response body is provided by the server, this will be set to NULL. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the POST request was successful, + * or an appropriate error code if the request failed. + */ +cardano_error_t +cardano_blockfrost_http_post( + cardano_provider_impl_t* provider_impl, + const char* url, + size_t url_size, + const byte_t* body, + size_t body_size, + cardano_blockfrost_content_type_t content_type, + uint64_t* response_code, + cardano_buffer_t** response_buffer); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif // BIGLUP_LABS_INCLUDE_CARDANO_BLOCKFROST_COMMON_H diff --git a/examples/providers/blockfrost/common/blockfrost_url_builders.c b/examples/providers/blockfrost/common/blockfrost_url_builders.c new file mode 100644 index 00000000..c80443d1 --- /dev/null +++ b/examples/providers/blockfrost/common/blockfrost_url_builders.c @@ -0,0 +1,289 @@ +/** + * \file blockfrost_url_builders.c + * + * \author angel.castillo + * \date Sep 28, 2024 + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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. + */ + +/* INCLUDES ******************************************************************/ + +#include "blockfrost_url_builders.h" +#include "blockfrost_common.h" +#include "utils.h" + +#include +#include +#include + +/* STATIC FUNCTIONS **********************************************************/ + +const char* +cardano_blockfrost_get_network_base_url(const cardano_network_magic_t network) +{ + switch (network) + { + case CARDANO_NETWORK_MAGIC_MAINNET: + return "https://cardano-mainnet.blockfrost.io/api/v0/"; + case CARDANO_NETWORK_MAGIC_PREPROD: + return "https://cardano-preprod.blockfrost.io/api/v0/"; + case CARDANO_NETWORK_MAGIC_PREVIEW: + return "https://cardano-preview.blockfrost.io/api/v0/"; + case CARDANO_NETWORK_MAGIC_SANCHONET: + return "https://cardano-sanchonet.blockfrost.io/api/v0/"; + default: + return NULL; + } +} + +char* +cardano_blockfrost_get_endpoint_url(const cardano_network_magic_t network, const char* endpoint) +{ + const char* base_url = cardano_blockfrost_get_network_base_url(network); + + if (base_url == NULL) + { + return NULL; + } + + const size_t base_url_size = cardano_utils_safe_strlen(base_url, 254U); + const size_t endpoint_size = cardano_utils_safe_strlen(endpoint, 254U); + + char* url = malloc(base_url_size + endpoint_size + 1U); + + if (url == NULL) + { + return NULL; + } + + cardano_utils_safe_memcpy(url, base_url_size + endpoint_size + 1U, base_url, base_url_size); + cardano_utils_safe_memcpy(url + base_url_size, endpoint_size + 1U, endpoint, endpoint_size); + url[base_url_size + endpoint_size] = '\0'; + + return url; +} + +char* +cardano_blockfrost_build_utxo_url( + cardano_provider_impl_t* provider_impl, + const char* bech32, + const size_t page, + const size_t max_results) +{ + blockfrost_context_t* context = (blockfrost_context_t*)provider_impl->context; + + char* base_path = cardano_blockfrost_get_endpoint_url(context->network, "addresses/"); + + const size_t base_path_len = strlen(base_path); + const size_t bech32_len = strlen(bech32); + const size_t pagination_len = 50U; + const size_t url_len = base_path_len + bech32_len + pagination_len + 20U; + + char* url = malloc(url_len); + + if (!url) + { + free(base_path); + + return NULL; + } + + CARDANO_UNUSED(snprintf(url, url_len, "%s%s/utxos?count=%zu&page=%zu", base_path, bech32, max_results, page)); + + free(base_path); + + return url; +} + +char* +cardano_blockfrost_build_utxo_with_asset_url( + cardano_provider_impl_t* provider_impl, + const char* bech32, + const char* asset_id, + const size_t page, + const size_t max_results) +{ + blockfrost_context_t* context = (blockfrost_context_t*)provider_impl->context; + + char* base_path = cardano_blockfrost_get_endpoint_url(context->network, "addresses/"); + + const size_t base_path_len = strlen(base_path); + const size_t bech32_len = strlen(bech32); + const size_t asset_id_len = strlen(asset_id); + const size_t pagination_len = 50U; + const size_t url_len = base_path_len + bech32_len + pagination_len + asset_id_len + 20U; + + char* url = malloc(url_len); + + if (!url) + { + free(base_path); + + return NULL; + } + + CARDANO_UNUSED(snprintf(url, url_len, "%s%s/utxos/%s?count=%zu&page=%zu", base_path, bech32, asset_id, max_results, page)); + + free(base_path); + + return url; +} + +char* +cardano_blockfrost_build_addresses_with_asset_url( + cardano_provider_impl_t* provider_impl, + const char* asset_id) +{ + blockfrost_context_t* context = (blockfrost_context_t*)provider_impl->context; + + char* base_path = cardano_blockfrost_get_endpoint_url(context->network, "assets/"); + + const size_t base_path_len = strlen(base_path); + const size_t asset_id_len = strlen(asset_id); + const size_t pagination_len = 50U; + const size_t url_len = base_path_len + pagination_len + asset_id_len + 1U; + + char* url = malloc(url_len); + + if (!url) + { + free(base_path); + + return NULL; + } + + CARDANO_UNUSED(snprintf(url, url_len, "%s%s/addresses", base_path, asset_id)); + + free(base_path); + + return url; +} + +char* +cardano_blockfrost_build_transaction_utxos_url( + cardano_provider_impl_t* provider_impl, + const char* tx_id) +{ + blockfrost_context_t* context = (blockfrost_context_t*)provider_impl->context; + + char* base_path = cardano_blockfrost_get_endpoint_url(context->network, "txs/"); + + const size_t base_path_len = strlen(base_path); + const size_t tx_id_len = strlen(tx_id); + const size_t url_len = base_path_len + tx_id_len + 10U; + + char* url = malloc(url_len); + + if (!url) + { + free(base_path); + + return NULL; + } + + CARDANO_UNUSED(snprintf(url, url_len, "%s%s/utxos", base_path, tx_id)); + + free(base_path); + + return url; +} + +char* +cardano_blockfrost_build_datum_url( + cardano_provider_impl_t* provider_impl, + const char* datum_hash) +{ + blockfrost_context_t* context = (blockfrost_context_t*)provider_impl->context; + + char* base_path = cardano_blockfrost_get_endpoint_url(context->network, "scripts/datum/"); + + const size_t base_path_len = strlen(base_path); + const size_t datum_hash_len = strlen(datum_hash); + const size_t url_len = base_path_len + datum_hash_len + 6U; + + char* url = malloc(url_len); + + if (!url) + { + free(base_path); + + return NULL; + } + + CARDANO_UNUSED(snprintf(url, url_len, "%s%s/cbor", base_path, datum_hash)); + + free(base_path); + + return url; +} + +char* +cardano_blockfrost_build_rewards_url( + cardano_provider_impl_t* provider_impl, + const char* bech32) +{ + blockfrost_context_t* context = (blockfrost_context_t*)provider_impl->context; + + char* base_path = cardano_blockfrost_get_endpoint_url(context->network, "accounts/"); + + const size_t base_path_len = strlen(base_path); + const size_t bech32_len = strlen(bech32); + const size_t url_len = base_path_len + bech32_len + 1U; + + char* url = malloc(url_len); + + if (!url) + { + free(base_path); + + return NULL; + } + + CARDANO_UNUSED(snprintf(url, url_len, "%s%s", base_path, bech32)); + + free(base_path); + + return url; +} + +char* +cardano_blockfrost_build_tx_metadata_cbor_url( + cardano_provider_impl_t* provider_impl, + const char* hash) +{ + blockfrost_context_t* context = (blockfrost_context_t*)provider_impl->context; + + char* base_path = cardano_blockfrost_get_endpoint_url(context->network, "txs/"); + + const size_t base_path_len = strlen(base_path); + const size_t hash_len = strlen(hash); + const size_t url_len = base_path_len + hash_len + 20U; + + char* url = malloc(url_len); + + if (!url) + { + free(base_path); + + return NULL; + } + + CARDANO_UNUSED(snprintf(url, url_len, "%s%s/metadata/cbor", base_path, hash)); + + free(base_path); + + return url; +} \ No newline at end of file diff --git a/examples/providers/blockfrost/common/blockfrost_url_builders.h b/examples/providers/blockfrost/common/blockfrost_url_builders.h new file mode 100644 index 00000000..8bbbd70c --- /dev/null +++ b/examples/providers/blockfrost/common/blockfrost_url_builders.h @@ -0,0 +1,210 @@ +/** + * \file blockfrost_url_builders.h + * + * \author angel.castillo + * \date Sep 30, 2024 + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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 BIGLUP_LABS_INCLUDE_CARDANO_BLOCKFROST_URL_BUILDERS_H +#define BIGLUP_LABS_INCLUDE_CARDANO_BLOCKFROST_URL_BUILDERS_H + +/* INCLUDES ******************************************************************/ + +#include +#include +#include +#include +#include + +/* DECLARATIONS **************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * \brief Retrieves the base URL for the Blockfrost API corresponding to the specified Cardano network. + * + * This function returns the appropriate base URL for the Blockfrost API based on the Cardano network specified + * by the `network` parameter. It selects the URL for either the mainnet or testnet based on the network magic value. + * + * \param[in] network A `cardano_network_magic_t` enum specifying the network type. Typically, mainnet and testnet + * networks have different magic values. + * + * \return A pointer to a constant string containing the base URL for the specified network. + * The returned string is either the URL for the mainnet or testnet. + * + * \note The returned URL is a constant string and should not be modified or freed. + */ +const char* +cardano_blockfrost_get_network_base_url(cardano_network_magic_t network); + +/** + * \brief Constructs the full URL for a given endpoint on the Blockfrost API based on the specified Cardano network. + * + * This function combines the base URL for the specified network (mainnet or testnet) with the provided API endpoint. + * The full URL is dynamically allocated and should be freed by the caller. + * + * \param[in] network A `cardano_network_magic_t` enum specifying the Cardano network (mainnet or testnet). + * \param[in] endpoint A pointer to a null-terminated string that specifies the API endpoint (e.g., "/blocks/latest"). + * + * \return A dynamically allocated string containing the full URL for the specified endpoint on the network. + * The caller is responsible for freeing the returned string. + * + * \note If memory allocation fails, this function returns `NULL`. + */ +char* +cardano_blockfrost_get_endpoint_url(cardano_network_magic_t network, const char* endpoint); + +/** + * \brief Constructs a URL string for retrieving UTXOs for a given address. + * + * The function constructs the URL string for a Cardano API request that retrieves the UTXOs + * associated with a Bech32 address. The URL includes pagination parameters (`count` and `page`). + * + * \param[in] provider_impl Pointer to the provider implementation instance. + * \param[in] bech32 Bech32 address for which to retrieve UTXOs. + * \param[in] page The current page of results. + * \param[in] max_results The maximum number of results to return. + * + * \return A dynamically allocated string containing the full URL. + * The caller is responsible for freeing the returned string. + */ +char* +cardano_blockfrost_build_utxo_url( + cardano_provider_impl_t* provider_impl, + const char* bech32, + size_t page, + size_t max_results); + +/** + * \brief Constructs the URL for retrieving staking rewards from the Blockfrost API for a given Bech32 address. + * + * This function constructs a URL used to query the Blockfrost API for the staking rewards associated with the specified Bech32 address. + * + * \param[in] provider_impl A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context (e.g., API base URL). + * This parameter must not be NULL. + * \param[in] bech32 A pointer to a null-terminated string containing the Bech32 address for which to retrieve rewards. + * This parameter must not be NULL. + * + * \return A dynamically allocated string containing the full URL for querying rewards. The caller is responsible for freeing the returned string + * using \c free when it is no longer needed. Returns NULL if the URL could not be constructed (e.g., due to invalid input). + */ +char* +cardano_blockfrost_build_rewards_url( + cardano_provider_impl_t* provider_impl, + const char* bech32); + +/** + * \brief Constructs the URL for retrieving UTXOs associated with a given Bech32 address and asset ID from the Blockfrost API. + * + * This function constructs a URL used to query the Blockfrost API for the UTXOs that are associated with the specified Bech32 address and asset ID. + * The URL will also include pagination parameters, allowing the user to specify the page and the maximum number of results per page. + * + * \param[in] provider_impl A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context (e.g., API base URL). + * This parameter must not be NULL. + * \param[in] bech32 A pointer to a null-terminated string containing the Bech32 address for which to retrieve UTXOs. This parameter must not be NULL. + * \param[in] asset_id A pointer to a null-terminated string containing the asset ID to filter the UTXOs by. This parameter must not be NULL. + * \param[in] page The page number to retrieve. Used for pagination. + * \param[in] max_results The maximum number of results to retrieve per page. + * + * \return A dynamically allocated string containing the full URL for querying UTXOs with the specified asset. The caller is responsible for freeing the + * returned string using \c free when it is no longer needed. Returns NULL if the URL could not be constructed (e.g., due to invalid input). + */ +char* +cardano_blockfrost_build_utxo_with_asset_url( + cardano_provider_impl_t* provider_impl, + const char* bech32, + const char* asset_id, + size_t page, + size_t max_results); + +/** + * \brief Constructs the URL for retrieving addresses associated with a given asset ID from the Blockfrost API. + * + * This function constructs a URL used to query the Blockfrost API for the addresses that hold the specified asset. + * + * \param[in] provider_impl A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context (e.g., API base URL). + * This parameter must not be NULL. + * \param[in] asset_id A pointer to a null-terminated string containing the asset ID to query for addresses. This parameter must not be NULL. + * + * \return A dynamically allocated string containing the full URL for querying addresses holding the specified asset. The caller is responsible for freeing the + * returned string using \c free when it is no longer needed. Returns NULL if the URL could not be constructed (e.g., due to invalid input). + */ +char* +cardano_blockfrost_build_addresses_with_asset_url( + cardano_provider_impl_t* provider_impl, + const char* asset_id); + +/** + * \brief Constructs a URL for retrieving UTXOs from a transaction. + * + * This function constructs the URL required to fetch the UTXOs associated with a given transaction ID. + * The URL is built based on the Blockfrost API and the network details provided in the `provider_impl`. + * + * \param[in] provider_impl A pointer to the provider implementation that contains network information. + * \param[in] tx_id A string representing the transaction ID. + * + * \return A dynamically allocated string containing the constructed URL, or NULL if memory allocation fails. + * The caller is responsible for freeing the memory returned by this function. + */ +char* +cardano_blockfrost_build_transaction_utxos_url( + cardano_provider_impl_t* provider_impl, + const char* tx_id); + +/** + * \brief Constructs a URL to query a datum by its hash from the Blockfrost API. + * + * This function constructs a URL string that can be used to retrieve a datum from the Blockfrost API by providing its hash. + * + * \param[in] provider_impl A pointer to an initialized \ref cardano_provider_impl_t object that contains the necessary context for building the URL. + * This parameter must not be NULL. + * \param[in] datum_hash A pointer to a null-terminated string representing the datum hash. This parameter must not be NULL. + * + * \return A pointer to a dynamically allocated string containing the URL to query the datum. The caller is responsible for freeing + * the returned string using \c free when it is no longer needed. If an error occurs (e.g., invalid parameters), the function returns NULL. + */ +char* +cardano_blockfrost_build_datum_url( + cardano_provider_impl_t* provider_impl, + const char* datum_hash); + +/** + * \brief Constructs a URL to query transaction metadata in CBOR format from the Blockfrost API. + * + * This function builds a URL string that can be used to retrieve transaction metadata (in CBOR format) from the Blockfrost API + * by providing the transaction hash. + * + * \param[in] provider_impl A pointer to an initialized \ref cardano_provider_impl_t object that contains the necessary context for building the URL. + * This parameter must not be NULL. + * \param[in] hash A pointer to a null-terminated string representing the transaction hash. This parameter must not be NULL. + * + * \return A pointer to a dynamically allocated string containing the URL to query the transaction metadata in CBOR format. + * The caller is responsible for freeing the returned string using \c free when it is no longer needed. + * If an error occurs (e.g., invalid parameters), the function returns NULL. + */ +char* +cardano_blockfrost_build_tx_metadata_cbor_url( + cardano_provider_impl_t* provider_impl, + const char* hash); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif // BIGLUP_LABS_INCLUDE_CARDANO_BLOCKFROST_URL_BUILDERS_H diff --git a/examples/providers/blockfrost/parsers/blockfrost_datum_parser.c b/examples/providers/blockfrost/parsers/blockfrost_datum_parser.c new file mode 100644 index 00000000..db4b8134 --- /dev/null +++ b/examples/providers/blockfrost/parsers/blockfrost_datum_parser.c @@ -0,0 +1,83 @@ +/** + * \file blockfrost_datum_parser.c + * + * \author angel.castillo + * \date Oct 02, 2024 + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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. + */ + +/* INCLUDES ******************************************************************/ + +#include "blockfrost_parsers.h" +#include "utils.h" + +#include +#include +#include + +/* STATIC FUNCTIONS **********************************************************/ + +cardano_error_t +cardano_blockfrost_parse_datum( + cardano_provider_impl_t* provider, + const char* json, + const size_t size, + cardano_plutus_data_t** datum) +{ + struct json_tokener* tok = json_tokener_new(); + struct json_object* parsed_json = json_tokener_parse_ex(tok, json, (int32_t)size); + + if (parsed_json == NULL) + { + cardano_utils_set_error_message(provider, "Failed to parse JSON response"); + json_tokener_free(tok); + + return CARDANO_ERROR_INVALID_JSON; + } + + struct json_object* datum_obj = NULL; + + if (!json_object_object_get_ex(parsed_json, "cbor", &datum_obj)) + { + cardano_utils_set_error_message(provider, "Failed to parse datum from JSON response"); + json_object_put(parsed_json); + json_tokener_free(tok); + + return CARDANO_ERROR_INVALID_JSON; + } + + const char* datum_data = json_object_get_string(datum_obj); + const size_t datum_len = json_object_get_string_len(datum_obj); + + cardano_cbor_reader_t* reader = cardano_cbor_reader_from_hex(datum_data, datum_len); + + if (!reader) + { + cardano_utils_set_error_message(provider, "Failed to parse datum from JSON response"); + json_object_put(parsed_json); + json_tokener_free(tok); + + return CARDANO_ERROR_INVALID_JSON; + } + + cardano_error_t error = cardano_plutus_data_from_cbor(reader, datum); + + cardano_cbor_reader_unref(&reader); + json_object_put(parsed_json); + json_tokener_free(tok); + + return error; +} diff --git a/examples/providers/blockfrost/parsers/blockfrost_parsers.h b/examples/providers/blockfrost/parsers/blockfrost_parsers.h new file mode 100644 index 00000000..26022f2f --- /dev/null +++ b/examples/providers/blockfrost/parsers/blockfrost_parsers.h @@ -0,0 +1,236 @@ +/** + * \file blockfrost_parsers.h + * + * \author angel.castillo + * \date Sep 30, 2024 + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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 BIGLUP_LABS_INCLUDE_CARDANO_BLOCKFROST_PARSERS_H +#define BIGLUP_LABS_INCLUDE_CARDANO_BLOCKFROST_PARSERS_H + +/* INCLUDES ******************************************************************/ + +#include +#include +#include +#include + +/* DECLARATIONS **************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * \brief Parses the Blockfrost protocol parameters from a JSON response. + * + * This function takes a JSON response string from the Blockfrost API, parses it, + * and sets the Cardano protocol parameters accordingly. The function validates + * the JSON and converts it into the appropriate data structures. + * + * \param[in] provider A pointer to the cardano provider implementation used for handling internal state and error reporting. + * \param[in] json The raw JSON string containing the protocol parameters returned by the Blockfrost API. + * \param[in] size The size of the JSON string. + * \param[out] parameters A pointer to the location where the parsed protocol parameters will be stored. + * + * \return CARDANO_SUCCESS on success, or an appropriate error code on failure. + * + * \note The caller is responsible for freeing the memory allocated for `parameters`. + */ +cardano_error_t +cardano_blockfrost_parse_protocol_parameters( + cardano_provider_impl_t* provider, + const char* json, + size_t size, + cardano_protocol_parameters_t** parameters); + +/** + * \brief Parses the unspent transaction outputs (UTXOs) from a Blockfrost API JSON response. + * + * This function parses the JSON data returned by the Blockfrost API, representing unspent transaction outputs (UTXOs), + * and converts it into a \ref cardano_utxo_list_t object. + * + * \param[in] provider A pointer to an initialized \ref cardano_provider_impl_t object that provides context for the parsing operation. + * This parameter must not be NULL. + * \param[in] json A pointer to a character array containing the JSON data returned by the Blockfrost API. This string must not be NULL. + * \param[in] size The size of the \p json string in bytes. + * \param[out] utxo_list On successful parsing, this will point to a newly created \ref cardano_utxo_list_t object containing the list of UTXOs parsed from the JSON. + * The caller is responsible for managing the lifecycle of this object and must call \ref cardano_utxo_list_unref when it is no longer needed. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the UTXOs were successfully parsed, + * or an appropriate error code if an error occurred (e.g., if the JSON format is invalid or if the UTXO data is missing). + */ +cardano_error_t +cardano_blockfrost_parse_unspent_outputs( + cardano_provider_impl_t* provider, + const char* json, + size_t size, + cardano_utxo_list_t** utxo_list); + +/** + * \brief Parses unspent transaction outputs (UTXOs) from a Blockfrost API response for a given transaction hash. + * + * This function parses the JSON response from the Blockfrost API to extract the UTXOs associated with the specified transaction hash. The results + * are stored in a UTXO list. + * + * \param[in] provider A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context for parsing the UTXOs. + * This parameter must not be NULL. + * \param[in] json A pointer to a JSON string representing the Blockfrost API response containing the UTXOs. This parameter must not be NULL. + * \param[in] size The size of the JSON string in bytes. + * \param[in] tx_hash A pointer to a null-terminated string representing the transaction hash for which to retrieve UTXOs. This parameter must not be NULL. + * \param[in] tx_hash_len The length of the \p tx_hash string. + * \param[out] utxo_list A pointer to a pointer of \ref cardano_utxo_list_t that will be populated with the UTXOs associated with the given transaction hash. + * The caller must manage the lifecycle of this list and free it when no longer needed. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the UTXOs were successfully parsed, + * or an appropriate error code if an error occurred (e.g., invalid JSON format, failure to parse the UTXOs, or invalid transaction hash). + */ +cardano_error_t +cardano_blockfrost_parse_tx_unspent_outputs( + cardano_provider_impl_t* provider, + const char* json, + size_t size, + const char* tx_hash, + size_t tx_hash_len, + cardano_utxo_list_t** utxo_list); + +/** + * \brief Retrieves a script from the Blockfrost API using a script hash. + * + * This function fetches a Cardano script from the Blockfrost API by querying with a provided script hash. The script is returned as a \ref cardano_script_t object. + * + * \param[in] provider_impl A pointer to an initialized \ref cardano_provider_impl_t object that contains the necessary context for querying the Blockfrost API. + * This parameter must not be NULL. + * \param[in] script_hash A pointer to a character array representing the script hash, which is used to identify the script on the blockchain. + * This string must be null-terminated. + * \param[in] script_hash_len The length of the \p script_hash string. + * \param[out] script On successful execution, this will point to a newly created \ref cardano_script_t object representing the script retrieved from the Blockfrost API. + * The caller is responsible for managing the lifecycle of this object and must call \ref cardano_script_unref when it is no longer needed. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the script was successfully retrieved, + * or an appropriate error code if an error occurred (e.g., if the API request failed or the script hash was invalid). + */ +cardano_error_t +cardano_blockfrost_get_script( + cardano_provider_impl_t* provider_impl, + const char* script_hash, + size_t script_hash_len, + cardano_script_t** script); + +/** + * \brief Parses rewards data from a Blockfrost API JSON response. + * + * This function parses the JSON data returned by the Blockfrost API, which contains staking rewards information, and retrieves the total rewards amount. + * + * \param[in] provider A pointer to an initialized \ref cardano_provider_impl_t object that provides context for the parsing operation. + * This parameter must not be NULL. + * \param[in] json A pointer to a character array containing the JSON data returned by the Blockfrost API. This string must not be NULL. + * \param[in] size The size of the \p json string in bytes. + * \param[out] rewards On successful parsing, this will contain the total rewards amount as a 64-bit unsigned integer. The caller must ensure this pointer is valid. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the rewards were successfully parsed, + * or an appropriate error code if an error occurred (e.g., if the JSON format is invalid or if the rewards data is missing). + */ +cardano_error_t +cardano_blockfrost_parse_rewards( + cardano_provider_impl_t* provider, + const char* json, + size_t size, + uint64_t* rewards); + +/** + * \brief Parses a datum from a Blockfrost API JSON response. + * + * This function parses the JSON representation of a Plutus datum returned by the Blockfrost API and constructs a corresponding + * \ref cardano_plutus_data_t object. + * + * \param[in] provider A pointer to an initialized \ref cardano_provider_impl_t object that provides necessary context for parsing. + * This parameter must not be NULL. + * \param[in] json A pointer to a JSON-encoded string representing the datum. This parameter must not be NULL. + * \param[in] size The size of the JSON string. + * \param[out] datum A pointer to a pointer of \ref cardano_plutus_data_t that will be populated with the parsed datum. The caller + * is responsible for managing the lifecycle of the returned datum and must release it using \ref cardano_plutus_data_unref + * when it is no longer needed. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the datum was successfully parsed, + * or an appropriate error code if an error occurred (e.g., invalid JSON format or failure to parse the datum). + */ +cardano_error_t +cardano_blockfrost_parse_datum( + cardano_provider_impl_t* provider, + const char* json, + size_t size, + cardano_plutus_data_t** datum); + +/** + * \brief Serializes transaction evaluation parameters and UTXOs to a JSON string. + * + * This function serializes the provided transaction evaluation parameters, including the transaction itself and associated UTXO list, into a JSON string. + * + * \param[in] transaction A pointer to an initialized \ref cardano_transaction_t object representing the transaction to be evaluated. + * This parameter must not be NULL. + * \param[in] utxos A pointer to an initialized \ref cardano_utxo_list_t object representing the UTXOs associated with the transaction. + * This parameter must not be NULL. + * \param[out] json_main_obj_str On success, this will point to the allocated JSON string that contains the serialized data. + * The caller is responsible for freeing this string using `free()` when no longer needed. + * \param[out] json_main_obj_size On success, this will be set to the size of the allocated JSON string, including the null terminator. + * + * \return \ref cardano_error_t indicating the outcome of the operation. + * + * \note The caller must ensure to free the memory allocated for the JSON string when it is no longer needed. + */ +cardano_error_t +cardano_evaluate_params_to_json( + cardano_transaction_t* transaction, + cardano_utxo_list_t* utxos, + char** json_main_obj_str, + size_t* json_main_obj_size); + +/** + * \brief Parses a transaction evaluation response from Blockfrost and updates redeemer execution units. + * + * This function parses the JSON response returned by Blockfrost after evaluating a transaction and extracts the updated redeemer execution units. + * + * \param[in] provider A pointer to an initialized \ref cardano_provider_impl_t object representing the Blockfrost provider. + * This parameter must not be NULL. + * \param[in] json A pointer to the JSON string containing the Blockfrost response for the transaction evaluation. This must be a valid JSON string. + * \param[in] size The size of the JSON string, in bytes. + * \param[in] original_redeemers A pointer to an initialized \ref cardano_redeemer_list_t object representing the original redeemers. + * This parameter must not be NULL. + * \param[out] redeemers On success, this will point to a newly created \ref cardano_redeemer_list_t object containing the updated redeemers + * with the applied execution units from the Blockfrost response. The caller is responsible for managing the lifecycle of + * this object, including calling \ref cardano_redeemer_list_unref when it is no longer needed. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the redeemers were successfully updated, + * or an appropriate error code such as \ref CARDANO_POINTER_IS_NULL if any of the input pointers are NULL, or \ref CARDANO_ERROR_JSON_PARSE + * if the JSON parsing fails. + * + * \note The caller must manage the memory of the \p redeemers object, ensuring it is properly released when no longer needed. + */ +cardano_error_t +cardano_blockfrost_parse_tx_eval_response( + cardano_provider_impl_t* provider, + const char* json, + size_t size, + cardano_redeemer_list_t* original_redeemers, + cardano_redeemer_list_t** redeemers); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif // BIGLUP_LABS_INCLUDE_CARDANO_BLOCKFROST_PARSERS_H diff --git a/examples/providers/blockfrost/parsers/blockfrost_pparam_parser.c b/examples/providers/blockfrost/parsers/blockfrost_pparam_parser.c new file mode 100644 index 00000000..0f4c1d0c --- /dev/null +++ b/examples/providers/blockfrost/parsers/blockfrost_pparam_parser.c @@ -0,0 +1,812 @@ +/** + * \file blockfrost_pparam_parser.c + * + * \author angel.castillo + * \date Sep 28, 2024 + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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. + */ + +/* INCLUDES ******************************************************************/ + +#include "utils.h" + +#include +#include +#include + +/* CALLBACKS PROTOTYPES ******************************************************/ + +/** + * \brief Function pointer type for handling different types of protocol parameters. + * + * This typedef defines a function pointer type for handling various types of protocol + * parameters during JSON parsing. The handler function is responsible for extracting + * the relevant parameter from the JSON object and applying the appropriate setter + * function to store the parameter in the protocol parameters structure. + * + * \param[in] key The key identifying the parameter in the JSON object. + * \param[in] parameters A pointer to the cardano_protocol_parameters_t structure where the parameter will be set. + * \param[in] json_obj The JSON object containing the parameter value to be processed. + * \param[in] setter_func A function pointer to the appropriate setter function for the parameter. + * + * \return CARDANO_SUCCESS on success, or an appropriate error code on failure. + */ +typedef cardano_error_t (*parameter_handler_t)( + const char* key, + cardano_protocol_parameters_t* parameters, + struct json_object* json_obj, + void* setter_func); + +/* STRUCTURES ****************************************************************/ + +/** + * \brief Struct for mapping JSON keys to handler functions and setters. + * + * This structure defines a mapping between a JSON key, a handler function to process + * the corresponding JSON value, and a setter function to store the value in the + * protocol parameters structure. + * + * \var key + * The JSON key to be mapped. It identifies the parameter in the JSON object. + * + * \var handler + * The function pointer of type \ref parameter_handler_t, which handles extracting + * and processing the value from the JSON object associated with the key. + * + * \var setter_func + * A function pointer to the appropriate setter function for the specific parameter, + * which will store the processed value in the \ref cardano_protocol_parameters_t structure. + */ +typedef struct +{ + const char* key; + parameter_handler_t handler; + void* setter_func; +} parameter_map_entry_t; + +/* STATIC FUNCTIONS **********************************************************/ + +/** + * \brief Processes a cost model from a JSON array for a given Plutus language version. + * + * This function extracts the cost model data from a JSON array, which contains the cost parameters + * for the specified Plutus language version. The extracted values are used to create a + * \ref cardano_cost_model_t object that is associated with the corresponding language version. + * + * \param[in] json_array The JSON array containing the cost model values. + * \param[in] language_version The Plutus language version for which the cost model is being processed. + * This should be one of the \ref cardano_plutus_language_version_t values (e.g., V1, V2, V3). + * \param[out] cost_model A pointer to a pointer of the \ref cardano_cost_model_t structure, + * where the processed cost model will be stored. + * + * \return A \ref cardano_error_t indicating the success or failure of the operation. + * - `CARDANO_SUCCESS`: If the cost model was successfully processed and created. + * - Appropriate error code otherwise, depending on the type of failure (e.g., invalid JSON structure). + */ +static cardano_error_t +process_cost_model( + struct json_object* json_array, + const cardano_plutus_language_version_t language_version, + cardano_cost_model_t** cost_model) +{ + const size_t array_len = json_object_array_length(json_array); + int64_t* cost_array = malloc(array_len * sizeof(int64_t)); + + if (cost_array == NULL) + { + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; + } + + for (size_t i = 0U; i < array_len; ++i) + { + struct json_object* item = json_object_array_get_idx(json_array, i); + cost_array[i] = json_object_get_int64(item); + } + + cardano_error_t result = cardano_cost_model_new(language_version, cost_array, array_len, cost_model); + + free(cost_array); + + return result; +} + +/** + * \brief Handles the extraction and setting of a 64-bit unsigned integer parameter from a JSON object. + * + * This function is used to extract a `uint64_t` value from the specified JSON object using the provided key. + * The extracted value is then passed to the provided setter function to update the corresponding field in + * the `cardano_protocol_parameters_t` structure. + * + * \param[in] key The JSON key associated with the parameter to extract. + * \param[in] parameters A pointer to the \ref cardano_protocol_parameters_t structure that holds + * the protocol parameters to be updated. + * \param[in] json_obj A pointer to the JSON object from which the `uint64_t` value will be extracted. + * \param[in] setter_func A function pointer to the setter function that takes the extracted `uint64_t` + * value and applies it to the \ref cardano_protocol_parameters_t structure. + * + * \return A \ref cardano_error_t indicating the success or failure of the operation. + * - Other error codes depending on the setter function’s result. + */ +static cardano_error_t +handle_uint64( + const char* key, + cardano_protocol_parameters_t* parameters, + struct json_object* json_obj, + cardano_error_t (*setter_func)(cardano_protocol_parameters_t*, uint64_t)) +{ + CARDANO_UNUSED(key); + + const uint64_t value = json_object_get_uint64(json_obj); + + return setter_func(parameters, value); +} + +/** + * \brief Handles the extraction and setting of a unit interval parameter from a JSON object. + * + * This function is used to extract a `double` value from the specified JSON object using the provided key, + * convert it into a `cardano_unit_interval_t`, and then pass it to the provided setter function to update + * the corresponding field in the `cardano_protocol_parameters_t` structure. + * + * \param[in] key The JSON key associated with the unit interval parameter to extract. + * \param[in] parameters A pointer to the \ref cardano_protocol_parameters_t structure that holds + * the protocol parameters to be updated. + * \param[in] json_obj A pointer to the JSON object from which the unit interval will be extracted. + * \param[in] setter_func A function pointer to the setter function that takes the extracted + * `cardano_unit_interval_t` value and applies it to the \ref cardano_protocol_parameters_t structure. + * + * \return A \ref cardano_error_t indicating the success or failure of the operation. + * - Other error codes depending on the setter function’s result or the conversion process. + */ +static cardano_error_t +handle_unit_interval( + const char* key, + cardano_protocol_parameters_t* parameters, + struct json_object* json_obj, + cardano_error_t (*setter_func)(cardano_protocol_parameters_t*, cardano_unit_interval_t*)) +{ + CARDANO_UNUSED(key); + + const double value = json_object_get_double(json_obj); + cardano_unit_interval_t* interval = NULL; + cardano_error_t result = cardano_unit_interval_from_double(value, &interval); + + if (result != CARDANO_SUCCESS) + { + cardano_unit_interval_unref(&interval); + return result; + } + + result = setter_func(parameters, interval); + cardano_unit_interval_unref(&interval); + + return result; +} + +/** + * \brief Handles the extraction and setting of the protocol version from a JSON object. + * + * This function is used to extract version-related information (major and minor) from the specified JSON object using the provided key, + * construct a `cardano_protocol_version_t` structure, and pass it to the provided setter function to update the corresponding field + * in the \ref cardano_protocol_parameters_t structure. + * + * \param[in] key The JSON key associated with the version parameter to extract. + * \param[in] parameters A pointer to the \ref cardano_protocol_parameters_t structure that holds + * the protocol parameters to be updated. + * \param[in] json_obj A pointer to the JSON object from which the version information will be extracted. + * \param[in] setter_func A function pointer to the setter function that takes the constructed + * \ref cardano_protocol_version_t and applies it to the \ref cardano_protocol_parameters_t structure. + * + * \return A \ref cardano_error_t indicating the success or failure of the operation. + * - Other error codes depending on the setter function’s result or the version construction process. + */ +static cardano_error_t +handle_version( + const char* key, + cardano_protocol_parameters_t* parameters, + struct json_object* json_obj, + cardano_error_t (*setter_func)(cardano_protocol_parameters_t*, cardano_protocol_version_t*)) +{ + const uint64_t value = json_object_get_uint64(json_obj); + cardano_protocol_version_t* version = cardano_protocol_parameters_get_protocol_version(parameters); + + cardano_error_t result = CARDANO_SUCCESS; + + if (strcmp(key, "protocol_major_ver") == 0) + { + result = cardano_protocol_version_set_major(version, value); + } + else if (strcmp(key, "protocol_minor_ver") == 0) + { + result = cardano_protocol_version_set_minor(version, value); + } + else + { + cardano_protocol_version_unref(&version); + + return CARDANO_ERROR_INVALID_JSON; + } + + if (result != CARDANO_SUCCESS) + { + cardano_protocol_version_unref(&version); + return result; + } + + result = setter_func(parameters, version); + cardano_protocol_version_unref(&version); + + return result; +} + +/** + * \brief Handles the extraction and setting of execution unit prices from a JSON object. + * + * This function is responsible for extracting execution unit pricing information (both memory and step prices) + * from the specified JSON object, constructing a \ref cardano_ex_unit_prices_t structure, and passing it to + * the provided setter function to update the corresponding field in the \ref cardano_protocol_parameters_t structure. + * + * \param[in] key The JSON key associated with the price parameter to extract. + * This is expected to differentiate between the "price_mem" and "price_step" fields. + * \param[in] parameters A pointer to the \ref cardano_protocol_parameters_t structure + * that holds the protocol parameters to be updated. + * \param[in] json_obj A pointer to the JSON object from which the price information + * (for both memory and steps) will be extracted. + * \param[in] setter_func A function pointer to the setter function that takes the constructed + * \ref cardano_ex_unit_prices_t structure and applies it to the \ref cardano_protocol_parameters_t structure. + * + * \return A \ref cardano_error_t indicating the success or failure of the operation. + * - Other error codes depending on the setter function’s result or the price construction process. + */ +static cardano_error_t +handle_prices( + const char* key, + cardano_protocol_parameters_t* parameters, + struct json_object* json_obj, + cardano_error_t (*setter_func)(cardano_protocol_parameters_t*, cardano_ex_unit_prices_t*)) +{ + const double value = json_object_get_double(json_obj); + cardano_unit_interval_t* interval = NULL; + cardano_error_t result = cardano_unit_interval_from_double(value, &interval); + + if (result != CARDANO_SUCCESS) + { + cardano_unit_interval_unref(&interval); + return result; + } + + cardano_ex_unit_prices_t* prices = cardano_protocol_parameters_get_execution_costs(parameters); + + if (strcmp(key, "price_mem") == 0) + { + result = cardano_ex_unit_prices_set_memory_prices(prices, interval); + } + else if (strcmp(key, "price_step") == 0) + { + result = cardano_ex_unit_prices_set_steps_prices(prices, interval); + } + else + { + cardano_unit_interval_unref(&interval); + cardano_ex_unit_prices_unref(&prices); + + return CARDANO_ERROR_INVALID_JSON; + } + + cardano_unit_interval_unref(&interval); + + if (result != CARDANO_SUCCESS) + { + cardano_ex_unit_prices_unref(&prices); + return result; + } + + result = setter_func(parameters, prices); + cardano_ex_unit_prices_unref(&prices); + + return result; +} + +/** + * \brief Handles the extraction and setting of maximum execution units (memory and steps) from a JSON object. + * + * This function extracts the maximum execution units (memory and step values) from the specified JSON object. + * It constructs a \ref cardano_ex_units_t structure and passes it to the provided setter function, which updates + * the corresponding field in the \ref cardano_protocol_parameters_t structure. + * + * \param[in] key The JSON key associated with the execution units to extract (e.g., "max_tx_ex_mem", "max_tx_ex_steps"). + * \param[in] parameters A pointer to the \ref cardano_protocol_parameters_t structure that holds the protocol parameters to be updated. + * \param[in] json_obj A pointer to the JSON object from which the maximum execution units will be extracted. + * \param[in] setter_func A function pointer to the setter function that takes the constructed + * \ref cardano_ex_units_t structure and applies it to the \ref cardano_protocol_parameters_t structure. + * + * \return A \ref cardano_error_t indicating the success or failure of the operation. + * - Other error codes depending on the setter function’s result or the execution unit construction process. + */ +static cardano_error_t +handle_max_ex( + const char* key, + cardano_protocol_parameters_t* parameters, + struct json_object* json_obj, + cardano_error_t (*setter_func)(cardano_protocol_parameters_t*, cardano_ex_units_t*)) +{ + const uint64_t value = json_object_get_uint64(json_obj); + + cardano_ex_units_t* units = NULL; + + if ((strcmp(key, "max_tx_ex_mem") == 0) || (strcmp(key, "max_tx_ex_steps") == 0)) + { + units = cardano_protocol_parameters_get_max_tx_ex_units(parameters); + } + else + { + units = cardano_protocol_parameters_get_max_block_ex_units(parameters); + } + + cardano_error_t result = CARDANO_SUCCESS; + + if ((strcmp(key, "max_tx_ex_mem") == 0) || (strcmp(key, "max_block_ex_mem") == 0)) + { + result = cardano_ex_units_set_memory(units, value); + } + else + { + result = cardano_ex_units_set_cpu_steps(units, value); + } + + if (result != CARDANO_SUCCESS) + { + cardano_ex_units_unref(&units); + return result; + } + + result = setter_func(parameters, units); + cardano_ex_units_unref(&units); + + return result; +} + +/** + * \brief Handles the extraction and setting of pool voting thresholds (PVT) from a JSON object. + * + * This function extracts the pool voting thresholds associated with governance operations + * (e.g., motion of no confidence, committee thresholds) from the specified JSON object. + * It constructs a \ref cardano_pool_voting_thresholds_t structure and passes it to the provided setter function, + * which updates the corresponding field in the \ref cardano_protocol_parameters_t structure. + * + * \param[in] key The JSON key associated with the specific pool voting threshold to extract. + * \param[in] parameters A pointer to the \ref cardano_protocol_parameters_t structure that holds the protocol parameters to be updated. + * \param[in] json_obj A pointer to the JSON object from which the pool voting threshold will be extracted. + * \param[in] setter_func A function pointer to the setter function that takes the constructed + * \ref cardano_pool_voting_thresholds_t structure and applies it to the \ref cardano_protocol_parameters_t structure. + * + * \return A \ref cardano_error_t indicating the success or failure of the operation. + * - Other error codes depending on the setter function’s result or the threshold construction process. + */ +static cardano_error_t +handle_pvt( + const char* key, + cardano_protocol_parameters_t* parameters, + struct json_object* json_obj, + cardano_error_t (*setter_func)(cardano_protocol_parameters_t*, cardano_pool_voting_thresholds_t*)) +{ + const double value = json_object_get_double(json_obj); + cardano_unit_interval_t* interval = NULL; + cardano_error_t result = cardano_unit_interval_from_double(value, &interval); + + if (result != CARDANO_SUCCESS) + { + cardano_unit_interval_unref(&interval); + return result; + } + + cardano_pool_voting_thresholds_t* threshold = cardano_protocol_parameters_get_pool_voting_thresholds(parameters); + + if (strcmp(key, "pvt_motion_no_confidence") == 0) + { + result = cardano_pool_voting_thresholds_set_motion_no_confidence(threshold, interval); + } + else if (strcmp(key, "pvt_committee_normal") == 0) + { + result = cardano_pool_voting_thresholds_set_committee_normal(threshold, interval); + } + else if (strcmp(key, "pvt_committee_no_confidence") == 0) + { + result = cardano_pool_voting_thresholds_set_committee_no_confidence(threshold, interval); + } + else if (strcmp(key, "pvt_hard_fork_initiation") == 0) + { + result = cardano_pool_voting_thresholds_set_hard_fork_initiation(threshold, interval); + } + else if (strcmp(key, "pvt_p_p_security_group") == 0) + { + result = cardano_pool_voting_thresholds_set_security_relevant_param(threshold, interval); + } + else + { + cardano_unit_interval_unref(&interval); + cardano_pool_voting_thresholds_unref(&threshold); + + return CARDANO_ERROR_INVALID_JSON; + } + + cardano_unit_interval_unref(&interval); + + if (result != CARDANO_SUCCESS) + { + cardano_pool_voting_thresholds_unref(&threshold); + return result; + } + + result = setter_func(parameters, threshold); + cardano_pool_voting_thresholds_unref(&threshold); + + return result; +} + +/** + * \brief Handles the extraction and setting of decentralized representative (DRep) voting thresholds (DVT) from a JSON object. + * + * This function extracts the decentralized representative (DRep) voting thresholds, which are related to governance actions, + * from the specified JSON object. It constructs a \ref cardano_drep_voting_thresholds_t structure and passes it to the provided setter function, + * which updates the corresponding field in the \ref cardano_protocol_parameters_t structure. + * + * \param[in] key The JSON key associated with the specific DRep voting threshold to extract. + * \param[in] parameters A pointer to the \ref cardano_protocol_parameters_t structure that holds the protocol parameters to be updated. + * \param[in] json_obj A pointer to the JSON object from which the DRep voting threshold will be extracted. + * \param[in] setter_func A function pointer to the setter function that takes the constructed + * \ref cardano_drep_voting_thresholds_t structure and applies it to the \ref cardano_protocol_parameters_t structure. + * + * \return A \ref cardano_error_t indicating the success or failure of the operation. + * - Other error codes depending on the setter function’s result or the threshold construction process. + */ +static cardano_error_t +handle_dvt( + const char* key, + cardano_protocol_parameters_t* parameters, + struct json_object* json_obj, + cardano_error_t (*setter_func)(cardano_protocol_parameters_t*, cardano_drep_voting_thresholds_t*)) +{ + const double value = json_object_get_double(json_obj); + cardano_unit_interval_t* interval = NULL; + cardano_error_t result = cardano_unit_interval_from_double(value, &interval); + + if (result != CARDANO_SUCCESS) + { + cardano_unit_interval_unref(&interval); + return result; + } + + cardano_drep_voting_thresholds_t* threshold = cardano_protocol_parameters_get_drep_voting_thresholds(parameters); + + if (strcmp(key, "dvt_motion_no_confidence") == 0) + { + result = cardano_drep_voting_thresholds_set_motion_no_confidence(threshold, interval); + } + else if (strcmp(key, "dvt_committee_normal") == 0) + { + result = cardano_drep_voting_thresholds_set_committee_normal(threshold, interval); + } + else if (strcmp(key, "dvt_committee_no_confidence") == 0) + { + result = cardano_drep_voting_thresholds_set_committee_no_confidence(threshold, interval); + } + else if (strcmp(key, "dvt_update_to_constitution") == 0) + { + result = cardano_drep_voting_thresholds_set_update_constitution(threshold, interval); + } + else if (strcmp(key, "dvt_hard_fork_initiation") == 0) + { + result = cardano_drep_voting_thresholds_set_hard_fork_initiation(threshold, interval); + } + else if (strcmp(key, "dvt_p_p_network_group") == 0) + { + result = cardano_drep_voting_thresholds_set_pp_network_group(threshold, interval); + } + else if (strcmp(key, "dvt_p_p_economic_group") == 0) + { + result = cardano_drep_voting_thresholds_set_pp_economic_group(threshold, interval); + } + else if (strcmp(key, "dvt_p_p_technical_group") == 0) + { + result = cardano_drep_voting_thresholds_set_pp_technical_group(threshold, interval); + } + else if (strcmp(key, "dvt_p_p_gov_group") == 0) + { + result = cardano_drep_voting_thresholds_set_pp_governance_group(threshold, interval); + } + else if (strcmp(key, "dvt_treasury_withdrawal") == 0) + { + result = cardano_drep_voting_thresholds_set_treasury_withdrawal(threshold, interval); + } + else + { + cardano_unit_interval_unref(&interval); + cardano_drep_voting_thresholds_unref(&threshold); + + return CARDANO_ERROR_INVALID_JSON; + } + + cardano_unit_interval_unref(&interval); + + if (result != CARDANO_SUCCESS) + { + cardano_drep_voting_thresholds_unref(&threshold); + return result; + } + + result = setter_func(parameters, threshold); + cardano_drep_voting_thresholds_unref(&threshold); + + return result; +} + +/** + * \brief Handles the extraction and setting of a buffer field from a JSON object. + * + * This function extracts a buffer (e.g., byte array) from the specified JSON object and converts it + * into a \ref cardano_buffer_t structure, which is then passed to the provided setter function. + * The setter function updates the corresponding field in the \ref cardano_protocol_parameters_t structure. + * + * \param[in] key The JSON key associated with the buffer to extract. + * \param[in] parameters A pointer to the \ref cardano_protocol_parameters_t structure that holds the protocol parameters to be updated. + * \param[in] json_obj A pointer to the JSON object from which the buffer will be extracted. + * \param[in] setter_func A function pointer to the setter function that takes the constructed + * \ref cardano_buffer_t structure and applies it to the \ref cardano_protocol_parameters_t structure. + * + * \return A \ref cardano_error_t indicating the success or failure of the operation. + * - Other error codes depending on the setter function’s result or buffer construction process. + */ +static cardano_error_t +handle_buffer( + const char* key, + cardano_protocol_parameters_t* parameters, + struct json_object* json_obj, + cardano_error_t (*setter_func)(cardano_protocol_parameters_t*, cardano_buffer_t*)) +{ + CARDANO_UNUSED(key); + + const char* value = json_object_get_string(json_obj); + const size_t value_len = json_object_get_string_len(json_obj); + cardano_buffer_t* entropy = cardano_buffer_from_hex(value, value_len); + + if (entropy == NULL) + { + return CARDANO_SUCCESS; + } + + cardano_error_t result = setter_func(parameters, entropy); + cardano_buffer_unref(&entropy); + + return result; +} + +/** + * \brief Processes a cost model from a JSON object and inserts it into the cost models container. + * + * This function extracts the cost model for a specific Plutus language version from the given JSON object, + * processes the cost model, and inserts it into the provided cost models container. + * + * \param[in] json_obj The JSON object that contains the cost model array associated with the Plutus version. + * \param[in] version_key The JSON key used to identify the cost model in the JSON object (e.g., "PlutusV1", "PlutusV2"). + * \param[in] language_version The Plutus language version identifier (e.g., `CARDANO_PLUTUS_LANGUAGE_VERSION_V1`). + * \param[out] cost_models The container to which the processed cost model will be inserted. + * + * \return A \ref cardano_error_t indicating the success or failure of the operation. + * - Other error codes depending on the cost model processing and insertion logic. + */ +static cardano_error_t +process_and_insert_cost_model( + struct json_object* json_obj, + const char* version_key, + int language_version, + cardano_costmdls_t* cost_models) +{ + struct json_object* json_version = NULL; + + if (json_object_object_get_ex(json_obj, version_key, &json_version)) + { + cardano_cost_model_t* cost_model = NULL; + cardano_error_t result = process_cost_model(json_version, language_version, &cost_model); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + result = cardano_costmdls_insert(cost_models, cost_model); + cardano_cost_model_unref(&cost_model); + + if (result != CARDANO_SUCCESS) + { + return result; + } + } + + return CARDANO_SUCCESS; +} + +/** + * \brief Handles the parsing, processing, and setting of cost models from a JSON object. + * + * This function extracts and processes the cost models (e.g., Plutus V1, V2, V3) from the provided JSON object, + * creates the corresponding \ref cardano_costmdls_t structure, and uses the provided setter function to + * assign the cost models to the protocol parameters. + * + * \param[in] key The JSON key associated with the cost models (e.g., "cost_models_raw"). + * \param[in] parameters A pointer to the \ref cardano_protocol_parameters_t structure where the cost models will be set. + * \param[in] json_obj The JSON object containing the cost models. + * \param[in] setter_func A function pointer that sets the processed cost models in the protocol parameters. + * + * \return A \ref cardano_error_t indicating the success or failure of the operation. + * - Other error codes depending on the cost model processing and setting logic. + */ +static cardano_error_t +handle_cost_models( + const char* key, + cardano_protocol_parameters_t* parameters, + struct json_object* json_obj, + cardano_error_t (*setter_func)(cardano_protocol_parameters_t*, cardano_costmdls_t*)) +{ + CARDANO_UNUSED(key); + + cardano_costmdls_t* cost_models = NULL; + cardano_error_t result = cardano_costmdls_new(&cost_models); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + result = process_and_insert_cost_model(json_obj, "PlutusV1", CARDANO_PLUTUS_LANGUAGE_VERSION_V1, cost_models); + + if (result != CARDANO_SUCCESS) + { + cardano_costmdls_unref(&cost_models); + return result; + } + + result = process_and_insert_cost_model(json_obj, "PlutusV2", CARDANO_PLUTUS_LANGUAGE_VERSION_V2, cost_models); + + if (result != CARDANO_SUCCESS) + { + cardano_costmdls_unref(&cost_models); + return result; + } + + result = process_and_insert_cost_model(json_obj, "PlutusV3", CARDANO_PLUTUS_LANGUAGE_VERSION_V3, cost_models); + + if (result != CARDANO_SUCCESS) + { + cardano_costmdls_unref(&cost_models); + return result; + } + + result = setter_func(parameters, cost_models); + cardano_costmdls_unref(&cost_models); + + return result; +} + +/* STATIC VARIABLES **********************************************************/ + +/** + * \brief Mapping between JSON keys and handler functions for protocol parameters. + */ +static parameter_map_entry_t parameter_map[] = { + { "min_fee_a", (void*)handle_uint64, (void*)cardano_protocol_parameters_set_min_fee_a }, + { "min_fee_b", (void*)handle_uint64, (void*)cardano_protocol_parameters_set_min_fee_b }, + { "max_block_size", (void*)handle_uint64, (void*)cardano_protocol_parameters_set_max_block_body_size }, + { "max_tx_size", (void*)handle_uint64, (void*)cardano_protocol_parameters_set_max_tx_size }, + { "max_block_header_size", (void*)handle_uint64, (void*)cardano_protocol_parameters_set_max_block_header_size }, + { "key_deposit", (void*)handle_uint64, (void*)cardano_protocol_parameters_set_key_deposit }, + { "pool_deposit", (void*)handle_uint64, (void*)cardano_protocol_parameters_set_pool_deposit }, + { "e_max", (void*)handle_uint64, (void*)cardano_protocol_parameters_set_max_epoch }, + { "n_opt", (void*)handle_uint64, (void*)cardano_protocol_parameters_set_n_opt }, + { "a0", (void*)handle_unit_interval, (void*)cardano_protocol_parameters_set_pool_pledge_influence }, + { "rho", (void*)handle_unit_interval, (void*)cardano_protocol_parameters_set_expansion_rate }, + { "tau", (void*)handle_unit_interval, (void*)cardano_protocol_parameters_set_treasury_growth_rate }, + { "decentralisation_param", (void*)handle_unit_interval, (void*)cardano_protocol_parameters_set_d }, + { "extra_entropy", (void*)handle_buffer, (void*)cardano_protocol_parameters_set_extra_entropy }, + { "protocol_major_ver", (void*)handle_version, (void*)cardano_protocol_parameters_set_protocol_version }, + { "protocol_minor_ver", (void*)handle_version, (void*)cardano_protocol_parameters_set_protocol_version }, + { "coins_per_utxo_word", (void*)handle_uint64, (void*)cardano_protocol_parameters_set_ada_per_utxo_byte }, + { "min_pool_cost", (void*)handle_uint64, (void*)cardano_protocol_parameters_set_min_pool_cost }, + { "cost_models_raw", (void*)handle_cost_models, (void*)cardano_protocol_parameters_set_cost_models }, + { "price_mem", (void*)handle_prices, (void*)cardano_protocol_parameters_set_execution_costs }, + { "price_step", (void*)handle_prices, (void*)cardano_protocol_parameters_set_execution_costs }, + { "max_tx_ex_mem", (void*)handle_max_ex, (void*)cardano_protocol_parameters_set_max_tx_ex_units }, + { "max_tx_ex_steps", (void*)handle_max_ex, (void*)cardano_protocol_parameters_set_max_tx_ex_units }, + { "max_block_ex_mem", (void*)handle_max_ex, (void*)cardano_protocol_parameters_set_max_block_ex_units }, + { "max_block_ex_steps", (void*)handle_max_ex, (void*)cardano_protocol_parameters_set_max_block_ex_units }, + { "max_val_size", (void*)handle_uint64, (void*)cardano_protocol_parameters_set_max_value_size }, + { "collateral_percent", (void*)handle_uint64, (void*)cardano_protocol_parameters_set_collateral_percentage }, + { "max_collateral_inputs", (void*)handle_uint64, (void*)cardano_protocol_parameters_set_max_collateral_inputs }, + { "pvt_motion_no_confidence", (void*)handle_pvt, (void*)cardano_protocol_parameters_set_pool_voting_thresholds }, + { "pvt_committee_normal", (void*)handle_pvt, (void*)cardano_protocol_parameters_set_pool_voting_thresholds }, + { "pvt_committee_no_confidence", (void*)handle_pvt, (void*)cardano_protocol_parameters_set_pool_voting_thresholds }, + { "pvt_hard_fork_initiation", (void*)handle_pvt, (void*)cardano_protocol_parameters_set_pool_voting_thresholds }, + { "pvt_p_p_security_group", (void*)handle_pvt, (void*)cardano_protocol_parameters_set_pool_voting_thresholds }, + { "dvt_motion_no_confidence", (void*)handle_dvt, (void*)cardano_protocol_parameters_set_drep_voting_thresholds }, + { "dvt_committee_normal", (void*)handle_dvt, (void*)cardano_protocol_parameters_set_drep_voting_thresholds }, + { "dvt_committee_no_confidence", (void*)handle_dvt, (void*)cardano_protocol_parameters_set_drep_voting_thresholds }, + { "dvt_update_to_constitution", (void*)handle_dvt, (void*)cardano_protocol_parameters_set_drep_voting_thresholds }, + { "dvt_hard_fork_initiation", (void*)handle_dvt, (void*)cardano_protocol_parameters_set_drep_voting_thresholds }, + { "dvt_p_p_network_group", (void*)handle_dvt, (void*)cardano_protocol_parameters_set_drep_voting_thresholds }, + { "dvt_p_p_economic_group", (void*)handle_dvt, (void*)cardano_protocol_parameters_set_drep_voting_thresholds }, + { "dvt_p_p_technical_group", (void*)handle_dvt, (void*)cardano_protocol_parameters_set_drep_voting_thresholds }, + { "dvt_p_p_gov_group", (void*)handle_dvt, (void*)cardano_protocol_parameters_set_drep_voting_thresholds }, + { "dvt_treasury_withdrawal", (void*)handle_dvt, (void*)cardano_protocol_parameters_set_drep_voting_thresholds }, + { "committee_min_size", (void*)handle_uint64, (void*)cardano_protocol_parameters_set_min_committee_size }, + { "committee_max_term_length", (void*)handle_uint64, (void*)cardano_protocol_parameters_set_committee_term_limit }, + { "gov_action_lifetime", (void*)handle_uint64, (void*)cardano_protocol_parameters_set_governance_action_validity_period }, + { "gov_action_deposit", (void*)handle_uint64, (void*)cardano_protocol_parameters_set_governance_action_deposit }, + { "drep_deposit", (void*)handle_uint64, (void*)cardano_protocol_parameters_set_drep_deposit }, + { "drep_activity", (void*)handle_uint64, (void*)cardano_protocol_parameters_set_drep_inactivity_period }, + { "min_fee_ref_script_cost_per_byte", (void*)handle_unit_interval, (void*)cardano_protocol_parameters_set_ref_script_cost_per_byte }, + { NULL, NULL, NULL } +}; + +/* IMPLEMENTATION *************************************************************/ + +cardano_error_t +cardano_blockfrost_parse_protocol_parameters( + cardano_provider_impl_t* provider, + const char* json, + const size_t size, + cardano_protocol_parameters_t** parameters) +{ + struct json_tokener* tok = json_tokener_new(); + struct json_object* parsed_json = json_tokener_parse_ex(tok, json, (int32_t)size); + + if (parsed_json == NULL) + { + cardano_utils_set_error_message(provider, "Failed to parse JSON response"); + json_tokener_free(tok); + + return CARDANO_ERROR_INVALID_JSON; + } + + cardano_error_t result = cardano_protocol_parameters_new(parameters); + if (result != CARDANO_SUCCESS) + { + json_tokener_free(tok); + return result; + } + + for (parameter_map_entry_t* entry = parameter_map; entry->key != NULL; ++entry) + { + struct json_object* json_obj = NULL; + + if (!json_object_object_get_ex(parsed_json, entry->key, &json_obj)) + { + continue; + } + + result = entry->handler(entry->key, *parameters, json_obj, entry->setter_func); + + if (result != CARDANO_SUCCESS) + { + json_object_put(parsed_json); + return result; + } + } + + json_object_put(parsed_json); + json_tokener_free(tok); + + return CARDANO_SUCCESS; +} \ No newline at end of file diff --git a/examples/providers/blockfrost/parsers/blockfrost_rewards_parser.c b/examples/providers/blockfrost/parsers/blockfrost_rewards_parser.c new file mode 100644 index 00000000..f65be992 --- /dev/null +++ b/examples/providers/blockfrost/parsers/blockfrost_rewards_parser.c @@ -0,0 +1,62 @@ +/** + * \file blockfrost_rewards_parser.c + * + * \author angel.castillo + * \date Oct 01, 2024 + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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. + */ + +/* INCLUDES ******************************************************************/ + +#include "blockfrost_parsers.h" +#include "utils.h" + +#include +#include +#include + +/* STATIC FUNCTIONS **********************************************************/ + +cardano_error_t +cardano_blockfrost_parse_rewards( + cardano_provider_impl_t* provider, + const char* json, + const size_t size, + uint64_t* rewards) +{ + struct json_tokener* tok = json_tokener_new(); + struct json_object* parsed_json = json_tokener_parse_ex(tok, json, (int32_t)size); + + if (parsed_json == NULL) + { + cardano_utils_set_error_message(provider, "Failed to parse JSON response"); + json_tokener_free(tok); + + return CARDANO_ERROR_INVALID_JSON; + } + + struct json_object* rewards_obj = NULL; + + if (json_object_object_get_ex(parsed_json, "withdrawable_amount", &rewards_obj)) + { + *rewards = json_object_get_int64(rewards_obj); + } + + json_object_put(parsed_json); + json_tokener_free(tok); + + return CARDANO_SUCCESS; +} diff --git a/examples/providers/blockfrost/parsers/blockfrost_script_parser.c b/examples/providers/blockfrost/parsers/blockfrost_script_parser.c new file mode 100644 index 00000000..178df107 --- /dev/null +++ b/examples/providers/blockfrost/parsers/blockfrost_script_parser.c @@ -0,0 +1,420 @@ +/** + * \file blockfrost_script_parser.c + * + * \author angel.castillo + * \date Oct 1, 2024 + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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. + */ + +/* INCLUDES ******************************************************************/ + +#include "blockfrost/common/blockfrost_common.h" +#include "blockfrost/common/blockfrost_url_builders.h" +#include "utils.h" + +#include +#include +#include +#include + +/* STATIC FUNCTIONS **********************************************************/ + +/** + * \brief Constructs a URL string for a given script hash and optional path suffix. + * + * This function constructs a URL string for the Blockfrost , including + * optional path suffixes such as `/json` or `/cbor` for different representations of the script. + * + * \param[in] provider_impl Pointer to the provider implementation instance. + * \param[in] script_hash Pointer to the script hash string to append to the URL. + * \param[in] script_hash_len The length of the script hash string. + * \param[in] path_suffix Optional path suffix to append to the URL (e.g., "/json", "/cbor"). + * Can be NULL or an empty string if no suffix is required. + * \return A dynamically allocated string containing the constructed URL. The caller is responsible for freeing the returned string. + */ +static char* +construct_script_url_with_suffix( + cardano_provider_impl_t* provider_impl, + const char* script_hash, + const size_t script_hash_len, + const char* path_suffix) +{ + blockfrost_context_t* context = (blockfrost_context_t*)provider_impl->context; + + char* base_path = cardano_blockfrost_get_endpoint_url(context->network, "scripts/"); + + if (!base_path) + { + return NULL; + } + + const char* suffix = path_suffix ? path_suffix : ""; + size_t suffix_len = strlen(suffix); + + size_t base_path_len = strlen(base_path); + size_t url_len = base_path_len + script_hash_len + suffix_len + 1; + + char* url = malloc(url_len); + + if (!url) + { + free(base_path); + return NULL; + } + + snprintf(url, url_len, "%s%s%s", base_path, script_hash, suffix); + + free(base_path); + + return url; +} + +/** + * \brief Parses the script language from a JSON string and returns a \ref cardano_script_language_t value. + * + * This static function parses the script language (e.g., Plutus V1, Plutus V2) from the provided JSON string and returns it as a \ref cardano_script_language_t value. + * + * \param[in] provider A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context for parsing. + * This parameter must not be NULL. + * \param[in] json A pointer to a character array representing the JSON string containing the script language data. + * This string must not be NULL. + * \param[in] size The size of the \p json string in bytes. + * \param[out] language On successful parsing, this will point to a \ref cardano_script_language_t value representing + * the parsed script language (e.g., PLUTUS_V1, PLUTUS_V2). The caller can use this to determine + * the script version. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the script language + * was successfully parsed, or an appropriate error code if an error occurred (e.g., if the JSON format is invalid). + */ +static cardano_error_t +parse_script_language( + cardano_provider_impl_t* provider, + const char* json, + const size_t size, + cardano_script_language_t* language) +{ + struct json_tokener* tok = json_tokener_new(); + struct json_object* parsed_json = json_tokener_parse_ex(tok, json, (int32_t)size); + + if (parsed_json == NULL) + { + cardano_utils_set_error_message(provider, "Failed to parse JSON response"); + + json_tokener_free(tok); + + return CARDANO_ERROR_INVALID_JSON; + } + + struct json_object* script_obj = NULL; + + if (!json_object_object_get_ex(parsed_json, "type", &script_obj)) + { + cardano_utils_set_error_message(provider, "Failed to parse script type from JSON response"); + + json_object_put(parsed_json); + json_tokener_free(tok); + + return CARDANO_ERROR_INVALID_JSON; + } + + const char* type = json_object_get_string(script_obj); + + if (strcmp(type, "timelock") == 0) + { + *language = CARDANO_SCRIPT_LANGUAGE_NATIVE; + } + else if (strcmp(type, "plutusV1") == 0) + { + *language = CARDANO_SCRIPT_LANGUAGE_PLUTUS_V1; + } + else if (strcmp(type, "plutusV2") == 0) + { + *language = CARDANO_SCRIPT_LANGUAGE_PLUTUS_V2; + } + else if (strcmp(type, "plutusV3") == 0) + { + *language = CARDANO_SCRIPT_LANGUAGE_PLUTUS_V3; + } + else + { + cardano_utils_set_error_message(provider, "Invalid script type"); + + json_object_put(parsed_json); + json_tokener_free(tok); + + return CARDANO_ERROR_INVALID_JSON; + } + + json_object_put(parsed_json); + json_tokener_free(tok); + + return CARDANO_SUCCESS; +} + +/** + * \brief Parses a Plutus script from a JSON string and returns a \ref cardano_script_t object. + * + * This function parses a Plutus script from a provided JSON string, which can represent a Plutus V1 or V2 script depending on the specified language. + * + * \param[in] provider A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context for parsing. + * This parameter must not be NULL. + * \param[in] json A pointer to a character array representing the JSON string containing the Plutus script data. This string must not be NULL. + * \param[in] size The size of the \p json string in bytes. + * \param[in] language A \ref cardano_script_language_t value indicating the version of the Plutus language (e.g., PLUTUS_V1, PLUTUS_V2). + * \param[out] script On successful parsing, this will point to a newly created \ref cardano_script_t object representing + * the parsed Plutus script. The caller is responsible for managing the lifecycle of this object and must + * call \ref cardano_script_unref when it is no longer needed. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the Plutus script + * was successfully parsed, or an appropriate error code if an error occurred (e.g., if the JSON format is invalid or unsupported). + */ +static cardano_error_t +parse_plutus_script( + cardano_provider_impl_t* provider, + const char* json, + const size_t size, + const cardano_script_language_t language, + cardano_script_t** script) +{ + struct json_tokener* tok = json_tokener_new(); + struct json_object* parsed_json = json_tokener_parse_ex(tok, json, (int32_t)size); + + if (parsed_json == NULL) + { + cardano_utils_set_error_message(provider, "Failed to parse JSON response"); + json_tokener_free(tok); + + return CARDANO_ERROR_INVALID_JSON; + } + + struct json_object* script_obj = NULL; + + if (!json_object_object_get_ex(parsed_json, "cbor", &script_obj)) + { + cardano_utils_set_error_message(provider, "Failed to parse script from JSON response"); + json_object_put(parsed_json); + json_tokener_free(tok); + + return CARDANO_ERROR_INVALID_JSON; + } + + const char* script_data = json_object_get_string(script_obj); + const size_t script_len = json_object_get_string_len(script_obj); + + cardano_error_t result = CARDANO_SUCCESS; + + switch (language) + { + case CARDANO_SCRIPT_LANGUAGE_PLUTUS_V1: + { + cardano_plutus_v1_script_t* plutus_v1_script = NULL; + + result = cardano_plutus_v1_script_new_bytes_from_hex(script_data, script_len, &plutus_v1_script); + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider, "Failed to parse Plutus V1 script from JSON response"); + json_object_put(parsed_json); + json_tokener_free(tok); + + return result; + } + + result = cardano_script_new_plutus_v1(plutus_v1_script, script); + + cardano_plutus_v1_script_unref(&plutus_v1_script); + break; + } + case CARDANO_SCRIPT_LANGUAGE_PLUTUS_V2: + { + cardano_plutus_v2_script_t* plutus_v2_script = NULL; + + result = cardano_plutus_v2_script_new_bytes_from_hex(script_data, script_len, &plutus_v2_script); + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider, "Failed to parse Plutus V2 script from JSON response"); + json_object_put(parsed_json); + json_tokener_free(tok); + + return result; + } + + result = cardano_script_new_plutus_v2(plutus_v2_script, script); + + cardano_plutus_v2_script_unref(&plutus_v2_script); + break; + } + case CARDANO_SCRIPT_LANGUAGE_PLUTUS_V3: + { + cardano_plutus_v3_script_t* plutus_v3_script = NULL; + + result = cardano_plutus_v3_script_new_bytes_from_hex(script_data, script_len, &plutus_v3_script); + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider, "Failed to parse Plutus V3 script from JSON response"); + json_object_put(parsed_json); + json_tokener_free(tok); + + return result; + } + + result = cardano_script_new_plutus_v3(plutus_v3_script, script); + + cardano_plutus_v3_script_unref(&plutus_v3_script); + + break; + } + default: + { + cardano_utils_set_error_message(provider, "Invalid Plutus language version"); + json_object_put(parsed_json); + json_tokener_free(tok); + + return CARDANO_ERROR_INVALID_JSON; + } + } + + json_object_put(parsed_json); + json_tokener_free(tok); + + return result; +} + +/** + * \brief Parses a native script from a JSON string and returns a \ref cardano_script_t object. + * + * This static function parses a native Cardano script from a JSON string and converts it into a \ref cardano_script_t object. + * + * \param[in] provider A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context for parsing. + * This parameter must not be NULL. + * \param[in] json A pointer to a character array representing the JSON string containing the native script data. + * This string must not be NULL. + * \param[in] size The size of the \p json string in bytes. + * \param[out] script On successful parsing, this will point to a newly created \ref cardano_script_t object representing + * the parsed native script. The caller is responsible for managing the lifecycle of this object + * and must call \ref cardano_script_unref when it is no longer needed. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the native script + * was successfully parsed, or an appropriate error code if an error occurred (e.g., if the JSON format is invalid). + */ +static cardano_error_t +parse_native_script( + cardano_provider_impl_t* provider, + const char* json, + const size_t size, + cardano_script_t** script) +{ + cardano_native_script_t* native_script = NULL; + + cardano_error_t result = cardano_native_script_from_json(json, size, &native_script); + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider, "Failed to parse native script from JSON response"); + return result; + } + + result = cardano_script_new_native(native_script, script); + + cardano_native_script_unref(&native_script); + + return result; +} + +/* IMPLEMENTATION ************************************************************/ + +cardano_error_t +cardano_blockfrost_get_script( + cardano_provider_impl_t* provider_impl, + const char* script_hash, + const size_t script_hash_len, + cardano_script_t** script) +{ + char* get_script_type_url = construct_script_url_with_suffix(provider_impl, script_hash, script_hash_len, NULL); + + cardano_buffer_t* response_buffer = NULL; + uint64_t response_code = 0U; + + cardano_error_t result = cardano_blockfrost_http_get(provider_impl, get_script_type_url, strlen(get_script_type_url), &response_code, &response_buffer); + free(get_script_type_url); + + if ((response_code != 200U) || (result != CARDANO_SUCCESS)) + { + cardano_blockfrost_parse_error(provider_impl, response_buffer); + + cardano_buffer_unref(&response_buffer); + + return CARDANO_ERROR_INVALID_HTTP_REQUEST; + } + + cardano_script_language_t language = CARDANO_SCRIPT_LANGUAGE_NATIVE; + result = parse_script_language(provider_impl, (char*)cardano_buffer_get_data(response_buffer), cardano_buffer_get_size(response_buffer), &language); + + cardano_buffer_unref(&response_buffer); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + if (language == CARDANO_SCRIPT_LANGUAGE_NATIVE) + { + char* get_script_json_url = construct_script_url_with_suffix(provider_impl, script_hash, script_hash_len, "/json"); + result = cardano_blockfrost_http_get(provider_impl, get_script_json_url, strlen(get_script_json_url), &response_code, &response_buffer); + free(get_script_json_url); + + if ((response_code != 200U) || (result != CARDANO_SUCCESS)) + { + cardano_blockfrost_parse_error(provider_impl, response_buffer); + + cardano_buffer_unref(&response_buffer); + + return CARDANO_ERROR_INVALID_HTTP_REQUEST; + } + + result = parse_native_script(provider_impl, (char*)cardano_buffer_get_data(response_buffer), cardano_buffer_get_size(response_buffer), script); + + cardano_buffer_unref(&response_buffer); + } + else + { + char* get_script_cbor_url = construct_script_url_with_suffix(provider_impl, script_hash, script_hash_len, "/cbor"); + result = cardano_blockfrost_http_get(provider_impl, get_script_cbor_url, strlen(get_script_cbor_url), &response_code, &response_buffer); + free(get_script_cbor_url); + + if ((response_code != 200U) || (result != CARDANO_SUCCESS)) + { + cardano_blockfrost_parse_error(provider_impl, response_buffer); + + cardano_buffer_unref(&response_buffer); + + return CARDANO_ERROR_INVALID_HTTP_REQUEST; + } + + result = parse_plutus_script(provider_impl, (char*)cardano_buffer_get_data(response_buffer), cardano_buffer_get_size(response_buffer), language, script); + + cardano_buffer_unref(&response_buffer); + } + + if (result != CARDANO_SUCCESS) + { + cardano_script_unref(script); + } + + return result; +} diff --git a/examples/providers/blockfrost/parsers/blockfrost_tx_eval_parser.c b/examples/providers/blockfrost/parsers/blockfrost_tx_eval_parser.c new file mode 100644 index 00000000..c18a042a --- /dev/null +++ b/examples/providers/blockfrost/parsers/blockfrost_tx_eval_parser.c @@ -0,0 +1,1333 @@ +/** + * \file blockfrost_tx_eval_parser.c + * + * \author angel.castillo + * \date Oct 03, 2024 + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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. + */ + +/* INCLUDES ******************************************************************/ + +#include "blockfrost_parsers.h" +#include "cardano/witness_set/redeemer_tag.h" +#include "utils.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* FORWARD DECLARATIONS ******************************************************/ + +/** + * \brief Serializes a native script clause into a JSON object. + * + * This function takes a native script clause, which can be a part of a multi-sig script or any other native script type, + * and converts it into a JSON format. The serialized clause is then added to the provided JSON object. + * + * \param[in] json_obj A pointer to an initialized JSON object where the serialized clause will be added. This parameter must not be NULL. + * \param[in] script A pointer to a \ref cardano_native_script_t representing the native script clause that is to be serialized. This parameter must not be NULL. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the clause was successfully serialized, + * or an appropriate error code if serialization failed. + */ +static cardano_error_t clause_to_json(struct json_object* json_obj, cardano_native_script_t* script); + +/* STATIC FUNCTIONS **********************************************************/ + +/** + * \brief Serializes a signature clause into a JSON object. + * + * This function converts a signature clause, typically used in a native script to represent a required signature, + * into a JSON format and appends it to the provided JSON object. + * + * \param[in] json_obj A pointer to an initialized JSON object where the serialized signature clause will be added. This parameter must not be NULL. + * \param[in] from A string representing the public key or address that is required to sign the transaction. This parameter must not be NULL. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the signature clause was successfully serialized, + * or an appropriate error code if serialization failed. + */ +static cardano_error_t +clause_signature_to_json(struct json_object* json_obj, const char* from) +{ + json_object_object_add(json_obj, "clause", json_object_new_string("signature")); + json_object_object_add(json_obj, "from", json_object_new_string(from)); + + return CARDANO_SUCCESS; +} + +/** + * \brief Serializes a "before" or "after" clause into a JSON object. + * + * This function converts a clause of type "before" or "after" (which represents time-locking conditions in a native script) + * into a JSON format and appends it to the provided JSON object. + * + * \param[in] json_obj A pointer to an initialized JSON object where the serialized clause will be added. This parameter must not be NULL. + * \param[in] clause A string representing the type of clause, either "before" or "after". This parameter must not be NULL. + * \param[in] slot A uint64_t representing the slot number used in the "before" or "after" clause. This parameter specifies the time-locking condition. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the clause was successfully serialized, + * or an appropriate error code if serialization failed. + */ +static cardano_error_t +clause_before_after_to_json(struct json_object* json_obj, const char* clause, const uint64_t slot) +{ + json_object_object_add(json_obj, "clause", json_object_new_string(clause)); + json_object_object_add(json_obj, "slot", json_object_new_uint64(slot)); + return CARDANO_SUCCESS; +} + +/** + * \brief Serializes a recursive native script clause into a JSON object. + * + * This function converts a recursive clause (such as "all", "any", or "atLeast") from a \ref cardano_native_script_list_t + * into a JSON format and appends it to the provided JSON object. + * + * \param[in] json_obj A pointer to an initialized JSON object where the serialized recursive clause will be added. This parameter must not be NULL. + * \param[in] clause A string representing the type of recursive clause (e.g., "all", "any", "atLeast"). This parameter must not be NULL. + * \param[in] from A pointer to the \ref cardano_native_script_list_t containing the scripts to be serialized. This parameter must not be NULL. + * \param[in] at_least The "atLeast" clause value. This parameter is used only if the clause is of type "atLeast". + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the clause was successfully serialized, + * or an appropriate error code if serialization failed. + */ +static cardano_error_t +clause_recursive_to_json( + struct json_object* json_obj, + const char* clause, + cardano_native_script_list_t* from, + const uint64_t at_least) +{ + json_object_object_add(json_obj, "clause", json_object_new_string(clause)); + + if (at_least > 0U) + { + json_object_object_add(json_obj, "atLeast", json_object_new_uint64(at_least)); + } + + struct json_object* from_array = json_object_new_array(); + const size_t from_len = cardano_native_script_list_get_length(from); + + for (size_t i = 0U; i < from_len; ++i) + { + cardano_native_script_t* native_script = NULL; + + cardano_error_t result = cardano_native_script_list_get(from, i, &native_script); + cardano_native_script_unref(&native_script); + + if (result != CARDANO_SUCCESS) + { + json_object_put(from_array); + return result; + } + + struct json_object* sub_script = json_object_new_object(); + result = clause_to_json(sub_script, native_script); + if (result != CARDANO_SUCCESS) + { + json_object_put(sub_script); + return result; + } + json_object_array_add(from_array, sub_script); + } + + json_object_object_add(json_obj, "from", from_array); + return CARDANO_SUCCESS; +} + +/** + * \brief Serializes a native script clause into a JSON object. + * + * This function converts a specific clause of a \ref cardano_native_script_t object into a JSON format + * and appends it to the provided JSON object. + * + * \param[in] json_obj A pointer to an initialized JSON object where the serialized clause will be added. This parameter must not be NULL. + * \param[in] script A pointer to the \ref cardano_native_script_t object containing the clause to serialize. This parameter must not be NULL. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the clause was successfully serialized, + * or an appropriate error code if serialization failed. + */ +static cardano_error_t +clause_to_json(struct json_object* json_obj, cardano_native_script_t* script) +{ + cardano_native_script_type_t type = CARDANO_NATIVE_SCRIPT_TYPE_REQUIRE_PUBKEY; + cardano_error_t result = cardano_native_script_get_type(script, &type); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + switch (type) + { + case CARDANO_NATIVE_SCRIPT_TYPE_REQUIRE_PUBKEY: + { + cardano_script_pubkey_t* pubkey_script = NULL; + result = cardano_native_script_to_pubkey(script, &pubkey_script); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + cardano_script_pubkey_unref(&pubkey_script); + + cardano_blake2b_hash_t* hash = NULL; + + result = cardano_script_pubkey_get_key_hash(pubkey_script, &hash); + cardano_blake2b_hash_unref(&hash); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + const size_t hash_size = cardano_blake2b_hash_get_hex_size(hash); + char* hash_str = malloc(hash_size); + + if (hash_str == NULL) + { + cardano_blake2b_hash_unref(&hash); + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; + } + + result = cardano_blake2b_hash_to_hex(hash, hash_str, hash_size); + + if (result != CARDANO_SUCCESS) + { + free(hash_str); + cardano_blake2b_hash_unref(&hash); + return result; + } + + result = clause_signature_to_json(json_obj, hash_str); + + free(hash_str); + + return result; + } + case CARDANO_NATIVE_SCRIPT_TYPE_INVALID_BEFORE: + { + cardano_script_invalid_before_t* invalid_before_script = NULL; + + result = cardano_native_script_to_invalid_before(script, &invalid_before_script); + cardano_script_invalid_before_unref(&invalid_before_script); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + uint64_t slot = 0; + result = cardano_script_invalid_before_get_slot(invalid_before_script, &slot); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + result = clause_before_after_to_json(json_obj, "before", slot); + + return result; + } + case CARDANO_NATIVE_SCRIPT_TYPE_INVALID_AFTER: + { + cardano_script_invalid_after_t* invalid_after_script = NULL; + + result = cardano_native_script_to_invalid_after(script, &invalid_after_script); + cardano_script_invalid_after_unref(&invalid_after_script); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + uint64_t slot = 0; + result = cardano_script_invalid_after_get_slot(invalid_after_script, &slot); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + result = clause_before_after_to_json(json_obj, "after", slot); + + return result; + } + case CARDANO_NATIVE_SCRIPT_TYPE_REQUIRE_ANY_OF: + { + cardano_script_any_t* any_script = NULL; + + result = cardano_native_script_to_any(script, &any_script); + cardano_script_any_unref(&any_script); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + cardano_native_script_list_t* from = NULL; + + result = cardano_script_any_get_scripts(any_script, &from); + cardano_native_script_list_unref(&from); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + return clause_recursive_to_json(json_obj, "any", from, 0); + } + case CARDANO_NATIVE_SCRIPT_TYPE_REQUIRE_ALL_OF: + { + cardano_script_all_t* all_script = NULL; + + result = cardano_native_script_to_all(script, &all_script); + cardano_script_all_unref(&all_script); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + cardano_native_script_list_t* from = NULL; + + result = cardano_script_all_get_scripts(all_script, &from); + cardano_native_script_list_unref(&from); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + return clause_recursive_to_json(json_obj, "all", from, 0); + } + case CARDANO_NATIVE_SCRIPT_TYPE_REQUIRE_N_OF_K: + { + cardano_script_n_of_k_t* n_of_k_script = NULL; + + result = cardano_native_script_to_n_of_k(script, &n_of_k_script); + cardano_script_n_of_k_unref(&n_of_k_script); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + cardano_native_script_list_t* from = NULL; + + result = cardano_script_n_of_k_get_scripts(n_of_k_script, &from); + cardano_native_script_list_unref(&from); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + uint64_t at_least = cardano_script_n_of_k_get_required(n_of_k_script); + + return clause_recursive_to_json(json_obj, "n_of_k", from, at_least); + } + default: + return CARDANO_ERROR_INVALID_ARGUMENT; + } +} + +/** + * \brief Serializes a native script into a JSON object. + * + * This function serializes the given \ref cardano_native_script_t object, which represents a native script, + * into a JSON representation and appends it to the provided JSON object. + * + * \param[in] json_obj A pointer to an initialized JSON object where the serialized native script will be appended. This parameter must not be NULL. + * \param[in] script A pointer to the \ref cardano_native_script_t object representing the native script. This parameter must not be NULL. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the script was successfully serialized, + * or an appropriate error code if serialization failed. + */ +static cardano_error_t +script_native_to_json(struct json_object* json_obj, cardano_native_script_t* script) +{ + struct json_object* json_clause_obj = json_object_new_object(); + cardano_error_t result = clause_to_json(json_clause_obj, script); + + if (result != CARDANO_SUCCESS) + { + json_object_put(json_clause_obj); + return result; + } + + struct json_object* script_object = json_object_new_object(); + json_object_object_add(script_object, "language", json_object_new_string("native")); + json_object_object_add(script_object, "json", json_clause_obj); + json_object_object_add(json_obj, "script", script_object); + + return CARDANO_SUCCESS; +} + +/** + * \brief Serializes a transaction input into a JSON object. + * + * This function serializes the given \ref cardano_transaction_input_t object, which represents the input of a transaction, + * into a JSON representation and appends it to the provided JSON object. + * + * \param[in] input A pointer to the \ref cardano_transaction_input_t object representing the transaction input. This parameter must not be NULL. + * \param[out] output_obj A pointer to an initialized JSON object where the serialized transaction input will be appended. This parameter must not be NULL. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the input was successfully serialized, + * or an appropriate error code if serialization failed. + */ +static cardano_error_t +transaction_input_to_json( + cardano_transaction_input_t* input, + struct json_object* output_obj) +{ + const uint64_t index = cardano_transaction_input_get_index(input); + cardano_blake2b_hash_t* hash = cardano_transaction_input_get_id(input); + + const size_t hash_size = cardano_blake2b_hash_get_hex_size(hash); + char* hash_str = malloc(hash_size); + + if (hash_str == NULL) + { + cardano_blake2b_hash_unref(&hash); + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; + } + + cardano_error_t result = cardano_blake2b_hash_to_hex(hash, hash_str, hash_size); + cardano_blake2b_hash_unref(&hash); + + if (result != CARDANO_SUCCESS) + { + free(hash_str); + return result; + } + + struct json_object* transaction_obj = json_object_new_object(); + + if (!transaction_obj) + { + free(hash_str); + + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; + } + + json_object_object_add(transaction_obj, "id", json_object_new_string(hash_str)); + json_object_object_add(output_obj, "index", json_object_new_uint64(index)); + json_object_object_add(output_obj, "transaction", transaction_obj); + + free(hash_str); + + return CARDANO_SUCCESS; +} + +/** + * \brief Serializes a transaction value into a JSON object. + * + * This function serializes the given \ref cardano_value_t object, which represents the value (amount of ADA and multi-assets) in a transaction, + * into a JSON representation and appends it to the provided JSON object. + * + * \param[in] value A pointer to the \ref cardano_value_t object representing the transaction value. This parameter must not be NULL. + * \param[out] final_obj A pointer to an initialized JSON object where the serialized transaction value will be appended. This parameter must not be NULL. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the value was successfully serialized, + * or an appropriate error code if serialization failed. + */ +static cardano_error_t +transaction_value_to_json( + cardano_value_t* value, + struct json_object* final_obj) +{ + const uint64_t lovelace = cardano_value_get_coin(value); + cardano_multi_asset_t* multi_asset = cardano_value_get_multi_asset(value); + + cardano_multi_asset_unref(&multi_asset); + + struct json_object* ada_value_obj = json_object_new_object(); + + if (!ada_value_obj) + { + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; + } + + json_object_object_add(ada_value_obj, "lovelace", json_object_new_uint64(lovelace)); + + struct json_object* tmp_obj = json_object_new_object(); + + if (!tmp_obj) + { + json_object_put(ada_value_obj); + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; + } + + json_object_object_add(tmp_obj, "ada", ada_value_obj); + + cardano_policy_id_list_t* policy_id_list = NULL; + cardano_error_t result = cardano_multi_asset_get_keys(multi_asset, &policy_id_list); + + if (result != CARDANO_SUCCESS) + { + json_object_put(tmp_obj); + return result; + } + + const size_t policy_id_list_size = cardano_policy_id_list_get_length(policy_id_list); + + for (size_t i = 0; i < policy_id_list_size; i++) + { + cardano_blake2b_hash_t* policy_id = NULL; + result = cardano_policy_id_list_get(policy_id_list, i, &policy_id); + + if (result != CARDANO_SUCCESS) + { + cardano_policy_id_list_unref(&policy_id_list); + json_object_put(tmp_obj); + + return result; + } + + const size_t policy_id_size = cardano_blake2b_hash_get_hex_size(policy_id); + char* policy_id_str = malloc(policy_id_size); + + if (policy_id_str == NULL) + { + cardano_blake2b_hash_unref(&policy_id); + cardano_policy_id_list_unref(&policy_id_list); + json_object_put(tmp_obj); + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; + } + + result = cardano_blake2b_hash_to_hex(policy_id, policy_id_str, policy_id_size); + cardano_blake2b_hash_unref(&policy_id); + + if (result != CARDANO_SUCCESS) + { + free(policy_id_str); + cardano_policy_id_list_unref(&policy_id_list); + json_object_put(tmp_obj); + return result; + } + + struct json_object* policy_obj = json_object_new_object(); + + if (!policy_obj) + { + free(policy_id_str); + cardano_policy_id_list_unref(&policy_id_list); + json_object_put(tmp_obj); + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; + } + + cardano_asset_name_map_t* assets = NULL; + result = cardano_multi_asset_get_assets(multi_asset, policy_id, &assets); + + if (result != CARDANO_SUCCESS) + { + free(policy_id_str); + json_object_put(policy_obj); + cardano_policy_id_list_unref(&policy_id_list); + json_object_put(tmp_obj); + + return result; + } + + cardano_asset_name_list_t* asset_names = NULL; + result = cardano_asset_name_map_get_keys(assets, &asset_names); + + if (result != CARDANO_SUCCESS) + { + cardano_asset_name_map_unref(&assets); + free(policy_id_str); + json_object_put(policy_obj); + cardano_policy_id_list_unref(&policy_id_list); + json_object_put(tmp_obj); + return result; + } + + const size_t asset_names_size = cardano_asset_name_list_get_length(asset_names); + + for (size_t j = 0; j < asset_names_size; j++) + { + cardano_asset_name_t* asset_name = NULL; + result = cardano_asset_name_list_get(asset_names, j, &asset_name); + + if (result != CARDANO_SUCCESS) + { + cardano_asset_name_list_unref(&asset_names); + cardano_asset_name_map_unref(&assets); + free(policy_id_str); + json_object_put(policy_obj); + cardano_policy_id_list_unref(&policy_id_list); + json_object_put(tmp_obj); + return result; + } + + int64_t asset_quantity = 0; + result = cardano_asset_name_map_get(assets, asset_name, &asset_quantity); + + if (result != CARDANO_SUCCESS) + { + cardano_asset_name_unref(&asset_name); + cardano_asset_name_list_unref(&asset_names); + cardano_asset_name_map_unref(&assets); + free(policy_id_str); + json_object_put(policy_obj); + cardano_policy_id_list_unref(&policy_id_list); + json_object_put(tmp_obj); + + return result; + } + + const char* asset_name_str = cardano_asset_name_get_hex(asset_name); + json_object_object_add(policy_obj, asset_name_str, json_object_new_int64(asset_quantity)); + + cardano_asset_name_unref(&asset_name); + } + + json_object_object_add(tmp_obj, policy_id_str, policy_obj); + + cardano_asset_name_list_unref(&asset_names); + cardano_asset_name_map_unref(&assets); + free(policy_id_str); + } + + json_object_object_add(final_obj, "value", tmp_obj); + + cardano_policy_id_list_unref(&policy_id_list); + + return CARDANO_SUCCESS; +} + +/** + * \brief Serializes a transaction output's address into a JSON object. + * + * This function serializes the given \ref cardano_address_t object into a JSON representation and appends it to the provided JSON object. + * The address is a fundamental part of a transaction output, representing the recipient of the funds. + * + * \param[in] address A pointer to the \ref cardano_address_t object representing the output address. This parameter must not be NULL. + * \param[out] json_output A pointer to an initialized JSON object where the serialized address will be appended. This parameter must not be NULL. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the address was successfully serialized, + * or an appropriate error code if serialization failed. + */ +static cardano_error_t +transaction_output_address_to_json( + cardano_address_t* address, + struct json_object* json_output) +{ + const char* bech32 = cardano_address_get_string(address); + + if (bech32 == NULL) + { + return CARDANO_ERROR_POINTER_IS_NULL; + } + + json_object_object_add(json_output, "address", json_object_new_string(bech32)); + + return CARDANO_SUCCESS; +} + +/** + * \brief Serializes a transaction output's datum into a JSON object. + * + * This function serializes the given \ref cardano_datum_t object into a JSON representation and appends it to the provided JSON object. + * The datum is a critical component of the output in a Plutus transaction, where it can be either inline or referenced by a hash. + * + * \param[in] datum A pointer to the \ref cardano_datum_t object that holds the datum to be serialized. This parameter must not be NULL. + * \param[out] json_output A pointer to an initialized JSON object where the serialized datum will be appended. This parameter must not be NULL. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the datum was successfully serialized, + * or an appropriate error code if serialization failed. + */ +static cardano_error_t +transaction_output_datum_to_json( + cardano_datum_t* datum, + struct json_object* json_output) +{ + cardano_datum_type_t type = CARDANO_DATUM_TYPE_DATA_HASH; + cardano_error_t result = cardano_datum_get_type(datum, &type); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + switch (type) + { + case CARDANO_DATUM_TYPE_DATA_HASH: + { + const char* hash_str = cardano_datum_get_data_hash_hex(datum); + + json_object_object_add(json_output, "datumHash", json_object_new_string(hash_str)); + + break; + } + case CARDANO_DATUM_TYPE_INLINE_DATA: + { + cardano_plutus_data_t* data = cardano_datum_get_inline_data(datum); + + if (data == NULL) + { + return CARDANO_ERROR_POINTER_IS_NULL; + } + + cardano_cbor_writer_t* writer = cardano_cbor_writer_new(); + + if (writer == NULL) + { + cardano_plutus_data_unref(&data); + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; + } + + result = cardano_plutus_data_to_cbor(data, writer); + + if (result != CARDANO_SUCCESS) + { + cardano_cbor_writer_unref(&writer); + cardano_plutus_data_unref(&data); + + return result; + } + + const size_t cbor_size = cardano_cbor_writer_get_hex_size(writer); + char* cbor_str = malloc(cbor_size); + + if (cbor_str == NULL) + { + cardano_cbor_writer_unref(&writer); + cardano_plutus_data_unref(&data); + + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; + } + + result = cardano_cbor_writer_encode_hex(writer, cbor_str, cbor_size); + + if (result != CARDANO_SUCCESS) + { + free(cbor_str); + cardano_cbor_writer_unref(&writer); + cardano_plutus_data_unref(&data); + + return result; + } + + json_object_object_add(json_output, "datum", json_object_new_string(cbor_str)); + + free(cbor_str); + cardano_cbor_writer_unref(&writer); + cardano_plutus_data_unref(&data); + } + } + + return CARDANO_SUCCESS; +} + +/** + * \brief Returns the string representation of a Plutus script language. + * + * This function returns a constant string that represents the given Plutus script language, based on the \ref cardano_script_language_t enum value. + * It helps in translating the language enum into a human-readable string for use in JSON serialization or logging. + * + * \param[in] language The \ref cardano_script_language_t representing the Plutus script language. It can be one of the supported languages, such as PlutusV1 or PlutusV2. + * + * \return A constant character pointer to the string representation of the language. If the language is unknown, it returns "Unknown". + */ +static const char* +get_plutus_script_string(cardano_script_language_t language) +{ + switch (language) + { + case CARDANO_SCRIPT_LANGUAGE_PLUTUS_V1: + return "plutus:v1"; + case CARDANO_SCRIPT_LANGUAGE_PLUTUS_V2: + return "plutus:v2"; + case CARDANO_SCRIPT_LANGUAGE_PLUTUS_V3: + return "plutus:v3"; + default: + return "native"; + } +} + +/** + * \brief Serializes a Plutus script within a transaction output into a JSON object. + * + * This function serializes the given Plutus script, represented by \ref cardano_script_t, into a JSON object, adding the result to the provided \c json_output object. + * The script is associated with the specified \c language. + * + * \param[in] language The \ref cardano_script_language_t representing the language of the Plutus script. + * It determines the type of Plutus script (e.g., PlutusV1 or PlutusV2). + * \param[in] script A pointer to the \ref cardano_script_t representing the Plutus script to be serialized. This must not be NULL. + * \param[out] json_output A pointer to an initialized \ref json_object where the serialized script data will be added. This must not be NULL. + * + * \return A \ref cardano_error_t value indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the script was successfully serialized, + * or an appropriate error code if an error occurred. + */ +static cardano_error_t +transaction_output_plutus_script_to_json( + cardano_script_language_t language, + cardano_script_t* script, + struct json_object* json_output) +{ + cardano_cbor_writer_t* writer = cardano_cbor_writer_new(); + + if (writer == NULL) + { + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; + } + + cardano_error_t result = cardano_script_to_cbor(script, writer); + + if (result != CARDANO_SUCCESS) + { + cardano_cbor_writer_unref(&writer); + return result; + } + + const size_t cbor_size = cardano_cbor_writer_get_hex_size(writer); + char* cbor_str = malloc(cbor_size); + + if (cbor_str == NULL) + { + cardano_cbor_writer_unref(&writer); + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; + } + + result = cardano_cbor_writer_encode_hex(writer, cbor_str, cbor_size); + + if (result != CARDANO_SUCCESS) + { + free(cbor_str); + cardano_cbor_writer_unref(&writer); + return result; + } + + struct json_object* script_object = json_object_new_object(); + json_object_object_add(script_object, "language", json_object_new_string(get_plutus_script_string(language))); + json_object_object_add(script_object, "cbor", json_object_new_string(cbor_str)); + json_object_object_add(json_output, "script", script_object); + + free(cbor_str); + cardano_cbor_writer_unref(&writer); + + return CARDANO_SUCCESS; +} + +/** + * \brief Serializes a transaction output script into a JSON object. + * + * This function serializes the given \ref cardano_script_t into a JSON object representation, adding the result to the provided \c json_output object. + * + * \param[in] script A pointer to the \ref cardano_script_t representing the script to be serialized. This must not be NULL. + * \param[out] json_output A pointer to an initialized \ref json_object where the serialized script data will be added. This must not be NULL. + * + * \return A \ref cardano_error_t value indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the script was successfully serialized, + * or an appropriate error code if an error occurred. + */ +static cardano_error_t +transaction_output_script_to_json( + cardano_script_t* script, + struct json_object* json_output) +{ + cardano_script_language_t language = CARDANO_SCRIPT_LANGUAGE_NATIVE; + cardano_error_t result = cardano_script_get_language(script, &language); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + switch (language) + { + case CARDANO_SCRIPT_LANGUAGE_NATIVE: + { + cardano_native_script_t* native_script = NULL; + + result = cardano_script_to_native(script, &native_script); + cardano_native_script_unref(&native_script); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + return script_native_to_json(json_output, native_script); + } + case CARDANO_SCRIPT_LANGUAGE_PLUTUS_V1: + case CARDANO_SCRIPT_LANGUAGE_PLUTUS_V2: + case CARDANO_SCRIPT_LANGUAGE_PLUTUS_V3: + { + return transaction_output_plutus_script_to_json(language, script, json_output); + } + default: + { + return CARDANO_ERROR_INVALID_SCRIPT_LANGUAGE; + } + } +} + +/** + * \brief Serializes a transaction output into a JSON object. + * + * This function serializes the given \ref cardano_transaction_output_t into a JSON object representation. + * The serialized transaction output will be added to the provided \c main_obj. + * + * \param[in] output A pointer to the \ref cardano_transaction_output_t representing the transaction output to be serialized. This must not be NULL. + * \param[out] main_obj A pointer to an initialized \ref json_object where the serialized transaction output data will be added. This must not be NULL. + * + * \return A \ref cardano_error_t value indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the transaction output was successfully serialized, + * or an appropriate error code if an error occurred. + */ +static cardano_error_t +transaction_output_to_json( + cardano_transaction_output_t* output, + struct json_object* main_obj) +{ + cardano_address_t* address = cardano_transaction_output_get_address(output); + cardano_address_unref(&address); + + if (address == NULL) + { + return CARDANO_ERROR_POINTER_IS_NULL; + } + + cardano_error_t result = transaction_output_address_to_json(address, main_obj); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + cardano_value_t* value = cardano_transaction_output_get_value(output); + cardano_value_unref(&value); + + if (value == NULL) + { + return CARDANO_ERROR_POINTER_IS_NULL; + } + + result = transaction_value_to_json(value, main_obj); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + cardano_datum_t* datum = cardano_transaction_output_get_datum(output); + cardano_datum_unref(&datum); + + if (datum != NULL) + { + result = transaction_output_datum_to_json(datum, main_obj); + + if (result != CARDANO_SUCCESS) + { + return result; + } + } + + cardano_script_t* script = cardano_transaction_output_get_script_ref(output); + cardano_script_unref(&script); + + if (script != NULL) + { + result = transaction_output_script_to_json(script, main_obj); + + if (result != CARDANO_SUCCESS) + { + return result; + } + } + + return CARDANO_SUCCESS; +} + +/** + * \brief Serializes a list of UTXOs into a JSON object. + * + * This function serializes the given \ref cardano_utxo_list_t into a JSON object representation. + * The serialized UTXOs will be added to the provided \c json_main_obj. + * + * \param[in] utxos A pointer to the \ref cardano_utxo_list_t representing the UTXOs to be serialized. This must not be NULL. + * \param[out] json_main_obj A pointer to an initialized \ref json_object where the serialized UTXO data will be added. This must not be NULL. + * + * \return A \ref cardano_error_t value indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the UTXOs were successfully serialized, + * or an appropriate error code if an error occurred. + */ +static cardano_error_t +additional_utxos_to_json( + cardano_utxo_list_t* utxos, + struct json_object* json_main_obj) +{ + const size_t utxos_len = cardano_utxo_list_get_length(utxos); + struct json_object* main_array_obj = json_object_new_array(); + + if (utxos == NULL) + { + json_object_object_add(json_main_obj, "additionalUtxo", main_array_obj); + + return CARDANO_SUCCESS; + } + + for (size_t i = 0U; i < utxos_len; ++i) + { + struct json_object* utxo_val_obj = json_object_new_object(); + + if (utxo_val_obj == NULL) + { + json_object_put(main_array_obj); + + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; + } + + cardano_utxo_t* utxo = NULL; + + cardano_error_t result = cardano_utxo_list_get(utxos, i, &utxo); + cardano_utxo_unref(&utxo); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + cardano_transaction_input_t* input = cardano_utxo_get_input(utxo); + cardano_transaction_input_unref(&input); + + if (input == NULL) + { + return CARDANO_ERROR_POINTER_IS_NULL; + } + + result = transaction_input_to_json(input, utxo_val_obj); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + cardano_transaction_output_t* output = cardano_utxo_get_output(utxo); + cardano_transaction_output_unref(&output); + + if (output == NULL) + { + return CARDANO_ERROR_POINTER_IS_NULL; + } + + result = transaction_output_to_json(output, utxo_val_obj); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + json_object_array_add(main_array_obj, utxo_val_obj); + } + + json_object_object_add(json_main_obj, "additionalUtxo", main_array_obj); + + return CARDANO_SUCCESS; +} + +/** + * \brief Serializes a Cardano transaction into a JSON object. + * + * This function serializes the given \ref cardano_transaction_t into a JSON object representation. + * The output \c out_obj must be an initialized \ref json_object where the serialized transaction will be stored. + * + * \param[in] transaction A pointer to the \ref cardano_transaction_t to be serialized. This must not be NULL. + * \param[out] out_obj A pointer to an initialized \ref json_object where the serialized transaction data will be written. This must not be NULL. + * + * \return A \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the transaction was successfully serialized, + * or an appropriate error code if an error occurred. + */ +static cardano_error_t +cardano_transaction_to_json( + cardano_transaction_t* transaction, + struct json_object* out_obj) +{ + cardano_cbor_writer_t* writer = cardano_cbor_writer_new(); + + if (writer == NULL) + { + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; + } + + cardano_error_t result = cardano_transaction_to_cbor(transaction, writer); + + if (result != CARDANO_SUCCESS) + { + cardano_cbor_writer_unref(&writer); + return result; + } + + const size_t cbor_size = cardano_cbor_writer_get_hex_size(writer); + char* cbor_str = malloc(cbor_size); + + if (cbor_str == NULL) + { + cardano_cbor_writer_unref(&writer); + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; + } + + result = cardano_cbor_writer_encode_hex(writer, cbor_str, cbor_size); + + if (result != CARDANO_SUCCESS) + { + free(cbor_str); + cardano_cbor_writer_unref(&writer); + return result; + } + + json_object_object_add(out_obj, "cbor", json_object_new_string(cbor_str)); + + free(cbor_str); + cardano_cbor_writer_unref(&writer); + + return CARDANO_SUCCESS; +} + +/** + * \brief Converts a redeemer tag string to its corresponding enum value. + * + * This function takes a string representing a redeemer tag (e.g., "spend", "mint", etc.) and converts it into the corresponding + * \ref cardano_redeemer_tag_t enum value. + * + * \param[in] tag_str A pointer to the string representing the redeemer tag. This must not be NULL. + * \param[out] tag_enum A pointer to a \ref cardano_redeemer_tag_t enum where the result will be stored if the conversion is successful. This must not be NULL. + * + * \return true if the conversion is successful, otherwise false. + */ +static bool +redeemer_tag_string_to_enum(const char* tag_str, cardano_redeemer_tag_t* tag_enum) +{ + if (strcmp(tag_str, "spend") == 0) + { + *tag_enum = CARDANO_REDEEMER_TAG_SPEND; + } + else if (strcmp(tag_str, "mint") == 0) + { + *tag_enum = CARDANO_REDEEMER_TAG_MINT; + } + else if (strcmp(tag_str, "publish") == 0) + { + *tag_enum = CARDANO_REDEEMER_TAG_CERTIFYING; + } + else if (strcmp(tag_str, "withdraw") == 0) + { + *tag_enum = CARDANO_REDEEMER_TAG_REWARD; + } + else if (strcmp(tag_str, "vote") == 0) + { + *tag_enum = CARDANO_REDEEMER_TAG_VOTING; + } + else if (strcmp(tag_str, "propose") == 0) + { + *tag_enum = CARDANO_REDEEMER_TAG_PROPOSING; + } + else + { + return false; + } + + return true; +} + +/* PUBLIC FUNCTIONS **********************************************************/ + +cardano_error_t +cardano_evaluate_params_to_json( + cardano_transaction_t* transaction, + cardano_utxo_list_t* utxos, + char** json_main_obj_str, + size_t* json_main_obj_size) +{ + struct json_object* obj = json_object_new_object(); + + cardano_error_t result = cardano_transaction_to_json(transaction, obj); + + if (result != CARDANO_SUCCESS) + { + json_object_put(obj); + + return result; + } + + result = additional_utxos_to_json(utxos, obj); + + if (result != CARDANO_SUCCESS) + { + json_object_put(obj); + + return result; + } + + const char* json_string = json_object_to_json_string_length(obj, JSON_C_TO_STRING_PLAIN, json_main_obj_size); + + if (json_string == NULL) + { + json_object_put(obj); + + return CARDANO_ERROR_INVALID_JSON; + } + + *json_main_obj_str = malloc(*json_main_obj_size + 1); + + CARDANO_UNUSED(memset(*json_main_obj_str, 0, (*json_main_obj_size) + 1)); + + if (*json_main_obj_str == NULL) + { + *json_main_obj_size = 0; + json_object_put(obj); + + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; + } + + cardano_utils_safe_memcpy(*json_main_obj_str, *json_main_obj_size, json_string, *json_main_obj_size); + + json_object_put(obj); + + return CARDANO_SUCCESS; +} + +cardano_error_t +cardano_blockfrost_parse_tx_eval_response( + cardano_provider_impl_t* provider, + const char* json, + const size_t size, + cardano_redeemer_list_t* original_redeemers, + cardano_redeemer_list_t** redeemers) +{ + cardano_error_t result = cardano_redeemer_list_clone(original_redeemers, redeemers); + + if (result != CARDANO_SUCCESS) + { + return result; + } + + struct json_tokener* tok = json_tokener_new(); + struct json_object* parsed_json = json_tokener_parse_ex(tok, json, (int32_t)size); + + if (parsed_json == NULL) + { + cardano_redeemer_list_unref(redeemers); + cardano_utils_set_error_message(provider, "Failed to parse JSON response"); + json_tokener_free(tok); + + return CARDANO_ERROR_INVALID_JSON; + } + + struct json_object* result_obj; + struct json_object* evaluation_result_obj; + + if (!json_object_object_get_ex(parsed_json, "result", &result_obj)) + { + cardano_utils_set_error_message(provider, "Failed to parse JSON response"); + json_object_put(parsed_json); + json_tokener_free(tok); + cardano_redeemer_list_unref(redeemers); + + return CARDANO_ERROR_INVALID_JSON; + } + + if (!json_object_object_get_ex(result_obj, "EvaluationResult", &evaluation_result_obj)) + { + cardano_utils_set_error_message(provider, "Failed to parse JSON response"); + json_object_put(parsed_json); + json_tokener_free(tok); + cardano_redeemer_list_unref(redeemers); + + return CARDANO_ERROR_INVALID_JSON; + } + + json_object_object_foreach(evaluation_result_obj, key, val) + { + const char* colon_ptr = strchr(key, ':'); + + if (colon_ptr == NULL) + { + continue; + } + + const size_t tag_length = colon_ptr - key; + char tag_str[128] = { 0 }; + + if (tag_length >= sizeof(tag_str)) + { + continue; + } + + cardano_utils_safe_memcpy(tag_str, 128, key, tag_length); + tag_str[tag_length] = '\0'; + + const char* index_str = colon_ptr + 1; + + if (*index_str == '\0') + { + continue; + } + + char* endptr = NULL; + const long index = strtol(index_str, &endptr, 10); + + if ((endptr == NULL) || (*endptr != '\0')) + { + continue; + } + + cardano_redeemer_tag_t tag_enum; + + if (!redeemer_tag_string_to_enum(tag_str, &tag_enum)) + { + continue; + } + + struct json_object* memory_obj = NULL; + struct json_object* steps_obj = NULL; + + if (json_object_object_get_ex(val, "memory", &memory_obj) && json_object_object_get_ex(val, "steps", &steps_obj)) + { + uint64_t memory = json_object_get_uint64(memory_obj); + uint64_t steps = json_object_get_uint64(steps_obj); + + result = cardano_redeemer_list_set_ex_units(*redeemers, tag_enum, index, memory, steps); + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider, "Failed to parse JSON response"); + json_object_put(parsed_json); + json_tokener_free(tok); + cardano_redeemer_list_unref(redeemers); + + return result; + } + } + else + { + continue; + } + } + + json_object_put(parsed_json); + json_tokener_free(tok); + + return CARDANO_SUCCESS; +} \ No newline at end of file diff --git a/examples/providers/blockfrost/parsers/blockfrost_utxos_parser.c b/examples/providers/blockfrost/parsers/blockfrost_utxos_parser.c new file mode 100644 index 00000000..23e180e7 --- /dev/null +++ b/examples/providers/blockfrost/parsers/blockfrost_utxos_parser.c @@ -0,0 +1,744 @@ +/** + * \file blockfrost_utxos_parser.c + * + * \author angel.castillo + * \date Sep 28, 2024 + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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. + */ + +/* INCLUDES ******************************************************************/ + +#include "blockfrost_parsers.h" +#include "utils.h" + +#include +#include +#include + +/* STATIC FUNCTIONS **********************************************************/ + +/** + * \brief Parses an address from a JSON object and returns a \ref cardano_address_t object. + * + * This static function parses an address from the provided JSON object and converts it into a \ref cardano_address_t object. + * + * \param[in] provider A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context for parsing. + * This parameter must not be NULL. + * \param[in] address_obj A pointer to a \ref json_object representing the address in JSON format. + * The object must contain the necessary fields to parse the address. + * \param[out] address On successful parsing, this will point to a newly created \ref cardano_address_t object + * representing the parsed address. The caller is responsible for managing the lifecycle of this object + * and must call \ref cardano_address_unref when it is no longer needed. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the address + * was successfully parsed, or an appropriate error code if an error occurred (e.g., if the JSON object format is invalid). + */ +static cardano_error_t +parse_address( + cardano_provider_impl_t* provider, + struct json_object* address_obj, + cardano_address_t** address) +{ + const char* address_data = json_object_get_string(address_obj); + const size_t address_len = json_object_get_string_len(address_obj); + + cardano_error_t result = cardano_address_from_string(address_data, address_len, address); + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider, "Failed to parse address from JSON response"); + } + + return result; +} + +/** + * \brief Parses a transaction hash from a JSON object and returns a \ref cardano_blake2b_hash_t object. + * + * This static function parses a transaction hash (TxHash) from the given JSON object and converts it into a \ref cardano_blake2b_hash_t object. + * + * \param[in] provider A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context for parsing. + * This parameter must not be NULL. + * \param[in] tx_hash_obj A pointer to a \ref json_object representing the transaction hash in JSON format. + * The object must contain the transaction hash as expected by the parser. + * \param[out] tx_id On successful parsing, this will point to a newly created \ref cardano_blake2b_hash_t object + * representing the parsed transaction ID. The caller is responsible for managing the lifecycle of this object + * and must call \ref cardano_blake2b_hash_unref when it is no longer needed. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the transaction hash + * was successfully parsed, or an appropriate error code if an error occurred (e.g., if the JSON object format is invalid). + */ +static cardano_error_t +parse_tx_hash( + cardano_provider_impl_t* provider, + struct json_object* tx_hash_obj, + cardano_blake2b_hash_t** tx_id) +{ + const char* tx_hash = json_object_get_string(tx_hash_obj); + const size_t tx_hash_len = json_object_get_string_len(tx_hash_obj); + + cardano_error_t result = cardano_blake2b_hash_from_hex(tx_hash, tx_hash_len, tx_id); + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider, "Failed to parse tx_hash from JSON response"); + } + + return result; +} + +/** + * \brief Parses an amount from a JSON array and returns a \ref cardano_value_t object. + * + * This static function parses a Cardano amount, which is encoded in a JSON array, and converts it into a \ref cardano_value_t object. + * + * \param[in] provider A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context for parsing. + * This parameter must not be NULL. + * \param[in] amount_array A pointer to a \ref json_object representing the amount in a JSON array format. The array contains + * the amount in lovelaces (ADA) and, optionally, other assets. + * \param[out] value On successful parsing, this will point to a newly created \ref cardano_value_t object representing + * the parsed amount, including ADA and multi-assets. The caller is responsible for managing the lifecycle + * of this object and must call \ref cardano_value_unref when it is no longer needed. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the amount was + * successfully parsed, or an appropriate error code if an error occurred (e.g., if the JSON array format is invalid). + */ +static cardano_error_t +parse_amount(cardano_provider_impl_t* provider, struct json_object* amount_array, cardano_value_t** value) +{ + size_t amount_len = json_object_array_length(amount_array); + cardano_asset_id_map_t* asset_id_map = NULL; + + cardano_error_t result = cardano_asset_id_map_new(&asset_id_map); + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider, "Failed to allocate memory for asset_id_map"); + + return result; + } + + for (size_t j = 0U; j < amount_len; ++j) + { + struct json_object* amount_obj = json_object_array_get_idx(amount_array, j); + struct json_object* unit_obj; + struct json_object* quantity_obj; + + uint64_t quantity = 0; + cardano_asset_id_t* asset_id = NULL; + + if (json_object_object_get_ex(amount_obj, "unit", &unit_obj)) + { + const char* unit = json_object_get_string(unit_obj); + const size_t unit_len = json_object_get_string_len(unit_obj); + + if (strcmp(unit, "lovelace") == 0) + { + result = cardano_asset_id_new_lovelace(&asset_id); + } + else + { + result = cardano_asset_id_from_hex(unit, unit_len, &asset_id); + } + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider, "Failed to parse asset_id from JSON response"); + cardano_asset_id_map_unref(&asset_id_map); + + return result; + } + } + + if (json_object_object_get_ex(amount_obj, "quantity", &quantity_obj)) + { + const char* quantity_str = json_object_get_string(quantity_obj); + const size_t quantity_len = json_object_get_string_len(quantity_obj); + + cardano_bigint_t* bigint = NULL; + result = cardano_bigint_from_string(quantity_str, quantity_len, 10, &bigint); + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider, "Failed to parse quantity from JSON response"); + cardano_asset_id_map_unref(&asset_id_map); + + return result; + } + + quantity = cardano_bigint_to_unsigned_int(bigint); + cardano_bigint_unref(&bigint); + } + + result = cardano_asset_id_map_insert(asset_id_map, asset_id, (int64_t)quantity); + cardano_asset_id_unref(&asset_id); + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider, "Failed to insert asset_id into asset_id_map"); + cardano_asset_id_map_unref(&asset_id_map); + + return result; + } + } + + result = cardano_value_from_asset_map(asset_id_map, value); + cardano_asset_id_map_unref(&asset_id_map); + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider, "Failed to create value from asset_id_map"); + } + + return result; +} + +/** + * \brief Parses a data hash from a JSON object and returns a \ref cardano_blake2b_hash_t object. + * + * This static function parses a data hash from the given JSON object and converts it into a \ref cardano_blake2b_hash_t object. + * + * \param[in] provider A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context for parsing. + * This parameter must not be NULL. + * \param[in] data_hash_obj A pointer to a \ref json_object representing the data hash in JSON format. The object must contain + * the necessary fields to parse the hash. + * \param[out] data_hash On successful parsing, this will point to a newly created \ref cardano_blake2b_hash_t object + * representing the parsed data hash. The caller is responsible for managing the lifecycle of this object + * and must call \ref cardano_blake2b_hash_unref when it is no longer needed. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the data hash + * was successfully parsed, or an appropriate error code if an error occurred (e.g., if the JSON object format is invalid). + */ +static cardano_error_t +parse_data_hash( + cardano_provider_impl_t* provider, + struct json_object* data_hash_obj, + cardano_blake2b_hash_t** data_hash) +{ + const char* data_hash_str = json_object_get_string(data_hash_obj); + const size_t data_hash_len = json_object_get_string_len(data_hash_obj); + + if ((data_hash_str != NULL) && (data_hash_len > 0U)) + { + cardano_error_t result = cardano_blake2b_hash_from_hex(data_hash_str, data_hash_len, data_hash); + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider, "Failed to parse data_hash from JSON response"); + + return result; + } + } + + return CARDANO_SUCCESS; +} + +/** + * \brief Parses an inline datum from a JSON object and returns a \ref cardano_plutus_data_t object. + * + * This static function parses an inline datum, which is encoded in a JSON object, and converts it into a \ref cardano_plutus_data_t object. + * + * \param[in] provider A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context for parsing. + * This parameter must not be NULL. + * \param[in] inline_datum_obj A pointer to a \ref json_object representing the inline datum in JSON format. + * This object must contain the necessary datum fields required for parsing. + * \param[out] plutus_data On successful parsing, this will point to a newly created \ref cardano_plutus_data_t object + * representing the parsed Plutus data. The caller is responsible for managing the lifecycle of this object + * and must call \ref cardano_plutus_data_unref when it is no longer needed. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the inline datum + * was successfully parsed, or an appropriate error code if an error occurred (e.g., if the JSON object format is invalid). + */ +static cardano_error_t +parse_inline_datum( + cardano_provider_impl_t* provider, + struct json_object* inline_datum_obj, + cardano_plutus_data_t** plutus_data) +{ + const char* inline_datum = json_object_get_string(inline_datum_obj); + const size_t inline_datum_len = json_object_get_string_len(inline_datum_obj); + + if ((inline_datum != NULL) && (inline_datum_len > 0U)) + { + cardano_cbor_reader_t* reader = cardano_cbor_reader_from_hex(inline_datum, inline_datum_len); + + if (!reader) + { + cardano_utils_set_error_message(provider, "Failed to create CBOR reader for inline_datum"); + + return CARDANO_ERROR_INVALID_JSON; + } + + cardano_error_t result = cardano_plutus_data_from_cbor(reader, plutus_data); + cardano_cbor_reader_unref(&reader); + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider, "Failed to parse inline_datum from JSON response"); + + return result; + } + } + + return CARDANO_SUCCESS; +} + +/** + * \brief Parses a reference script from a JSON object and returns a \ref cardano_script_t object. + * + * This static function parses a reference script from the given JSON object, which typically includes a script hash. + * + * \param[in] provider A pointer to an initialized \ref cardano_provider_impl_t object that interacts with the blockchain API. + * This parameter must not be NULL. + * \param[in] script_hash_obj A pointer to a \ref json_object representing the script hash in JSON format. This object must contain + * the script hash data required to parse the reference script. + * \param[out] reference_script On successful parsing, this will point to the newly created \ref cardano_script_t object + * representing the reference script. The caller is responsible for managing the lifecycle of the script + * and must call \ref cardano_script_unref when it is no longer needed. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the reference script + * was successfully parsed, or an appropriate error code if an error occurred (e.g., if the JSON object format is invalid). + */ +static cardano_error_t +parse_reference_script( + cardano_provider_impl_t* provider, + struct json_object* script_hash_obj, + cardano_script_t** reference_script) +{ + const char* reference_script_hash_hex = json_object_get_string(script_hash_obj); + const size_t reference_script_hash_hex_len = json_object_get_string_len(script_hash_obj); + + if ((reference_script_hash_hex != NULL) && (reference_script_hash_hex_len > 0U)) + { + cardano_error_t result = cardano_blockfrost_get_script(provider, reference_script_hash_hex, reference_script_hash_hex_len, reference_script); + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider, "Failed to retrieve reference script from JSON response"); + + return result; + } + } + + return CARDANO_SUCCESS; +} + +/* IMPLEMENTATION *************************************************************/ + +cardano_error_t +cardano_blockfrost_parse_unspent_outputs( + cardano_provider_impl_t* provider, + const char* json, + size_t size, + cardano_utxo_list_t** utxo_list) +{ + struct json_tokener* tok = json_tokener_new(); + struct json_object* parsed_json = json_tokener_parse_ex(tok, json, (int32_t)size); + + if (parsed_json == NULL) + { + cardano_utils_set_error_message(provider, "Failed to parse JSON response"); + json_tokener_free(tok); + return CARDANO_ERROR_INVALID_JSON; + } + + cardano_error_t result = cardano_utxo_list_new(utxo_list); + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider, "Failed to allocate memory for UTXO list"); + json_object_put(parsed_json); + json_tokener_free(tok); + return result; + } + + size_t array_len = json_object_array_length(parsed_json); + + uint64_t tx_index; + cardano_blake2b_hash_t* tx_id = NULL; + cardano_address_t* address = NULL; + cardano_value_t* value = NULL; + cardano_blake2b_hash_t* plutus_data_hash = NULL; + cardano_plutus_data_t* plutus_data = NULL; + cardano_script_t* reference_script = NULL; + cardano_transaction_input_t* input = NULL; + cardano_transaction_output_t* output = NULL; + cardano_utxo_t* utxo = NULL; + + for (size_t i = 0U; i < array_len; ++i) + { + // These are always freed at the end of the loop iteration, + // so we can safely set them to NULL here. + tx_id = NULL; + tx_index = 0; + address = NULL; + value = NULL; + plutus_data_hash = NULL; + plutus_data = NULL; + reference_script = NULL; + input = NULL; + output = NULL; + utxo = NULL; + + struct json_object* tx_output = json_object_array_get_idx(parsed_json, i); + + struct json_object* address_obj = NULL; + + if (json_object_object_get_ex(tx_output, "address", &address_obj)) + { + result = parse_address(provider, address_obj, &address); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + } + + struct json_object* tx_hash_obj = NULL; + + if (json_object_object_get_ex(tx_output, "tx_hash", &tx_hash_obj)) + { + result = parse_tx_hash(provider, tx_hash_obj, &tx_id); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + } + + struct json_object* output_index_obj = NULL; + + if (json_object_object_get_ex(tx_output, "output_index", &output_index_obj)) + { + tx_index = json_object_get_int64(output_index_obj); + } + + struct json_object* amount_array = NULL; + + if (json_object_object_get_ex(tx_output, "amount", &amount_array)) + { + result = parse_amount(provider, amount_array, &value); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + } + + struct json_object* data_hash_obj = NULL; + + if (json_object_object_get_ex(tx_output, "data_hash", &data_hash_obj)) + { + result = parse_data_hash(provider, data_hash_obj, &plutus_data_hash); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + } + + struct json_object* inline_datum_obj = NULL; + + if (json_object_object_get_ex(tx_output, "inline_datum", &inline_datum_obj)) + { + result = parse_inline_datum(provider, inline_datum_obj, &plutus_data); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + } + + struct json_object* reference_script_hash_obj = NULL; + + if (json_object_object_get_ex(tx_output, "reference_script_hash", &reference_script_hash_obj)) + { + result = parse_reference_script(provider, reference_script_hash_obj, &reference_script); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + } + + result = cardano_transaction_input_new(tx_id, tx_index, &input); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + + result = cardano_transaction_output_new(address, 0, &output); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + + result = cardano_transaction_output_set_value(output, value); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + + result = cardano_utxo_new(input, output, &utxo); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + + result = cardano_utxo_list_add(*utxo_list, utxo); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + + // Clean up allocated resources for this loop iteration + cardano_transaction_input_unref(&input); + cardano_transaction_output_unref(&output); + cardano_utxo_unref(&utxo); + cardano_blake2b_hash_unref(&tx_id); + cardano_address_unref(&address); + cardano_value_unref(&value); + cardano_blake2b_hash_unref(&plutus_data_hash); + cardano_plutus_data_unref(&plutus_data); + cardano_script_unref(&reference_script); + } + +cleanup: + if (result != CARDANO_SUCCESS) + { + cardano_utxo_list_unref(utxo_list); + cardano_transaction_input_unref(&input); + cardano_transaction_output_unref(&output); + cardano_utxo_unref(&utxo); + cardano_blake2b_hash_unref(&tx_id); + cardano_address_unref(&address); + cardano_value_unref(&value); + cardano_blake2b_hash_unref(&plutus_data_hash); + cardano_plutus_data_unref(&plutus_data); + cardano_script_unref(&reference_script); + } + + json_object_put(parsed_json); + json_tokener_free(tok); + + return result; +} + +cardano_error_t +cardano_blockfrost_parse_tx_unspent_outputs( + cardano_provider_impl_t* provider, + const char* json, + const size_t size, + const char* tx_hash, + const size_t tx_hash_len, + cardano_utxo_list_t** utxo_list) +{ + struct json_tokener* tok = json_tokener_new(); + struct json_object* parsed_json = json_tokener_parse_ex(tok, json, (int32_t)size); + + if (parsed_json == NULL) + { + cardano_utils_set_error_message(provider, "Failed to parse JSON response"); + json_tokener_free(tok); + return CARDANO_ERROR_INVALID_JSON; + } + + cardano_error_t result = cardano_utxo_list_new(utxo_list); + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider, "Failed to allocate memory for UTXO list"); + json_object_put(parsed_json); + json_tokener_free(tok); + return result; + } + + size_t array_len = json_object_array_length(parsed_json); + + uint64_t tx_index; + cardano_blake2b_hash_t* tx_id = NULL; + cardano_address_t* address = NULL; + cardano_value_t* value = NULL; + cardano_blake2b_hash_t* plutus_data_hash = NULL; + cardano_plutus_data_t* plutus_data = NULL; + cardano_script_t* reference_script = NULL; + cardano_transaction_input_t* input = NULL; + cardano_transaction_output_t* output = NULL; + cardano_utxo_t* utxo = NULL; + + for (size_t i = 0U; i < array_len; ++i) + { + // These are always freed at the end of the loop iteration, + // so we can safely set them to NULL here. + tx_id = NULL; + tx_index = 0; + address = NULL; + value = NULL; + plutus_data_hash = NULL; + plutus_data = NULL; + reference_script = NULL; + input = NULL; + output = NULL; + utxo = NULL; + + result = cardano_blake2b_hash_from_hex(tx_hash, tx_hash_len, &tx_id); + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider, "Failed to parse tx_hash from JSON response"); + goto cleanup; + } + + struct json_object* tx_output = json_object_array_get_idx(parsed_json, i); + + struct json_object* address_obj = NULL; + + if (json_object_object_get_ex(tx_output, "address", &address_obj)) + { + result = parse_address(provider, address_obj, &address); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + } + + struct json_object* output_index_obj = NULL; + + if (json_object_object_get_ex(tx_output, "output_index", &output_index_obj)) + { + tx_index = json_object_get_int64(output_index_obj); + } + + struct json_object* amount_array = NULL; + + if (json_object_object_get_ex(tx_output, "amount", &amount_array)) + { + result = parse_amount(provider, amount_array, &value); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + } + + struct json_object* data_hash_obj = NULL; + + if (json_object_object_get_ex(tx_output, "data_hash", &data_hash_obj)) + { + result = parse_data_hash(provider, data_hash_obj, &plutus_data_hash); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + } + + struct json_object* inline_datum_obj = NULL; + + if (json_object_object_get_ex(tx_output, "inline_datum", &inline_datum_obj)) + { + result = parse_inline_datum(provider, inline_datum_obj, &plutus_data); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + } + + struct json_object* reference_script_hash_obj = NULL; + + if (json_object_object_get_ex(tx_output, "reference_script_hash", &reference_script_hash_obj)) + { + result = parse_reference_script(provider, reference_script_hash_obj, &reference_script); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + } + + result = cardano_transaction_input_new(tx_id, tx_index, &input); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + + result = cardano_transaction_output_new(address, 0, &output); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + + result = cardano_transaction_output_set_value(output, value); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + + result = cardano_utxo_new(input, output, &utxo); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + + result = cardano_utxo_list_add(*utxo_list, utxo); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + + // Clean up allocated resources for this loop iteration + cardano_transaction_input_unref(&input); + cardano_transaction_output_unref(&output); + cardano_utxo_unref(&utxo); + cardano_blake2b_hash_unref(&tx_id); + cardano_address_unref(&address); + cardano_value_unref(&value); + cardano_blake2b_hash_unref(&plutus_data_hash); + cardano_plutus_data_unref(&plutus_data); + cardano_script_unref(&reference_script); + } + +cleanup: + if (result != CARDANO_SUCCESS) + { + cardano_utxo_list_unref(utxo_list); + cardano_transaction_input_unref(&input); + cardano_transaction_output_unref(&output); + cardano_utxo_unref(&utxo); + cardano_blake2b_hash_unref(&tx_id); + cardano_address_unref(&address); + cardano_value_unref(&value); + cardano_blake2b_hash_unref(&plutus_data_hash); + cardano_plutus_data_unref(&plutus_data); + cardano_script_unref(&reference_script); + } + + json_object_put(parsed_json); + json_tokener_free(tok); + + return result; +} diff --git a/examples/providers/provider_factory.h b/examples/providers/provider_factory.h new file mode 100644 index 00000000..a2cd02d7 --- /dev/null +++ b/examples/providers/provider_factory.h @@ -0,0 +1,65 @@ +/** + * \file provider_factory.h + * + * \author angel.castillo + * \date Sep 28, 2024 + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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 BIGLUP_LABS_INCLUDE_CARDANO_PROVIDER_FACTORY_H +#define BIGLUP_LABS_INCLUDE_CARDANO_PROVIDER_FACTORY_H + +/* INCLUDES ******************************************************************/ + +#include +#include + +#include +#include + +/* DECLARATIONS **************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * \brief Creates a Blockfrost provider for interacting with the Cardano blockchain. + * + * This function creates a Blockfrost provider that can be used to communicate with the Cardano blockchain + * via the Blockfrost API. The provider is initialized with the network magic and a project ID associated + * with the Blockfrost project. + * + * \param[in] network The Cardano network magic number (e.g., mainnet or testnet) that the provider will interact with. + * \param[in] project_id A pointer to the Blockfrost project ID string. This is used to authenticate with the Blockfrost API. + * \param[in] project_id_size The size of the `project_id` string in bytes. + * \param[out] provider A pointer to a pointer where the newly created Blockfrost provider will be stored. + * The caller is responsible for deallocating the provider using the appropriate function. + * + * \return A \ref cardano_error_t indicating the success or failure of the operation. + */ +CARDANO_NODISCARD +CARDANO_EXPORT cardano_error_t create_blockfrost_provider( + cardano_network_magic_t network, + const char* project_id, + size_t project_id_size, + cardano_provider_t** provider); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif // BIGLUP_LABS_INCLUDE_CARDANO_PROVIDER_FACTORY_H \ No newline at end of file diff --git a/examples/utils/console.c b/examples/utils/console.c new file mode 100644 index 00000000..cf3dceec --- /dev/null +++ b/examples/utils/console.c @@ -0,0 +1,254 @@ +/** + * \file console.c + * + * \author angel.castillo + * \date Oct 04, 2024 + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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. + */ + +/* INCLUDES ******************************************************************/ + +#include "console.h" +#include + +#include +#include +#include +#include + +/* CONSTANTS *****************************************************************/ + +static const char* FOREGROUND_COLOR_FORMAT = "\033[3%dm"; +static const char* BACKGROUND_COLOR_FORMAT = "\033[4%dm"; +static const char* LOG_SEVERITY_ENV = "LOG_SEVERITY"; +static const char* LOG_SEVERITY_INFO_STR = "info"; +static const char* LOG_SEVERITY_ERROR_STR = "error"; +static const char* LOG_SEVERITY_WARN_STR = "warn"; +static const char* LOG_SEVERITY_DEBUG_STR = "debug"; + +static const uint8_t LOG_SEVERITY_UNKNOWN = 0; +static const uint8_t LOG_SEVERITY_INFO = 1; +static const uint8_t LOG_SEVERITY_ERROR = 2; +static const uint8_t LOG_SEVERITY_WARN = 3; +static const uint8_t LOG_SEVERITY_DEBUG = 4; + +/* STATIC VARIABLES **********************************************************/ + +static console_color_t g_foreground_color = CONSOLE_COLOR_DEFAULT; +static console_color_t g_background_color = CONSOLE_COLOR_DEFAULT; + +/* STATIC FUNCTIONS **********************************************************/ + +/** + * Sets the current terminal color. + */ +static void +set_color() +{ + printf(FOREGROUND_COLOR_FORMAT, g_foreground_color); + printf(BACKGROUND_COLOR_FORMAT, g_background_color); + fflush(stdout); +} + +/** + * Resets the current terminal color to default. + */ +static void +reset_color() +{ + printf(FOREGROUND_COLOR_FORMAT, CONSOLE_COLOR_DEFAULT); + printf(BACKGROUND_COLOR_FORMAT, CONSOLE_COLOR_DEFAULT); + fflush(stdout); +} + +/** + * Writes a color-coded log line to the terminal. + * + * \param color The color to be used to render the log line. + * \param format printf style format. + * \param ... variadic parameters to be used in the format. + */ +static void +write_log_line(console_color_t color, const char* format, va_list args) +{ + g_foreground_color = color; + set_color(); + + vprintf(format, args); + va_end(args); + + printf("\n"); + reset_color(); +} + +/** + * Writes a color-coded log line to the terminal. + * + * \param color The color to be used to render the log line. + * \param message The message to be printed. + */ +static void +write_log_lineMessage(console_color_t color, const char* message) +{ + g_foreground_color = color; + set_color(); + + printf("%s\n", message); + reset_color(); +} + +/** + * Gets log severity from the environment. + * + * \return The log severity. + */ +uint8_t +get_log_severity() +{ + static uint8_t log_severity = LOG_SEVERITY_UNKNOWN; + + if (log_severity == LOG_SEVERITY_UNKNOWN) + { + const char* env_str = getenv(LOG_SEVERITY_ENV); + + if (env_str == NULL) + { + log_severity = LOG_SEVERITY_WARN; + return log_severity; + } + + if (strcmp(env_str, LOG_SEVERITY_INFO_STR) == 0) + { + log_severity = LOG_SEVERITY_INFO; + } + else if (strcmp(env_str, LOG_SEVERITY_ERROR_STR) == 0) + { + log_severity = LOG_SEVERITY_ERROR; + } + else if (strcmp(env_str, LOG_SEVERITY_DEBUG_STR) == 0) + { + log_severity = LOG_SEVERITY_DEBUG; + } + else + { + log_severity = LOG_SEVERITY_WARN; // Default severity + } + } + + return log_severity; +} + +/* PUBLIC FUNCTIONS **********************************************************/ + +void +console_info(const char* format, ...) +{ + va_list args; + va_start(args, format); + write_log_line(CONSOLE_COLOR_DEFAULT, format, args); +} + +void +console_debug(const char* format, ...) +{ + if (get_log_severity() < LOG_SEVERITY_DEBUG) + { + return; + } + + va_list args; + va_start(args, format); + write_log_line(CONSOLE_COLOR_BLUE, format, args); +} + +void +console_warn(const char* format, ...) +{ + if (get_log_severity() < LOG_SEVERITY_WARN) + { + return; + } + + va_list args; + va_start(args, format); + write_log_line(CONSOLE_COLOR_YELLOW, format, args); +} + +void +console_error(const char* format, ...) +{ + if (get_log_severity() < LOG_SEVERITY_ERROR) + { + return; + } + + va_list args; + va_start(args, format); + write_log_line(CONSOLE_COLOR_RED, format, args); +} + +void +console_write(const char* format, ...) +{ + set_color(); + + va_list args; + va_start(args, format); + vprintf(format, args); + va_end(args); + + reset_color(); +} + +void +console_write_line(const char* format, ...) +{ + va_list args; + va_start(args, format); + write_log_line(CONSOLE_COLOR_DEFAULT, format, args); +} + +void +console_set_background_color(console_color_t color) +{ + g_background_color = color; +} + +void +console_set_foreground_color(console_color_t color) +{ + g_foreground_color = color; +} + +console_color_t +console_get_background_color() +{ + return g_background_color; +} + +console_color_t +console_get_foreground_color() +{ + return g_foreground_color; +} + +void +console_reset_color() +{ + g_foreground_color = CONSOLE_COLOR_DEFAULT; + g_background_color = CONSOLE_COLOR_DEFAULT; + reset_color(); +} diff --git a/examples/utils/console.h b/examples/utils/console.h new file mode 100644 index 00000000..3517bff2 --- /dev/null +++ b/examples/utils/console.h @@ -0,0 +1,175 @@ +/** + * \file console.h + * + * \author angel.castillo + * \date Oct 04, 2024 + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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 BIGLUP_LABS_INCLUDE_CONSOLE_H +#define BIGLUP_LABS_INCLUDE_CONSOLE_H + +/* ENUMERATIONS **************************************************************/ + +/** + * \brief The console color. + */ +typedef enum +{ + /** + * \brief Black color. + */ + CONSOLE_COLOR_BLACK = 0x00, + + /** + * \brief Red color. + */ + CONSOLE_COLOR_RED = 0x01, + + /** + * \brief Green color. + */ + CONSOLE_COLOR_GREEN = 0x02, + + /** + * \brief Yellow color. + */ + CONSOLE_COLOR_YELLOW = 0x03, + + /** + * \brief Blue color. + */ + CONSOLE_COLOR_BLUE = 0x04, + + /** + * \brief Purple color. + */ + CONSOLE_COLOR_PURPLE = 0x05, + + /** + * \brief Cyan color. + */ + CONSOLE_COLOR_CYAN = 0x06, + + /** + * \brief Light gray color. + */ + CONSOLE_COLOR_LIGHT_GRAY = 0x07, + + /** + * \brief Dark gray color. + */ + CONSOLE_COLOR_DEFAULT = 0x09 +} console_color_t; + +/* DECLARATIONS **************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * Writes the specified string value to the standard output stream + * as a info message. + * + * \param format printf style format. + * \param ... variadic parameters to be use in the format. + */ +void console_info(const char* format, ...); + +/** + * Writes the specified string value to the standard output stream + * as a debug message. + * + * \param format printf style format. + * \param ... variadic parameters to be use in the format. + */ +void console_debug(const char* format, ...); + +/** + * Writes the specified string value to the standard output stream + * as a warn message. + * + * \param format printf style format. + * \param ... variadic parameters to be use in the format. + */ +void console_warn(const char* format, ...); + +/** + * Writes the specified string value to the standard output stream + * as a error message. + * + * \param format printf style format. + * \param ... variadic parameters to be use in the format. + */ +void console_error(const char* format, ...); + +/** + * Writes the specified string value to the standard output stream. + * + * \param format printf style format. + * \param ... variadic parameters to be use in the format. + */ +void console_write(const char* format, ...); + +/** + * Writes the specified string value to the standard output stream. + * + * \param format printf style format. + * \param ... variadic parameters to be use in the format. + */ +void console_write_line(const char* format, ...); + +/** + * Sets the background color of the console. + * + * \param color A value that specifies the background color of the console; that is, the color that appears + * behind each character. The default is black. + */ +void console_set_background_color(console_color_t color); + +/** + * Sets the foreground color of the console. + * + * \param color A ConsoleColor that specifies the foreground color of the console; that is, the color of each + * character that is displayed. The default is gray. + */ +void console_set_foreground_color(console_color_t color); + +/** + * Gets the background color of the console. + * + * \return A ConsoleColor that specifies the current background color of the console. + */ +console_color_t console_get_background_color(); + +/** + * Gets the foreground color of the console. + * + * \return A ConsoleColor that specifies the current foreground color of the console. + */ +console_color_t console_get_foreground_color(); + +/** + * Sets the foreground and background console colors to their defaults. + */ +void console_reset_color(); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif // BIGLUP_LABS_INCLUDE_CONSOLE_H \ No newline at end of file diff --git a/examples/utils/utils.c b/examples/utils/utils.c new file mode 100644 index 00000000..3eb3004d --- /dev/null +++ b/examples/utils/utils.c @@ -0,0 +1,106 @@ +/** + * \file utils.c + * + * \author luisd.bianchi + * \date Sep 30, 2024 + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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. + */ + +/* INCLUDES ******************************************************************/ + +#include "utils.h" + +#include + +#include +#include +#include + +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +/* DEFINITIONS ***************************************************************/ + +void +cardano_utils_safe_memcpy(void* dest, const size_t dest_size, const void* src, const size_t src_size) +{ + if ((dest == NULL) || (src == NULL) || (dest_size == 0U) || (src_size == 0U)) + { + return; + } + + size_t copy_size = (src_size < dest_size) ? src_size : dest_size; + + CARDANO_UNUSED(memcpy(dest, src, copy_size)); // nosemgrep +} + +size_t +cardano_utils_safe_strlen(const char* str, const size_t max_length) +{ + if (str == NULL) + { + return 0U; + } + + size_t index = 0U; + + while ((index < max_length) && (str[index] != '\0')) + { + ++index; + } + + return index; +} + +void +cardano_utils_set_error_message(cardano_provider_impl_t* provider_impl, const char* message) +{ + assert(provider_impl != NULL); + + size_t message_size = cardano_utils_safe_strlen(message, 1023); + + CARDANO_UNUSED(memcpy(provider_impl->error_message, message, message_size)); + provider_impl->error_message[message_size] = '\0'; +} + +uint64_t +cardano_utils_get_time() +{ + return (uint64_t)time(NULL); +} + +uint64_t +cardano_utils_get_elapsed_time_since(const uint64_t start) +{ + const uint64_t current_time = cardano_utils_get_time(); + + return ((current_time >= start) ? (current_time - start) : 0U); +} + +void +cardano_utils_sleep(const uint64_t milliseconds) +{ +#ifdef _WIN32 + Sleep((DWORD)milliseconds); // Windows Sleep function takes milliseconds +#else + usleep(milliseconds * 1000U); // POSIX usleep takes microseconds +#endif +} diff --git a/examples/utils/utils.h b/examples/utils/utils.h new file mode 100644 index 00000000..1c2197e8 --- /dev/null +++ b/examples/utils/utils.h @@ -0,0 +1,110 @@ +/** + * \file utils.h + * + * \author luisd.bianchi + * \date Sep 30, 2024 + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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 BIGLUP_LABS_INCLUDE_CARDANO_UTILS_H +#define BIGLUP_LABS_INCLUDE_CARDANO_UTILS_H + +/* INCLUDES ******************************************************************/ + +#include +#include + +/* DECLARATIONS **************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * \brief Safe version of memcpy that copies up to buffer size into the destination buffer. + * + * \param dest Destination buffer. + * \param dest_size Size of the destination buffer. + * \param src Source buffer. + * \param src_size Number of bytes to copy. + */ +void +cardano_utils_safe_memcpy(void* dest, size_t dest_size, const void* src, size_t src_size); + +/** + * \brief Safe version of strlen that limits the maximum number of characters to inspect. + * + * \param str The string to measure. + * \param max_length Maximum number of characters to inspect. + * + * \return Length of the string or \c max_length if the string exceeds max_length. + */ +size_t +cardano_utils_safe_strlen(const char* str, size_t max_length); + +/** + * \brief Sets a NULL terminated message as the inner error message of a provider implementation object. + * + * \param provider_impl Provider implementation object. + * \param message Error message to set. + */ +void +cardano_utils_set_error_message(cardano_provider_impl_t* provider_impl, const char* message); + +/** + * \brief Retrieves the current timestamp in seconds. + * + * This function returns the current time as the number of seconds since the Unix epoch (January 1, 1970). + * + * \return The current time as a \c uint64_t representing the Unix timestamp in seconds. + */ +uint64_t +cardano_utils_get_time(void); + +/** + * \brief Calculates the elapsed time in seconds since a given start time. + * + * This function computes the elapsed time in seconds between a specified start time and the current time. + * + * \param[in] start A \c uint64_t representing the start time as a Unix timestamp (in seconds). + * + * \return The elapsed time in seconds as a \c uint64_t. If the current time is less than the start time (unlikely under normal circumstances), + * the function returns 0. + * + * Usage Example: + * \code{.c} + * uint64_t start_time = cardano_utils_get_time(); + * // Perform some operations... + * uint64_t elapsed_time = cardano_utils_get_elapsed_time_since(start_time); + * printf("Elapsed time: %llu seconds\n", elapsed_time); + * \endcode + */ +uint64_t +cardano_utils_get_elapsed_time_since(uint64_t start); + +/** + * \brief Suspends the execution of the current thread for a specified number of milliseconds. + * + * @param milliseconds Number of milliseconds to sleep. + */ +void +cardano_utils_sleep(uint64_t milliseconds); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif // BIGLUP_LABS_INCLUDE_CARDANO_UTILS_H diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 004207fc..b59d9e05 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -14,6 +14,8 @@ INCLUDE_DIRECTORIES (SYSTEM ${jsonc_INCLUDE_DIR}) INCLUDE_DIRECTORIES (SYSTEM ${gmp_INCLUDE_DIR}) INCLUDE_DIRECTORIES (SYSTEM ./include) +FIND_LIBRARY(MATH_LIBRARY m REQUIRED) + IF (UNIX AND NOT APPLE) SET (RT_LIBRARIES rt) ELSE () @@ -60,6 +62,7 @@ TARGET_LINK_LIBRARIES (${LIB_OUTPUT_NAME} PRIVATE ${sodium_LIBRARY_RELEASE} PRIVATE ${jsonc_LIBRARY_RELEASE} PRIVATE ${gmp_LIBRARY_RELEASE} + PRIVATE ${MATH_LIBRARY} ) # Allow to build static and shared libraries at the same time @@ -76,6 +79,7 @@ if (BUILD_STATIC_LIBS AND BUILD_SHARED_LIBS) PRIVATE ${sodium_LIBRARY_RELEASE} PRIVATE ${jsonc_LIBRARY_RELEASE} PRIVATE ${gmp_LIBRARY_RELEASE} + PRIVATE ${MATH_LIBRARY} ) # rename the static library diff --git a/lib/include/cardano/certs/update_drep_cert.h b/lib/include/cardano/certs/update_drep_cert.h index b04dd12e..c9326862 100644 --- a/lib/include/cardano/certs/update_drep_cert.h +++ b/lib/include/cardano/certs/update_drep_cert.h @@ -324,33 +324,6 @@ cardano_update_drep_cert_set_anchor(cardano_update_drep_cert_t* certificate, car */ CARDANO_EXPORT void cardano_update_drep_cert_unref(cardano_update_drep_cert_t** update_drep_cert); -/** - * \brief Decrements the reference count of a cardano_update_drep_cert_t object. - * - * This function is responsible for managing the lifecycle of a \ref cardano_update_drep_cert_t object - * by decreasing its reference count. When the reference count reaches zero, the update_drep_cert is - * finalized; its associated resources are released, and its memory is deallocated. - * - * \param[in,out] update_drep_cert A pointer to the pointer of the update_drep_cert object. This double - * indirection allows the function to set the caller's pointer to - * NULL, avoiding dangling pointer issues after the object has been - * freed. - * - * Usage Example: - * \code{.c} - * cardano_update_drep_cert_t* update_drep_cert = cardano_update_drep_cert_new(major, minor); - * - * // Perform operations with the update_drep_cert... - * - * cardano_update_drep_cert_unref(&update_drep_cert); - * // At this point, update_drep_cert is NULL and cannot be used. - * \endcode - * - * \note After calling \ref cardano_update_drep_cert_unref, the pointer to the \ref cardano_update_drep_cert_t object - * will be set to NULL to prevent its reuse. - */ -CARDANO_EXPORT void cardano_update_drep_cert_unref(cardano_update_drep_cert_t** update_drep_cert); - /** * \brief Increases the reference count of the cardano_update_drep_cert_t object. * diff --git a/lib/include/cardano/common/network_magic.h b/lib/include/cardano/common/network_magic.h new file mode 100644 index 00000000..5f8d3dd1 --- /dev/null +++ b/lib/include/cardano/common/network_magic.h @@ -0,0 +1,92 @@ +/** + * \file network_magic.h + * + * \author angel.castillo + * \date Sep 28, 2024 + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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 BIGLUP_LABS_INCLUDE_CARDANO_NETWORK_MAGIC_H +#define BIGLUP_LABS_INCLUDE_CARDANO_NETWORK_MAGIC_H + +/* INCLUDES ******************************************************************/ + +#include + +/* DECLARATIONS **************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * \brief Enumerates the available Cardano network environments. + * + * This enumeration defines the different network environments that can be used + * with the Cardano provider. + */ +typedef enum +{ + /** + * \brief The Pre-Production test network. + * + * The Pre-Production network is a Cardano testnet used for testing features + * before they are deployed to the Mainnet. It closely mirrors the Mainnet + * environment, providing a final testing ground for applications. + */ + CARDANO_NETWORK_MAGIC_PREPROD = 1, + + /** + * \brief The Preview test network. + * + * The Preview network is a Cardano testnet used for testing upcoming features + * before they are released to the Pre-Production network. It allows developers + * to experiment with new functionalities in a controlled environment. + */ + CARDANO_NETWORK_MAGIC_PREVIEW = 2, + + /** + * \brief The SanchoNet test network. + * + * SanchoNet is the testnet for rolling out governance features for the Cardano blockchain, + * aligning with the comprehensive CIP-1694 specifications. + */ + CARDANO_NETWORK_MAGIC_SANCHONET = 4, + + /** + * \brief The Mainnet network. + * + * The Mainnet is the live Cardano network where real transactions occur. + * Applications interacting with the Mainnet are dealing with actual ADA and + * other assets. Caution should be exercised to ensure correctness and security. + */ + CARDANO_NETWORK_MAGIC_MAINNET = 764824073, +} cardano_network_magic_t; + +/** + * \brief Converts network magics to their human readable form. + * + * \param[in] magic The network magic to get the string representation for. + * \return Human readable form of the given network magic. + */ +CARDANO_NODISCARD +CARDANO_EXPORT const char* cardano_network_magic_to_string(cardano_network_magic_t magic); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif // BIGLUP_LABS_INCLUDE_CARDANO_NETWORK_MAGIC_H \ No newline at end of file diff --git a/lib/include/cardano/error.h b/lib/include/cardano/error.h index c37f83a3..cdc94b9b 100644 --- a/lib/include/cardano/error.h +++ b/lib/include/cardano/error.h @@ -132,6 +132,11 @@ typedef enum */ CARDANO_ERROR_INVALID_CERTIFICATE_TYPE = 18, + /** + * \brief The operation is not implemented. + */ + CARDANO_ERROR_NOT_IMPLEMENTED = 19, + /* Serialization errors */ /** @@ -280,6 +285,13 @@ typedef enum */ CARDANO_ERROR_INVALID_METADATUM_BOUNDED_BYTES_SIZE = 902, + // HTTP + + /** + * \brief The HTTP request is invalid. + */ + CARDANO_ERROR_INVALID_HTTP_REQUEST = 1000, + } cardano_error_t; /** diff --git a/lib/include/cardano/object.h b/lib/include/cardano/object.h index f1883a43..050965d5 100644 --- a/lib/include/cardano/object.h +++ b/lib/include/cardano/object.h @@ -19,8 +19,8 @@ * limitations under the License. */ -#ifndef cardano_object_H -#define cardano_object_H +#ifndef BIGLUP_LABS_INCLUDE_CARDANO_OBJECT_H +#define BIGLUP_LABS_INCLUDE_CARDANO_OBJECT_H /* INCLUDES ******************************************************************/ @@ -140,4 +140,4 @@ CARDANO_EXPORT const char* cardano_object_get_last_error(const cardano_object_t* } #endif /* __cplusplus */ -#endif // cardano_object_H +#endif // BIGLUP_LABS_INCLUDE_CARDANO_OBJECT_H diff --git a/lib/include/cardano/proposal_procedures/committee.h b/lib/include/cardano/proposal_procedures/committee.h index 102401af..48b9a7c2 100644 --- a/lib/include/cardano/proposal_procedures/committee.h +++ b/lib/include/cardano/proposal_procedures/committee.h @@ -527,33 +527,6 @@ CARDANO_EXPORT cardano_error_t cardano_committee_get_key_value_at( */ CARDANO_EXPORT void cardano_committee_unref(cardano_committee_t** committee); -/** - * \brief Decrements the reference count of a cardano_committee_t object. - * - * This function is responsible for managing the lifecycle of a \ref cardano_committee_t object - * by decreasing its reference count. When the reference count reaches zero, the committee is - * finalized; its associated resources are released, and its memory is deallocated. - * - * \param[in,out] committee A pointer to the pointer of the committee object. This double - * indirection allows the function to set the caller's pointer to - * NULL, avoiding dangling pointer issues after the object has been - * freed. - * - * Usage Example: - * \code{.c} - * cardano_committee_t* committee = cardano_committee_new(major, minor); - * - * // Perform operations with the committee... - * - * cardano_committee_unref(&committee); - * // At this point, committee is NULL and cannot be used. - * \endcode - * - * \note After calling \ref cardano_committee_unref, the pointer to the \ref cardano_committee_t object - * will be set to NULL to prevent its reuse. - */ -CARDANO_EXPORT void cardano_committee_unref(cardano_committee_t** committee); - /** * \brief Increases the reference count of the cardano_committee_t object. * diff --git a/lib/include/cardano/proposal_procedures/constitution.h b/lib/include/cardano/proposal_procedures/constitution.h index ad213d0f..d257987d 100644 --- a/lib/include/cardano/proposal_procedures/constitution.h +++ b/lib/include/cardano/proposal_procedures/constitution.h @@ -353,33 +353,6 @@ cardano_constitution_get_script_hash(cardano_constitution_t* constitution); */ CARDANO_EXPORT void cardano_constitution_unref(cardano_constitution_t** constitution); -/** - * \brief Decrements the reference count of a cardano_constitution_t object. - * - * This function is responsible for managing the lifecycle of a \ref cardano_constitution_t object - * by decreasing its reference count. When the reference count reaches zero, the constitution is - * finalized; its associated resources are released, and its memory is deallocated. - * - * \param[in,out] constitution A pointer to the pointer of the constitution object. This double - * indirection allows the function to set the caller's pointer to - * NULL, avoiding dangling pointer issues after the object has been - * freed. - * - * Usage Example: - * \code{.c} - * cardano_constitution_t* constitution = cardano_constitution_new(major, minor); - * - * // Perform operations with the constitution... - * - * cardano_constitution_unref(&constitution); - * // At this point, constitution is NULL and cannot be used. - * \endcode - * - * \note After calling \ref cardano_constitution_unref, the pointer to the \ref cardano_constitution_t object - * will be set to NULL to prevent its reuse. - */ -CARDANO_EXPORT void cardano_constitution_unref(cardano_constitution_t** constitution); - /** * \brief Increases the reference count of the cardano_constitution_t object. * diff --git a/lib/include/cardano/proposal_procedures/hard_fork_initiation_action.h b/lib/include/cardano/proposal_procedures/hard_fork_initiation_action.h index 499a0aa4..3c8eaa81 100644 --- a/lib/include/cardano/proposal_procedures/hard_fork_initiation_action.h +++ b/lib/include/cardano/proposal_procedures/hard_fork_initiation_action.h @@ -352,33 +352,6 @@ cardano_hard_fork_initiation_action_get_governance_action_id(cardano_hard_fork_i */ CARDANO_EXPORT void cardano_hard_fork_initiation_action_unref(cardano_hard_fork_initiation_action_t** hard_fork_initiation_action); -/** - * \brief Decrements the reference count of a cardano_hard_fork_initiation_action_t object. - * - * This function is responsible for managing the lifecycle of a \ref cardano_hard_fork_initiation_action_t object - * by decreasing its reference count. When the reference count reaches zero, the hard_fork_initiation_action is - * finalized; its associated resources are released, and its memory is deallocated. - * - * \param[in,out] hard_fork_initiation_action A pointer to the pointer of the hard_fork_initiation_action object. This double - * indirection allows the function to set the caller's pointer to - * NULL, avoiding dangling pointer issues after the object has been - * freed. - * - * Usage Example: - * \code{.c} - * cardano_hard_fork_initiation_action_t* hard_fork_initiation_action = cardano_hard_fork_initiation_action_new(major, minor); - * - * // Perform operations with the hard_fork_initiation_action... - * - * cardano_hard_fork_initiation_action_unref(&hard_fork_initiation_action); - * // At this point, hard_fork_initiation_action is NULL and cannot be used. - * \endcode - * - * \note After calling \ref cardano_hard_fork_initiation_action_unref, the pointer to the \ref cardano_hard_fork_initiation_action_t object - * will be set to NULL to prevent its reuse. - */ -CARDANO_EXPORT void cardano_hard_fork_initiation_action_unref(cardano_hard_fork_initiation_action_t** hard_fork_initiation_action); - /** * \brief Increases the reference count of the cardano_hard_fork_initiation_action_t object. * diff --git a/lib/include/cardano/proposal_procedures/info_action.h b/lib/include/cardano/proposal_procedures/info_action.h index b3a752f7..891256f1 100644 --- a/lib/include/cardano/proposal_procedures/info_action.h +++ b/lib/include/cardano/proposal_procedures/info_action.h @@ -26,8 +26,6 @@ #include #include -#include -#include #include #include @@ -194,33 +192,6 @@ CARDANO_EXPORT cardano_error_t cardano_info_action_to_cbor( */ CARDANO_EXPORT void cardano_info_action_unref(cardano_info_action_t** info_action); -/** - * \brief Decrements the reference count of a cardano_info_action_t object. - * - * This function is responsible for managing the lifecycle of a \ref cardano_info_action_t object - * by decreasing its reference count. When the reference count reaches zero, the info_action is - * finalized; its associated resources are released, and its memory is deallocated. - * - * \param[in,out] info_action A pointer to the pointer of the info_action object. This double - * indirection allows the function to set the caller's pointer to - * NULL, avoiding dangling pointer issues after the object has been - * freed. - * - * Usage Example: - * \code{.c} - * cardano_info_action_t* info_action = cardano_info_action_new(major, minor); - * - * // Perform operations with the info_action... - * - * cardano_info_action_unref(&info_action); - * // At this point, info_action is NULL and cannot be used. - * \endcode - * - * \note After calling \ref cardano_info_action_unref, the pointer to the \ref cardano_info_action_t object - * will be set to NULL to prevent its reuse. - */ -CARDANO_EXPORT void cardano_info_action_unref(cardano_info_action_t** info_action); - /** * \brief Increases the reference count of the cardano_info_action_t object. * diff --git a/lib/include/cardano/proposal_procedures/new_constitution_action.h b/lib/include/cardano/proposal_procedures/new_constitution_action.h index 478031f4..01d30e75 100644 --- a/lib/include/cardano/proposal_procedures/new_constitution_action.h +++ b/lib/include/cardano/proposal_procedures/new_constitution_action.h @@ -351,33 +351,6 @@ cardano_new_constitution_action_get_governance_action_id(cardano_new_constitutio */ CARDANO_EXPORT void cardano_new_constitution_action_unref(cardano_new_constitution_action_t** new_constitution_action); -/** - * \brief Decrements the reference count of a cardano_new_constitution_action_t object. - * - * This function is responsible for managing the lifecycle of a \ref cardano_new_constitution_action_t object - * by decreasing its reference count. When the reference count reaches zero, the new_constitution_action is - * finalized; its associated resources are released, and its memory is deallocated. - * - * \param[in,out] new_constitution_action A pointer to the pointer of the new_constitution_action object. This double - * indirection allows the function to set the caller's pointer to - * NULL, avoiding dangling pointer issues after the object has been - * freed. - * - * Usage Example: - * \code{.c} - * cardano_new_constitution_action_t* new_constitution_action = cardano_new_constitution_action_new(major, minor); - * - * // Perform operations with the new_constitution_action... - * - * cardano_new_constitution_action_unref(&new_constitution_action); - * // At this point, new_constitution_action is NULL and cannot be used. - * \endcode - * - * \note After calling \ref cardano_new_constitution_action_unref, the pointer to the \ref cardano_new_constitution_action_t object - * will be set to NULL to prevent its reuse. - */ -CARDANO_EXPORT void cardano_new_constitution_action_unref(cardano_new_constitution_action_t** new_constitution_action); - /** * \brief Increases the reference count of the cardano_new_constitution_action_t object. * diff --git a/lib/include/cardano/proposal_procedures/no_confidence_action.h b/lib/include/cardano/proposal_procedures/no_confidence_action.h index 4f00bc08..6e52401f 100644 --- a/lib/include/cardano/proposal_procedures/no_confidence_action.h +++ b/lib/include/cardano/proposal_procedures/no_confidence_action.h @@ -278,33 +278,6 @@ cardano_no_confidence_action_get_governance_action_id(cardano_no_confidence_acti */ CARDANO_EXPORT void cardano_no_confidence_action_unref(cardano_no_confidence_action_t** no_confidence_action); -/** - * \brief Decrements the reference count of a cardano_no_confidence_action_t object. - * - * This function is responsible for managing the lifecycle of a \ref cardano_no_confidence_action_t object - * by decreasing its reference count. When the reference count reaches zero, the no_confidence_action is - * finalized; its associated resources are released, and its memory is deallocated. - * - * \param[in,out] no_confidence_action A pointer to the pointer of the no_confidence_action object. This double - * indirection allows the function to set the caller's pointer to - * NULL, avoiding dangling pointer issues after the object has been - * freed. - * - * Usage Example: - * \code{.c} - * cardano_no_confidence_action_t* no_confidence_action = cardano_no_confidence_action_new(major, minor); - * - * // Perform operations with the no_confidence_action... - * - * cardano_no_confidence_action_unref(&no_confidence_action); - * // At this point, no_confidence_action is NULL and cannot be used. - * \endcode - * - * \note After calling \ref cardano_no_confidence_action_unref, the pointer to the \ref cardano_no_confidence_action_t object - * will be set to NULL to prevent its reuse. - */ -CARDANO_EXPORT void cardano_no_confidence_action_unref(cardano_no_confidence_action_t** no_confidence_action); - /** * \brief Increases the reference count of the cardano_no_confidence_action_t object. * diff --git a/lib/include/cardano/proposal_procedures/parameter_change_action.h b/lib/include/cardano/proposal_procedures/parameter_change_action.h index b67e1c34..619d979a 100644 --- a/lib/include/cardano/proposal_procedures/parameter_change_action.h +++ b/lib/include/cardano/proposal_procedures/parameter_change_action.h @@ -436,33 +436,6 @@ cardano_parameter_change_action_get_governance_action_id(cardano_parameter_chang */ CARDANO_EXPORT void cardano_parameter_change_action_unref(cardano_parameter_change_action_t** parameter_change_action); -/** - * \brief Decrements the reference count of a cardano_parameter_change_action_t object. - * - * This function is responsible for managing the lifecycle of a \ref cardano_parameter_change_action_t object - * by decreasing its reference count. When the reference count reaches zero, the parameter_change_action is - * finalized; its associated resources are released, and its memory is deallocated. - * - * \param[in,out] parameter_change_action A pointer to the pointer of the parameter_change_action object. This double - * indirection allows the function to set the caller's pointer to - * NULL, avoiding dangling pointer issues after the object has been - * freed. - * - * Usage Example: - * \code{.c} - * cardano_parameter_change_action_t* parameter_change_action = cardano_parameter_change_action_new(major, minor); - * - * // Perform operations with the parameter_change_action... - * - * cardano_parameter_change_action_unref(¶meter_change_action); - * // At this point, parameter_change_action is NULL and cannot be used. - * \endcode - * - * \note After calling \ref cardano_parameter_change_action_unref, the pointer to the \ref cardano_parameter_change_action_t object - * will be set to NULL to prevent its reuse. - */ -CARDANO_EXPORT void cardano_parameter_change_action_unref(cardano_parameter_change_action_t** parameter_change_action); - /** * \brief Increases the reference count of the cardano_parameter_change_action_t object. * diff --git a/lib/include/cardano/proposal_procedures/treasury_withdrawals_action.h b/lib/include/cardano/proposal_procedures/treasury_withdrawals_action.h index eb7dd527..3890c066 100644 --- a/lib/include/cardano/proposal_procedures/treasury_withdrawals_action.h +++ b/lib/include/cardano/proposal_procedures/treasury_withdrawals_action.h @@ -356,33 +356,6 @@ cardano_treasury_withdrawals_action_get_policy_hash(cardano_treasury_withdrawals */ CARDANO_EXPORT void cardano_treasury_withdrawals_action_unref(cardano_treasury_withdrawals_action_t** treasury_withdrawals_action); -/** - * \brief Decrements the reference count of a cardano_treasury_withdrawals_action_t object. - * - * This function is responsible for managing the lifecycle of a \ref cardano_treasury_withdrawals_action_t object - * by decreasing its reference count. When the reference count reaches zero, the treasury_withdrawals_action is - * finalized; its associated resources are released, and its memory is deallocated. - * - * \param[in,out] treasury_withdrawals_action A pointer to the pointer of the treasury_withdrawals_action object. This double - * indirection allows the function to set the caller's pointer to - * NULL, avoiding dangling pointer issues after the object has been - * freed. - * - * Usage Example: - * \code{.c} - * cardano_treasury_withdrawals_action_t* treasury_withdrawals_action = cardano_treasury_withdrawals_action_new(major, minor); - * - * // Perform operations with the treasury_withdrawals_action... - * - * cardano_treasury_withdrawals_action_unref(&treasury_withdrawals_action); - * // At this point, treasury_withdrawals_action is NULL and cannot be used. - * \endcode - * - * \note After calling \ref cardano_treasury_withdrawals_action_unref, the pointer to the \ref cardano_treasury_withdrawals_action_t object - * will be set to NULL to prevent its reuse. - */ -CARDANO_EXPORT void cardano_treasury_withdrawals_action_unref(cardano_treasury_withdrawals_action_t** treasury_withdrawals_action); - /** * \brief Increases the reference count of the cardano_treasury_withdrawals_action_t object. * diff --git a/lib/include/cardano/proposal_procedures/update_committee_action.h b/lib/include/cardano/proposal_procedures/update_committee_action.h index cb944954..9b43d605 100644 --- a/lib/include/cardano/proposal_procedures/update_committee_action.h +++ b/lib/include/cardano/proposal_procedures/update_committee_action.h @@ -512,33 +512,6 @@ cardano_update_committee_action_get_governance_action_id(cardano_update_committe */ CARDANO_EXPORT void cardano_update_committee_action_unref(cardano_update_committee_action_t** update_committee_action); -/** - * \brief Decrements the reference count of a cardano_update_committee_action_t object. - * - * This function is responsible for managing the lifecycle of a \ref cardano_update_committee_action_t object - * by decreasing its reference count. When the reference count reaches zero, the update_committee_action is - * finalized; its associated resources are released, and its memory is deallocated. - * - * \param[in,out] update_committee_action A pointer to the pointer of the update_committee_action object. This double - * indirection allows the function to set the caller's pointer to - * NULL, avoiding dangling pointer issues after the object has been - * freed. - * - * Usage Example: - * \code{.c} - * cardano_update_committee_action_t* update_committee_action = cardano_update_committee_action_new(major, minor); - * - * // Perform operations with the update_committee_action... - * - * cardano_update_committee_action_unref(&update_committee_action); - * // At this point, update_committee_action is NULL and cannot be used. - * \endcode - * - * \note After calling \ref cardano_update_committee_action_unref, the pointer to the \ref cardano_update_committee_action_t object - * will be set to NULL to prevent its reuse. - */ -CARDANO_EXPORT void cardano_update_committee_action_unref(cardano_update_committee_action_t** update_committee_action); - /** * \brief Increases the reference count of the cardano_update_committee_action_t object. * diff --git a/lib/include/cardano/protocol_params/protocol_parameters.h b/lib/include/cardano/protocol_params/protocol_parameters.h index c50d4b71..477adf57 100644 --- a/lib/include/cardano/protocol_params/protocol_parameters.h +++ b/lib/include/cardano/protocol_params/protocol_parameters.h @@ -289,10 +289,7 @@ CARDANO_EXPORT uint64_t cardano_protocol_parameters_get_pool_deposit( const cardano_protocol_parameters_t* protocol_parameters); /** - * \brief Retrieves the maximum epoch value from the protocol parameters. - * - * This function returns the maximum epoch duration allowed in the Cardano blockchain, as specified - * in the given \ref cardano_protocol_parameters_t object. + * \brief Retrieves the max pool retirement epoch bounds. * * \param[in] protocol_parameters A pointer to an initialized \ref cardano_protocol_parameters_t object. * This parameter must not be NULL. diff --git a/lib/include/cardano/providers/provider.h b/lib/include/cardano/providers/provider.h new file mode 100644 index 00000000..ff9a3cca --- /dev/null +++ b/lib/include/cardano/providers/provider.h @@ -0,0 +1,720 @@ +/** + * \file provider.h + * + * \author angel.castillo + * \date Sep 27, 2024 + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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 BIGLUP_LABS_INCLUDE_CARDANO_PROVIDER_H +#define BIGLUP_LABS_INCLUDE_CARDANO_PROVIDER_H + +/* INCLUDES ******************************************************************/ + +#include +#include + +#include + +/* DECLARATIONS **************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * \brief Opaque structure representing a Cardano blockchain data provider instance. + * + * This structure serves as a handle to a Cardano provider, encapsulating the necessary context and + * state required to interact with the Cardano blockchain. + */ +typedef struct cardano_provider_t cardano_provider_t; + +/** + * \brief Creates a new `cardano_provider_t` object using the provided implementation. + * + * This function initializes a new \ref cardano_provider_t object by wrapping the given + * \ref cardano_provider_impl_t implementation. The newly created provider object manages + * the lifecycle of the underlying implementation and provides an interface for interacting + * with the Cardano blockchain functionalities. + * + * \param[in] impl The provider implementation containing function pointers and context. + * \param[out] provider A pointer to store the address of the newly created provider object. + * This should be a valid pointer to a \ref cardano_provider_t* variable. + * + * \returns A \ref cardano_error_t indicating success or failure of the operation. + * + * Usage Example: + * \code{.c} + * // Assume 'impl' is a valid cardano_provider_impl_t initialized elsewhere. + * cardano_provider_t* provider = NULL; + * cardano_error_t result = cardano_provider_new(impl, &provider); + * + * if (result == CARDANO_SUCCESS) + * { + * // Use the provider + * } + * else + * { + * printf("Error: %s\n", cardano_error_to_string(result)); + * } + * + * // When done with the provider + * cardano_provider_unref(&provider); + * \endcode + * + * \note After successfully creating a \ref cardano_provider_t object, you are responsible for + * managing its lifecycle. Ensure that you call \ref cardano_provider_unref when the + * provider is no longer needed to release resources and prevent memory leaks. + */ +CARDANO_NODISCARD +CARDANO_EXPORT cardano_error_t cardano_provider_new(cardano_provider_impl_t impl, cardano_provider_t** provider); + +/** + * \brief Retrieves the name of the provider implementation. + * + * This function returns a constant string representing the name of the provider implementation. + * The name can be used for logging, debugging, or informational purposes to identify which + * provider implementation is being used. + * + * \param[in] provider Pointer to the \ref cardano_provider_t object. + * + * \returns A constant character pointer to the provider's name string. + * The returned string is owned by the provider and must not be modified or freed by the caller. + * If the \p provider is NULL or invalid, the function may return NULL. + * + * Usage Example: + * \code{.c} + * const char* provider_name = cardano_provider_get_name(provider); + * + * if (provider_name) + * { + * printf("Using provider: %s\n", provider_name); + * } + * else + * { + * printf("Failed to retrieve provider name.\n"); + * } + * \endcode + * + * \note The returned string remains valid as long as the \ref cardano_provider_t object is valid. + * Do not attempt to modify or free the returned string. + */ +CARDANO_NODISCARD +CARDANO_EXPORT const char* cardano_provider_get_name(const cardano_provider_t* provider); + +/** + * \brief Retrieves the current protocol parameters from the Cardano blockchain. + * + * This function obtains the protocol parameters using the specified provider instance. + * The protocol parameters include important configuration details of the Cardano network, + * such as parameters to compute fees, maximum block sizes, and other protocol-related settings. + * + * \param[in] provider Pointer to the \ref cardano_provider_t instance. + * Must not be `NULL`. + * \param[out] parameters Pointer to a location where the function will store a pointer + * to the retrieved \ref cardano_protocol_parameters_t structure. + * This parameter must not be `NULL`. On success, the caller is + * responsible for freeing the returned \ref cardano_protocol_parameters_t + * object using the \ref cardano_protocol_parameters_unref function. + * + * \returns A \ref cardano_error_t indicating success or failure of the operation. + * - \ref CARDANO_SUCCESS on success. + * - An appropriate error code on failure. + * + * Usage Example: + * \code{.c} + * cardano_protocol_parameters_t* parameters = NULL; + * cardano_error_t result = cardano_provider_get_parameters(provider, ¶meters); + * + * if (result == CARDANO_SUCCESS) + * { + * // Use the parameters + * // ... + * // When done, free the parameters + * cardano_protocol_parameters_unref(parameters); + * } + * else + * { + * printf("Error: %s\n", cardano_error_to_string(result)); + * } + * + * cardano_provider_unref(&provider); + * \endcode + */ +CARDANO_NODISCARD +CARDANO_EXPORT cardano_error_t +cardano_provider_get_parameters(cardano_provider_t* provider, cardano_protocol_parameters_t** parameters); + +/** + * \brief Retrieves unspent transaction outputs (UTXOs) for a given address. + * + * This function obtains a list of unspent transaction outputs (UTXOs) associated with the specified + * address using the provided Cardano provider instance. UTXOs represent funds that can be used as + * inputs in new transactions. Retrieving UTXOs is essential for constructing transactions and + * managing balances. + * + * \param[in] provider Pointer to the \ref cardano_provider_t instance. + * Must not be `NULL`. + * \param[in] address Pointer to a \ref cardano_address_t representing the address for which + * to retrieve unspent outputs. Must not be `NULL`. + * \param[out] utxo_list Pointer to a location where the function will store a pointer to the + * retrieved \ref cardano_utxo_list_t structure. Must not be `NULL`. On success, + * the caller is responsible for freeing the returned \ref cardano_utxo_list_t + * object using the \ref cardano_utxo_list_unref function. + * + * \returns A \ref cardano_error_t indicating success or failure of the operation. + * - \ref CARDANO_SUCCESS on success. + * - An appropriate error code on failure. + * + * Usage Example: + * \code{.c} + * cardano_utxo_list_t* utxo_list = NULL; + * cardano_error_t result = cardano_provider_get_unspent_outputs(provider, address, &utxo_list); + * + * if (result == CARDANO_SUCCESS) + * { + * // Iterate over the UTXOs + * size_t utxo_count = cardano_utxo_list_length(utxo_list); + * + * for (size_t i = 0U; i < utxo_count; ++i) + * { + * // Process the UTXOs + * // ... + * } + * // When done, free the UTXO list + * cardano_utxo_list_unref(utxo_list); + * } + * else + * { + * printf("Error: %s\n", cardano_error_to_string(result)); + * } + * + * cardano_provider_unref(&provider); + * \endcode + */ +CARDANO_NODISCARD +CARDANO_EXPORT cardano_error_t +cardano_provider_get_unspent_outputs( + cardano_provider_t* provider, + cardano_address_t* address, + cardano_utxo_list_t** utxo_list); + +/** + * \brief Retrieves the staking rewards for a given address. + * + * This function obtains the current available staking rewards associated with the specified address. + * It uses the provided Cardano provider instance to access blockchain data and retrieve + * the reward balance for the address. Staking rewards are accumulated for addresses that + * delegate their stake to a stake pool. + * + * \param[in] provider Pointer to the \ref cardano_provider_t instance. + * Must not be `NULL`. + * \param[in] address Pointer to a \ref cardano_address_t representing the address + * for which to retrieve staking rewards. Must not be `NULL`. + * \param[out] rewards Pointer to a `uint64_t` variable where the function will store + * the amount of rewards (in Lovelace). Must not be `NULL`. + * + * \returns A \ref cardano_error_t indicating success or failure of the operation. + * - Other error codes for provider-specific failures. + * + * Example: + * \code{.c} + * uint64_t rewards = 0; + * + * cardano_error_t result = cardano_provider_get_rewards_available(provider, address, &rewards) + * + * if (result == CARDANO_SUCCESS) + * { + * // Use the rewards value + * printf("Rewards: %ul Lovelace\n", rewards); + * } + * else + * { + * printf("Error: %s\n", cardano_error_to_string(result)); + * } + * + * cardano_provider_unref(&provider); + * + * \endcode + */ +CARDANO_NODISCARD +CARDANO_EXPORT cardano_error_t +cardano_provider_get_rewards_available(cardano_provider_t* provider, cardano_reward_address_t* address, uint64_t* rewards); + +/** + * \brief Retrieves unspent transaction outputs (UTXOs) for a given address that contain a specific asset. + * + * This function obtains a list of unspent transaction outputs (UTXOs) associated with the specified + * address that contain the specified asset. It uses the provided Cardano provider instance to access + * the blockchain data. UTXOs represent funds that can be used as inputs in new transactions. + * + * \param[in] provider Pointer to the \ref cardano_provider_t instance. + * Must not be `NULL`. + * \param[in] address Pointer to a \ref cardano_address_t representing the address for which + * to retrieve unspent outputs. Must not be `NULL`. + * \param[in] asset_id Pointer to a \ref cardano_asset_id_t representing the asset identifier + * used to filter the UTXOs. Must not be `NULL`. + * \param[out] utxo_list Pointer to a location where the function will store a pointer to the + * retrieved \ref cardano_utxo_list_t structure containing UTXOs that include + * the specified asset. Must not be `NULL`. On success, the caller is responsible + * for freeing the returned \ref cardano_utxo_list_t object using the \ref cardano_utxo_list_unref function. + * + * \returns A \ref cardano_error_t indicating success or failure of the operation. + * - \ref CARDANO_SUCCESS on success. + * - Other error codes for provider-specific failures. + * + * Usage Example: + * \code{.c} + * cardano_utxo_list_t* utxo_list = NULL; + * cardano_error_t result = cardano_provider_get_unspent_outputs_with_asset(provider, address, asset_id, &utxo_list); + * + * if (result == CARDANO_SUCCESS) + * { + * // Iterate over the UTXOs + * size_t utxo_count = cardano_utxo_list_length(utxo_list); + * + * for (size_t i = 0U; i < utxo_count; ++i) + * { + * // Process the UTXO + * // ... + * } + * + * // When done, free the UTXO list + * cardano_utxo_list_unref(utxo_list); + * } + * else + * { + * printf("Error: %s\n", cardano_error_to_string(result)); + * } + * + * cardano_provider_unref(&provider); + * \endcode + */ +CARDANO_NODISCARD +CARDANO_EXPORT cardano_error_t cardano_provider_get_unspent_outputs_with_asset( + cardano_provider_t* provider, + cardano_address_t* address, + cardano_asset_id_t* asset_id, + cardano_utxo_list_t** utxo_list); + +/** + * \brief Retrieves an unspent transaction output (UTXO) containing a specific Non-Fungible Token (NFT). + * + * This function obtains a UTXO that contains the specified NFT (Non-Fungible Token) identified by the given asset ID. + * It uses the provided Cardano provider instance to access the blockchain data. + * + * \param[in] provider Pointer to the \ref cardano_provider_t instance. + * Must not be `NULL`. + * \param[in] asset_id Pointer to a \ref cardano_asset_id_t representing the asset identifier of the NFT. + * Must not be `NULL`. + * \param[out] utxo Pointer to a location where the function will store a pointer to the retrieved + * \ref cardano_utxo_t structure containing the NFT. Must not be `NULL`. + * On success, the caller is responsible for freeing the returned \ref cardano_utxo_t + * object using the \ref cardano_utxo_unref function. + * + * \returns A \ref cardano_error_t indicating success or failure of the operation. + * - \ref CARDANO_SUCCESS on success. + * - Other error codes for provider-specific failures. + * + * Usage Example: + * \code{.c} + * cardano_utxo_t* utxo = NULL; + * cardano_error_t result = cardano_provider_get_unspent_output_by_nft(provider, asset_id, &utxo); + * + * if (result == CARDANO_SUCCESS) + * { + * // Use the UTXO containing the NFT + * // ... + * // When done, free the UTXO + * cardano_utxo_unref(utxo); + * } + * else + * { + * printf("Error: %s\n", cardano_error_to_string(result)); + * } + * + * cardano_provider_unref(&provider); + * \endcode + */ +CARDANO_NODISCARD +CARDANO_EXPORT cardano_error_t cardano_provider_get_unspent_output_by_nft( + cardano_provider_t* provider, + cardano_asset_id_t* asset_id, + cardano_utxo_t** utxo); + +/** + * \brief Resolves unspent transaction outputs (UTXOs) for given transaction inputs. + * + * This function obtains the unspent transaction outputs (UTXOs) corresponding to the specified + * set of transaction inputs. It uses the provided Cardano provider instance to access the blockchain + * data. + * + * \param[in] provider Pointer to the \ref cardano_provider_t instance. + * Must not be `NULL`. + * \param[in] tx_ins Pointer to a \ref cardano_transaction_input_set_t containing the set of + * transaction inputs to resolve. Must not be `NULL`. + * \param[out] utxo_list Pointer to a location where the function will store a pointer to the + * retrieved \ref cardano_utxo_list_t structure containing the resolved UTXOs. + * Must not be `NULL`. On success, the caller is responsible for freeing the + * returned \ref cardano_utxo_list_t object using the \ref cardano_utxo_list_unref function. + * + * \returns A \ref cardano_error_t indicating success or failure of the operation. + * - Other error codes for provider-specific failures. + * + * Usage Example: + * \code{.c} + * cardano_utxo_list_t* utxo_list = NULL; + * cardano_error_t result = cardano_provider_resolve_unspent_outputs(provider, tx_ins, &utxo_list); + * + * if (result == CARDANO_SUCCESS) + * { + * // Iterate over the resolved UTXOs + * size_t utxo_count = cardano_utxo_list_length(utxo_list); + * + * for (size_t i = 0; i < utxo_count; ++i) + * { + * // Process the UTXO + * // ... + * } + * + * // When done, free the UTXO list + * cardano_utxo_list_unref(utxo_list); + * } + * else + * { + * printf("Error: %s\n", cardano_error_to_string(result)); + * } + * + * cardano_provider_unref(&provider); + * \endcode + */ +CARDANO_EXPORT cardano_error_t cardano_provider_resolve_unspent_outputs( + cardano_provider_t* provider, + cardano_transaction_input_set_t* tx_ins, + cardano_utxo_list_t** utxo_list); + +/** + * \brief Resolves a Plutus datum from its hash. + * + * This function retrieves the Plutus datum associated with the given datum hash. + * It uses the provided Cardano provider instance to access the blockchain data. + * + * \param[in] provider Pointer to the \ref cardano_provider_t instance. + * Must not be `NULL`. + * \param[in] datum_hash Pointer to a \ref cardano_blake2b_hash_t representing the hash + * of the datum to resolve. Must not be `NULL`. + * \param[out] datum Pointer to a location where the function will store a pointer + * to the retrieved \ref cardano_plutus_data_t structure containing + * the datum. Must not be `NULL`. On success, the caller is responsible + * for freeing the returned \ref cardano_plutus_data_t object using + * the \ref cardano_plutus_data_unref function. + * + * \returns A \ref cardano_error_t indicating success or failure of the operation. + * - Other error codes for provider-specific failures. + * + * Usage Example: + * \code{.c} + * cardano_plutus_data_t* datum = NULL; + * cardano_error_t result = cardano_provider_resolve_datum(provider, datum_hash, &datum); + * + * if (result == CARDANO_SUCCESS) + * { + * // Use the retrieved datum + * // ... + * // When done, free the datum + * cardano_plutus_data_unref(datum); + * } + * else + * { + * printf("Error: %s\n", cardano_error_to_string(result)); + * } + * + * cardano_provider_unref(&provider); + * \endcode + */ +CARDANO_EXPORT cardano_error_t +cardano_provider_resolve_datum(cardano_provider_t* provider, cardano_blake2b_hash_t* datum_hash, cardano_plutus_data_t** datum); + +/** + * \brief Confirms the inclusion of a transaction in the blockchain within a specified timeout period. + * + * This function waits for the specified transaction to be confirmed (i.e., included in a block) + * within a given timeout period. It uses the provided Cardano provider instance to monitor + * the blockchain for the transaction's confirmation. + * + * \param[in] provider Pointer to the \ref cardano_provider_t instance. + * Must not be `NULL`. + * \param[in] tx_id Pointer to a \ref cardano_blake2b_hash_t representing the transaction ID + * to confirm. Must not be `NULL`. + * \param[in] timeout_ms Timeout in milliseconds to wait for the transaction confirmation. + * If the transaction is not confirmed within this period, the function will return + * an appropriate error code. + * \param[out] confirmed Pointer to a boolean variable where the function will store the confirmation status. + * + * \returns A \ref cardano_error_t indicating success or failure of the operation. + * + * Usage Example: + * \code{.c} + * bool confirmed = false; + * cardano_error_t result = cardano_provider_confirm_transaction(provider, tx_id, 60000, &confirmed); // Wait up to 60 seconds + * + * if (result == CARDANO_SUCCESS) + * { + * // Check if the transaction is confirmed + * // Proceed with dependent operations + * } + * else + * { + * printf("Error: %s\n", cardano_error_to_string(result)); + * } + * + * cardano_provider_unref(&provider); + * \endcode + */ +CARDANO_EXPORT cardano_error_t +cardano_provider_confirm_transaction( + cardano_provider_t* provider, + cardano_blake2b_hash_t* tx_id, + uint64_t timeout_ms, + bool* confirmed); + +/** + * \brief Submits a transaction to the Cardano blockchain network. + * + * This function submits the given transaction to the Cardano network for processing. + * Upon successful submission, it returns the transaction ID (hash) of the submitted transaction. + * The transaction ID can be used to track the transaction's status and confirmation. + * + * \param[in] provider Pointer to the \ref cardano_provider_t instance. + * Must not be `NULL`. + * \param[in] tx Pointer to the \ref cardano_transaction_t representing the transaction to submit. + * Must not be `NULL`. + * \param[out] tx_id Pointer to a location where the function will store a pointer to the + * newly allocated \ref cardano_blake2b_hash_t representing the transaction ID. + * Must not be `NULL`. On success, the caller is responsible for freeing the + * returned \ref cardano_blake2b_hash_t object using the appropriate deallocation function. + * + * \returns A \ref cardano_error_t indicating success or failure of the operation. + * - Other error codes for provider-specific failures. + * + * Usage Example: + * \code{.c} + * cardano_blake2b_hash_t* tx_id = NULL; + * cardano_error_t result = cardano_provider_submit_transaction(provider, tx, &tx_id); + * + * if (result == CARDANO_SUCCESS) + * { + * // Transaction submitted successfully + * // Use tx_id to track the transaction + * // ... + * // When done, free the transaction ID + * cardano_blake2b_hash_unref(tx_id); + * } + * else + * { + * printf("Error: %s\n", cardano_error_to_string(result)); + * } + * + * cardano_provider_unref(&provider); + * \endcode + */ +CARDANO_EXPORT cardano_error_t +cardano_provider_submit_transaction( + cardano_provider_t* provider, + cardano_transaction_t* tx, + cardano_blake2b_hash_t** tx_id); + +/** + * \brief Evaluates a transaction to estimate the required execution units for Plutus scripts. + * + * This function evaluates the given transaction to determine the execution units (e.g., memory and CPU usage) + * required by any Plutus scripts involved in the transaction. The evaluation considers any additional UTXOs + * and redeemers provided. + * + * \param[in] provider Pointer to the \ref cardano_provider_t instance. + * Must not be `NULL`. + * \param[in] tx Pointer to the \ref cardano_transaction_t representing the transaction to evaluate. + * Must not be `NULL`. + * \param[in] additional_utxos Pointer to a \ref cardano_utxo_list_t containing additional UTXOs required + * for evaluation. Can be `NULL` if not needed. + * \param[out] redeemers Pointer to a location where the function will store a pointer to the + * \ref cardano_redeemer_list_t containing updated redeemers with estimated + * execution units. Must not be `NULL`. On success, the caller is responsible + * for freeing the returned \ref cardano_redeemer_list_t object using the + * \ref cardano_redeemer_list_unref function. + * + * \returns A \ref cardano_error_t indicating success or failure of the operation. + * - Other error codes for provider-specific failures. + * + * Usage Example: + * \code{.c} + * cardano_redeemer_list_t* redeemers = NULL; + * + * cardano_error_t result = cardano_provider_evaluate_transaction(provider, tx, additional_utxos, &redeemers); + * + * if (result == CARDANO_SUCCESS) + * { + * // Use the updated redeemers with estimated execution units + * // ... + * // When done, free the redeemers list + * cardano_redeemer_list_unref(redeemers); + * } + * else + * { + * printf("Error: %s\n", cardano_error_to_string(result)); + * } + * + * cardano_provider_unref(&provider); + * \endcode + */ +CARDANO_EXPORT cardano_error_t +cardano_provider_evaluate_transaction( + cardano_provider_t* provider, + cardano_transaction_t* tx, + cardano_utxo_list_t* additional_utxos, + cardano_redeemer_list_t** redeemers); + +/** + * \brief Decrements the reference count of a cardano_provider_t object. + * + * This function is responsible for managing the lifecycle of a \ref cardano_provider_t object + * by decreasing its reference count. When the reference count reaches zero, the provider is + * finalized; its associated resources are released, and its memory is deallocated. + * + * \param[in,out] provider A pointer to the pointer of the provider object. This double + * indirection allows the function to set the caller's pointer to + * NULL, avoiding dangling pointer issues after the object has been + * freed. + * + * Usage Example: + * \code{.c} + * cardano_provider_t* provider = cardano_provider_new(major, minor); + * + * // Perform operations with the provider... + * + * cardano_provider_unref(&provider); + * // At this point, provider is NULL and cannot be used. + * \endcode + * + * \note After calling \ref cardano_provider_unref, the pointer to the \ref cardano_provider_t object + * will be set to NULL to prevent its reuse. + */ +CARDANO_EXPORT void cardano_provider_unref(cardano_provider_t** provider); + +/** + * \brief Increases the reference count of the cardano_provider_t object. + * + * This function is used to manually increment the reference count of an cardano_provider_t + * object, indicating that another part of the code has taken ownership of it. This + * ensures the object remains allocated and valid until all owners have released their + * reference by calling \ref cardano_provider_unref. + * + * \param provider A pointer to the cardano_provider_t object whose reference count is to be incremented. + * + * Usage Example: + * \code{.c} + * // Assuming provider is a previously created provider object + * + * cardano_provider_ref(provider); + * + * // Now provider can be safely used elsewhere without worrying about premature deallocation + * \endcode + * + * \note Always ensure that for every call to \ref cardano_provider_ref there is a corresponding + * call to \ref cardano_provider_unref to prevent memory leaks. + */ +CARDANO_EXPORT void cardano_provider_ref(cardano_provider_t* provider); + +/** + * \brief Retrieves the current reference count of the cardano_provider_t object. + * + * This function returns the number of active references to an cardano_provider_t object. It's useful + * for debugging purposes or managing the lifecycle of the object in complex scenarios. + * + * \warning This function does not account for transitive references. A transitive reference + * occurs when an object holds a reference to another object, rather than directly to the + * cardano_provider_t. As such, the reported count may not fully represent the total number + * of conceptual references in cases where such transitive relationships exist. + * + * \param provider A pointer to the cardano_provider_t object whose reference count is queried. + * The object must not be NULL. + * + * \return The number of active references to the specified cardano_provider_t object. If the object + * is properly managed (i.e., every \ref cardano_provider_ref call is matched with a + * \ref cardano_provider_unref call), this count should reach zero right before the object + * is deallocated. + * + * Usage Example: + * \code{.c} + * // Assuming provider is a previously created provider object + * + * size_t ref_count = cardano_provider_refcount(provider); + * + * printf("Reference count: %zu\n", ref_count); + * \endcode + */ +CARDANO_NODISCARD +CARDANO_EXPORT size_t cardano_provider_refcount(const cardano_provider_t* provider); + +/** + * \brief Sets the last error message for a given cardano_provider_t object. + * + * Records an error message in the provider's last_error buffer, overwriting any existing message. + * This is useful for storing descriptive error information that can be later retrieved. The message + * is truncated if it exceeds the buffer's capacity. + * + * \param[in] provider A pointer to the \ref cardano_provider_t instance whose last error message is + * to be set. If \c NULL, the function does nothing. + * \param[in] message A null-terminated string containing the error message. If \c NULL, the provider's + * last_error is set to an empty string, indicating no error. + * + * \note The error message is limited to 1023 characters, including the null terminator, due to the + * fixed size of the last_error buffer. + */ +CARDANO_EXPORT void cardano_provider_set_last_error( + cardano_provider_t* provider, + const char* message); + +/** + * \brief Retrieves the last error message recorded for a specific provider. + * + * This function returns a pointer to the null-terminated string containing + * the last error message set by \ref cardano_provider_set_last_error for the given + * provider. If no error message has been set, or if the last_error buffer was + * explicitly cleared, an empty string is returned, indicating no error. + * + * \param[in] provider A pointer to the \ref cardano_provider_t instance whose last error + * message is to be retrieved. If the provider is NULL, the function + * returns a generic error message indicating the null provider. + * + * \return A pointer to a null-terminated string containing the last error + * message for the specified provider. If the provider is NULL, "Object is NULL." + * is returned to indicate the error. + * + * \note The returned string points to internal storage within the object and + * must not be modified by the caller. The string remains valid until the + * next call to \ref cardano_provider_set_last_error for the same provider, or until + * the provider is deallocated. + */ +CARDANO_NODISCARD +CARDANO_EXPORT const char* cardano_provider_get_last_error(const cardano_provider_t* provider); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif // BIGLUP_LABS_INCLUDE_CARDANO_PROVIDER_H \ No newline at end of file diff --git a/lib/include/cardano/providers/provider_impl.h b/lib/include/cardano/providers/provider_impl.h new file mode 100644 index 00000000..b9082e34 --- /dev/null +++ b/lib/include/cardano/providers/provider_impl.h @@ -0,0 +1,327 @@ +/** + * \file provider_impl.h + * + * \author angel.castillo + * \date Sep 27, 2024 + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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 BIGLUP_LABS_INCLUDE_CARDANO_PROVIDER_IMPL_H +#define BIGLUP_LABS_INCLUDE_CARDANO_PROVIDER_IMPL_H + +/* INCLUDES ******************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* DECLARATIONS **************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* FORWARD DECLARATIONS *******************************************************/ + +/** + * \brief Opaque structure representing the implementation details of a Cardano blockchain data provider. + * + * This structure encapsulates the internal state and implementation details of a Cardano blockchain data provider. + * + * \note Users should interact with Cardano providers through the provided \ref cardano_provider_t API functions and should + * not attempt to manipulate this structure directly. + */ +typedef struct cardano_provider_impl_t cardano_provider_impl_t; + +/* CALLBACKS *****************************************************************/ + +/** + * \brief Function pointer type for retrieving protocol parameters. + * + * This function retrieves the protocol parameters using the given provider implementation. + * + * \param[in] provider_impl Pointer to the provider implementation instance. + * \param[out] parameters Pointer to store the retrieved protocol parameters. + * \return Error code indicating success or failure of the operation. + */ +typedef cardano_error_t (*cardano_get_parameters_func_t)( + cardano_provider_impl_t* provider_impl, + cardano_protocol_parameters_t** parameters); + +/** + * \brief Function pointer type for retrieving unspent outputs for an address. + * + * Retrieves a list of unspent transaction outputs (UTXOs) associated with the specified address. + * + * \param[in] provider_impl Pointer to the provider implementation instance. + * \param[in] address Address for which to retrieve unspent outputs. + * \param[out] utxo_list Pointer to store the list of unspent outputs. + * \return Error code indicating success or failure of the operation. + */ +typedef cardano_error_t (*cardano_get_unspent_outputs_func_t)( + cardano_provider_impl_t* provider_impl, + cardano_address_t* address, + cardano_utxo_list_t** utxo_list); + +/** + * \brief Function pointer type for retrieving staking rewards for an address. + * + * This function retrieves the current staking rewards associated with the specified address. + * It uses the provided provider implementation instance to access blockchain data and + * obtain the reward balance for the address. Staking rewards are accumulated for addresses + * that delegate their stake to a stake pool. + * + * \param[in] provider_impl Pointer to the provider implementation instance. + * Must not be `NULL`. + * \param[in] address Pointer to a \ref cardano_address_t representing the address + * for which to retrieve staking rewards. Must not be `NULL`. + * \param[out] rewards Pointer to a `uint64_t` variable where the function will store + * the amount of rewards (in Lovelace). Must not be `NULL`. + * + * \returns A \ref cardano_error_t indicating success or failure of the operation. + * - Other error codes for provider-specific failures. + */ +typedef cardano_error_t (*cardano_get_rewards_balance_func_t)( + cardano_provider_impl_t* provider_impl, + cardano_reward_address_t* address, + uint64_t* rewards); + +/** + * \brief Function pointer type for retrieving unspent outputs for an address and specific asset. + * + * Retrieves a list of unspent transaction outputs (UTXOs) associated with the specified address + * that contain the specified asset. + * + * \param[in] provider_impl Pointer to the provider implementation instance. + * \param[in] address Address for which to retrieve unspent outputs. + * \param[in] asset_id Asset identifier to filter UTXOs. + * \param[out] utxo_list Pointer to store the list of unspent outputs containing the asset. + * \return Error code indicating success or failure of the operation. + */ +typedef cardano_error_t (*cardano_get_unspent_outputs_with_asset_func_t)( + cardano_provider_impl_t* provider_impl, + cardano_address_t* address, + cardano_asset_id_t* asset_id, + cardano_utxo_list_t** utxo_list); + +/** + * \brief Function pointer type for retrieving an unspent output containing a specific NFT. + * + * Retrieves an unspent transaction output (UTXO) that contains the specified NFT (Non-Fungible Token). + * + * \param[in] provider_impl Pointer to the provider implementation instance. + * \param[in] asset_id Asset identifier of the NFT. + * \param[out] utxo Pointer to store the retrieved unspent output containing the NFT. + * \return Error code indicating success or failure of the operation. + */ +typedef cardano_error_t (*cardano_get_unspent_output_by_nft_func_t)( + cardano_provider_impl_t* provider_impl, + cardano_asset_id_t* asset_id, + cardano_utxo_t** utxo); + +/** + * \brief Function pointer type for resolving unspent outputs for given transaction inputs. + * + * Resolves a list of unspent transaction outputs (UTXOs) corresponding to the provided transaction inputs. + * + * \param[in] provider_impl Pointer to the provider implementation instance. + * \param[in] tx_ins Set of transaction inputs to resolve. + * \param[out] utxo_list Pointer to store the list of resolved unspent outputs. + * \return Error code indicating success or failure of the operation. + */ +typedef cardano_error_t (*cardano_resolve_unspent_outputs_func_t)( + cardano_provider_impl_t* provider_impl, + cardano_transaction_input_set_t* tx_ins, + cardano_utxo_list_t** utxo_list); + +/** + * \brief Function pointer type for resolving a datum from its hash. + * + * Retrieves the Plutus datum associated with the given datum hash. + * + * \param[in] provider_impl Pointer to the provider implementation instance. + * \param[in] datum_hash Hash of the datum to resolve. + * \param[out] datum Pointer to store the retrieved Plutus datum. + * \return Error code indicating success or failure of the operation. + */ +typedef cardano_error_t (*cardano_resolve_datum_func_t)( + cardano_provider_impl_t* provider_impl, + cardano_blake2b_hash_t* datum_hash, + cardano_plutus_data_t** datum); + +/** + * \brief Function pointer type for confirming a transaction's inclusion in the blockchain. + * + * Waits for the specified transaction to be confirmed within a given timeout period. + * + * \param[in] provider_impl Pointer to the provider implementation instance. + * \param[in] tx_id Transaction ID to confirm. + * \param[in] timeout_ms Timeout in milliseconds to wait for confirmation. + * \param[out] confirmed Pointer to store the confirmation status of the transaction. + * + * \return Error code indicating success or failure of the operation. + */ +typedef cardano_error_t (*cardano_confirm_transaction_func_t)( + cardano_provider_impl_t* provider_impl, + cardano_blake2b_hash_t* tx_id, + uint64_t timeout_ms, + bool* confirmed); + +/** + * \brief Function pointer type for submitting a transaction to the blockchain. + * + * Submits the given transaction to the network and returns its transaction ID. + * + * \param[in] provider_impl Pointer to the provider implementation instance. + * \param[in] tx Transaction to submit. + * \param[out] tx_id Pointer to store the transaction ID after submission. + * \return Error code indicating success or failure of the operation. + */ +typedef cardano_error_t (*cardano_submit_transaction_func_t)( + cardano_provider_impl_t* provider_impl, + cardano_transaction_t* tx, + cardano_blake2b_hash_t** tx_id); + +/** + * \brief Function pointer type for evaluating a transaction's execution units. + * + * Evaluates the execution units required by the transaction, considering any additional UTXOs and redeemers. + * + * \param[in] provider_impl Pointer to the provider implementation instance. + * \param[in] tx Transaction to evaluate. + * \param[in] additional_utxos Additional UTXOs required for evaluation (optional). + * \param[in] redeemers Redeemers to be evaluated with the transaction. + * \return Error code indicating success or failure of the operation. + */ +typedef cardano_error_t (*cardano_evaluate_transaction_func_t)( + cardano_provider_impl_t* provider_impl, + cardano_transaction_t* tx, + cardano_utxo_list_t* additional_utxos, + cardano_redeemer_list_t** redeemers); + +/* STRUCTURES ****************************************************************/ + +/** + * \brief Implementation of the Cardano provider interface. + * + * This structure contains the context and function pointers required to interact with + * the Cardano blockchain. It serves as the implementation of the provider interface, + * encapsulating the necessary state and behaviors. + */ +typedef struct cardano_provider_impl_t +{ + /** + * \brief Name of the provider implementation. + */ + char name[256]; + + /** + * \brief Error message buffer for provider-specific error messages. + */ + char error_message[1024]; + + /** + * \brief Opaque pointer to the implementation-specific context. + * + * This pointer holds the state or context required by the provider implementation. + * Users should not access or modify this directly. + */ + cardano_object_t* context; + + /** + * \brief Function to retrieve protocol parameters. + * + * \see cardano_get_parameters_func_t + */ + cardano_get_parameters_func_t get_parameters; + + /** + * \brief Function to retrieve unspent outputs for an address. + * + * \see cardano_get_unspent_outputs_func_t + */ + cardano_get_unspent_outputs_func_t get_unspent_outputs; + + /** + * \brief Function to retrieve rewards for an address. + * + * \see cardano_get_rewards_balance_func_t + */ + cardano_get_rewards_balance_func_t get_rewards_balance; + + /** + * \brief Function to retrieve unspent outputs for an address and asset. + * + * \see cardano_get_unspent_outputs_with_asset_func_t + */ + cardano_get_unspent_outputs_with_asset_func_t get_unspent_outputs_with_asset; + + /** + * \brief Function to retrieve an unspent output for a given NFT. + * + * \see cardano_get_unspent_output_by_nft_func_t + */ + cardano_get_unspent_output_by_nft_func_t get_unspent_output_by_nft; + + /** + * \brief Function to resolve unspent outputs for transaction inputs. + * + * \see cardano_resolve_unspent_outputs_func_t + */ + cardano_resolve_unspent_outputs_func_t resolve_unspent_outputs; + + /** + * \brief Function to resolve a datum for a given datum hash. + * + * \see cardano_resolve_datum_func_t + */ + cardano_resolve_datum_func_t resolve_datum; + + /** + * \brief Function to await transaction confirmation. + * + * \see cardano_confirm_transaction_func_t + */ + cardano_confirm_transaction_func_t await_transaction_confirmation; + + /** + * \brief Function to submit a transaction to the blockchain. + * + * \see cardano_submit_transaction_func_t + */ + cardano_submit_transaction_func_t post_transaction_to_chain; + + /** + * \brief Function to evaluate a transaction. + * + * \see cardano_evaluate_transaction_func_t + */ + cardano_evaluate_transaction_func_t evaluate_transaction; +} cardano_provider_impl_t; + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif // BIGLUP_LABS_INCLUDE_CARDANO_PROVIDER_IMPL_H \ No newline at end of file diff --git a/lib/include/cardano/scripts/native_scripts/script_pubkey.h b/lib/include/cardano/scripts/native_scripts/script_pubkey.h index b841281d..00db9252 100644 --- a/lib/include/cardano/scripts/native_scripts/script_pubkey.h +++ b/lib/include/cardano/scripts/native_scripts/script_pubkey.h @@ -257,7 +257,7 @@ cardano_script_pubkey_from_json(const char* json, size_t json_size, cardano_scri * \endcode */ CARDANO_NODISCARD -CARDANO_EXPORT cardano_error_t cardano_script_pubkey_get_key_hash(const cardano_script_pubkey_t* script_pubkey, cardano_blake2b_hash_t** key_hash); +CARDANO_EXPORT cardano_error_t cardano_script_pubkey_get_key_hash(cardano_script_pubkey_t* script_pubkey, cardano_blake2b_hash_t** key_hash); /** * \brief Sets the key hash for a script_pubkey. diff --git a/lib/include/cardano/transaction_body/value.h b/lib/include/cardano/transaction_body/value.h index 02899f8c..b0e12f9c 100644 --- a/lib/include/cardano/transaction_body/value.h +++ b/lib/include/cardano/transaction_body/value.h @@ -97,6 +97,51 @@ cardano_value_new( cardano_multi_asset_t* assets, cardano_value_t** value); +/** + * \brief Creates a \ref cardano_value_t from an asset map. + * + * This function creates a \ref cardano_value_t object from a given asset map (\ref cardano_asset_id_map_t). + * The resulting value represents the sum of the assets contained in the map, where each entry in the map is + * an asset ID and its corresponding quantity. + * + * \param[in] asset_map A pointer to an initialized \ref cardano_asset_id_map_t object containing the assets. + * This parameter must not be NULL. + * \param[out] value On successful creation, this will point to the newly created \ref cardano_value_t object. + * The caller is responsible for managing the lifecycle of the object and must call + * \ref cardano_value_unref when it is no longer needed. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if + * the value was successfully created, or an appropriate error code if an error occurred. + * + * \note The caller is responsible for managing the lifecycle of both the \ref cardano_value_t object and the \ref cardano_asset_id_map_t. + * + * Usage Example: + * \code{.c} + * cardano_asset_id_map_t* asset_map = ...; // Assume asset_map is initialized + * cardano_value_t* value = NULL; + * + * cardano_error_t result = cardano_value_from_asset_map(asset_map, &value); + * + * if (result == CARDANO_SUCCESS) + * { + * printf("Value successfully created from asset map.\n"); + * } + * else + * { + * printf("Failed to create value from asset map.\n"); + * } + * + * // Clean up when done + * cardano_asset_id_map_unref(&asset_map); + * cardano_value_unref(&value); + * \endcode + */ +CARDANO_NODISCARD +CARDANO_EXPORT cardano_error_t +cardano_value_from_asset_map( + cardano_asset_id_map_t* asset_map, + cardano_value_t** value); + /** * \brief Creates a \ref cardano_value_t from a CBOR reader. * diff --git a/lib/include/cardano/witness_set/redeemer_list.h b/lib/include/cardano/witness_set/redeemer_list.h index fcee47d4..e5242450 100644 --- a/lib/include/cardano/witness_set/redeemer_list.h +++ b/lib/include/cardano/witness_set/redeemer_list.h @@ -24,6 +24,7 @@ /* INCLUDES ******************************************************************/ +#include "redeemer_tag.h" #include #include #include @@ -283,6 +284,89 @@ CARDANO_EXPORT cardano_error_t cardano_redeemer_list_get(const cardano_redeemer_ CARDANO_NODISCARD CARDANO_EXPORT cardano_error_t cardano_redeemer_list_add(cardano_redeemer_list_t* redeemer_list, cardano_redeemer_t* element); +/** + * \brief Sets the execution units (memory and steps) for a specific redeemer in the redeemer list. + * + * This function sets the execution units (memory and steps) for a given redeemer identified by its tag and index within the \ref cardano_redeemer_list_t. + * + * \param[in,out] redeemer_list A pointer to an initialized \ref cardano_redeemer_list_t object where the execution units will be set. + * This parameter must not be NULL. + * \param[in] tag The \ref cardano_redeemer_tag_t representing the type of the redeemer (e.g., spending, minting). + * \param[in] index The index of the redeemer for which the execution units will be set. + * \param[in] mem The amount of memory required for the redeemer execution. + * \param[in] steps The number of steps required for the redeemer execution. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the execution units were successfully set, + * or an appropriate error code indicating the failure reason (e.g., \ref CARDANO_ERROR_INDEX_OUT_OF_BOUNDS if the specified index is invalid). + * + * Usage Example: + * \code{.c} + * cardano_redeemer_list_t* redeemer_list = ...; // Assume redeemer_list is initialized + * cardano_redeemer_tag_t tag = CARDANO_REDEEMER_TAG_SPEND; + * uint64_t index = 0; + * uint64_t mem = 5000; + * uint64_t steps = 10000; + * + * cardano_error_t result = cardano_redeemer_list_set_ex_units(redeemer_list, tag, index, mem, steps); + * + * if (result == CARDANO_SUCCESS) + * { + * printf("Execution units set successfully.\n"); + * } + * else + * { + * printf("Failed to set execution units: %d\n", result); + * } + * \endcode + */ +CARDANO_NODISCARD +CARDANO_EXPORT cardano_error_t cardano_redeemer_list_set_ex_units( + cardano_redeemer_list_t* redeemer_list, + cardano_redeemer_tag_t tag, + uint64_t index, + uint64_t mem, + uint64_t steps); + +/** + * \brief Deep clones a redeemer list. + * + * This function creates a deep copy of the specified \ref cardano_redeemer_list_t object. The cloned list contains copies of all the redeemers + * and their associated data, ensuring that modifications to the original list do not affect the cloned list and vice versa. + * + * \param[in] redeemer_list A pointer to an initialized \ref cardano_redeemer_list_t object that will be cloned. + * This parameter must not be NULL. + * \param[out] cloned_redeemer_list On success, this will point to a newly created \ref cardano_redeemer_list_t object that is a deep copy of the original list. + * The caller is responsible for managing the lifecycle of the cloned list and must call \ref cardano_redeemer_list_unref to release it when no longer needed. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the redeemer list was successfully cloned, + * or an appropriate error code indicating the failure reason (e.g., \ref CARDANO_ERROR_MEMORY_ALLOCATION_FAILED if memory allocation fails). + * + * Usage Example: + * \code{.c} + * cardano_redeemer_list_t* redeemer_list = ...; // Assume redeemer_list is initialized + * cardano_redeemer_list_t* cloned_redeemer_list = NULL; + * + * cardano_error_t result = cardano_redeemer_list_clone(redeemer_list, &cloned_redeemer_list); + * + * if (result == CARDANO_SUCCESS) + * { + * printf("Redeemer list successfully cloned.\n"); + * // Use the cloned list + * + * // Free the cloned list when no longer needed + * cardano_redeemer_list_unref(&cloned_redeemer_list); + * } + * else + * { + * printf("Failed to clone redeemer list: %d\n", result); + * } + * \endcode + */ +CARDANO_NODISCARD +CARDANO_EXPORT cardano_error_t cardano_redeemer_list_clone( + cardano_redeemer_list_t* redeemer_list, + cardano_redeemer_list_t** cloned_redeemer_list); + /** * \brief Clears the cached CBOR representation from a redeemer list. * diff --git a/lib/src/common/network_magic.c b/lib/src/common/network_magic.c new file mode 100644 index 00000000..5ef93d0d --- /dev/null +++ b/lib/src/common/network_magic.c @@ -0,0 +1,53 @@ +/** + * \file network_magic.c + * + * \author angel.castillo + * \date Sep 28, 2024 + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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. + */ + +/* INCLUDES ******************************************************************/ + +#include + +/* DEFINITIONS ***************************************************************/ + +const char* +cardano_network_magic_to_string(const cardano_network_magic_t network_magic) +{ + const char* message; + + switch (network_magic) + { + case CARDANO_NETWORK_MAGIC_PREPROD: + message = "preprod"; + break; + case CARDANO_NETWORK_MAGIC_PREVIEW: + message = "preview"; + break; + case CARDANO_NETWORK_MAGIC_SANCHONET: + message = "sanchonet"; + break; + case CARDANO_NETWORK_MAGIC_MAINNET: + message = "mainnet"; + break; + default: + message = "unknown"; + break; + } + + return message; +} diff --git a/lib/src/common/unit_interval.c b/lib/src/common/unit_interval.c index 8ed8bb32..0c0b3c64 100644 --- a/lib/src/common/unit_interval.c +++ b/lib/src/common/unit_interval.c @@ -86,17 +86,27 @@ compute_greatest_common_divisor(const uint64_t a, const uint64_t b) static void float_to_fraction(const double value, uint64_t* numerator, uint64_t* denominator) { - double dec_part = value - floor(value); + double int_part = floor(value); + double dec_part = value - int_part; + + if (dec_part == 0.0) + { + *numerator = (uint64_t)int_part; + *denominator = 1; + return; + } int64_t num_digits = snprintf(NULL, 0, "%.15f", dec_part) - 2; - *numerator = (int64_t)floor(dec_part * pow(10, (double)num_digits)); - *denominator = (int64_t)pow(10, (double)num_digits); + *numerator = (uint64_t)floor(dec_part * pow(10, (double)num_digits)); + *denominator = (uint64_t)pow(10, (double)num_digits); uint64_t greatest_common_divisor = compute_greatest_common_divisor(*numerator, *denominator); *numerator /= greatest_common_divisor; *denominator /= greatest_common_divisor; + + *numerator += (uint64_t)int_part * (*denominator); } /* STRUCTURES ****************************************************************/ diff --git a/lib/src/error.c b/lib/src/error.c index 4aec2e06..484b2878 100644 --- a/lib/src/error.c +++ b/lib/src/error.c @@ -39,133 +39,139 @@ cardano_error_to_string(const cardano_error_t error) message = "Generic error"; break; case CARDANO_ERROR_INSUFFICIENT_BUFFER_SIZE: - message = "Invalid operation. Insufficient buffer size"; + message = "Insufficient buffer size"; break; case CARDANO_ERROR_POINTER_IS_NULL: - message = "Invalid operation. Argument is a NULL pointer"; + message = "Argument is a NULL pointer"; break; case CARDANO_ERROR_MEMORY_ALLOCATION_FAILED: - message = "Invalid operation. Requested memory could not be allocated"; + message = "Requested memory could not be allocated"; break; case CARDANO_ERROR_OUT_OF_BOUNDS_MEMORY_READ: - message = "Invalid operation. Out of bounds memory read"; + message = "Out of bounds memory read"; break; case CARDANO_ERROR_OUT_OF_BOUNDS_MEMORY_WRITE: - message = "Invalid operation. Out of bounds memory write"; + message = "Out of bounds memory write"; break; case CARDANO_ERROR_INVALID_BLAKE2B_HASH_SIZE: - message = "Invalid operation. Invalid Blake2b hash size"; + message = "Invalid Blake2b hash size"; break; case CARDANO_ERROR_INVALID_ED25519_SIGNATURE_SIZE: - message = "Invalid operation. Invalid Ed25519 signature size"; + message = "Invalid Ed25519 signature size"; break; case CARDANO_ERROR_INVALID_ED25519_PUBLIC_KEY_SIZE: - message = "Invalid operation. Invalid Ed25519 public key size"; + message = "Invalid Ed25519 public key size"; break; case CARDANO_ERROR_INVALID_ED25519_PRIVATE_KEY_SIZE: - message = "Invalid operation. Invalid Ed25519 private key size"; + message = "Invalid Ed25519 private key size"; break; case CARDANO_ERROR_INVALID_BIP32_PUBLIC_KEY_SIZE: - message = "Invalid operation. Invalid BIP32 public key size"; + message = "Invalid BIP32 public key size"; break; case CARDANO_ERROR_INVALID_BIP32_PRIVATE_KEY_SIZE: - message = "Invalid operation. Invalid BIP32 private key size"; + message = "Invalid BIP32 private key size"; break; case CARDANO_ERROR_INVALID_ARGUMENT: - message = "Invalid operation. Invalid argument"; + message = "Invalid argument"; break; case CARDANO_ERROR_INVALID_URL: message = "Invalid argument. Invalid URL"; break; case CARDANO_ERROR_ELEMENT_NOT_FOUND: - message = "Invalid operation. Element not found"; + message = "Element not found"; break; case CARDANO_ERROR_INVALID_BIP32_DERIVATION_INDEX: - message = "Invalid operation. Invalid BIP32 derivation index"; + message = "Invalid BIP32 derivation index"; break; case CARDANO_ERROR_ENCODING: - message = "Invalid operation. Encoding failure"; + message = "Encoding failure"; break; case CARDANO_ERROR_DECODING: - message = "Invalid operation. Decoding failure"; + message = "Decoding failure"; break; case CARDANO_ERROR_CHECKSUM_MISMATCH: - message = "Invalid operation. Checksum mismatch"; + message = "Checksum mismatch"; break; case CARDANO_ERROR_INVALID_JSON: - message = "Invalid operation. Invalid JSON"; + message = "Invalid JSON"; break; case CARDANO_ERROR_INTEGER_OVERFLOW: - message = "Invalid operation. Integer overflow"; + message = "Integer overflow"; break; case CARDANO_ERROR_INTEGER_UNDERFLOW: - message = "Invalid operation. Integer underflow"; + message = "Integer underflow"; break; case CARDANO_ERROR_CONVERSION_FAILED: - message = "Invalid operation. Conversion error"; + message = "Conversion error"; break; case CARDANO_ERROR_LOSS_OF_PRECISION: - message = "Invalid operation. Loss of precision"; + message = "Loss of precision"; break; case CARDANO_ERROR_UNEXPECTED_CBOR_TYPE: - message = "Invalid operation. Unexpected CBOR type"; + message = "Unexpected CBOR type"; break; case CARDANO_ERROR_INVALID_CBOR_VALUE: - message = "Invalid operation. Invalid CBOR value"; + message = "Invalid CBOR value"; break; case CARDANO_ERROR_INVALID_CBOR_ARRAY_SIZE: - message = "Invalid operation. Invalid CBOR array size"; + message = "Invalid CBOR array size"; break; case CARDANO_ERROR_INVALID_CBOR_MAP_SIZE: - message = "Invalid operation. Invalid CBOR map size"; + message = "Invalid CBOR map size"; break; case CARDANO_ERROR_INVALID_ADDRESS_TYPE: - message = "Invalid operation. Invalid address type"; + message = "Invalid address type"; break; case CARDANO_ERROR_INVALID_ADDRESS_FORMAT: - message = "Invalid operation. Invalid address format"; + message = "Invalid address format"; break; case CARDANO_ERROR_INVALID_CREDENTIAL_TYPE: - message = "Invalid operation. Invalid credential type"; + message = "Invalid credential type"; break; case CARDANO_ERROR_INVALID_PLUTUS_DATA_CONVERSION: - message = "Invalid operation. Invalid Plutus data conversion"; + message = "Invalid Plutus data conversion"; break; case CARDANO_ERROR_INVALID_DATUM_TYPE: - message = "Invalid operation. Invalid datum type"; + message = "Invalid datum type"; break; case CARDANO_ERROR_INVALID_SCRIPT_LANGUAGE: - message = "Invalid operation. Invalid script language"; + message = "Invalid script language"; break; case CARDANO_ERROR_INVALID_NATIVE_SCRIPT_TYPE: - message = "Invalid operation. Invalid native script type"; + message = "Invalid native script type"; break; case CARDANO_ERROR_INVALID_PLUTUS_COST_MODEL: - message = "Invalid operation. Invalid Plutus cost model"; + message = "Invalid Plutus cost model"; break; case CARDANO_ERROR_INDEX_OUT_OF_BOUNDS: - message = "Invalid operation. Index out of bounds"; + message = "Index out of bounds"; break; case CARDANO_ERROR_INVALID_CERTIFICATE_TYPE: - message = "Invalid operation. Invalid certificate type"; + message = "Invalid certificate type"; + break; + case CARDANO_ERROR_NOT_IMPLEMENTED: + message = "Not implemented"; break; case CARDANO_ERROR_DUPLICATED_CBOR_MAP_KEY: - message = "Invalid operation. Duplicated CBOR map key"; + message = "Duplicated CBOR map key"; break; case CARDANO_ERROR_INVALID_CBOR_MAP_KEY: - message = "Invalid operation. Invalid CBOR map key"; + message = "Invalid CBOR map key"; break; case CARDANO_ERROR_INVALID_PROCEDURE_PROPOSAL_TYPE: - message = "Invalid operation. Invalid procedure proposal type"; + message = "Invalid procedure proposal type"; break; case CARDANO_ERROR_INVALID_METADATUM_CONVERSION: - message = "Invalid operation. Invalid metadatum conversion"; + message = "Invalid metadatum conversion"; break; case CARDANO_ERROR_INVALID_METADATUM_TEXT_STRING_SIZE: - message = "Invalid operation. Invalid metadatum text string size, must be less than 64 bytes"; + message = "Invalid metadatum text string size, must be less than 64 bytes"; break; case CARDANO_ERROR_INVALID_METADATUM_BOUNDED_BYTES_SIZE: - message = "Invalid operation. Invalid metadatum bounded bytes size, must be less than 64 bytes"; + message = "Invalid metadatum bounded bytes size, must be less than 64 bytes"; + break; + case CARDANO_ERROR_INVALID_HTTP_REQUEST: + message = "Invalid HTTP request"; break; default: message = "Unknown error code"; diff --git a/lib/src/providers/provider.c b/lib/src/providers/provider.c new file mode 100644 index 00000000..5d3f7ea1 --- /dev/null +++ b/lib/src/providers/provider.c @@ -0,0 +1,428 @@ +/** + * \file provider.c + * + * \author angel.castillo + * \date Sep 27, 2024 + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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. + */ + +/* INCLUDES ******************************************************************/ + +#include +#include + +#include "../allocators.h" + +#include +#include + +/* STRUCTURES ****************************************************************/ + +/** + * \brief Opaque structure representing a Cardano blockchain data provider instance. + * + * This structure serves as a handle to a Cardano provider, encapsulating the necessary context and + * state required to interact with the Cardano blockchain. + */ +typedef struct cardano_provider_t +{ + cardano_object_t base; + cardano_provider_impl_t impl; +} cardano_provider_t; + +/* STATIC FUNCTIONS **********************************************************/ + +/** + * \brief Deallocates a provider object. + * + * This function is responsible for properly deallocating a provider object (`cardano_provider_t`) + * and its associated resources. + * + * \param object A void pointer to the provider object to be deallocated. The function casts this + * pointer to the appropriate type (`cardano_provider_t*`). + * + * \note It is assumed that this function is called only when the reference count of the provider + * object reaches zero, as part of the reference counting mechanism implemented for managing the + * lifecycle of these objects. + */ +static void +cardano_provider_deallocate(void* object) +{ + assert(object != NULL); + + cardano_provider_t* provider = (cardano_provider_t*)object; + + cardano_object_unref(&provider->impl.context); + + _cardano_free(object); +} + +/* DEFINITIONS ****************************************************************/ + +cardano_error_t +cardano_provider_new( + cardano_provider_impl_t impl, + cardano_provider_t** provider) +{ + if (provider == NULL) + { + return CARDANO_ERROR_POINTER_IS_NULL; + } + + *provider = _cardano_malloc(sizeof(cardano_provider_t)); + + if (*provider == NULL) + { + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; + } + + (*provider)->base.deallocator = cardano_provider_deallocate; + (*provider)->base.ref_count = 1; + (*provider)->base.last_error[0] = '\0'; + + (*provider)->impl = impl; + + // Make sure provider name is null terminated + (*provider)->impl.name[sizeof((*provider)->impl.name) - 1U] = '\0'; + (*provider)->impl.error_message[0] = '\0'; + + return CARDANO_SUCCESS; +} + +const char* +cardano_provider_get_name(const cardano_provider_t* provider) +{ + if (provider == NULL) + { + return ""; + } + + return provider->impl.name; +} + +cardano_error_t +cardano_provider_get_parameters( + cardano_provider_t* provider, + cardano_protocol_parameters_t** parameters) +{ + if ((provider == NULL) || (parameters == NULL)) + { + return CARDANO_ERROR_POINTER_IS_NULL; + } + + if (provider->impl.get_parameters == NULL) + { + return CARDANO_ERROR_NOT_IMPLEMENTED; + } + + cardano_error_t result = provider->impl.get_parameters(&provider->impl, parameters); + + if (result != CARDANO_SUCCESS) + { + cardano_provider_set_last_error(provider, provider->impl.error_message); // LCOV_EXCL_LINE + } + + return result; +} + +cardano_error_t +cardano_provider_get_unspent_outputs( + cardano_provider_t* provider, + cardano_address_t* address, + cardano_utxo_list_t** utxo_list) +{ + if ((provider == NULL) || (address == NULL) || (utxo_list == NULL)) + { + return CARDANO_ERROR_POINTER_IS_NULL; + } + + if (provider->impl.get_unspent_outputs == NULL) + { + return CARDANO_ERROR_NOT_IMPLEMENTED; + } + + cardano_error_t result = provider->impl.get_unspent_outputs(&provider->impl, address, utxo_list); + + if (result != CARDANO_SUCCESS) + { + cardano_provider_set_last_error(provider, provider->impl.error_message); // LCOV_EXCL_LINE + } + + return result; +} + +cardano_error_t +cardano_provider_get_rewards_available( + cardano_provider_t* provider, + cardano_reward_address_t* address, + uint64_t* rewards) +{ + if ((provider == NULL) || (address == NULL) || (rewards == NULL)) + { + return CARDANO_ERROR_POINTER_IS_NULL; + } + + if (provider->impl.get_rewards_balance == NULL) + { + return CARDANO_ERROR_NOT_IMPLEMENTED; + } + + cardano_error_t result = provider->impl.get_rewards_balance(&provider->impl, address, rewards); + + if (result != CARDANO_SUCCESS) + { + cardano_provider_set_last_error(provider, provider->impl.error_message); // LCOV_EXCL_LINE + } + + return result; +} + +cardano_error_t +cardano_provider_get_unspent_outputs_with_asset( + cardano_provider_t* provider, + cardano_address_t* address, + cardano_asset_id_t* asset_id, + cardano_utxo_list_t** utxo_list) +{ + if ((provider == NULL) || (address == NULL) || (asset_id == NULL) || (utxo_list == NULL)) + { + return CARDANO_ERROR_POINTER_IS_NULL; + } + + if (provider->impl.get_unspent_outputs_with_asset == NULL) + { + return CARDANO_ERROR_NOT_IMPLEMENTED; + } + + cardano_error_t result = provider->impl.get_unspent_outputs_with_asset(&provider->impl, address, asset_id, utxo_list); + + if (result != CARDANO_SUCCESS) + { + cardano_provider_set_last_error(provider, provider->impl.error_message); // LCOV_EXCL_LINE + } + + return result; +} + +cardano_error_t +cardano_provider_get_unspent_output_by_nft( + cardano_provider_t* provider, + cardano_asset_id_t* asset_id, + cardano_utxo_t** utxo) +{ + if ((provider == NULL) || (asset_id == NULL) || (utxo == NULL)) + { + return CARDANO_ERROR_POINTER_IS_NULL; + } + + if (provider->impl.get_unspent_output_by_nft == NULL) + { + return CARDANO_ERROR_NOT_IMPLEMENTED; + } + + cardano_error_t result = provider->impl.get_unspent_output_by_nft(&provider->impl, asset_id, utxo); + + if (result != CARDANO_SUCCESS) + { + cardano_provider_set_last_error(provider, provider->impl.error_message); // LCOV_EXCL_LINE + } + + return result; +} + +cardano_error_t +cardano_provider_resolve_unspent_outputs( + cardano_provider_t* provider, + cardano_transaction_input_set_t* tx_ins, + cardano_utxo_list_t** utxo_list) +{ + if ((provider == NULL) || (tx_ins == NULL) || (utxo_list == NULL)) + { + return CARDANO_ERROR_POINTER_IS_NULL; + } + + if (provider->impl.resolve_unspent_outputs == NULL) + { + return CARDANO_ERROR_NOT_IMPLEMENTED; + } + + cardano_error_t result = provider->impl.resolve_unspent_outputs(&provider->impl, tx_ins, utxo_list); + + if (result != CARDANO_SUCCESS) + { + cardano_provider_set_last_error(provider, provider->impl.error_message); // LCOV_EXCL_LINE + } + + return result; +} + +cardano_error_t +cardano_provider_resolve_datum( + cardano_provider_t* provider, + cardano_blake2b_hash_t* datum_hash, + cardano_plutus_data_t** datum) +{ + if ((provider == NULL) || (datum_hash == NULL) || (datum == NULL)) + { + return CARDANO_ERROR_POINTER_IS_NULL; + } + + if (provider->impl.resolve_datum == NULL) + { + return CARDANO_ERROR_NOT_IMPLEMENTED; + } + + cardano_error_t result = provider->impl.resolve_datum(&provider->impl, datum_hash, datum); + + if (result != CARDANO_SUCCESS) + { + cardano_provider_set_last_error(provider, provider->impl.error_message); // LCOV_EXCL_LINE + } + + return result; +} + +cardano_error_t +cardano_provider_confirm_transaction( + cardano_provider_t* provider, + cardano_blake2b_hash_t* tx_id, + const uint64_t timeout_ms, + bool* confirmed) +{ + if ((provider == NULL) || (tx_id == NULL)) + { + return CARDANO_ERROR_POINTER_IS_NULL; + } + + if (provider->impl.await_transaction_confirmation == NULL) + { + return CARDANO_ERROR_NOT_IMPLEMENTED; + } + + cardano_error_t result = provider->impl.await_transaction_confirmation(&provider->impl, tx_id, timeout_ms, confirmed); + + if (result != CARDANO_SUCCESS) + { + cardano_provider_set_last_error(provider, provider->impl.error_message); // LCOV_EXCL_LINE + } + + return result; +} + +cardano_error_t +cardano_provider_submit_transaction( + cardano_provider_t* provider, + cardano_transaction_t* tx, + cardano_blake2b_hash_t** tx_id) +{ + if ((provider == NULL) || (tx == NULL) || (tx_id == NULL)) + { + return CARDANO_ERROR_POINTER_IS_NULL; + } + + if (provider->impl.post_transaction_to_chain == NULL) + { + return CARDANO_ERROR_NOT_IMPLEMENTED; + } + + cardano_error_t result = provider->impl.post_transaction_to_chain(&provider->impl, tx, tx_id); + + if (result != CARDANO_SUCCESS) + { + cardano_provider_set_last_error(provider, provider->impl.error_message); // LCOV_EXCL_LINE + } + + return result; +} + +cardano_error_t +cardano_provider_evaluate_transaction( + cardano_provider_t* provider, + cardano_transaction_t* tx, + cardano_utxo_list_t* additional_utxos, + cardano_redeemer_list_t** redeemers) +{ + if ((provider == NULL) || (tx == NULL) || (redeemers == NULL)) + { + return CARDANO_ERROR_POINTER_IS_NULL; + } + + if (provider->impl.evaluate_transaction == NULL) + { + return CARDANO_ERROR_NOT_IMPLEMENTED; + } + + cardano_error_t result = provider->impl.evaluate_transaction(&provider->impl, tx, additional_utxos, redeemers); + + if (result != CARDANO_SUCCESS) + { + cardano_provider_set_last_error(provider, provider->impl.error_message); // LCOV_EXCL_LINE + } + + return result; +} + +void +cardano_provider_unref(cardano_provider_t** provider) +{ + if ((provider == NULL) || (*provider == NULL)) + { + return; + } + + cardano_object_t* object = &(*provider)->base; + cardano_object_unref(&object); + + if (object == NULL) + { + *provider = NULL; + return; + } +} + +void +cardano_provider_ref(cardano_provider_t* provider) +{ + if (provider == NULL) + { + return; + } + + cardano_object_ref(&provider->base); +} + +size_t +cardano_provider_refcount(const cardano_provider_t* provider) +{ + if (provider == NULL) + { + return 0; + } + + return cardano_object_refcount(&provider->base); +} + +void +cardano_provider_set_last_error(cardano_provider_t* provider, const char* message) +{ + cardano_object_set_last_error(&provider->base, message); +} + +const char* +cardano_provider_get_last_error(const cardano_provider_t* provider) +{ + return cardano_object_get_last_error(&provider->base); +} \ No newline at end of file diff --git a/lib/src/scripts/native_scripts/script_pubkey.c b/lib/src/scripts/native_scripts/script_pubkey.c index ae88d57d..a09ee905 100644 --- a/lib/src/scripts/native_scripts/script_pubkey.c +++ b/lib/src/scripts/native_scripts/script_pubkey.c @@ -307,6 +307,25 @@ cardano_script_pubkey_from_json(const char* json, size_t json_size, cardano_scri return create_pubkey_new_result; } +cardano_error_t +cardano_script_pubkey_get_key_hash(cardano_script_pubkey_t* script_pubkey, cardano_blake2b_hash_t** key_hash) +{ + if (script_pubkey == NULL) + { + return CARDANO_ERROR_POINTER_IS_NULL; + } + + if (key_hash == NULL) + { + return CARDANO_ERROR_POINTER_IS_NULL; + } + + cardano_blake2b_hash_ref(script_pubkey->key_hash); + *key_hash = script_pubkey->key_hash; + + return CARDANO_SUCCESS; +} + bool cardano_script_pubkey_equals(const cardano_script_pubkey_t* lhs, const cardano_script_pubkey_t* rhs) { diff --git a/lib/src/transaction_body/value.c b/lib/src/transaction_body/value.c index d307d655..f498f07c 100644 --- a/lib/src/transaction_body/value.c +++ b/lib/src/transaction_body/value.c @@ -131,6 +131,104 @@ cardano_value_new( return CARDANO_SUCCESS; } +cardano_error_t +cardano_value_from_asset_map( + cardano_asset_id_map_t* asset_map, + cardano_value_t** value) +{ + if (value == NULL) + { + return CARDANO_ERROR_POINTER_IS_NULL; + } + + if (asset_map == NULL) + { + *value = NULL; + return CARDANO_ERROR_POINTER_IS_NULL; + } + + uint64_t coin = 0U; + cardano_multi_asset_t* multi_asset = NULL; + + cardano_error_t result = cardano_multi_asset_new(&multi_asset); + + if (result != CARDANO_SUCCESS) + { + // LCOV_EXCL_START + *value = NULL; + return result; + // LCOV_EXCL_STOP + } + + const size_t asset_count = cardano_asset_id_map_get_length(asset_map); + + for (size_t i = 0U; i < asset_count; ++i) + { + cardano_asset_id_t* asset_id = NULL; + int64_t amount = 0; + + result = cardano_asset_id_map_get_key_value_at(asset_map, i, &asset_id, &amount); + + if (result != CARDANO_SUCCESS) + { + // LCOV_EXCL_START + cardano_multi_asset_unref(&multi_asset); + + *value = NULL; + return result; + // LCOV_EXCL_STOP + } + + if (cardano_asset_id_is_lovelace(asset_id)) + { + coin = (uint64_t)amount; + + cardano_asset_id_unref(&asset_id); + } + else + { + cardano_asset_name_t* asset_name = cardano_asset_id_get_asset_name(asset_id); + cardano_blake2b_hash_t* policy_id = cardano_asset_id_get_policy_id(asset_id); + + cardano_asset_id_unref(&asset_id); + + if ((asset_name == NULL) || (policy_id == NULL)) + { + // LCOV_EXCL_START + cardano_multi_asset_unref(&multi_asset); + cardano_asset_name_unref(&asset_name); + cardano_blake2b_hash_unref(&policy_id); + + *value = NULL; + + return CARDANO_ERROR_POINTER_IS_NULL; + // LCOV_EXCL_STOP + } + + result = cardano_multi_asset_set(multi_asset, policy_id, asset_name, amount); + + cardano_asset_name_unref(&asset_name); + cardano_blake2b_hash_unref(&policy_id); + + if (result != CARDANO_SUCCESS) + { + // LCOV_EXCL_START + cardano_multi_asset_unref(&multi_asset); + + *value = NULL; + return result; + // LCOV_EXCL_STOP + } + } + } + + result = cardano_value_new(coin, multi_asset, value); + + cardano_multi_asset_unref(&multi_asset); + + return result; +} + cardano_error_t cardano_value_from_cbor(cardano_cbor_reader_t* reader, cardano_value_t** value) { diff --git a/lib/src/witness_set/redeemer_list.c b/lib/src/witness_set/redeemer_list.c index 100329e1..0e2e4942 100644 --- a/lib/src/witness_set/redeemer_list.c +++ b/lib/src/witness_set/redeemer_list.c @@ -604,6 +604,118 @@ cardano_redeemer_list_add(cardano_redeemer_list_t* redeemer_list, cardano_redeem return CARDANO_SUCCESS; } +cardano_error_t +cardano_redeemer_list_set_ex_units( + cardano_redeemer_list_t* redeemer_list, + const cardano_redeemer_tag_t tag, + const uint64_t index, + const uint64_t mem, + const uint64_t steps) +{ + if (redeemer_list == NULL) + { + return CARDANO_ERROR_POINTER_IS_NULL; + } + + for (size_t i = 0U; i < cardano_redeemer_list_get_length(redeemer_list); ++i) + { + cardano_redeemer_t* redeemer = NULL; + + cardano_error_t result = cardano_redeemer_list_get(redeemer_list, i, &redeemer); + cardano_redeemer_unref(&redeemer); + + if (result != CARDANO_SUCCESS) + { + return result; // LCOV_EXCL_LINE + } + + if ((cardano_redeemer_get_tag(redeemer) == tag) && (cardano_redeemer_get_index(redeemer) == index)) + { + cardano_ex_units_t* ex_units = cardano_redeemer_get_ex_units(redeemer); + cardano_ex_units_unref(&ex_units); + + if (ex_units == NULL) + { + return CARDANO_ERROR_POINTER_IS_NULL; // LCOV_EXCL_LINE + } + + cardano_error_t set_result = cardano_ex_units_set_memory(ex_units, mem); + + if (set_result != CARDANO_SUCCESS) + { + return set_result; // LCOV_EXCL_LINE + } + + set_result = cardano_ex_units_set_cpu_steps(ex_units, steps); + + return set_result; + } + } + + return CARDANO_ERROR_ELEMENT_NOT_FOUND; +} + +cardano_error_t +cardano_redeemer_list_clone( + cardano_redeemer_list_t* redeemer_list, + cardano_redeemer_list_t** cloned_redeemer_list) +{ + if (redeemer_list == NULL) + { + return CARDANO_ERROR_POINTER_IS_NULL; + } + + if (cloned_redeemer_list == NULL) + { + return CARDANO_ERROR_POINTER_IS_NULL; + } + + cardano_cbor_writer_t* writer = cardano_cbor_writer_new(); + + if (writer == NULL) + { + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; // LCOV_EXCL_LINE + } + + cardano_error_t result = cardano_redeemer_list_to_cbor(redeemer_list, writer); + + if (result != CARDANO_SUCCESS) + { + // LCOV_EXCL_START + cardano_cbor_writer_unref(&writer); + return result; + // LCOV_EXCL_STOP + } + + cardano_buffer_t* buffer = NULL; + result = cardano_cbor_writer_encode_in_buffer(writer, &buffer); + + cardano_cbor_writer_unref(&writer); + + if (result != CARDANO_SUCCESS) + { + return result; // LCOV_EXCL_LINE + } + + cardano_cbor_reader_t* reader = cardano_cbor_reader_new(cardano_buffer_get_data(buffer), cardano_buffer_get_size(buffer)); + cardano_buffer_unref(&buffer); + + if (reader == NULL) + { + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; // LCOV_EXCL_LINE + } + + result = cardano_redeemer_list_from_cbor(reader, cloned_redeemer_list); + + cardano_cbor_reader_unref(&reader); + + cardano_redeemer_list_clear_cbor_cache(*cloned_redeemer_list); + + cardano_array_sort(redeemer_list->array, compare_by_key); + + return result; +} + void cardano_redeemer_list_clear_cbor_cache(cardano_redeemer_list_t* redeemer_list) { diff --git a/lib/tests/common/ex_units.cpp b/lib/tests/common/ex_units.cpp index d52100b0..70e596ce 100644 --- a/lib/tests/common/ex_units.cpp +++ b/lib/tests/common/ex_units.cpp @@ -23,7 +23,6 @@ #include -#include #include #include "../allocators_helpers.h" diff --git a/lib/tests/common/network_magic.cpp b/lib/tests/common/network_magic.cpp new file mode 100644 index 00000000..98f17059 --- /dev/null +++ b/lib/tests/common/network_magic.cpp @@ -0,0 +1,90 @@ +/** + * \file network_magic.cpp + * + * \author angel.castillo + * \date Sep 28, 2024 + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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. + */ + +/* INCLUDES ******************************************************************/ + +#include + +#include "../allocators_helpers.h" + +#include + +/* UNIT TESTS ****************************************************************/ + +TEST(cardano_network_magic_to_string, canConvertMainnet) +{ + // Arrange + cardano_network_magic_t network_magic = CARDANO_NETWORK_MAGIC_MAINNET; + + // Act + const char* message = cardano_network_magic_to_string(network_magic); + + // Assert + ASSERT_STREQ(message, "mainnet"); +} + +TEST(cardano_network_magic_to_string, canConvertPreprod) +{ + // Arrange + cardano_network_magic_t network_magic = CARDANO_NETWORK_MAGIC_PREPROD; + + // Act + const char* message = cardano_network_magic_to_string(network_magic); + + // Assert + ASSERT_STREQ(message, "preprod"); +} + +TEST(cardano_network_magic_to_string, canConvertPreview) +{ + // Arrange + cardano_network_magic_t network_magic = CARDANO_NETWORK_MAGIC_PREVIEW; + + // Act + const char* message = cardano_network_magic_to_string(network_magic); + + // Assert + ASSERT_STREQ(message, "preview"); +} + +TEST(cardano_network_magic_to_string, canConvertSanchonet) +{ + // Arrange + cardano_network_magic_t network_magic = CARDANO_NETWORK_MAGIC_SANCHONET; + + // Act + const char* message = cardano_network_magic_to_string(network_magic); + + // Assert + ASSERT_STREQ(message, "sanchonet"); +} + +TEST(cardano_network_magic_to_string, canConvertUnknown) +{ + // Arrange + cardano_network_magic_t network_magic = (cardano_network_magic_t)0; + + // Act + const char* message = cardano_network_magic_to_string(network_magic); + + // Assert + ASSERT_STREQ(message, "unknown"); +} \ No newline at end of file diff --git a/lib/tests/common/unit_interval.cpp b/lib/tests/common/unit_interval.cpp index c3d20e9a..f0509102 100644 --- a/lib/tests/common/unit_interval.cpp +++ b/lib/tests/common/unit_interval.cpp @@ -552,6 +552,24 @@ TEST(cardano_unit_interval_from_double, setsTheDoubleValue) cardano_unit_interval_unref(&unit_interval); } +TEST(cardano_unit_interval_from_double, canConvertDooubleWithoutDecimal) +{ + // Arrange + cardano_unit_interval_t* unit_interval = nullptr; + + // Act + cardano_error_t error = cardano_unit_interval_from_double(15.0, &unit_interval); + + // Assert + EXPECT_EQ(error, CARDANO_SUCCESS); + EXPECT_THAT(unit_interval, testing::Not((cardano_unit_interval_t*)nullptr)); + EXPECT_EQ(cardano_unit_interval_get_numerator(unit_interval), 15); + EXPECT_EQ(cardano_unit_interval_get_denominator(unit_interval), 1); + + // Cleanup + cardano_unit_interval_unref(&unit_interval); +} + TEST(cardano_unit_interval_from_double, returnErrorIfValueIsNegative) { // Arrange diff --git a/lib/tests/error.cpp b/lib/tests/error.cpp index 5bfb0f6d..4f56aae4 100644 --- a/lib/tests/error.cpp +++ b/lib/tests/error.cpp @@ -61,7 +61,7 @@ TEST(cardano_error_to_string, canConvertInsufficientBufferSize) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Insufficient buffer size"); + ASSERT_STREQ(message, "Insufficient buffer size"); } TEST(cardano_error_to_string, canConvertPointerIsNull) @@ -73,7 +73,7 @@ TEST(cardano_error_to_string, canConvertPointerIsNull) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Argument is a NULL pointer"); + ASSERT_STREQ(message, "Argument is a NULL pointer"); } TEST(cardano_error_to_string, canConvertMemoryAllocationFailed) @@ -85,7 +85,7 @@ TEST(cardano_error_to_string, canConvertMemoryAllocationFailed) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Requested memory could not be allocated"); + ASSERT_STREQ(message, "Requested memory could not be allocated"); } TEST(cardano_error_to_string, canConvertOutOfBoundsMemoryRead) @@ -97,7 +97,7 @@ TEST(cardano_error_to_string, canConvertOutOfBoundsMemoryRead) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Out of bounds memory read"); + ASSERT_STREQ(message, "Out of bounds memory read"); } TEST(cardano_error_to_string, canConvertOutOfBoundsMemoryWrite) @@ -109,7 +109,7 @@ TEST(cardano_error_to_string, canConvertOutOfBoundsMemoryWrite) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Out of bounds memory write"); + ASSERT_STREQ(message, "Out of bounds memory write"); } TEST(cardano_error_to_string, canConvertInvalidBlake2bHashSize) @@ -121,7 +121,7 @@ TEST(cardano_error_to_string, canConvertInvalidBlake2bHashSize) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid Blake2b hash size"); + ASSERT_STREQ(message, "Invalid Blake2b hash size"); } TEST(cardano_error_to_string, canConvertInvalidEd25519SignatureSize) @@ -133,7 +133,7 @@ TEST(cardano_error_to_string, canConvertInvalidEd25519SignatureSize) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid Ed25519 signature size"); + ASSERT_STREQ(message, "Invalid Ed25519 signature size"); } TEST(cardano_error_to_string, canConvertInvalidEd25519PublicKeySize) @@ -145,7 +145,7 @@ TEST(cardano_error_to_string, canConvertInvalidEd25519PublicKeySize) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid Ed25519 public key size"); + ASSERT_STREQ(message, "Invalid Ed25519 public key size"); } TEST(cardano_error_to_string, canConvertInvalidEd25519PrivateKeySize) @@ -157,7 +157,7 @@ TEST(cardano_error_to_string, canConvertInvalidEd25519PrivateKeySize) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid Ed25519 private key size"); + ASSERT_STREQ(message, "Invalid Ed25519 private key size"); } TEST(cardano_error_to_string, canConvertInvalidBip32PublicKeySize) @@ -169,7 +169,7 @@ TEST(cardano_error_to_string, canConvertInvalidBip32PublicKeySize) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid BIP32 public key size"); + ASSERT_STREQ(message, "Invalid BIP32 public key size"); } TEST(cardano_error_to_string, canConvertInvalidBip32PrivateKeySize) @@ -181,7 +181,7 @@ TEST(cardano_error_to_string, canConvertInvalidBip32PrivateKeySize) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid BIP32 private key size"); + ASSERT_STREQ(message, "Invalid BIP32 private key size"); } TEST(cardano_error_to_string, canConvertInvalidArgument) @@ -193,7 +193,7 @@ TEST(cardano_error_to_string, canConvertInvalidArgument) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid argument"); + ASSERT_STREQ(message, "Invalid argument"); } TEST(cardano_error_to_string, canConvertInvalidBip32DerivationIndex) @@ -205,7 +205,7 @@ TEST(cardano_error_to_string, canConvertInvalidBip32DerivationIndex) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid BIP32 derivation index"); + ASSERT_STREQ(message, "Invalid BIP32 derivation index"); } TEST(cardano_error_to_string, canConvertEncoding) @@ -217,7 +217,7 @@ TEST(cardano_error_to_string, canConvertEncoding) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Encoding failure"); + ASSERT_STREQ(message, "Encoding failure"); } TEST(cardano_error_to_string, canConvertDecoding) @@ -229,7 +229,7 @@ TEST(cardano_error_to_string, canConvertDecoding) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Decoding failure"); + ASSERT_STREQ(message, "Decoding failure"); } TEST(cardano_error_to_string, canConvertChecksumMismatch) @@ -241,7 +241,7 @@ TEST(cardano_error_to_string, canConvertChecksumMismatch) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Checksum mismatch"); + ASSERT_STREQ(message, "Checksum mismatch"); } TEST(cardano_error_to_string, canConvertLossOfPrecision) @@ -253,7 +253,7 @@ TEST(cardano_error_to_string, canConvertLossOfPrecision) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Loss of precision"); + ASSERT_STREQ(message, "Loss of precision"); } TEST(cardano_error_to_string, canConvertUnexpectedCborType) @@ -265,7 +265,7 @@ TEST(cardano_error_to_string, canConvertUnexpectedCborType) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Unexpected CBOR type"); + ASSERT_STREQ(message, "Unexpected CBOR type"); } TEST(cardano_error_to_string, canConvertInvalidCborValue) @@ -277,7 +277,7 @@ TEST(cardano_error_to_string, canConvertInvalidCborValue) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid CBOR value"); + ASSERT_STREQ(message, "Invalid CBOR value"); } TEST(cardano_error_to_string, canConvertInvalidCborArraySize) @@ -289,7 +289,7 @@ TEST(cardano_error_to_string, canConvertInvalidCborArraySize) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid CBOR array size"); + ASSERT_STREQ(message, "Invalid CBOR array size"); } TEST(cardano_error_to_string, canConvertInvalidCborMapSize) @@ -301,7 +301,7 @@ TEST(cardano_error_to_string, canConvertInvalidCborMapSize) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid CBOR map size"); + ASSERT_STREQ(message, "Invalid CBOR map size"); } TEST(cardano_error_to_string, canConvertInvalidAddressType) @@ -313,7 +313,7 @@ TEST(cardano_error_to_string, canConvertInvalidAddressType) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid address type"); + ASSERT_STREQ(message, "Invalid address type"); } TEST(cardano_error_to_string, canConvertInvalidAddressFormat) @@ -325,7 +325,7 @@ TEST(cardano_error_to_string, canConvertInvalidAddressFormat) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid address format"); + ASSERT_STREQ(message, "Invalid address format"); } TEST(cardano_error_to_string, canConvertInvalidCredentialType) @@ -337,7 +337,7 @@ TEST(cardano_error_to_string, canConvertInvalidCredentialType) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid credential type"); + ASSERT_STREQ(message, "Invalid credential type"); } TEST(cardano_error_to_string, canConvertInvalidUrl) @@ -361,7 +361,7 @@ TEST(cardano_error_to_string, canConvertInvalidPlutusDataConversion) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid Plutus data conversion"); + ASSERT_STREQ(message, "Invalid Plutus data conversion"); } TEST(cardano_error_to_string, canConvertElementNotFound) @@ -373,7 +373,7 @@ TEST(cardano_error_to_string, canConvertElementNotFound) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Element not found"); + ASSERT_STREQ(message, "Element not found"); } TEST(cardano_error_to_string, canConvertInvalidDatumType) @@ -385,7 +385,7 @@ TEST(cardano_error_to_string, canConvertInvalidDatumType) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid datum type"); + ASSERT_STREQ(message, "Invalid datum type"); } TEST(cardano_error_to_string, canConvertInvalidScriptLanguage) @@ -397,7 +397,7 @@ TEST(cardano_error_to_string, canConvertInvalidScriptLanguage) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid script language"); + ASSERT_STREQ(message, "Invalid script language"); } TEST(cardano_error_to_string, canConvertInvalidNativeScriptType) @@ -409,7 +409,7 @@ TEST(cardano_error_to_string, canConvertInvalidNativeScriptType) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid native script type"); + ASSERT_STREQ(message, "Invalid native script type"); } TEST(CARDANO_ERROR_INVALID_JSON, canConvertInvalidJson) @@ -421,7 +421,7 @@ TEST(CARDANO_ERROR_INVALID_JSON, canConvertInvalidJson) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid JSON"); + ASSERT_STREQ(message, "Invalid JSON"); } TEST(CARDANO_ERROR_INTEGER_OVERFLOW, canConvertIntegerOverflow) @@ -433,7 +433,7 @@ TEST(CARDANO_ERROR_INTEGER_OVERFLOW, canConvertIntegerOverflow) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Integer overflow"); + ASSERT_STREQ(message, "Integer overflow"); } TEST(CARDANO_ERROR_INTEGER_UNDERFLOW, canConvertIntegerUnderflow) @@ -445,7 +445,7 @@ TEST(CARDANO_ERROR_INTEGER_UNDERFLOW, canConvertIntegerUnderflow) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Integer underflow"); + ASSERT_STREQ(message, "Integer underflow"); } TEST(CARDANO_ERROR_CONVERSION_FAILED, canConvertConversionError) @@ -457,7 +457,7 @@ TEST(CARDANO_ERROR_CONVERSION_FAILED, canConvertConversionError) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Conversion error"); + ASSERT_STREQ(message, "Conversion error"); } TEST(cardano_error_to_string, canConvertInvalidPlutusCostModel) @@ -469,7 +469,7 @@ TEST(cardano_error_to_string, canConvertInvalidPlutusCostModel) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid Plutus cost model"); + ASSERT_STREQ(message, "Invalid Plutus cost model"); } TEST(cardano_error_to_string, canConvertIndexOutOfBounds) @@ -481,7 +481,7 @@ TEST(cardano_error_to_string, canConvertIndexOutOfBounds) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Index out of bounds"); + ASSERT_STREQ(message, "Index out of bounds"); } TEST(cardano_error_to_string, canConvertDuplicatedCborMapKey) @@ -493,7 +493,7 @@ TEST(cardano_error_to_string, canConvertDuplicatedCborMapKey) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Duplicated CBOR map key"); + ASSERT_STREQ(message, "Duplicated CBOR map key"); } TEST(cardano_error_to_string, canConvertInvalidCborMapKey) @@ -505,7 +505,7 @@ TEST(cardano_error_to_string, canConvertInvalidCborMapKey) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid CBOR map key"); + ASSERT_STREQ(message, "Invalid CBOR map key"); } TEST(cardano_error_to_string, canConvertInvalidCertificateType) @@ -517,7 +517,7 @@ TEST(cardano_error_to_string, canConvertInvalidCertificateType) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid certificate type"); + ASSERT_STREQ(message, "Invalid certificate type"); } TEST(cardano_error_to_string, canConvertInvalidProcedureProposalType) @@ -529,7 +529,7 @@ TEST(cardano_error_to_string, canConvertInvalidProcedureProposalType) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid procedure proposal type"); + ASSERT_STREQ(message, "Invalid procedure proposal type"); } TEST(cardano_error_to_string, canConvertInvalidMetadatumConversion) @@ -541,7 +541,7 @@ TEST(cardano_error_to_string, canConvertInvalidMetadatumConversion) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid metadatum conversion"); + ASSERT_STREQ(message, "Invalid metadatum conversion"); } TEST(cardano_error_to_string, canConvertInvalidMetadatumTextStringSize) @@ -553,7 +553,7 @@ TEST(cardano_error_to_string, canConvertInvalidMetadatumTextStringSize) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid metadatum text string size, must be less than 64 bytes"); + ASSERT_STREQ(message, "Invalid metadatum text string size, must be less than 64 bytes"); } TEST(cardano_error_to_string, canConvertInvalidMetadatumBoundedBytesSize) @@ -565,7 +565,31 @@ TEST(cardano_error_to_string, canConvertInvalidMetadatumBoundedBytesSize) const char* message = cardano_error_to_string(error); // Assert - ASSERT_STREQ(message, "Invalid operation. Invalid metadatum bounded bytes size, must be less than 64 bytes"); + ASSERT_STREQ(message, "Invalid metadatum bounded bytes size, must be less than 64 bytes"); +} + +TEST(cardano_error_to_string, canConvertNotImplemented) +{ + // Arrange + cardano_error_t error = CARDANO_ERROR_NOT_IMPLEMENTED; + + // Act + const char* message = cardano_error_to_string(error); + + // Assert + ASSERT_STREQ(message, "Not implemented"); +} + +TEST(cardano_error_to_string, canConvertInvalidHttpRequest) +{ + // Arrange + cardano_error_t error = CARDANO_ERROR_INVALID_HTTP_REQUEST; + + // Act + const char* message = cardano_error_to_string(error); + + // Assert + ASSERT_STREQ(message, "Invalid HTTP request"); } TEST(cardano_error_to_string, canConvertUnknown) diff --git a/lib/tests/providers/provider.cpp b/lib/tests/providers/provider.cpp new file mode 100644 index 00000000..34f356e2 --- /dev/null +++ b/lib/tests/providers/provider.cpp @@ -0,0 +1,939 @@ +/** + * \file provider.cpp + * + * \author angel.castillo + * \date Sep 27, 2024 + * + * \section LICENSE + * + * Copyright 2024 Biglup Labs + * + * 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 + * + * 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. + */ + +/* INCLUDES ******************************************************************/ + +#include "../allocators_helpers.h" +#include "../src/allocators.h" + +#include + +#include + +/* DECLARATIONS **************************************************************/ + +/** + * \brief Allocates and initializes a new Cardano provider context. + */ +typedef struct api_context_t +{ + cardano_object_t base; + char key[256]; +} ref_counted_string_t; + +/** + * \brief Allocates and initializes a new Cardano provider context. + */ +static cardano_provider_impl_t +cardano_provider_impl_new() +{ + cardano_provider_impl_t impl = { 0 }; + api_context_t* context = reinterpret_cast(malloc(sizeof(api_context_t))); + + if (context != NULL) + { + context->base.ref_count = 1U; + context->base.deallocator = _cardano_free; + context->base.last_error[0] = '\0'; + + CARDANO_UNUSED(memset(context->key, 0, sizeof(context->key))); + CARDANO_UNUSED(memccpy((void*)&context->key[0], "This is a test key", strlen("This is a test key"), sizeof(context->key))); + + impl.context = (cardano_object_t*)context; + } + + impl.post_transaction_to_chain = [](cardano_provider_impl_t*, cardano_transaction_t*, cardano_blake2b_hash_t** tx_id) -> cardano_error_t + { + return cardano_blake2b_compute_hash((const byte_t*)"a", 1, 32, tx_id); + }; + + impl.await_transaction_confirmation = [](cardano_provider_impl_t*, cardano_blake2b_hash_t*, uint64_t, bool*) -> cardano_error_t + { + return CARDANO_SUCCESS; + }; + + impl.get_parameters = [](cardano_provider_impl_t*, cardano_protocol_parameters_t** param) -> cardano_error_t + { + return cardano_protocol_parameters_new(param); + }; + + impl.get_unspent_outputs = [](cardano_provider_impl_t*, cardano_address_t*, cardano_utxo_list_t** utxo_list) -> cardano_error_t + { + return cardano_utxo_list_new(utxo_list); + }; + + impl.get_unspent_outputs_with_asset = [](cardano_provider_impl_t*, cardano_address_t*, cardano_asset_id_t*, cardano_utxo_list_t** utxo_list) -> cardano_error_t + { + return cardano_utxo_list_new(utxo_list); + }; + + impl.get_unspent_output_by_nft = [](cardano_provider_impl_t*, cardano_asset_id_t*, cardano_utxo_t** utxo) -> cardano_error_t + { + static const char* CBOR = "82825820bb217abaca60fc0ca68c1555eca6a96d2478547818ae76ce6836133f3cc546e001a200583900287a7e37219128cfb05322626daa8b19d1ad37c6779d21853f7b94177c16240714ea0e12b41a914f2945784ac494bb19573f0ca61a08afa801821af0078c21a2581c1ec85dcee27f2d90ec1f9a1e4ce74a667dc9be8b184463223f9c9601a14350584c05581c659f2917fb63f12b33667463ee575eeac1845bbc736b9c0bbc40ba82a14454534c410a"; + cardano_cbor_reader_t* reader = cardano_cbor_reader_from_hex(CBOR, strlen(CBOR)); + + if (reader == NULL) + { + return CARDANO_ERROR_GENERIC; + } + + cardano_error_t result = cardano_utxo_from_cbor(reader, utxo); + + cardano_cbor_reader_unref(&reader); + + return result; + }; + + impl.resolve_unspent_outputs = [](cardano_provider_impl_t*, cardano_transaction_input_set_t*, cardano_utxo_list_t** utxo_list) -> cardano_error_t + { + return cardano_utxo_list_new(utxo_list); + }; + + impl.resolve_datum = [](cardano_provider_impl_t*, cardano_blake2b_hash_t*, cardano_plutus_data_t** datum) -> cardano_error_t + { + return cardano_plutus_data_new_integer_from_int(0, datum); + }; + + impl.evaluate_transaction = [](cardano_provider_impl_t*, cardano_transaction_t*, cardano_utxo_list_t*, cardano_redeemer_list_t**) -> cardano_error_t + { + return CARDANO_SUCCESS; + }; + + impl.get_rewards_balance = [](cardano_provider_impl_t*, cardano_reward_address_t*, uint64_t* balance) -> cardano_error_t + { + *balance = 0U; + + return CARDANO_SUCCESS; + }; + + return impl; +} + +/** + * \brief Allocates and initializes a new Cardano provider context. + */ +static cardano_provider_impl_t +cardano_empty_provider_impl_new() +{ + cardano_provider_impl_t impl = { 0 }; + api_context_t* context = reinterpret_cast(malloc(sizeof(api_context_t))); + + if (context != NULL) + { + context->base.ref_count = 1U; + context->base.deallocator = _cardano_free; + context->base.last_error[0] = '\0'; + + CARDANO_UNUSED(memset(context->key, 0, sizeof(context->key))); + CARDANO_UNUSED(memccpy((void*)&context->key[0], "This is a test key", strlen("This is a test key"), sizeof(context->key))); + + impl.context = (cardano_object_t*)context; + } + + CARDANO_UNUSED(memset(impl.name, 0, sizeof(impl.name))); + CARDANO_UNUSED(memccpy((void*)&impl.name[0], "Empty Provider", strlen("Empty Provider"), sizeof(impl.name))); + + impl.post_transaction_to_chain = NULL; + impl.await_transaction_confirmation = NULL; + impl.get_parameters = NULL; + impl.get_unspent_outputs = NULL; + impl.get_unspent_outputs_with_asset = NULL; + impl.get_unspent_output_by_nft = NULL; + impl.resolve_unspent_outputs = NULL; + impl.resolve_datum = NULL; + impl.evaluate_transaction = NULL; + impl.get_rewards_balance = NULL; + + return impl; +} + +/* UNIT TESTS ****************************************************************/ + +TEST(cardano_provider_ref, increasesTheReferenceCount) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_error_t error = cardano_provider_new(cardano_provider_impl_new(), &provider); + + ASSERT_EQ(error, CARDANO_SUCCESS); + + // Act + cardano_provider_ref(provider); + + // Assert + EXPECT_THAT(provider, testing::Not((cardano_provider_t*)nullptr)); + EXPECT_EQ(cardano_provider_refcount(provider), 2); + + // Cleanup - We need to unref twice since one reference was added. + cardano_provider_unref(&provider); + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_ref, doesntCrashIfGivenANullPtr) +{ + // Act + cardano_provider_ref(nullptr); +} + +TEST(cardano_provider_unref, doesntCrashIfGivenAPtrToANullPtr) +{ + // Arrange + cardano_provider_t* provider = nullptr; + + // Act + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_unref, doesntCrashIfGivenANullPtr) +{ + // Act + cardano_provider_unref((cardano_provider_t**)nullptr); +} + +TEST(cardano_provider_unref, decreasesTheReferenceCount) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_error_t error = cardano_provider_new(cardano_provider_impl_new(), &provider); + + ASSERT_EQ(error, CARDANO_SUCCESS); + + // Act + cardano_provider_ref(provider); + size_t ref_count = cardano_provider_refcount(provider); + + cardano_provider_unref(&provider); + size_t updated_ref_count = cardano_provider_refcount(provider); + + // Assert + EXPECT_EQ(ref_count, 2); + EXPECT_EQ(updated_ref_count, 1); + + // Cleanup + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_unref, freesTheObjectIfReferenceReachesZero) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_error_t error = cardano_provider_new(cardano_provider_impl_new(), &provider); + + ASSERT_EQ(error, CARDANO_SUCCESS); + + // Act + cardano_provider_ref(provider); + size_t ref_count = cardano_provider_refcount(provider); + + cardano_provider_unref(&provider); + size_t updated_ref_count = cardano_provider_refcount(provider); + + cardano_provider_unref(&provider); + + // Assert + EXPECT_EQ(ref_count, 2); + EXPECT_EQ(updated_ref_count, 1); + EXPECT_EQ(provider, (cardano_provider_t*)nullptr); + + // Cleanup + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_refcount, returnsZeroIfGivenANullPtr) +{ + // Act + size_t ref_count = cardano_provider_refcount(nullptr); + + // Assert + EXPECT_EQ(ref_count, 0); +} + +TEST(cardano_provider_set_last_error, doesNothingWhenObjectIsNull) +{ + // Arrange + cardano_provider_t* provider = nullptr; + const char* message = "This is a test message"; + + // Act + cardano_provider_set_last_error(provider, message); + + // Assert + EXPECT_STREQ(cardano_provider_get_last_error(provider), "Object is NULL."); +} + +TEST(cardano_provider_set_last_error, doesNothingWhenWhenMessageIsNull) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_error_t error = cardano_provider_new(cardano_provider_impl_new(), &provider); + + EXPECT_EQ(error, CARDANO_SUCCESS); + + const char* message = nullptr; + + // Act + cardano_provider_set_last_error(provider, message); + + // Assert + EXPECT_STREQ(cardano_provider_get_last_error(provider), ""); + + // Cleanup + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_new, returnsErrorIfGivenANullPtr) +{ + cardano_provider_impl_t impl = cardano_provider_impl_new(); + // Act + cardano_error_t error = cardano_provider_new(impl, nullptr); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_POINTER_IS_NULL); + + cardano_object_unref(&impl.context); +} + +TEST(cardano_provider_new, returnsSuccessIfGivenAValidImpl) +{ + // Arrange + cardano_provider_t* provider = nullptr; + + // Act + cardano_error_t error = cardano_provider_new(cardano_provider_impl_new(), &provider); + + // Assert + EXPECT_EQ(error, CARDANO_SUCCESS); + + // Cleanup + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_get_name, returnsEmptyStringIfGivenANullPtr) +{ + // Act + const char* name = cardano_provider_get_name(nullptr); + + // Assert + EXPECT_STREQ(name, ""); +} + +TEST(cardano_provider_get_name, returnsTheNameOfTheProvider) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_error_t error = cardano_provider_new(cardano_empty_provider_impl_new(), &provider); + + ASSERT_EQ(error, CARDANO_SUCCESS); + + // Act + const char* name = cardano_provider_get_name(provider); + + // Assert + EXPECT_STREQ(name, "Empty Provider"); + + // Cleanup + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_get_parameters, returnsErrorIfGivenANullPtr) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_protocol_parameters_t* parameters = nullptr; + + // Act + cardano_error_t error = cardano_provider_get_parameters(provider, ¶meters); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_POINTER_IS_NULL); +} + +TEST(cardano_provider_get_parameters, returnsErrorIfGetParametersIsNotImplemented) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_error_t error = cardano_provider_new(cardano_empty_provider_impl_new(), &provider); + + ASSERT_EQ(error, CARDANO_SUCCESS); + + cardano_protocol_parameters_t* parameters = nullptr; + + // Act + error = cardano_provider_get_parameters(provider, ¶meters); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_NOT_IMPLEMENTED); + + // Cleanup + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_get_parameters, returnsSuccessIfGetParametersIsImplemented) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_error_t error = cardano_provider_new(cardano_provider_impl_new(), &provider); + + ASSERT_EQ(error, CARDANO_SUCCESS); + + cardano_protocol_parameters_t* parameters = nullptr; + + // Act + error = cardano_provider_get_parameters(provider, ¶meters); + + // Assert + EXPECT_EQ(error, CARDANO_SUCCESS); + + // Cleanup + cardano_protocol_parameters_unref(¶meters); + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_get_unspent_outputs, returnsErrorIfGivenANullPtr) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_address_t* address = nullptr; + cardano_utxo_list_t* utxo_list = nullptr; + + // Act + cardano_error_t error = cardano_provider_get_unspent_outputs(provider, address, &utxo_list); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_POINTER_IS_NULL); +} + +TEST(cardano_provider_get_unspent_outputs, returnsSuccessIfGetUnspentOutputsIsImplemented) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_error_t error = cardano_provider_new(cardano_provider_impl_new(), &provider); + + ASSERT_EQ(error, CARDANO_SUCCESS); + + cardano_address_t* address = (cardano_address_t*)""; + cardano_utxo_list_t* utxo_list = nullptr; + + // Act + error = cardano_provider_get_unspent_outputs(provider, address, &utxo_list); + + // Assert + EXPECT_EQ(error, CARDANO_SUCCESS); + + // Cleanup + cardano_utxo_list_unref(&utxo_list); + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_get_unspent_outputs_with_asset, returnsErrorIfGivenANullPtr) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_address_t* address = nullptr; + cardano_asset_id_t* asset_id = nullptr; + cardano_utxo_list_t* utxo_list = nullptr; + + // Act + cardano_error_t error = cardano_provider_get_unspent_outputs_with_asset(provider, address, asset_id, &utxo_list); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_POINTER_IS_NULL); +} + +TEST(cardano_provider_get_unspent_outputs_with_asset, returnsSuccessIfGetUnspentOutputsIsImplemented) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_error_t error = cardano_provider_new(cardano_provider_impl_new(), &provider); + + ASSERT_EQ(error, CARDANO_SUCCESS); + + cardano_utxo_list_t* utxo_list = nullptr; + + // Act + error = cardano_provider_get_unspent_outputs_with_asset(provider, (cardano_address_t*)"", (cardano_asset_id_t*)"", &utxo_list); + + // Assert + EXPECT_EQ(error, CARDANO_SUCCESS); + + // Cleanup + cardano_utxo_list_unref(&utxo_list); + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_get_unspent_output_by_nft, returnsErrorIfGivenANullPtr) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_asset_id_t* asset_id = nullptr; + cardano_utxo_t* utxo = nullptr; + + // Act + cardano_error_t error = cardano_provider_get_unspent_output_by_nft(provider, asset_id, &utxo); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_POINTER_IS_NULL); +} + +TEST(cardano_provider_get_unspent_output_by_nft, returnsSuccessIfGetUnspentOutputByNftIsImplemented) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_error_t error = cardano_provider_new(cardano_provider_impl_new(), &provider); + + ASSERT_EQ(error, CARDANO_SUCCESS); + + cardano_utxo_t* utxo = nullptr; + + // Act + error = cardano_provider_get_unspent_output_by_nft(provider, (cardano_asset_id_t*)"", &utxo); + + // Assert + EXPECT_EQ(error, CARDANO_SUCCESS); + + // Cleanup + cardano_utxo_unref(&utxo); + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_resolve_unspent_outputs, returnsErrorIfGivenANullPtr) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_transaction_input_set_t* input_set = nullptr; + cardano_utxo_list_t* utxo_list = nullptr; + + // Act + cardano_error_t error = cardano_provider_resolve_unspent_outputs(provider, input_set, &utxo_list); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_POINTER_IS_NULL); +} + +TEST(cardano_provider_resolve_unspent_outputs, returnsSuccessIfResolveUnspentOutputsIsImplemented) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_error_t error = cardano_provider_new(cardano_provider_impl_new(), &provider); + + ASSERT_EQ(error, CARDANO_SUCCESS); + + cardano_utxo_list_t* utxo_list = nullptr; + + // Act + error = cardano_provider_resolve_unspent_outputs(provider, (cardano_transaction_input_set_t*)"", &utxo_list); + + // Assert + EXPECT_EQ(error, CARDANO_SUCCESS); + + // Cleanup + cardano_utxo_list_unref(&utxo_list); + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_resolve_datum, returnsErrorIfGivenANullPtr) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_blake2b_hash_t* hash = nullptr; + cardano_plutus_data_t* datum = nullptr; + + // Act + cardano_error_t error = cardano_provider_resolve_datum(provider, hash, &datum); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_POINTER_IS_NULL); +} + +TEST(cardano_provider_resolve_datum, returnsSuccessIfResolveDatumIsImplemented) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_error_t error = cardano_provider_new(cardano_provider_impl_new(), &provider); + + ASSERT_EQ(error, CARDANO_SUCCESS); + + cardano_plutus_data_t* datum = nullptr; + + // Act + error = cardano_provider_resolve_datum(provider, (cardano_blake2b_hash_t*)"", &datum); + + // Assert + EXPECT_EQ(error, CARDANO_SUCCESS); + + // Cleanup + cardano_plutus_data_unref(&datum); + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_evaluate_transaction, returnsErrorIfGivenANullPtr) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_transaction_t* transaction = nullptr; + cardano_utxo_list_t* utxo_list = nullptr; + cardano_redeemer_list_t* redeemer_list = nullptr; + + // Act + cardano_error_t error = cardano_provider_evaluate_transaction(provider, transaction, utxo_list, &redeemer_list); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_POINTER_IS_NULL); +} + +TEST(cardano_provider_evaluate_transaction, returnsSuccessIfEvaluateTransactionIsImplemented) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_error_t error = cardano_provider_new(cardano_provider_impl_new(), &provider); + + ASSERT_EQ(error, CARDANO_SUCCESS); + + cardano_redeemer_list_t* redeemer_list = nullptr; + + // Act + error = cardano_provider_evaluate_transaction(provider, (cardano_transaction_t*)"", (cardano_utxo_list_t*)"", &redeemer_list); + + // Assert + EXPECT_EQ(error, CARDANO_SUCCESS); + + // Cleanup + cardano_redeemer_list_unref(&redeemer_list); + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_submit_transaction, returnsErrorIfGivenANullPtr) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_transaction_t* transaction = nullptr; + cardano_blake2b_hash_t* tx_id = nullptr; + + // Act + cardano_error_t error = cardano_provider_submit_transaction(provider, transaction, &tx_id); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_POINTER_IS_NULL); +} + +TEST(cardano_provider_submit_transaction, returnsSuccessIfPostTransactionToChainIsImplemented) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_error_t error = cardano_provider_new(cardano_provider_impl_new(), &provider); + + ASSERT_EQ(error, CARDANO_SUCCESS); + + cardano_blake2b_hash_t* tx_id = nullptr; + + // Act + error = cardano_provider_submit_transaction(provider, (cardano_transaction_t*)"", &tx_id); + + // Assert + EXPECT_EQ(error, CARDANO_SUCCESS); + + // Cleanup + cardano_blake2b_hash_unref(&tx_id); + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_confirm_transaction, returnsErrorIfGivenANullPtr) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_blake2b_hash_t* tx_id = nullptr; + uint64_t timeout = 0; + bool result = false; + + // Act + cardano_error_t error = cardano_provider_confirm_transaction(provider, tx_id, timeout, &result); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_POINTER_IS_NULL); +} + +TEST(cardano_provider_confirm_transaction, returnsSuccessIfAwaitTransactionConfirmationIsImplemented) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_error_t error = cardano_provider_new(cardano_provider_impl_new(), &provider); + + ASSERT_EQ(error, CARDANO_SUCCESS); + + uint64_t timeout = 0; + + // Act + error = cardano_provider_confirm_transaction(provider, (cardano_blake2b_hash_t*)"", timeout, (bool*)""); + + // Assert + EXPECT_EQ(error, CARDANO_SUCCESS); + + // Cleanup + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_get_unspent_outputs, returnsNotImplementedIfNotImplemented) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_error_t error = cardano_provider_new(cardano_empty_provider_impl_new(), &provider); + + ASSERT_EQ(error, CARDANO_SUCCESS); + + cardano_address_t* address = (cardano_address_t*)""; + cardano_utxo_list_t* utxo_list = nullptr; + + // Act + error = cardano_provider_get_unspent_outputs(provider, address, &utxo_list); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_NOT_IMPLEMENTED); + + // Cleanup + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_get_unspent_outputs_with_asset, returnsNotImplementedIfNotImplemented) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_error_t error = cardano_provider_new(cardano_empty_provider_impl_new(), &provider); + + ASSERT_EQ(error, CARDANO_SUCCESS); + + cardano_address_t* address = (cardano_address_t*)""; + cardano_asset_id_t* asset_id = (cardano_asset_id_t*)""; + cardano_utxo_list_t* utxo_list = nullptr; + + // Act + error = cardano_provider_get_unspent_outputs_with_asset(provider, address, asset_id, &utxo_list); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_NOT_IMPLEMENTED); + + // Cleanup + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_get_unspent_output_by_nft, returnsNotImplementedIfNotImplemented) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_error_t error = cardano_provider_new(cardano_empty_provider_impl_new(), &provider); + + ASSERT_EQ(error, CARDANO_SUCCESS); + + cardano_asset_id_t* asset_id = (cardano_asset_id_t*)""; + cardano_utxo_t* utxo = nullptr; + + // Act + error = cardano_provider_get_unspent_output_by_nft(provider, asset_id, &utxo); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_NOT_IMPLEMENTED); + + // Cleanup + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_resolve_unspent_outputs, returnsNotImplementedIfNotImplemented) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_transaction_input_set_t* input_set = (cardano_transaction_input_set_t*)""; + cardano_utxo_list_t* utxo_list = nullptr; + + cardano_error_t error = cardano_provider_new(cardano_empty_provider_impl_new(), &provider); + + ASSERT_EQ(error, CARDANO_SUCCESS); + + // Act + error = cardano_provider_resolve_unspent_outputs(provider, input_set, &utxo_list); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_NOT_IMPLEMENTED); + + // Cleanup + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_resolve_datum, returnsNotImplementedIfNotImplemented) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_blake2b_hash_t* hash = (cardano_blake2b_hash_t*)""; + cardano_plutus_data_t* datum = (cardano_plutus_data_t*)""; + + cardano_error_t error = cardano_provider_new(cardano_empty_provider_impl_new(), &provider); + + ASSERT_EQ(error, CARDANO_SUCCESS); + + // Act + error = cardano_provider_resolve_datum(provider, hash, &datum); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_NOT_IMPLEMENTED); + + // Cleanup + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_evaluate_transaction, returnsNotImplementedIfNotImplemented) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_transaction_t* transaction = (cardano_transaction_t*)""; + cardano_utxo_list_t* utxo_list = (cardano_utxo_list_t*)""; + cardano_redeemer_list_t* redeemer_list = nullptr; + + cardano_error_t error = cardano_provider_new(cardano_empty_provider_impl_new(), &provider); + + ASSERT_EQ(error, CARDANO_SUCCESS); + + // Act + error = cardano_provider_evaluate_transaction(provider, transaction, utxo_list, &redeemer_list); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_NOT_IMPLEMENTED); + + // Cleanup + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_submit_transaction, returnsNotImplementedIfNotImplemented) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_transaction_t* transaction = (cardano_transaction_t*)""; + cardano_blake2b_hash_t* tx_id = nullptr; + + cardano_error_t error = cardano_provider_new(cardano_empty_provider_impl_new(), &provider); + + ASSERT_EQ(error, CARDANO_SUCCESS); + + // Act + error = cardano_provider_submit_transaction(provider, transaction, &tx_id); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_NOT_IMPLEMENTED); + + // Cleanup + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_confirm_transaction, returnsNotImplementedIfNotImplemented) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_blake2b_hash_t* tx_id = (cardano_blake2b_hash_t*)""; + uint64_t timeout = 0; + bool result = false; + + cardano_error_t error = cardano_provider_new(cardano_empty_provider_impl_new(), &provider); + + ASSERT_EQ(error, CARDANO_SUCCESS); + + // Act + error = cardano_provider_confirm_transaction(provider, tx_id, timeout, &result); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_NOT_IMPLEMENTED); + + // Cleanup + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_new, returnsErrorIfMemoryAllocationFails) +{ + reset_allocators_run_count(); + cardano_set_allocators(fail_right_away_malloc, realloc, free); + + cardano_provider_t* provider = nullptr; + + cardano_provider_impl_t impl = cardano_empty_provider_impl_new(); + + // Act + cardano_error_t error = cardano_provider_new(impl, &provider); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_MEMORY_ALLOCATION_FAILED); + EXPECT_EQ(provider, (cardano_provider_t*)nullptr); + + // Cleanup + cardano_set_allocators(malloc, realloc, free); + cardano_object_unref(&impl.context); +} + +TEST(cardano_provider_get_rewards_available, returnsErrorIfGivenANullPtr) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_reward_address_t* address = nullptr; + uint64_t balance = 0; + + // Act + cardano_error_t error = cardano_provider_get_rewards_available(provider, address, &balance); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_POINTER_IS_NULL); +} + +TEST(cardano_provider_get_rewards_available, returnsSuccessIfGetRewardsBalanceIsImplemented) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_error_t error = cardano_provider_new(cardano_provider_impl_new(), &provider); + + ASSERT_EQ(error, CARDANO_SUCCESS); + + uint64_t balance = 0; + + // Act + error = cardano_provider_get_rewards_available(provider, (cardano_reward_address_t*)"", &balance); + + // Assert + EXPECT_EQ(error, CARDANO_SUCCESS); + + // Cleanup + cardano_provider_unref(&provider); +} + +TEST(cardano_provider_get_rewards_available, returnsErrorIfRewardsIsNotImplemented) +{ + // Arrange + cardano_provider_t* provider = nullptr; + cardano_error_t error = cardano_provider_new(cardano_empty_provider_impl_new(), &provider); + + ASSERT_EQ(error, CARDANO_SUCCESS); + + uint64_t balance = 0; + + // Act + error = cardano_provider_get_rewards_available(provider, (cardano_reward_address_t*)"", &balance); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_NOT_IMPLEMENTED); + + // Cleanup + cardano_provider_unref(&provider); +} diff --git a/lib/tests/scripts/native_scripts/script_pubkey.cpp b/lib/tests/scripts/native_scripts/script_pubkey.cpp index 272be0a2..7d3a5ade 100644 --- a/lib/tests/scripts/native_scripts/script_pubkey.cpp +++ b/lib/tests/scripts/native_scripts/script_pubkey.cpp @@ -493,3 +493,58 @@ TEST(cardano_script_pubkey_new, returnsErrorIfMemoryAllocationFails) // Cleanup cardano_set_allocators(malloc, realloc, free); } + +TEST(cardano_script_pubkey_get_key_hash, returnsErrorIfPubKeyIsNull) +{ + // Act + cardano_error_t error = cardano_script_pubkey_get_key_hash(nullptr, nullptr); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_POINTER_IS_NULL); +} + +TEST(cardano_script_pubkey_get_key_hash, returnsErrorIfKeyHashIsNull) +{ + // Arrange + cardano_script_pubkey_t* pubkey = nullptr; + + cardano_error_t error = cardano_script_pubkey_from_json(PUBKEY_SCRIPT, strlen(PUBKEY_SCRIPT), &pubkey); + ASSERT_EQ(error, CARDANO_SUCCESS); + + // Act + error = cardano_script_pubkey_get_key_hash(pubkey, nullptr); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_POINTER_IS_NULL); + + // Cleanup + cardano_script_pubkey_unref(&pubkey); +} + +TEST(cardano_script_pubkey_get_key_hash, returnsTheKey) +{ + // Arrange + cardano_script_pubkey_t* pubkey = nullptr; + cardano_blake2b_hash_t* key_hash = nullptr; + + cardano_error_t error = cardano_script_pubkey_from_json(PUBKEY_SCRIPT, strlen(PUBKEY_SCRIPT), &pubkey); + ASSERT_EQ(error, CARDANO_SUCCESS); + + // Act + error = cardano_script_pubkey_get_key_hash(pubkey, &key_hash); + + // key_hash convert to hex + const size_t hex_size = cardano_blake2b_hash_get_hex_size(key_hash); + char* hex = (char*)malloc(hex_size); + + EXPECT_EQ(cardano_blake2b_hash_to_hex(key_hash, hex, hex_size), CARDANO_SUCCESS); + + // Assert + EXPECT_EQ(error, CARDANO_SUCCESS); + EXPECT_STREQ(hex, "966e394a544f242081e41d1965137b1bb412ac230d40ed5407821c37"); + + // Cleanup + cardano_script_pubkey_unref(&pubkey); + free(hex); + cardano_blake2b_hash_unref(&key_hash); +} \ No newline at end of file diff --git a/lib/tests/transaction_body/value.cpp b/lib/tests/transaction_body/value.cpp index a6737a3a..9260329c 100644 --- a/lib/tests/transaction_body/value.cpp +++ b/lib/tests/transaction_body/value.cpp @@ -2121,3 +2121,50 @@ TEST(cardano_value_equals, returnFalseIfOneIsNull) // Cleanup cardano_value_unref(&value); } + +TEST(cardano_value_from_asset_map, canCreateValueFromAssetMap) +{ + // Arrange + cardano_value_t* valA = new_default_value(CBOR_VALUE_WITH_TWICE_THE_ASSETS); + cardano_asset_id_map_t* asset_id_map = cardano_value_as_assets_map(valA); + + // Act + cardano_value_t* value = NULL; + + EXPECT_EQ(cardano_value_from_asset_map(asset_id_map, &value), CARDANO_SUCCESS); + + // to cbor + cardano_cbor_writer_t* writer = cardano_cbor_writer_new(); + + cardano_error_t error = cardano_value_to_cbor(value, writer); + + EXPECT_EQ(error, CARDANO_SUCCESS); + + const size_t hex_size = cardano_cbor_writer_get_hex_size(writer); + + EXPECT_EQ(hex_size, strlen(CBOR_VALUE_WITH_TWICE_THE_ASSETS) + 1); + + char* actual_cbor = (char*)malloc(hex_size); + + error = cardano_cbor_writer_encode_hex(writer, actual_cbor, hex_size); + + EXPECT_EQ(error, CARDANO_SUCCESS); + + EXPECT_STREQ(actual_cbor, CBOR_VALUE_WITH_TWICE_THE_ASSETS); + + // Cleanup + cardano_asset_id_map_unref(&asset_id_map); + cardano_value_unref(&value); + cardano_value_unref(&valA); + cardano_cbor_writer_unref(&writer); + free(actual_cbor); +} + +TEST(cardano_value_from_asset_map, returnErrorIfValueIsNull) +{ + // Act + cardano_error_t error = cardano_value_from_asset_map(nullptr, nullptr); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_POINTER_IS_NULL); +} \ No newline at end of file diff --git a/lib/tests/witness_set/redeemer_list.cpp b/lib/tests/witness_set/redeemer_list.cpp index fff96e81..706cc94b 100644 --- a/lib/tests/witness_set/redeemer_list.cpp +++ b/lib/tests/witness_set/redeemer_list.cpp @@ -998,4 +998,178 @@ TEST(cardano_redeemer_list_clear_cbor_cache, doesNothingIfRedeemerSetIsNull) { // Act cardano_redeemer_list_clear_cbor_cache(nullptr); +} + +TEST(cardano_redeemer_list_set_ex_units, returnsErrorIfRedeemerSetIsNull) +{ + // Act + cardano_error_t error = cardano_redeemer_list_set_ex_units(nullptr, CARDANO_REDEEMER_TAG_SPEND, 0, 0, 0); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_POINTER_IS_NULL); +} + +TEST(cardano_redeemer_list_set_ex_units, returnsErrorIfElementWithTagAndIndexNotFound) +{ + // Arrange + cardano_redeemer_list_t* redeemer_list = nullptr; + cardano_error_t error = cardano_redeemer_list_new(&redeemer_list); + + EXPECT_EQ(error, CARDANO_SUCCESS); + + // Act + error = cardano_redeemer_list_set_ex_units(redeemer_list, CARDANO_REDEEMER_TAG_SPEND, 0, 0, 0); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_ELEMENT_NOT_FOUND); + + // Cleanup + cardano_redeemer_list_unref(&redeemer_list); +} + +TEST(cardano_redeemer_list_set_ex_units, canSetTheExecutionUnits) +{ + // Arrange + cardano_redeemer_list_t* redeemer_list = nullptr; + cardano_error_t error = cardano_redeemer_list_new(&redeemer_list); + + EXPECT_EQ(error, CARDANO_SUCCESS); + + const char* redeemers[] = { REDEEMER1_CBOR, REDEEMER2_CBOR, REDEEMER3_CBOR, REDEEMER4_CBOR }; + + for (size_t i = 0; i < 4; ++i) + { + cardano_redeemer_t* redeemer = new_default_redeemer(redeemers[i]); + + EXPECT_EQ(cardano_redeemer_list_add(redeemer_list, redeemer), CARDANO_SUCCESS); + + cardano_redeemer_unref(&redeemer); + } + + // Act + error = cardano_redeemer_list_set_ex_units(redeemer_list, CARDANO_REDEEMER_TAG_SPEND, 0, 1, 2); + EXPECT_EQ(error, CARDANO_SUCCESS); + + // Assert + cardano_redeemer_t* elem1 = NULL; + + EXPECT_EQ(cardano_redeemer_list_get(redeemer_list, 0, &elem1), CARDANO_SUCCESS); + + cardano_ex_units_t* ex_units = cardano_redeemer_get_ex_units(elem1); + + uint64_t cpu = cardano_ex_units_get_cpu_steps(ex_units); + uint64_t memory = cardano_ex_units_get_memory(ex_units); + + EXPECT_EQ(cpu, 2); + EXPECT_EQ(memory, 1); + + // Cleanup + cardano_redeemer_list_unref(&redeemer_list); + cardano_redeemer_unref(&elem1); + cardano_ex_units_unref(&ex_units); +} + +TEST(cardano_redeemer_list_clone, returnsErrorIfRedeemerSetIsNull) +{ + // Act + cardano_redeemer_list_t* cloned = nullptr; + cardano_error_t error = cardano_redeemer_list_clone(nullptr, &cloned); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_POINTER_IS_NULL); + EXPECT_EQ(cloned, (cardano_redeemer_list_t*)nullptr); +} + +TEST(cardano_redeemer_list_clone, returnsErrorIfClonedIsNull) +{ + // Arrange + cardano_redeemer_list_t* redeemer_list = nullptr; + cardano_error_t error = cardano_redeemer_list_new(&redeemer_list); + + EXPECT_EQ(error, CARDANO_SUCCESS); + + // Act + error = cardano_redeemer_list_clone(redeemer_list, nullptr); + + // Assert + EXPECT_EQ(error, CARDANO_ERROR_POINTER_IS_NULL); + + // Cleanup + cardano_redeemer_list_unref(&redeemer_list); +} + +TEST(cardano_redeemer_list_clone, canCloneRedeemerSet) +{ + // Arrange + cardano_redeemer_list_t* redeemer_list = nullptr; + cardano_error_t error = cardano_redeemer_list_new(&redeemer_list); + + EXPECT_EQ(error, CARDANO_SUCCESS); + + const char* redeemers[] = { REDEEMER1_CBOR, REDEEMER2_CBOR, REDEEMER3_CBOR, REDEEMER4_CBOR }; + + for (size_t i = 0; i < 4; ++i) + { + cardano_redeemer_t* redeemer = new_default_redeemer(redeemers[i]); + + EXPECT_EQ(cardano_redeemer_list_add(redeemer_list, redeemer), CARDANO_SUCCESS); + + cardano_redeemer_unref(&redeemer); + } + + // Act + cardano_redeemer_list_t* cloned = nullptr; + error = cardano_redeemer_list_clone(redeemer_list, &cloned); + + // Assert + EXPECT_EQ(error, CARDANO_SUCCESS); + EXPECT_THAT(cloned, testing::Not((cardano_redeemer_list_t*)nullptr)); + + const size_t length = cardano_redeemer_list_get_length(cloned); + + EXPECT_EQ(length, 4); + + cardano_redeemer_t* elem1 = NULL; + cardano_redeemer_t* elem2 = NULL; + cardano_redeemer_t* elem3 = NULL; + cardano_redeemer_t* elem4 = NULL; + + EXPECT_EQ(cardano_redeemer_list_get(cloned, 0, &elem1), CARDANO_SUCCESS); + EXPECT_EQ(cardano_redeemer_list_get(cloned, 1, &elem2), CARDANO_SUCCESS); + EXPECT_EQ(cardano_redeemer_list_get(cloned, 2, &elem3), CARDANO_SUCCESS); + EXPECT_EQ(cardano_redeemer_list_get(cloned, 3, &elem4), CARDANO_SUCCESS); + + const char* redeemers2[] = { REDEEMER1_CBOR, REDEEMER4_CBOR, REDEEMER3_CBOR, REDEEMER2_CBOR }; + + cardano_redeemer_t* redeemers_array[] = { elem1, elem2, elem3, elem4 }; + + for (size_t i = 0; i < 4; ++i) + { + cardano_cbor_writer_t* writer = cardano_cbor_writer_new(); + + error = cardano_redeemer_to_cbor(redeemers_array[i], writer); + + EXPECT_EQ(error, CARDANO_SUCCESS); + + const size_t hex_size = cardano_cbor_writer_get_hex_size(writer); + EXPECT_EQ(hex_size, strlen(redeemers2[i]) + 1); + + char* actual_cbor = (char*)malloc(hex_size); + + error = cardano_cbor_writer_encode_hex(writer, actual_cbor, hex_size); + EXPECT_EQ(error, CARDANO_SUCCESS); + + EXPECT_STREQ(actual_cbor, redeemers2[i]); + + cardano_cbor_writer_unref(&writer); + free(actual_cbor); + } + + // Cleanup + cardano_redeemer_list_unref(&redeemer_list); + cardano_redeemer_list_unref(&cloned); + cardano_redeemer_unref(&elem1); + cardano_redeemer_unref(&elem2); + cardano_redeemer_unref(&elem3); + cardano_redeemer_unref(&elem4); } \ No newline at end of file