diff --git a/dap-sdk/core/include/dap_math_ops.h b/dap-sdk/core/include/dap_math_ops.h index a6efc23c6f7f9468bc91767948eb094614954960..d25478e5de620dfc897c0195fdf9df1e49c16851 100755 --- a/dap-sdk/core/include/dap_math_ops.h +++ b/dap-sdk/core/include/dap_math_ops.h @@ -1031,6 +1031,27 @@ static inline int MULT_256_COIN(uint256_t a_val, uint256_t b_val, uint256_t* res return overflow; } +/** + * Divides to fixed-point values, represented as 256-bit values + * @param a_val + * @param b_val + * @param result is a fixed-point value, represented as 256-bit value + * @return + */ +static inline void DIV_256_COIN(uint256_t a, uint256_t b, uint256_t *res) +{ + // define 10^36 +#ifdef DAP_GLOBAL_IS_INT128 + uint128_t quad = *(uint128_t *)"\x0\x0\x0\x0\x10\x9f\x4b\xb3\x15\x07\xc9\x7b\xce\x97\xc0\x0"; +#else + uint128_t quad = {.lo = 54210108624275221ULL, .hi = 12919594847110692864ULL}; +#endif + uint256_t quad256 = GET_256_FROM_128(quad); + uint256_t tmp = uint256_0; + DIV_256(quad256, b, &tmp); // assertion with zero divisor inside + MULT_256_COIN(a, tmp, res); +} + #ifdef __cplusplus } #endif diff --git a/modules/common/dap_chain_common.c b/modules/common/dap_chain_common.c index 3592d548586e937689c4d9f564ad560108a8ce09..ff8056793e3c2012c314e968d98469828402b21f 100644 --- a/modules/common/dap_chain_common.c +++ b/modules/common/dap_chain_common.c @@ -605,10 +605,10 @@ uint256_t dap_chain_coins_to_balance256(const char *a_coins) char *dap_cvt_uint256_to_str(uint256_t a_uint256) { char *l_buf = DAP_NEW_Z_SIZE(char, DATOSHI_POW256 + 3); int l_pos = 0; +#ifdef DAP_GLOBAL_IS_INT128 uint256_t l_value = a_uint256; uint256_t uint256_ten = {.hi = 0, .lo = 10}; uint256_t rem; -#ifdef DAP_GLOBAL_IS_INT128 do { divmod_impl_256(l_value, uint256_ten, &l_value, &rem); l_buf[l_pos++] = rem.lo + '0'; diff --git a/modules/common/dap_chain_datum.c b/modules/common/dap_chain_datum.c index b01de17951b1fb56e2f71c9e5db7ffe1ede98540..0584789487100c41c97871a4ceb6521fa8ee4bfb 100644 --- a/modules/common/dap_chain_datum.c +++ b/modules/common/dap_chain_datum.c @@ -436,15 +436,15 @@ bool dap_chain_datum_dump_tx(dap_chain_datum_tx_t *a_datum, DAP_DELETE(l_coins_str); } break; case DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_XCHANGE: { - char *l_value_str = dap_chain_balance_print(((dap_chain_tx_out_cond_t*)item)->subtype.srv_xchange.value); - char *l_coins_str = dap_chain_balance_to_coins(((dap_chain_tx_out_cond_t*)item)->subtype.srv_xchange.value); + char *l_value_str = dap_chain_balance_print(((dap_chain_tx_out_cond_t*)item)->subtype.srv_xchange.buy_value); + char *l_coins_str = dap_chain_balance_to_coins(((dap_chain_tx_out_cond_t*)item)->subtype.srv_xchange.buy_value); dap_string_append_printf(a_str_out, "\t\t\t uid: 0x%016"DAP_UINT64_FORMAT_x"\n" "\t\t\t net id: 0x%016"DAP_UINT64_FORMAT_x"\n" "\t\t\t token: %s\n" "\t\t\t value: %s (%s)\n", ((dap_chain_tx_out_cond_t*)item)->header.srv_uid.uint64, - ((dap_chain_tx_out_cond_t*)item)->subtype.srv_xchange.net_id.uint64, - ((dap_chain_tx_out_cond_t*)item)->subtype.srv_xchange.token, + ((dap_chain_tx_out_cond_t*)item)->subtype.srv_xchange.buy_net_id.uint64, + ((dap_chain_tx_out_cond_t*)item)->subtype.srv_xchange.buy_token, l_coins_str, l_value_str); DAP_DELETE(l_value_str); diff --git a/modules/common/dap_chain_datum_tx_items.c b/modules/common/dap_chain_datum_tx_items.c index c109cf8b8a912db297d940ab5c8340d6b20843fe..7540fd0f9c83d531a36d6c26cc664f24d9e3f17f 100644 --- a/modules/common/dap_chain_datum_tx_items.c +++ b/modules/common/dap_chain_datum_tx_items.c @@ -358,21 +358,24 @@ dap_chain_tx_out_cond_t* dap_chain_datum_tx_item_out_cond_create_srv_pay(dap_pke return l_item; } -dap_chain_tx_out_cond_t *dap_chain_datum_tx_item_out_cond_create_srv_xchange(dap_chain_net_srv_uid_t a_srv_uid, dap_chain_net_id_t a_net_id, - const char *a_token, uint256_t a_value, +dap_chain_tx_out_cond_t *dap_chain_datum_tx_item_out_cond_create_srv_xchange(dap_chain_net_srv_uid_t a_srv_uid, dap_chain_net_id_t a_sell_net_id, + uint256_t a_value_sell, dap_chain_net_id_t a_buy_net_id, + const char *a_token, uint256_t a_value_buy, const void *a_params, uint32_t a_params_size) { if (!a_token) return NULL; - if (IS_ZERO_256(a_value)) + if (IS_ZERO_256(a_value_sell) || IS_ZERO_256(a_value_buy)) return NULL; dap_chain_tx_out_cond_t *l_item = DAP_NEW_Z_SIZE(dap_chain_tx_out_cond_t, sizeof(dap_chain_tx_out_cond_t) + a_params_size); l_item->header.item_type = TX_ITEM_TYPE_OUT_COND; - l_item->header.value = a_value; + l_item->header.value = a_value_sell; l_item->header.subtype = DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_XCHANGE; l_item->header.srv_uid = a_srv_uid; - l_item->subtype.srv_xchange.net_id = a_net_id; - strcpy(l_item->subtype.srv_xchange.token, a_token); + l_item->subtype.srv_xchange.buy_net_id = a_buy_net_id; + l_item->subtype.srv_xchange.sell_net_id = a_sell_net_id; + strncpy(l_item->subtype.srv_xchange.buy_token, a_token, DAP_CHAIN_TICKER_SIZE_MAX); + l_item->subtype.srv_xchange.buy_value = a_value_buy; l_item->params_size = a_params_size; if (a_params_size) { memcpy(l_item->params, a_params, a_params_size); diff --git a/modules/common/include/dap_chain_datum_tx_items.h b/modules/common/include/dap_chain_datum_tx_items.h index 8aa908cc308ddc5b3ec6a19df002f78c8aa2da0e..8879e6261d1550c18be4412e937442f6e3e6ad86 100644 --- a/modules/common/include/dap_chain_datum_tx_items.h +++ b/modules/common/include/dap_chain_datum_tx_items.h @@ -154,9 +154,10 @@ dap_chain_tx_out_cond_t* dap_chain_datum_tx_item_out_cond_create_srv_pay(dap_pke * * return item, NULL Error */ -dap_chain_tx_out_cond_t *dap_chain_datum_tx_item_out_cond_create_srv_xchange(dap_chain_net_srv_uid_t a_srv_uid, - dap_chain_net_id_t a_net_id, const char *a_token, uint256_t a_value, - const void *a_params, uint32_t a_params_size); +dap_chain_tx_out_cond_t *dap_chain_datum_tx_item_out_cond_create_srv_xchange(dap_chain_net_srv_uid_t a_srv_uid, dap_chain_net_id_t a_sell_net_id, + uint256_t a_value_sell, dap_chain_net_id_t a_buy_net_id, + const char *a_token, uint256_t a_value_buy, + const void *a_params, uint32_t a_params_size); /** * Create item dap_chain_tx_out_cond_t for stake service * diff --git a/modules/common/include/dap_chain_datum_tx_out_cond.h b/modules/common/include/dap_chain_datum_tx_out_cond.h index 004b1e0be325c48b693f8764de692a6cb8c1a2f2..acd3dd82422e0036813259387dec363be3502c29 100644 --- a/modules/common/include/dap_chain_datum_tx_out_cond.h +++ b/modules/common/include/dap_chain_datum_tx_out_cond.h @@ -87,12 +87,14 @@ typedef struct dap_chain_tx_out_cond { uint256_t unit_price_max_datoshi; } srv_pay; struct { - // Token ticker to change to - char token[DAP_CHAIN_TICKER_SIZE_MAX]; + // Chain network to change from + dap_chain_net_id_t sell_net_id; // Chain network to change to - dap_chain_net_id_t net_id; + dap_chain_net_id_t buy_net_id; // Total amount of datoshi to change to - uint256_t value; + uint256_t buy_value; + // Token ticker to change to + char buy_token[DAP_CHAIN_TICKER_SIZE_MAX]; } srv_xchange; struct { // Stake holder address diff --git a/modules/net/dap_chain_node_cli_cmd.c b/modules/net/dap_chain_node_cli_cmd.c index b7d0946537995eb6603f209a6840fa08ce42033d..7d1c2f0e00fc7c0c7ea100aa1189fb62b8374f03 100644 --- a/modules/net/dap_chain_node_cli_cmd.c +++ b/modules/net/dap_chain_node_cli_cmd.c @@ -4340,7 +4340,7 @@ int com_tx_create_json(int a_argc, char ** a_argv, char **a_str_reply) } const char *l_params_str = s_json_get_text(l_json_item_obj, "params"); size_t l_params_size = dap_strlen(l_params_str); - dap_chain_tx_out_cond_t *l_out_cond_item = dap_chain_datum_tx_item_out_cond_create_srv_xchange(l_srv_uid, l_net->pub.id, l_token, l_value, l_params_str, l_params_size); + dap_chain_tx_out_cond_t *l_out_cond_item = NULL;//dap_chain_datum_tx_item_out_cond_create_srv_xchange(l_srv_uid, l_net->pub.id, l_token, l_value, l_params_str, l_params_size); l_item = (const uint8_t*) l_out_cond_item; } break; diff --git a/modules/service/xchange/dap_chain_net_srv_xchange.c b/modules/service/xchange/dap_chain_net_srv_xchange.c index 1840ca716cbcf7845a4b2f72a303e6fb418ea7f3..adbc853d200d27c67fd376bf77587f90c46688b4 100644 --- a/modules/service/xchange/dap_chain_net_srv_xchange.c +++ b/modules/service/xchange/dap_chain_net_srv_xchange.c @@ -1,1438 +1,1507 @@ -/* - * Authors: - * Roman Khlopkov <roman.khlopkov@demlabs.net> - * DeM Labs Inc. https://demlabs.net - * DeM Labs Open source community https://gitlab.demlabs.net - * Copyright (c) 2017-2020 - * All rights reserved. - - This file is part of DAP (Deus Applications Prototypes) the open source project - - DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - DAP is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with any DAP based project. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include <math.h> -#include <stdint.h> -#include "dap_chain_datum_token.h" -#include "dap_chain_datum_tx.h" -#include "dap_chain_datum_tx_in.h" -#include "dap_chain_ledger.h" -#include "dap_chain_net.h" -#include "dap_chain_node_cli.h" -#include "dap_hash.h" -#include "dap_list.h" -#include "dap_math_ops.h" -#include "dap_strfuncs.h" -#include "dap_string.h" -#include "dap_chain_common.h" -#include "dap_chain_mempool.h" -#include "dap_chain_net_srv.h" -#include "dap_chain_net_srv_xchange.h" -#include "dap_time.h" - -#define LOG_TAG "dap_chain_net_srv_xchange" - -const dap_chain_net_srv_uid_t c_dap_chain_net_srv_xchange_uid = {.uint64= DAP_CHAIN_NET_SRV_XCHANGE_ID}; - - -static int s_cli_srv_xchange(int a_argc, char **a_argv, char **a_str_reply); -static int s_callback_requested(dap_chain_net_srv_t *a_srv, uint32_t a_usage_id, dap_chain_net_srv_client_remote_t *a_srv_client, const void *a_data, size_t a_data_size); -static int s_callback_response_success(dap_chain_net_srv_t *a_srv, uint32_t a_usage_id, dap_chain_net_srv_client_remote_t *a_srv_client, const void *a_data, size_t a_data_size); -static int s_callback_response_error(dap_chain_net_srv_t *a_srv, uint32_t a_usage_id, dap_chain_net_srv_client_remote_t *a_srv_client, const void *a_data, size_t a_data_size); -static int s_callback_receipt_next_success(dap_chain_net_srv_t *a_srv, uint32_t a_usage_id, dap_chain_net_srv_client_remote_t *a_srv_client, const void *a_data, size_t a_data_size); -static dap_chain_net_srv_xchange_price_t *s_xchange_db_load(char *a_key, uint8_t *a_item); - -static dap_chain_net_srv_xchange_t *s_srv_xchange; - -/** - * @brief dap_stream_ch_vpn_init Init actions for VPN stream channel - * @param vpn_addr Zero if only client mode. Address if the node shares its local VPN - * @param vpn_mask Zero if only client mode. Mask if the node shares its local VPN - * @return 0 if everything is okay, lesser then zero if errors - */ -int dap_chain_net_srv_xchange_init() -{ - dap_chain_node_cli_cmd_item_create("srv_xchange", s_cli_srv_xchange, "eXchange service commands", - "srv_xchange price create -net_sell <net name> -token_sell <token ticker> -net_buy <net_name> -token_buy <token ticker> -wallet <name> -coins <value> -rate <value>\n" - "\tCreate a new price with specified amount of datoshi to exchange with specified rate (sell : buy)\n" - "srv_xchange price remove -net_sell <net name> -token_sell <token ticker> -net_buy <net_name> -token_buy <token ticker>\n" - "\tRemove price with specified tickers within specified net names\n" - "srv_xchange price list\n" - "\tList all active prices\n" - "srv_xchange price update -net_sell <net name> -token_sell <token ticker> -net_buy <net_name> -token_buy <token ticker> {-coins <value> | -rate <value> | -wallet <name>}\n" - "\tUpdate price with specified tickers within specified net names\n" - "srv_xchange orders -net <net name>\n" - "\tGet the exchange orders list within specified net name\n" - "srv_xchange purchase -order <order hash> -net <net name> -wallet <wallet_name> -coins <value>\n" - "\tExchange tokens with specified order within specified net name. Specify how datoshies to buy\n" - - "srv_xchange tx_list -net <net name> [-time_from <yymmdd> -time_to <yymmdd>]" - "[[-addr <wallet_addr> [-status closed | open] ]\n" /* @RRL: #6294 */ - - "\tList of exchange transactions\n" - "srv_xchange token_pair -net <net name> list all\n" - "\tList of all token pairs\n" - "srv_xchange token_pair -net <net name> price average -token1 <token 1> -token2 <token 2> [-time_from <From time>] [-time_to <To time>] \n" - "\tGet average price for token pair <token 1>:<token 2> from <From time> to <To time> \n" - "\tAll times are in RFC822\n" - "srv_xchange token_pair -net <net name> price history -token1 <token 1> -token2 <token 2> [-time_from <From time>] [-time_to <To time>] \n" - "\tPrint price history for token pair <token 1>:<token 2> from <From time> to <To time>\n" - "\tAll times are in RFC822\n" - "srv_xchange enable\n" - "\tEnable eXchange service\n" - "srv_xchange disable\n" - "\tDisable eXchange service\n" - ); - dap_chain_net_srv_uid_t l_uid = { .uint64 = DAP_CHAIN_NET_SRV_XCHANGE_ID }; - dap_chain_net_srv_t* l_srv = dap_chain_net_srv_add(l_uid, "srv_xchange", s_callback_requested, - s_callback_response_success, s_callback_response_error, - s_callback_receipt_next_success, NULL); - s_srv_xchange = DAP_NEW_Z(dap_chain_net_srv_xchange_t); - l_srv->_internal = s_srv_xchange; - s_srv_xchange->parent = l_srv; - s_srv_xchange->enabled = false; - size_t l_prices_count = 0; - dap_global_db_obj_t *l_prices = dap_global_db_get_all_sync(GROUP_LOCAL_XCHANGE, &l_prices_count); - for (size_t i = 0; i < l_prices_count; i++) { - dap_chain_net_srv_xchange_price_t *l_price = s_xchange_db_load(l_prices[i].key, l_prices[i].value); - HASH_ADD_KEYPTR(hh, s_srv_xchange->pricelist, l_price->key_ptr, strlen(l_price->key_ptr), l_price); - } - dap_global_db_objs_delete(l_prices, l_prices_count); - return 1; -} - -void dap_chain_net_srv_xchange_deinit() -{ - if(!s_srv_xchange) - return; - dap_chain_net_srv_xchange_price_t *l_price = NULL, *l_tmp; - HASH_ITER(hh, s_srv_xchange->pricelist, l_price, l_tmp) { - // Clang bug at this, l_price should change at every loop cycle - HASH_DEL(s_srv_xchange->pricelist, l_price); - DAP_DELETE(l_price->wallet_str); - DAP_DELETE(l_price->key_ptr); - DAP_DELETE(l_price); - } - dap_chain_net_srv_del(s_srv_xchange->parent); - DAP_DELETE(s_srv_xchange); -} - -bool dap_chain_net_srv_xchange_verificator(dap_chain_tx_out_cond_t *a_cond, dap_chain_datum_tx_t *a_tx, bool a_owner) -{ - if (a_owner) - return true; - /* Check the condition for verification success - * a_cond.srv_xchange.rate >= a_tx.out.rate - */ - dap_list_t *l_list_out = dap_chain_datum_tx_items_get(a_tx, TX_ITEM_TYPE_OUT_EXT, NULL); - uint256_t l_out_val = {}, l_back_val = {}; - char *l_ticker_ctrl = NULL; - for (dap_list_t *l_list_tmp = l_list_out; l_list_tmp; l_list_tmp = l_list_tmp->next) { - dap_chain_tx_out_ext_t *l_tx_out = (dap_chain_tx_out_ext_t *)l_list_tmp->data; - if (memcmp(&l_tx_out->addr, &a_cond->params, sizeof(dap_chain_addr_t))) { - continue; - } - if (strcmp(l_tx_out->token, a_cond->subtype.srv_xchange.token)) { - if (l_ticker_ctrl && strcmp(l_ticker_ctrl, l_tx_out->token)) { - return false; // too many tokens - } - l_ticker_ctrl = l_tx_out->token; - SUM_256_256(l_back_val, l_tx_out->header.value, &l_back_val); - } else { // buying token - SUM_256_256(l_out_val, l_tx_out->header.value, &l_out_val); - } - } - //long double l_buyer_rate = (a_cond->header.value - l_back_val) / (long double)l_out_val; - //long double l_seller_rate = (long double)a_cond->header.value / a_cond->subtype.srv_xchange.value; - uint256_t l_buyer_val = {}, l_buyer_mul = {}, l_seller_mul = {}; - SUBTRACT_256_256(a_cond->header.value, l_back_val, &l_buyer_val); - MULT_256_256(l_buyer_val, a_cond->subtype.srv_xchange.value, &l_buyer_mul); - MULT_256_256(l_out_val, a_cond->header.value, &l_seller_mul); - if (compare256(l_seller_mul, l_buyer_mul) == -1) { - return false; // wrong changing rate - } - return true; -} - -static dap_chain_datum_tx_receipt_t *s_xchage_receipt_create(dap_chain_net_srv_xchange_price_t *a_price) -{ - uint32_t l_ext_size = sizeof(uint256_t) + DAP_CHAIN_TICKER_SIZE_MAX; - uint8_t *l_ext = DAP_NEW_STACK_SIZE(uint8_t, l_ext_size); - uint256_t l_datoshi_buy = uint256_0; // TODO rework it with fixed point MULT_256_FRAC_FRAC(a_price->datoshi_sell, 1 / a_price->rate); +++ - DIV_256(dap_chain_coins_to_balance("1.0"), a_price->rate, &l_datoshi_buy); - if (MULT_256_COIN(a_price->datoshi_sell, l_datoshi_buy, &l_datoshi_buy)) { - log_it(L_WARNING, "DANGER: MULT_256_COIN overflow! in s_xchage_receipt_create()"); - l_datoshi_buy = uint256_0; - } - memcpy(l_ext, &l_datoshi_buy, sizeof(uint256_t)); - strcpy((char *)&l_ext[sizeof(uint256_t)], a_price->token_buy); - dap_chain_net_srv_price_unit_uid_t l_unit = { .uint32 = SERV_UNIT_UNDEFINED}; - dap_chain_net_srv_uid_t l_uid = { .uint64 = DAP_CHAIN_NET_SRV_XCHANGE_ID }; - dap_chain_datum_tx_receipt_t *l_receipt = dap_chain_datum_tx_receipt_create(l_uid, l_unit, 0, a_price->datoshi_sell, - l_ext, l_ext_size); - return l_receipt; -} - -static dap_chain_datum_tx_t *s_xchange_tx_create_request(dap_chain_net_srv_xchange_price_t *a_price, dap_chain_wallet_t *a_wallet) -{ - if (!a_price || !a_price->net_sell || !a_price->net_buy || !*a_price->token_sell || !*a_price->token_buy || !a_wallet) { - return NULL; - } - - // create empty transaction - dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create(); - - dap_ledger_t *l_ledger = dap_chain_ledger_by_net_name(a_price->net_sell->pub.name); - dap_chain_addr_t *l_seller_addr = (dap_chain_addr_t *)dap_chain_wallet_get_addr(a_wallet, a_price->net_sell->pub.id); - dap_enc_key_t *l_seller_key = dap_chain_wallet_get_key(a_wallet, 0); - uint256_t l_value_sell = {}; // how many coins to transfer - // list of transaction with 'out' items to sell - dap_list_t *l_list_used_out = dap_chain_ledger_get_list_tx_outs_with_val(l_ledger, a_price->token_sell, - l_seller_addr, a_price->datoshi_sell, &l_value_sell); - if(!l_list_used_out) { - dap_chain_datum_tx_delete(l_tx); - DAP_DELETE(l_seller_addr); - log_it(L_WARNING, "Nothing to change (not enough funds)"); - return NULL; - } - - // add 'in' items to sell - uint256_t l_value_to_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_used_out); - dap_list_free_full(l_list_used_out, NULL); - if (compare256(l_value_to_items, l_value_sell) != 0) { - dap_chain_datum_tx_delete(l_tx); - DAP_DELETE(l_seller_addr); - log_it(L_ERROR, "Can't compose the transaction input"); - return NULL; - } - - // add 'out_cond' & 'out' items - { - dap_chain_net_srv_uid_t l_uid = { .uint64 = DAP_CHAIN_NET_SRV_XCHANGE_ID }; - dap_chain_tx_out_cond_t *l_tx_out = dap_chain_datum_tx_item_out_cond_create_srv_xchange(l_uid, a_price->net_sell->pub.id, - a_price->token_sell, a_price->datoshi_sell, - (void *)l_seller_addr, sizeof(dap_chain_addr_t)); - if (!l_tx_out) { - dap_chain_datum_tx_delete(l_tx); - DAP_DELETE(l_seller_addr); - log_it(L_ERROR, "Can't compose the transaction conditional output"); - return NULL; - } - dap_chain_datum_tx_add_item(&l_tx, (const uint8_t *)l_tx_out); - DAP_DELETE(l_tx_out); - // coin back - uint256_t l_value_back = {}; - SUBTRACT_256_256(l_value_sell, a_price->datoshi_sell, &l_value_back); - if (!IS_ZERO_256(l_value_back)) { - if (dap_chain_datum_tx_add_out_item(&l_tx, l_seller_addr, l_value_back) != 1) { - dap_chain_datum_tx_delete(l_tx); - DAP_DELETE(l_seller_addr); - log_it(L_ERROR, "Cant add coin back output"); - return NULL; - } - } - } - DAP_DELETE(l_seller_addr); - - // add 'sign' item - if(dap_chain_datum_tx_add_sign_item(&l_tx, l_seller_key) != 1) { - dap_chain_datum_tx_delete(l_tx); - log_it(L_ERROR, "Can't add sign output"); - return NULL; - } - - return l_tx; -} - -static dap_chain_datum_tx_t *s_xchange_tx_create_exchange(dap_chain_net_srv_xchange_price_t *a_price, dap_chain_hash_fast_t *a_tx_cond_hash, dap_chain_wallet_t *a_wallet) -{ - if (!a_price || !a_price->net_sell || !a_price->net_buy || !*a_price->token_sell || !*a_price->token_buy || !a_wallet) { - return NULL; - } - - // create empty transaction - dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create(); - - dap_ledger_t *l_ledger = dap_chain_ledger_by_net_name(a_price->net_buy->pub.name); - dap_chain_addr_t *l_seller_addr = (dap_chain_addr_t *)dap_chain_wallet_get_addr(a_wallet, a_price->net_buy->pub.id); - dap_enc_key_t *l_seller_key = dap_chain_wallet_get_key(a_wallet, 0); - uint256_t l_value_buy = {}; // how many coins to transfer - // list of transaction with 'out' items to sell - uint256_t l_datoshi_buy = uint256_0; // TODO rework it with fixed point MULT_256_FRAC_FRAC(a_price->datoshi_sell, 1 / a_price->rate); +++ - DIV_256(dap_chain_coins_to_balance("1.0"), a_price->rate, &l_datoshi_buy); - if (MULT_256_COIN(a_price->datoshi_sell, l_datoshi_buy, &l_datoshi_buy)) { - log_it(L_WARNING, "DANGER: MULT_256_COIN overflow! in s_xchange_tx_create_exchange()"); - l_datoshi_buy = uint256_0; - } - dap_list_t *l_list_used_out = dap_chain_ledger_get_list_tx_outs_with_val(l_ledger, a_price->token_buy, - l_seller_addr, l_datoshi_buy, &l_value_buy); - if(!l_list_used_out) { - dap_chain_datum_tx_delete(l_tx); - log_it(L_WARNING, "Nothing to change (not enough funds)"); - return NULL; - } - - // create and add reciept - dap_chain_datum_tx_receipt_t *l_receipt = s_xchage_receipt_create(a_price); - dap_chain_datum_tx_add_item(&l_tx, (byte_t *)l_receipt); - DAP_DELETE(l_receipt); - // add 'in' items to sell - uint256_t l_value_to_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_used_out); - dap_list_free_full(l_list_used_out, NULL); - if (compare256(l_value_to_items, l_value_buy) != 0) { - dap_chain_datum_tx_delete(l_tx); - DAP_DELETE(l_seller_addr); - log_it(L_ERROR, "Can't compose the transaction input"); - return NULL; - } - // add 'in' item to buy from conditional transaction - dap_chain_datum_tx_t *l_cond_tx = dap_chain_ledger_tx_find_by_hash(l_ledger, a_tx_cond_hash); - if (!l_cond_tx) { - log_it(L_WARNING, "Requested conditional transaction not found"); - return NULL; - } - int l_prev_cond_idx = 0; - dap_chain_tx_out_cond_t *l_tx_out_cond = dap_chain_datum_tx_out_cond_get(l_cond_tx, &l_prev_cond_idx); - if (dap_chain_ledger_tx_hash_is_used_out_item(l_ledger, &a_price->tx_hash, l_prev_cond_idx)) { - log_it(L_WARNING, "Requested conditional transaction is already used out"); - return false; - } - dap_chain_datum_tx_add_in_cond_item(&l_tx, a_tx_cond_hash, l_prev_cond_idx, 0); - // add 'out' items - { - // transfer selling coins - const dap_chain_addr_t *l_buyer_addr = (dap_chain_addr_t *)l_tx_out_cond->params; - if (dap_chain_datum_tx_add_out_ext_item(&l_tx, l_buyer_addr, l_datoshi_buy, a_price->token_buy) == -1) { - dap_chain_datum_tx_delete(l_tx); - DAP_DELETE(l_seller_addr); - log_it(L_ERROR, "Can't add selling coins output"); - return NULL; - } - // coin back - uint256_t l_value_back = {}; - SUBTRACT_256_256(l_value_buy, l_datoshi_buy, &l_value_back); - if (!IS_ZERO_256(l_value_back)) { - if (dap_chain_datum_tx_add_out_ext_item(&l_tx, l_seller_addr, l_value_back, a_price->token_buy) == -1) { - dap_chain_datum_tx_delete(l_tx); - DAP_DELETE(l_seller_addr); - log_it(L_ERROR, "Can't add selling coins back output"); - return NULL; - } - } - //transfer buying coins - if (dap_chain_datum_tx_add_out_ext_item(&l_tx, l_seller_addr, a_price->datoshi_sell, a_price->token_sell) == -1) { - dap_chain_datum_tx_delete(l_tx); - DAP_DELETE(l_seller_addr); - log_it(L_ERROR, "Can't add buying coins output"); - return NULL; - } - DAP_DELETE(l_seller_addr); - //transfer unbuying coins (partial exchange) - SUBTRACT_256_256(l_tx_out_cond->header.value, a_price->datoshi_sell, &l_value_back); - if (!IS_ZERO_256(l_value_back)) { - if (dap_chain_datum_tx_add_out_ext_item(&l_tx, l_buyer_addr, l_value_back, a_price->token_sell) == -1) { - log_it(L_WARNING, "Can't add buying coins back output (cashback)"); - return NULL; - } - } - } - - // add 'sign' items - if(dap_chain_datum_tx_add_sign_item(&l_tx, l_seller_key) != 1) { - dap_chain_datum_tx_delete(l_tx); - log_it( L_ERROR, "Can't add sign output"); - return NULL; - } - - return l_tx; -} - - -// Put the transaction to mempool or directly to chains & write transaction's hash to the price -static bool s_xchange_tx_put(dap_chain_datum_tx_t *a_tx, dap_chain_net_t *a_net) -{ - // Put the transaction to mempool or directly to chains - size_t l_tx_size = dap_chain_datum_tx_get_size(a_tx); - dap_chain_datum_t *l_datum = dap_chain_datum_create(DAP_CHAIN_DATUM_TX, a_tx, l_tx_size); - DAP_DELETE(a_tx); - dap_chain_t *l_chain = dap_chain_net_get_chain_by_chain_type(a_net, CHAIN_TYPE_TX); - if (!l_chain) { - DAP_DELETE(l_datum); - return false; - } - // Processing will be made according to autoprocess policy - char *l_ret = dap_chain_mempool_datum_add(l_datum, l_chain); - - DAP_DELETE(l_datum); - - if ( !l_ret ) - return false; - - DAP_DELETE(l_ret); - - return true; -} - -static bool s_xchage_tx_invalidate(dap_chain_net_srv_xchange_price_t *a_price, dap_chain_wallet_t *a_wallet) -{ - // create empty transaction - dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create(); - - dap_ledger_t *l_ledger = dap_chain_ledger_by_net_name(a_price->net_buy->pub.name); - dap_chain_addr_t *l_seller_addr = (dap_chain_addr_t *)dap_chain_wallet_get_addr(a_wallet, a_price->net_buy->pub.id); - dap_enc_key_t *l_seller_key = dap_chain_wallet_get_key(a_wallet, 0); - - // create and add reciept - dap_chain_datum_tx_receipt_t *l_receipt = s_xchage_receipt_create(a_price); - dap_chain_datum_tx_add_item(&l_tx, (byte_t *)l_receipt); - DAP_DELETE(l_receipt); - - // add 'in' item to buy from conditional transaction - dap_chain_datum_tx_t *l_cond_tx = dap_chain_ledger_tx_find_by_hash(l_ledger, &a_price->tx_hash); - if (!l_cond_tx) { - log_it(L_WARNING, "Requested conditional transaction not found"); - return false; - } - int l_prev_cond_idx = 0; - dap_chain_tx_out_cond_t *l_tx_out_cond = dap_chain_datum_tx_out_cond_get(l_cond_tx, &l_prev_cond_idx); - if (dap_chain_ledger_tx_hash_is_used_out_item(l_ledger, &a_price->tx_hash, l_prev_cond_idx)) { - log_it(L_WARNING, "Requested conditional transaction is already used out"); - return false; - } - dap_chain_datum_tx_add_in_cond_item(&l_tx, &a_price->tx_hash, l_prev_cond_idx, 0); - - // add 'out' item - const dap_chain_addr_t *l_buyer_addr = (dap_chain_addr_t *)l_tx_out_cond->params; - if (memcmp(l_seller_addr->data.hash, l_buyer_addr->data.hash, sizeof(dap_chain_hash_fast_t))) { - log_it(L_WARNING, "Only owner can invalidate exchange transaction"); - return false; - } - if (dap_chain_datum_tx_add_out_item(&l_tx, l_seller_addr, l_tx_out_cond->header.value) == -1) { - dap_chain_datum_tx_delete(l_tx); - DAP_DELETE(l_seller_addr); - log_it(L_ERROR, "Cant add returning coins output"); - return false; - } - DAP_DELETE(l_seller_addr); - - // add 'sign' items - if(dap_chain_datum_tx_add_sign_item(&l_tx, l_seller_key) != 1) { - dap_chain_datum_tx_delete(l_tx); - log_it( L_ERROR, "Can't add sign output"); - return false; - } - if (!s_xchange_tx_put(l_tx, a_price->net_buy)) { - return false; - } - return true; -} - -char *s_xchange_order_create(dap_chain_net_srv_xchange_price_t *a_price, dap_chain_datum_tx_t *a_tx) -{ - dap_chain_hash_fast_t l_tx_hash = {}; - dap_hash_fast(a_tx, dap_chain_datum_tx_get_size(a_tx), &l_tx_hash); - memcpy(&a_price->tx_hash, &l_tx_hash, sizeof(dap_chain_hash_fast_t)); - dap_srv_xchange_order_ext_t l_ext; - l_ext.net_sell_id = a_price->net_sell->pub.id.uint64; - l_ext.datoshi_sell = a_price->datoshi_sell; - strcpy(l_ext.token_sell, a_price->token_sell); - uint32_t l_ext_size = sizeof(dap_srv_xchange_order_ext_t); - dap_chain_node_addr_t *l_node_addr = dap_chain_net_get_cur_addr(a_price->net_sell); - dap_chain_net_srv_price_unit_uid_t l_unit = { .uint32 = SERV_UNIT_UNDEFINED}; - dap_chain_net_srv_uid_t l_uid = { .uint64 = DAP_CHAIN_NET_SRV_XCHANGE_ID }; - uint256_t l_datoshi_buy = uint256_0; // TODO rework it with fixed point MULT_256_FRAC_FRAC(a_price->datoshi_sell, 1 / a_price->rate); +++ - DIV_256(dap_chain_coins_to_balance("1.0"), a_price->rate, &l_datoshi_buy); - if (MULT_256_COIN(a_price->datoshi_sell, l_datoshi_buy, &l_datoshi_buy)) { - log_it(L_WARNING, "DANGER: MULT_256_COIN overflow! in s_xchange_order_create()"); - l_datoshi_buy = uint256_0; - } - char *l_order_hash_str = dap_chain_net_srv_order_create(a_price->net_buy, SERV_DIR_SELL, l_uid, *l_node_addr, - l_tx_hash, l_datoshi_buy, l_unit, a_price->token_buy, 0, - (uint8_t *)&l_ext, l_ext_size, NULL, 0, a_price->wallet_key); - return l_order_hash_str; -} - -dap_chain_net_srv_xchange_price_t *s_xchange_price_from_order(dap_chain_net_t *a_net, dap_chain_net_srv_order_t *a_order) -{ - dap_chain_net_srv_xchange_price_t *l_price = DAP_NEW_Z(dap_chain_net_srv_xchange_price_t); - dap_srv_xchange_order_ext_t *l_ext = (dap_srv_xchange_order_ext_t *)a_order->ext_n_sign; - dap_chain_net_id_t l_net_buy_id = { .uint64 = l_ext->net_sell_id }; - l_price->net_sell = dap_chain_net_by_id(l_net_buy_id); - l_price->datoshi_sell = l_ext->datoshi_sell; - strcpy(l_price->token_sell, l_ext->token_sell); - l_price->net_buy = a_net; - strcpy(l_price->token_buy, a_order->price_ticker); - DIV_256(l_price->datoshi_sell, a_order->price, &l_price->rate);//l_price->rate = dap_chain_coins_to_balance("1.0");//1; // TODO (long double)l_price->datoshi_sell / a_order->price; - return l_price; -} - -static bool s_xchange_db_add(dap_chain_net_srv_xchange_price_t *a_price) -{ - int rc; - - size_t l_size = sizeof(dap_chain_net_srv_xchange_db_item_t) + strlen(a_price->wallet_str) + 1; - dap_chain_net_srv_xchange_db_item_t *l_item = DAP_NEW_Z_SIZE(dap_chain_net_srv_xchange_db_item_t, l_size); - strcpy(l_item->token_sell, a_price->token_sell); - strcpy(l_item->token_buy, a_price->token_buy); - l_item->net_sell_id = a_price->net_sell->pub.id.uint64; - l_item->net_buy_id = a_price->net_buy->pub.id.uint64; - l_item->datoshi_sell = a_price->datoshi_sell; - l_item->rate = a_price->rate; - memcpy(&l_item->tx_hash, &a_price->tx_hash, sizeof(dap_chain_hash_fast_t)); - memcpy(&l_item->order_hash, &a_price->order_hash, sizeof(dap_chain_hash_fast_t)); - strcpy(l_item->wallet_str, a_price->wallet_str); - - rc = dap_global_db_set_sync(GROUP_LOCAL_XCHANGE,a_price->key_ptr, l_item, l_size, false); - DAP_DELETE(l_item); - - return rc; -} - -static dap_chain_net_srv_xchange_price_t *s_xchange_db_load(char *a_key, uint8_t *a_item) -{ - dap_chain_net_srv_xchange_db_item_t *l_item = (dap_chain_net_srv_xchange_db_item_t *)a_item; - dap_chain_net_srv_xchange_price_t *l_price = DAP_NEW_Z(dap_chain_net_srv_xchange_price_t); - l_price->key_ptr = dap_strdup(a_key); - strcpy(l_price->token_sell, l_item->token_sell); - strcpy(l_price->token_buy, l_item->token_buy); - dap_chain_net_id_t l_id = { .uint64 = l_item->net_sell_id}; - l_price->net_sell = dap_chain_net_by_id(l_id); - l_id.uint64 = l_item->net_buy_id; - l_price->net_buy = dap_chain_net_by_id(l_id); - l_price->datoshi_sell = l_item->datoshi_sell; - l_price->rate = l_item->rate; - memcpy(&l_price->tx_hash, &l_item->tx_hash, sizeof(dap_chain_hash_fast_t)); - memcpy(&l_price->order_hash, &l_item->order_hash, sizeof(dap_chain_hash_fast_t)); - l_price->wallet_str = dap_strdup(l_item->wallet_str); - return l_price; -} - -static int s_cli_srv_xchange_price(int a_argc, char **a_argv, int a_arg_index, char **a_str_reply) -{ - enum { - CMD_NONE, CMD_CREATE, CMD_REMOVE, CMD_LIST, CMD_UPDATE - }; - int l_cmd_num = CMD_NONE; - if(dap_chain_node_cli_find_option_val(a_argv, a_arg_index, min(a_argc, a_arg_index + 1), "create", NULL)) { - l_cmd_num = CMD_CREATE; - } - else if(dap_chain_node_cli_find_option_val(a_argv, a_arg_index, min(a_argc, a_arg_index + 1), "remove", NULL)) { - l_cmd_num = CMD_REMOVE; - } - else if(dap_chain_node_cli_find_option_val(a_argv, a_arg_index, min(a_argc, a_arg_index + 1), "list", NULL)) { - l_cmd_num = CMD_LIST; - } - else if(dap_chain_node_cli_find_option_val(a_argv, a_arg_index, min(a_argc, a_arg_index + 1), "update", NULL)) { - l_cmd_num = CMD_UPDATE; - } - int l_arg_index = a_arg_index + 1; - const char *l_net_sell_str = NULL, *l_net_buy_str = NULL; - const char *l_token_sell_str = NULL, *l_token_buy_str = NULL; - dap_chain_net_t *l_net_sell = NULL, *l_net_buy = NULL; - char *l_strkey; - if (l_cmd_num == CMD_CREATE || l_cmd_num == CMD_REMOVE || l_cmd_num == CMD_UPDATE) { - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-net_sell", &l_net_sell_str); - if (!l_net_sell_str) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'price %s' required parameter -net_sell", - l_cmd_num == CMD_CREATE ? "create" : (l_cmd_num == CMD_REMOVE ? "remove" : "update")); - return -2; - } - l_net_sell = dap_chain_net_by_name(l_net_sell_str); - if (!l_net_sell) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Network %s not found", l_net_sell_str); - return -3; - } - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-net_buy", &l_net_buy_str); - if (!l_net_buy_str) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'price %s' required parameter -net_buy", - l_cmd_num == CMD_CREATE ? "create" : (l_cmd_num == CMD_REMOVE ? "remove" : "update")); - return -2; - } - l_net_buy = dap_chain_net_by_name(l_net_buy_str); - if (!l_net_sell) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Network %s not found", l_net_buy_str); - return -3; - } - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-token_sell", &l_token_sell_str); - if (!l_token_sell_str) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'price %s' required parameter -token_sell", - l_cmd_num == CMD_CREATE ? "create" : (l_cmd_num == CMD_REMOVE ? "remove" : "update")); - return -5; - } - if (!dap_chain_ledger_token_ticker_check(l_net_sell->pub.ledger, l_token_sell_str)) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Token ticker %s not found", l_token_sell_str); - return -6; - } - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-token_buy", &l_token_buy_str); - if (!l_token_buy_str) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'price %s' required parameter -token_buy", - l_cmd_num == CMD_CREATE ? "create" : (l_cmd_num == CMD_REMOVE ? "remove" : "update")); - return -5; - } - if (!dap_chain_ledger_token_ticker_check(l_net_buy->pub.ledger, l_token_buy_str)) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Token ticker %s not found", l_token_buy_str); - return -6; - } - l_strkey = DAP_NEW_SIZE(char, dap_strlen(l_token_sell_str) + dap_strlen(l_net_sell_str) + - dap_strlen(l_token_buy_str) + dap_strlen(l_net_buy_str) + 1); - dap_stpcpy(l_strkey, l_token_sell_str); - strcat(l_strkey, l_net_sell_str); - strcat(l_strkey, l_token_buy_str); - strcat(l_strkey, l_net_buy_str); - } - switch (l_cmd_num) { - case CMD_CREATE: { - dap_chain_net_srv_xchange_price_t *l_price = NULL; - HASH_FIND_STR(s_srv_xchange->pricelist, l_strkey, l_price); - if (l_price) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Price with provided pair of token ticker + net name already exist"); - return -7; - } - const char *l_val_sell_str = NULL, *l_val_rate_str = NULL, *l_wallet_str = NULL; - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-coins", &l_val_sell_str); - if (!l_val_sell_str) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'price create' required parameter -coins"); - return -8; - } - uint256_t l_datoshi_sell = dap_chain_balance_scan(l_val_sell_str); - if (IS_ZERO_256(l_datoshi_sell)) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Format -coins <unsigned long long>"); - return -9; - } - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-rate", &l_val_rate_str); - if (!l_val_rate_str) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'price create' required parameter -rate"); - return -8; - } - uint256_t l_rate = dap_chain_coins_to_balance(l_val_rate_str); - if (!compare256(l_rate, uint256_0)) { // if (l_rate == 0) - dap_chain_node_cli_set_reply_text(a_str_reply, "Format -rate <long double> = sell / buy"); - return -9; - } - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-wallet", &l_wallet_str); - if (!l_wallet_str) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'price create' required parameter -wallet"); - return -10; - } - dap_chain_wallet_t *l_wallet = dap_chain_wallet_open(l_wallet_str, dap_chain_wallet_get_path(g_config)); - if (!l_wallet) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Specified wallet not found"); - return -11; - } - uint256_t l_value = dap_chain_wallet_get_balance(l_wallet, l_net_sell->pub.id, l_token_sell_str); - if (compare256(l_value, l_datoshi_sell) == -1) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Not enough cash in specified wallet"); - dap_chain_wallet_close(l_wallet); - return -12; - } - // Create the price - l_price = DAP_NEW_Z(dap_chain_net_srv_xchange_price_t); - l_price->wallet_str = dap_strdup(l_wallet_str); - dap_stpcpy(l_price->token_sell, l_token_sell_str); - l_price->net_sell = l_net_sell; - dap_stpcpy(l_price->token_buy, l_token_buy_str); - l_price->net_buy = l_net_buy; - l_price->key_ptr = l_strkey; - l_price->datoshi_sell = l_datoshi_sell; - l_price->rate = l_rate; - // Create conditional transaction - dap_chain_datum_tx_t *l_tx = s_xchange_tx_create_request(l_price, l_wallet); - if (!l_tx) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Can't compose the conditional transaction"); - DAP_DELETE(l_price->key_ptr); - DAP_DELETE(l_price->wallet_str); - DAP_DELETE(l_price); - dap_chain_wallet_close(l_wallet); - return -14; - } - // Create the order & put it to GDB - l_price->wallet_key = dap_chain_wallet_get_key(l_wallet, 0); - char *l_order_hash_str = s_xchange_order_create(l_price, l_tx); - dap_chain_wallet_close(l_wallet); - if (l_order_hash_str) { - dap_chain_hash_fast_from_str(l_order_hash_str, &l_price->order_hash); - if(!s_xchange_tx_put(l_tx, l_net_buy)) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Can't put transaction to mempool"); - dap_chain_net_srv_order_delete_by_hash_str_sync(l_net_buy, l_order_hash_str); - DAP_DELETE(l_order_hash_str); - DAP_DELETE(l_price->key_ptr); - DAP_DELETE(l_price->wallet_str); - DAP_DELETE(l_price); - return -15; - } - if (!s_xchange_db_add(l_price)) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Can't save price in database"); - dap_chain_net_srv_order_delete_by_hash_str_sync(l_net_buy, l_order_hash_str); - DAP_DELETE(l_order_hash_str); - DAP_DELETE(l_price->key_ptr); - DAP_DELETE(l_price->wallet_str); - DAP_DELETE(l_price); - return -16; - } - dap_chain_node_cli_set_reply_text(a_str_reply, "Successfully created order %s", l_order_hash_str); - DAP_DELETE(l_order_hash_str); - // Add active price to pricelist - HASH_ADD_KEYPTR(hh, s_srv_xchange->pricelist, l_price->key_ptr, strlen(l_price->key_ptr), l_price); - } else { - dap_chain_node_cli_set_reply_text(a_str_reply, "Can't compose the order"); - DAP_DELETE(l_price->key_ptr); - DAP_DELETE(l_price->wallet_str); - DAP_DELETE(l_price); - return -18; - } - } break; - case CMD_REMOVE: - case CMD_UPDATE: { - dap_chain_net_srv_xchange_price_t *l_price = NULL; - HASH_FIND_STR(s_srv_xchange->pricelist, l_strkey, l_price); - if (!l_price) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Price with provided pair of token ticker + net name is not exist"); - return -1; - } - if (l_cmd_num == CMD_REMOVE) { - dap_string_t *l_str_reply = dap_string_new(""); - HASH_DEL(s_srv_xchange->pricelist, l_price); - dap_global_db_del_sync(GROUP_LOCAL_XCHANGE, l_price->key_ptr); - dap_chain_wallet_t *l_wallet = dap_chain_wallet_open(l_price->wallet_str, dap_chain_wallet_get_path(g_config)); - bool l_ret = s_xchage_tx_invalidate(l_price, l_wallet); - dap_chain_wallet_close(l_wallet); - if (!l_ret) { - char *l_tx_hash_str = dap_chain_hash_fast_to_str_new(&l_price->tx_hash); - dap_string_append_printf(l_str_reply, "Can't invalidate transaction %s\n", l_tx_hash_str); - DAP_DELETE(l_tx_hash_str); - } - char *l_order_hash_str = dap_chain_hash_fast_to_str_new(&l_price->order_hash); - if (dap_chain_net_srv_order_delete_by_hash_str_sync(l_price->net_buy, l_order_hash_str)) { - dap_string_append_printf(l_str_reply, "Can't remove order %s\n", l_order_hash_str); - } - DAP_DELETE(l_order_hash_str); - DAP_DELETE(l_price->wallet_str); - DAP_DELETE(l_price); - if (!l_str_reply->len) { - dap_string_append(l_str_reply, "Price successfully removed"); - } - *a_str_reply = dap_string_free(l_str_reply, false); - } else { // CMD_UPDATE - const char *l_val_sell_str = NULL, *l_val_rate_str = NULL, *l_wallet_str = NULL, *l_new_wallet_str = NULL; - uint256_t l_datoshi_sell = {}; - uint256_t l_rate = uint256_0; - dap_chain_wallet_t *l_wallet = NULL; - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-coins", &l_val_sell_str); - if (l_val_sell_str) { - l_datoshi_sell = dap_chain_balance_scan(l_val_sell_str); - if (IS_ZERO_256(l_datoshi_sell)) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Format -coins <unsigned long long>"); - return -9; - } - } - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-rate", &l_val_rate_str); - if (l_val_rate_str) { - l_rate = dap_chain_coins_to_balance(l_val_rate_str); - if (!compare256(l_rate, uint256_0)) { // if (l_rate == 0) - dap_chain_node_cli_set_reply_text(a_str_reply, "Format -rate <long double> = sell / buy"); - return -9; - } - } - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-wallet", &l_new_wallet_str); - l_wallet_str = l_new_wallet_str ? l_new_wallet_str : l_price->wallet_str; - l_wallet = dap_chain_wallet_open(l_wallet_str, dap_chain_wallet_get_path(g_config)); - if (!l_wallet) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Specified wallet not found"); - return -11; - } - if (!l_val_sell_str && !l_val_rate_str && !l_wallet_str) { - dap_chain_node_cli_set_reply_text(a_str_reply, "At least one of updating parameters is mandatory"); - return -13; - } - uint256_t l_value = dap_chain_wallet_get_balance(l_wallet, l_net_sell->pub.id, l_token_sell_str); - if (!IS_ZERO_256(l_datoshi_sell) && compare256(l_value, l_datoshi_sell) == -1) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Not enough cash in specified wallet"); - dap_chain_wallet_close(l_wallet); - return -12; - } - if (l_val_sell_str) { - l_price->datoshi_sell = l_datoshi_sell; - } - if (l_val_rate_str) { - l_price->rate = l_rate; - } - // Update the transaction - dap_chain_datum_tx_t *l_tx = s_xchange_tx_create_request(l_price, l_wallet); - if (l_new_wallet_str) { - dap_chain_wallet_close(l_wallet); - l_wallet = dap_chain_wallet_open(l_price->wallet_str, dap_chain_wallet_get_path(g_config)); - DAP_DELETE(l_price->wallet_str); - l_price->wallet_str = dap_strdup(l_new_wallet_str); - } - if (!l_tx) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Can't compose the conditional transaction"); - return -14; - } - HASH_DEL(s_srv_xchange->pricelist, l_price); - dap_global_db_del_sync(GROUP_LOCAL_XCHANGE, l_price->key_ptr); - bool l_ret = s_xchage_tx_invalidate(l_price, l_wallet); // may be changed to old price later - dap_chain_wallet_close(l_wallet); - if (!l_ret) { - char *l_tx_hash_str = dap_chain_hash_fast_to_str_new(&l_price->tx_hash); - dap_chain_node_cli_set_reply_text(a_str_reply, "Can't invalidate transaction %s\n", l_tx_hash_str); - DAP_DELETE(l_tx_hash_str); - return -17; - } - // Update the order - char *l_order_hash_str = dap_chain_hash_fast_to_str_new(&l_price->order_hash); - dap_chain_net_srv_order_delete_by_hash_str_sync(l_price->net_buy, l_order_hash_str); - DAP_DELETE(l_order_hash_str); - l_order_hash_str = s_xchange_order_create(l_price, l_tx); - if (l_order_hash_str) { - dap_chain_hash_fast_from_str(l_order_hash_str, &l_price->order_hash); - if(!s_xchange_tx_put(l_tx, l_net_buy)) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Can't put transaction to mempool"); - dap_chain_net_srv_order_delete_by_hash_str_sync(l_net_buy, l_order_hash_str); - DAP_DELETE(l_order_hash_str); - return -15; - } - if (!s_xchange_db_add(l_price)) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Can't save price in database"); - dap_chain_net_srv_order_delete_by_hash_str_sync(l_net_buy, l_order_hash_str); - DAP_DELETE(l_order_hash_str); - return -16; - } - dap_chain_node_cli_set_reply_text(a_str_reply, "Successfully created order %s", l_order_hash_str); - DAP_DELETE(l_order_hash_str); - } else { - dap_chain_node_cli_set_reply_text(a_str_reply, "Can't compose the order"); - DAP_DELETE(l_price->wallet_str); - DAP_DELETE(l_price->key_ptr); - DAP_DELETE(l_price); - return -18; - } - // Update the pricelist - HASH_ADD_KEYPTR(hh, s_srv_xchange->pricelist, l_price->key_ptr, strlen(l_price->key_ptr), l_price); - } - } break; - case CMD_LIST: { - dap_chain_net_srv_xchange_price_t *l_price = NULL, *l_tmp; - dap_string_t *l_reply_str = dap_string_new(""); - HASH_ITER(hh, s_srv_xchange->pricelist, l_price, l_tmp) { - char *l_order_hash_str = dap_chain_hash_fast_to_str_new(&l_price->order_hash); - dap_string_append_printf(l_reply_str, "%s %s %s %s %s %s %s %s\n", l_order_hash_str, l_price->token_sell, - l_price->net_sell->pub.name, l_price->token_buy, l_price->net_buy->pub.name, - dap_chain_balance_print(l_price->datoshi_sell), dap_chain_balance_print(l_price->rate), l_price->wallet_str); - DAP_DELETE(l_order_hash_str); - } - if (!l_reply_str->len) { - dap_string_append(l_reply_str, "Pricelist is empty"); - } - *a_str_reply = dap_string_free(l_reply_str, false); - } break; - default: { - dap_chain_node_cli_set_reply_text(a_str_reply, "Subcommand %s not recognized", a_argv[a_arg_index]); - return -4; - } - } - return 0; -} - - - - -static int s_cli_srv_xchange_tx_list_addr ( - dap_chain_net_t *a_net, - dap_time_t a_after, - dap_time_t a_before, - dap_chain_addr_t *a_addr, - int a_tx_closed, - char **a_str_reply - ) -{ -char l_hash_str [DAP_CHAIN_HASH_FAST_STR_SIZE + 8] = {0}; -dap_chain_hash_fast_t l_tx_first_hash = {0}; -dap_chain_datum_tx_t *l_datum_tx; -size_t l_datum_tx_size, l_tx_total, l_tx_count; -int l_item_idx; -dap_string_t *l_reply_str; -dap_hash_fast_t l_hash; -dap_chain_tx_out_cond_t *l_out_cond_item; -char *l_value_str; -uint256_t l_256value; - - - if ( !(l_reply_str = dap_string_new("")) ) /* Prepare output string discriptor*/ - return log_it(L_ERROR, "Cannot allocate a memory, errno=%d", errno), -ENOMEM; - - memset(&l_tx_first_hash, 0, sizeof(dap_chain_hash_fast_t)); /* Initial hash == zero */ - - - for ( l_tx_count = l_tx_total = 0; - (l_datum_tx = dap_chain_ledger_tx_find_by_addr(a_net->pub.ledger, NULL, a_addr, &l_tx_first_hash)); - l_tx_total++) - { - /* Check time range (if need ) */ - if ( !(l_datum_tx->header.ts_created > a_after) ) - continue; - - if ( a_before && (l_datum_tx->header.ts_created > a_before) ) - continue; - - - /* TX hash */ - l_datum_tx_size = dap_chain_datum_tx_get_size(l_datum_tx); - - if ( !dap_hash_fast(l_datum_tx, l_datum_tx_size, &l_hash) ) - { /* Never must be happend, but ... */ - log_it(L_ERROR, "dap_hash_fast(..., %zu octets) return error", l_datum_tx_size); - dump_it("l_datum_tx", l_datum_tx, l_datum_tx_size); - continue; - } - - dap_chain_hash_fast_to_str(&l_hash, l_hash_str, DAP_CHAIN_HASH_FAST_STR_SIZE + 1); - dap_string_append_printf(l_reply_str, "hash: %s\n", l_hash_str); - - /* Find SRV_XCHANGE out_cond item */ - for (l_out_cond_item = NULL, l_item_idx = 0; - (l_out_cond_item = (dap_chain_tx_out_cond_t *) dap_chain_datum_tx_item_get(l_datum_tx, &l_item_idx, TX_ITEM_TYPE_OUT_COND, NULL)); - l_item_idx++) - { - if ( l_out_cond_item->header.subtype != DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_XCHANGE) - continue; - - if ( a_tx_closed ) - dap_chain_ledger_tx_hash_is_used_out_item(a_net->pub.ledger, &l_hash, l_item_idx); - - l_256value = l_out_cond_item->subtype.srv_xchange.value; - l_value_str = dap_cvt_uint256_to_str(l_256value); - dap_string_append_printf(l_reply_str, "value: %s %s", l_value_str, l_out_cond_item->subtype.srv_xchange.token); - DAP_DELETE(l_value_str); - } - - dap_string_append(l_reply_str, "\n\n"); - } - - - *a_str_reply = dap_string_free(l_reply_str, false); /* Free string descriptor, but keep ASCIZ buffer itself */ - return 0; -} - - - - - - -// Filter for find tx with DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_XCHANGE -static bool s_filter_tx_list(dap_chain_datum_t *a_datum, dap_chain_t *a_chain, void *a_filter_func_param) -{ - // Datum type filter -> only tx - if(!a_datum || a_datum->header.type_id != DAP_CHAIN_DATUM_TX) - return false; - dap_chain_datum_tx_t *l_datum_tx = (dap_chain_datum_tx_t*) a_datum->data; - // Get time from parameters - dap_time_t *l_time_mass = (dap_time_t*) a_filter_func_param; - dap_time_t l_time_begin = 0; - dap_time_t l_time_end = 0; - if(l_time_mass) { - l_time_begin = l_time_mass[0]; - l_time_end = l_time_mass[1]; - } - // Time filter - if(l_time_begin && l_datum_tx->header.ts_created < l_time_begin) - return false; - if(l_time_end && l_datum_tx->header.ts_created > l_time_end) - return false; - // Item filter -> if present tx_out_cond with subtype == SRV_XCHANGE - dap_chain_tx_out_cond_t *l_out_cond_item = NULL; - int l_item_idx = 0; - do { - l_out_cond_item = (dap_chain_tx_out_cond_t*) dap_chain_datum_tx_item_get(l_datum_tx, &l_item_idx, TX_ITEM_TYPE_OUT_COND, NULL); - l_item_idx++; - if(l_out_cond_item && l_out_cond_item->header.subtype == DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_XCHANGE) { - return true; - } - - } - while(l_out_cond_item); - return false; -} - - -static int s_cli_srv_xchange(int a_argc, char **a_argv, char **a_str_reply) -{ - enum { - CMD_NONE, CMD_PRICE, CMD_ORDERS, CMD_PURCHASE, CMD_ENABLE, CMD_DISABLE, CMD_TX_LIST, CMD_TOKEN_PAIR - }; - int l_arg_index = 1; - int l_cmd_num = CMD_NONE; - if(dap_chain_node_cli_find_option_val(a_argv, l_arg_index, min(a_argc, l_arg_index + 1), "price", NULL)) { - l_cmd_num = CMD_PRICE; - } - else if(dap_chain_node_cli_find_option_val(a_argv, l_arg_index, min(a_argc, l_arg_index + 1), "orders", NULL)) { - l_cmd_num = CMD_ORDERS; - } - else if(dap_chain_node_cli_find_option_val(a_argv, l_arg_index, min(a_argc, l_arg_index + 1), "purchase", NULL)) { - l_cmd_num = CMD_PURCHASE; - } - else if(dap_chain_node_cli_find_option_val(a_argv, l_arg_index, min(a_argc, l_arg_index + 1), "enable", NULL)) { - l_cmd_num = CMD_ENABLE; - } - else if(dap_chain_node_cli_find_option_val(a_argv, l_arg_index, min(a_argc, l_arg_index + 1), "disable", NULL)) { - l_cmd_num = CMD_DISABLE; - } - else if(dap_chain_node_cli_find_option_val(a_argv, l_arg_index, min(a_argc, l_arg_index + 1), "tx_list", NULL)) { - l_cmd_num = CMD_TX_LIST; - } - else if(dap_chain_node_cli_find_option_val(a_argv, l_arg_index, min(a_argc, l_arg_index + 1), "token_pair", NULL)) { - l_cmd_num = CMD_TOKEN_PAIR; - } - switch (l_cmd_num) { - case CMD_PRICE: - return s_cli_srv_xchange_price(a_argc, a_argv, l_arg_index + 1, a_str_reply); - case CMD_ORDERS: { - const char *l_net_str = NULL; - l_arg_index++; - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-net", &l_net_str); - if (!l_net_str) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'purchase' required parameter -net"); - return -2; - } - dap_chain_net_t *l_net = dap_chain_net_by_name(l_net_str); - if (!l_net) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Network %s not found", l_net_str); - return -3; - } - char * l_gdb_group_str = dap_chain_net_srv_order_get_gdb_group(l_net); - size_t l_orders_count = 0; - dap_global_db_obj_t * l_orders = dap_global_db_get_all_sync(l_gdb_group_str, &l_orders_count); - dap_chain_net_srv_xchange_price_t *l_price; - dap_string_t *l_reply_str = dap_string_new(""); - for (size_t i = 0; i < l_orders_count; i++) { - dap_chain_net_srv_order_t *l_order = (dap_chain_net_srv_order_t *)l_orders[i].value; - if (l_order->srv_uid.uint64 != DAP_CHAIN_NET_SRV_XCHANGE_ID) - continue; - // TODO add filters to list (tokens, network, etc.) - l_price = s_xchange_price_from_order(l_net, l_order); - dap_string_append_printf(l_reply_str, "%s %s %s %s %s %s %s\n", l_orders[i].key, l_price->token_sell, - l_price->net_sell->pub.name, l_price->token_buy, l_price->net_buy->pub.name, - dap_chain_balance_print(l_price->datoshi_sell), dap_chain_balance_print(l_price->rate)); - DAP_DELETE(l_price); - } - dap_global_db_objs_delete(l_orders, l_orders_count); - DAP_DELETE( l_gdb_group_str); - if (!l_reply_str->len) { - dap_string_append(l_reply_str, "No orders found"); - } - *a_str_reply = dap_string_free(l_reply_str, false); - } break; - case CMD_PURCHASE: { - const char *l_net_str = NULL, *l_wallet_str = NULL, *l_order_hash_str = NULL, *l_val_sell_str = NULL; - l_arg_index++; - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-net", &l_net_str); - if (!l_net_str) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'purchase' required parameter -net"); - return -2; - } - dap_chain_net_t *l_net = dap_chain_net_by_name(l_net_str); - if (!l_net) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Network %s not found", l_net_str); - return -3; - } - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-wallet", &l_wallet_str); - if (!l_wallet_str) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'purchase' required parameter -wallet"); - return -10; - } - dap_chain_wallet_t *l_wallet = dap_chain_wallet_open(l_wallet_str, dap_chain_wallet_get_path(g_config)); - if (!l_wallet) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Specified wallet not found"); - return -11; - } - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-order", &l_order_hash_str); - if (!l_order_hash_str) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'purchase' required parameter -order"); - return -12; - } - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-coins", &l_val_sell_str); - if (!l_val_sell_str) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'purchase' required parameter -coins"); - return -8; - } - uint256_t l_datoshi_sell = dap_chain_balance_scan(l_val_sell_str); - if (IS_ZERO_256(l_datoshi_sell)) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Format -coins <unsigned long long>"); - return -9; - } - dap_chain_net_srv_order_t *l_order = dap_chain_net_srv_order_find_by_hash_str(l_net, l_order_hash_str); - if (l_order) { - dap_chain_net_srv_xchange_price_t *l_price = s_xchange_price_from_order(l_net, l_order); - l_price->datoshi_sell = l_datoshi_sell; - // Create conditional transaction - dap_chain_datum_tx_t *l_tx = s_xchange_tx_create_exchange(l_price, &l_order->tx_cond_hash, l_wallet); - if (l_tx && s_xchange_tx_put(l_tx, l_net)) { - // TODO send request to seller to update / delete order & price - dap_chain_net_srv_order_delete_by_hash_str_sync(l_price->net_buy, l_order_hash_str); - } - DAP_DELETE(l_price); - DAP_DELETE(l_order); - dap_chain_node_cli_set_reply_text(a_str_reply, l_tx ? "Exchange transaction has done" : - "Exchange transaction error"); - } else { - dap_chain_node_cli_set_reply_text(a_str_reply, "Specified order not found"); - return -13; - } - } break; - case CMD_ENABLE: { - s_srv_xchange->enabled = true; - } break; - case CMD_DISABLE: { - s_srv_xchange->enabled = false; - } break; - - // Token pair control - case CMD_TOKEN_PAIR: { - - // Find and check the network - const char *l_net_str = NULL; - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-net", &l_net_str); - if(!l_net_str) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'token_pair' required parameter -net"); - return -3; - } - dap_chain_net_t *l_net = dap_chain_net_by_name(l_net_str); - if(!l_net) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Network %s not found", l_net_str); - return -4; - } - - - // Select subcommands - - // check for price subcommand - const char * l_price_subcommand = NULL; - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "price", &l_price_subcommand); - - // check for get subcommand - if ( l_price_subcommand ){ - // Check for token1 - const char * l_token1 = NULL; - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-token1", &l_token1); - if(!l_token1){ - dap_chain_node_cli_set_reply_text(a_str_reply,"No argument '-token1'"); - return -5; - } - dap_chain_datum_token_t * l_token1_datum = dap_chain_ledger_token_ticker_check( l_net->pub.ledger, l_token1); - if(!l_token1_datum){ - dap_chain_node_cli_set_reply_text(a_str_reply,"Can't find \"%s\" token in network \"%s\" for argument '-token1' ", l_token1, l_net->pub.name); - return -6; - } - - // Check for token2 - const char * l_token2 = NULL; - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-token2", &l_token2); - if(!l_token2){ - dap_chain_node_cli_set_reply_text(a_str_reply,"No argument '-token2'"); - return -5; - } - dap_chain_datum_token_t * l_token2_datum = dap_chain_ledger_token_ticker_check( l_net->pub.ledger, l_token2); - if(!l_token2_datum){ - dap_chain_node_cli_set_reply_text(a_str_reply,"Can't find \"%s\" token in network \"%s\" for argument '-token2' ", l_token2, l_net->pub.name); - return -6; - } - - // Read time_from - dap_time_t l_time_from = 0; - const char * l_time_from_str = NULL; - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-time_from", &l_time_from_str); - l_time_from = dap_time_from_str_rfc822(l_time_from_str); - - // Read time_to - dap_time_t l_time_to = 0; - const char * l_time_to_str = NULL; - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-time_to", &l_time_to_str); - l_time_to = dap_time_from_str_rfc822(l_time_to_str); - - - // Check for price subcommand - if (strcmp(l_price_subcommand,"average") == 0){ - dap_string_t *l_reply_str = dap_string_new(""); - - dap_list_t *l_tx_cond_list = dap_chain_net_get_tx_cond_all_by_srv_uid(l_net, c_dap_chain_net_srv_xchange_uid, - l_time_from,l_time_to,TX_SEARCH_TYPE_NET ); - dap_list_t * l_cur = l_tx_cond_list; - uint256_t l_total_rates = {0}; - uint256_t l_total_rates_count = {0}; - while(l_cur){ - dap_chain_datum_tx_t * l_tx =(dap_chain_datum_tx_t *) l_cur->data; - if(l_tx){ - dap_hash_fast_t * l_tx_hash = dap_chain_node_datum_tx_calc_hash(l_tx); - - // Get input token ticker - const char * l_tx_input_ticker = dap_chain_ledger_tx_get_token_ticker_by_hash( - l_net->pub.ledger, l_tx_hash); - DAP_DEL_Z(l_tx_hash); - - // Compare with token1 and token2 - if( dap_strcmp(l_tx_input_ticker, l_token1) != 0 && - dap_strcmp(l_tx_input_ticker, l_token2) != 0) - continue; - - dap_chain_tx_out_cond_t *l_out_cond_item = NULL; - int l_item_idx = 0; - do { - l_out_cond_item = (dap_chain_tx_out_cond_t*) dap_chain_datum_tx_item_get(l_tx, &l_item_idx, TX_ITEM_TYPE_OUT_COND, NULL); - l_item_idx++; - if(l_out_cond_item && l_out_cond_item->header.subtype == DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_XCHANGE) { - //const char *token = l_out_cond_item->subtype.srv_xchange.token; - uint256_t value = l_out_cond_item->subtype.srv_xchange.value; - uint256_t l_total_rates_old = l_total_rates; - if(SUM_256_256(l_out_cond_item->subtype.srv_xchange.value,l_total_rates_old, &l_total_rates )!= 0) - log_it(L_ERROR, "Overflow on avarage price calculation (summing)"); - INCR_256(&l_total_rates_count); - } - } - while(l_out_cond_item); - - } - l_cur = dap_list_next(l_cur); - } - dap_list_free(l_tx_cond_list); - uint256_t l_rate_average = {0}; - DIV_256(l_total_rates,l_total_rates_count,&l_rate_average); - char *l_rate_average_str = dap_cvt_uint256_to_str(l_rate_average); - dap_string_append_printf(l_reply_str,"Average rate: %s",l_rate_average_str); - DAP_DELETE(l_rate_average_str); - - *a_str_reply = dap_string_free(l_reply_str, false); - break; - }else if (strcmp(l_price_subcommand,"history") == 0){ - dap_string_t *l_reply_str = dap_string_new(""); - - dap_list_t *l_tx_cond_list = dap_chain_net_get_tx_cond_all_by_srv_uid(l_net, c_dap_chain_net_srv_xchange_uid, - l_time_from,l_time_to,TX_SEARCH_TYPE_NET_SPENT ); - dap_list_t * l_cur = l_tx_cond_list; - while(l_cur){ - dap_chain_datum_tx_t * l_tx =(dap_chain_datum_tx_t *) l_cur->data; - if(l_tx){ - dap_hash_fast_t * l_tx_hash = dap_chain_node_datum_tx_calc_hash(l_tx); - - // Get input token ticker - const char * l_tx_input_ticker = dap_chain_ledger_tx_get_token_ticker_by_hash( - l_net->pub.ledger, l_tx_hash); - - // Compare with token1 and token2 - if( dap_strcmp(l_tx_input_ticker, l_token1) != 0 && - dap_strcmp(l_tx_input_ticker, l_token2) != 0) - continue; - - char * l_tx_hash_str = dap_chain_hash_fast_to_str_new(l_tx_hash);\ - - char l_tx_ts_created_str[72]; - l_tx_ts_created_str[0] = '\0'; - dap_time_to_str_rfc822(l_tx_ts_created_str,sizeof(l_tx_ts_created_str),l_tx->header.ts_created); - dap_string_append_printf(l_reply_str,"Tx hash: %s\n", l_tx_hash_str); - dap_string_append_printf(l_reply_str,"\tts_created: %s\n", l_tx_ts_created_str); - DAP_DEL_Z(l_tx_hash); - DAP_DEL_Z(l_tx_hash_str); - - // Calc inputs - uint256_t l_tx_input_values = dap_chain_net_get_tx_total_value(l_net, l_tx); - - // Find output - int l_item_idx = 0; - dap_chain_tx_out_cond_t *l_out_cond_item = NULL; - do { - l_out_cond_item = (dap_chain_tx_out_cond_t*) dap_chain_datum_tx_item_get(l_tx, &l_item_idx, TX_ITEM_TYPE_OUT_COND, NULL); - l_item_idx++; - if(l_out_cond_item && l_out_cond_item->header.subtype == DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_XCHANGE) { - //const char *token = l_out_cond_item->subtype.srv_xchange.token; - uint256_t l_value_to = l_out_cond_item->subtype.srv_xchange.value; - char * l_value_from_str = dap_cvt_uint256_to_str(l_tx_input_values); - char * l_value_to_str = dap_cvt_uint256_to_str(l_value_to); - dap_string_append_printf(l_reply_str, - "\tFrom %s %s To %s %s\n",l_tx_input_ticker,l_value_from_str, - l_value_to_str,l_out_cond_item->subtype.srv_xchange.token ); - DAP_DELETE(l_value_from_str); - DAP_DELETE(l_value_to_str); - } - } - while(l_out_cond_item); - - } - // Delimiter between tx - dap_string_append_printf(l_reply_str,"\n\n"); - - l_cur = dap_list_next(l_cur); - } - - dap_list_free(l_tx_cond_list); - - *a_str_reply = dap_string_free(l_reply_str, false); - break; - - }break; - } - - const char * l_list_subcommand = NULL; - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "list", &l_list_subcommand); - if( l_list_subcommand ){ - if (strcmp(l_list_subcommand,"all") == 0){ - dap_string_t *l_reply_str = dap_string_new(""); - char ** l_tickers = NULL; - size_t l_tickers_count = 0; - dap_chain_ledger_addr_get_token_ticker_all( l_net->pub.ledger,NULL,&l_tickers,&l_tickers_count); - - size_t l_pairs_count = 0; - if(l_tickers){ - for(size_t i = 0; i< l_tickers_count; i++){ - for(size_t j = i+1; j< l_tickers_count; j++){ - if(l_tickers[i] && l_tickers[j]){ - dap_string_append_printf(l_reply_str,"%s:%s ", l_tickers[i], l_tickers[j]); - l_pairs_count++; - } - } - - } - - // Free tickers array - for(size_t i = 0; i< l_tickers_count; i++){ - DAP_DELETE(l_tickers[i]); - } - DAP_DELETE(l_tickers); - } - dap_string_prepend_printf( l_reply_str,"Tokens count pair: %zd\n", l_pairs_count); - *a_str_reply = dap_string_free(l_reply_str, false); - break; - } - } - - // No subcommand selected - dap_chain_node_cli_set_reply_text(a_str_reply,"Command 'token pair' required proper subcommand, please read its manual with command 'help srv_xchange'"); - - - } break; - case CMD_TX_LIST: { - const char *l_net_str = NULL, *l_time_begin_str = NULL, *l_time_end_str = NULL; - const char *l_status_str = NULL, *l_addr_str = NULL; /* @RRL: #6294 */ - int l_status_closed; - dap_chain_addr_t *l_addr; - - l_arg_index++; - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-net", &l_net_str); - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-time_from", &l_time_begin_str); - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-time_to", &l_time_end_str); - - /* - * @RRL: #6294: [[-addr <addr> [-status closed | open]] - * we should check for valid combination of the status and addr options - */ - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-addr", &l_addr_str); - dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-status", &l_status_str); - - /* Validate input arguments ... */ - if(!l_net_str) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'tx_list' required parameter -net"); - return -3; - } - dap_chain_net_t *l_net = dap_chain_net_by_name(l_net_str); - if(!l_net) { - dap_chain_node_cli_set_reply_text(a_str_reply, "Network %s not found", l_net_str); - return -4; - } - dap_time_t l_time[2]; - l_time[0] = dap_time_from_str_rfc822(l_time_begin_str); - l_time[1] = dap_time_from_str_rfc822(l_time_end_str); - - /* @RRL: #6294: Dispatch request processing to ... */ - if ( l_addr_str ) - { - l_status_closed = (!l_addr_str) ? 0 : !dap_strncmp (l_status_str, "close", 5); - - if ( !(l_addr = dap_chain_addr_from_str(l_addr_str)) ) - return dap_chain_node_cli_set_reply_text(a_str_reply, "Cannot convert -addr '%s' to internal representative", l_addr_str), -EINVAL; - - return s_cli_srv_xchange_tx_list_addr (l_net, l_time[0], l_time[1], l_addr, l_status_closed, a_str_reply); - } - - // Prepare output string - dap_string_t *l_reply_str = dap_string_new(""); - // Find transactions using filter function s_filter_tx_list() - dap_list_t *l_datum_list0 = dap_chain_datum_list(l_net, NULL, s_filter_tx_list, l_time); - size_t l_datum_num = dap_list_length(l_datum_list0); - if(l_datum_num > 0) { - dap_string_append_printf(l_reply_str, "Found %zu transactions:\n", l_datum_num); - dap_list_t *l_datum_list = l_datum_list0; - char *l_hash_str = DAP_NEW_SIZE(char, DAP_CHAIN_HASH_FAST_STR_SIZE+1); - while(l_datum_list) { - dap_chain_datum_tx_t *l_datum_tx = (dap_chain_datum_tx_t*) ((dap_chain_datum_t*) l_datum_list->data)->data; - size_t l_datum_tx_size = dap_chain_datum_tx_get_size(l_datum_tx); - // Delimiter between tx - if(l_datum_list != l_datum_list0) { - dap_string_append(l_reply_str, "\n\n"); - } - // Tx hash - dap_hash_fast_t l_hash; - memset(&l_hash, 0, sizeof(dap_hash_fast_t)); - dap_hash_fast(l_datum_tx, l_datum_tx_size, &l_hash); - dap_chain_hash_fast_to_str(&l_hash, l_hash_str, DAP_CHAIN_HASH_FAST_STR_SIZE + 1); - dap_string_append_printf(l_reply_str, "hash: %s\n", l_hash_str); - // Find SRV_XCHANGE out_cond item - dap_chain_tx_out_cond_t *l_out_cond_item = NULL; - int l_item_idx = 0; - do { - l_out_cond_item = (dap_chain_tx_out_cond_t*) dap_chain_datum_tx_item_get(l_datum_tx, &l_item_idx, TX_ITEM_TYPE_OUT_COND, NULL); - l_item_idx++; - if(l_out_cond_item && l_out_cond_item->header.subtype == DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_XCHANGE) { - const char *token = l_out_cond_item->subtype.srv_xchange.token; - uint256_t value = l_out_cond_item->subtype.srv_xchange.value; - char *value_str = dap_cvt_uint256_to_str(value); - dap_string_append_printf(l_reply_str, "value: %s %s", value_str, token); - DAP_DELETE(value_str); - } - } - while(l_out_cond_item); - l_datum_list = dap_list_next(l_datum_list); - } - DAP_DELETE(l_hash_str); - } - else{ - dap_string_append(l_reply_str, "Transactions not found"); - } - dap_list_free(l_datum_list0); - *a_str_reply = dap_string_free(l_reply_str, false); - } - break; - default: { - dap_chain_node_cli_set_reply_text(a_str_reply, "Command %s not recognized", a_argv[l_arg_index]); - return -1; - } - } - return 0; -} - -static int s_callback_requested(dap_chain_net_srv_t *a_srv, uint32_t a_usage_id, dap_chain_net_srv_client_remote_t *a_srv_client, const void *a_data, size_t a_data_size) -{ - return 0; -} - -static int s_callback_response_success(dap_chain_net_srv_t *a_srv, uint32_t a_usage_id, dap_chain_net_srv_client_remote_t *a_srv_client, const void *a_data, size_t a_data_size) -{ - return 0; -} - -static int s_callback_response_error(dap_chain_net_srv_t *a_srv, uint32_t a_usage_id, dap_chain_net_srv_client_remote_t *a_srv_client, const void *a_data, size_t a_data_size) -{ - return 0; -} - -static int s_callback_receipt_next_success(dap_chain_net_srv_t *a_srv, uint32_t a_usage_id, dap_chain_net_srv_client_remote_t *a_srv_client, const void *a_data, size_t a_data_size) -{ - return 0; -} +/* + * Authors: + * Roman Khlopkov <roman.khlopkov@demlabs.net> + * DeM Labs Inc. https://demlabs.net + * DeM Labs Open source community https://gitlab.demlabs.net + * Copyright (c) 2017-2020 + * All rights reserved. + + This file is part of DAP (Deus Applications Prototypes) the open source project + + DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + DAP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with any DAP based project. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <math.h> +#include "dap_chain_node_cli.h" +#include "dap_string.h" +#include "dap_chain_common.h" +#include "dap_chain_mempool.h" +#include "dap_chain_net_srv.h" +#include "dap_chain_net_srv_xchange.h" + +#define LOG_TAG "dap_chain_net_srv_xchange" + +const dap_chain_net_srv_uid_t c_dap_chain_net_srv_xchange_uid = {.uint64= DAP_CHAIN_NET_SRV_XCHANGE_ID}; + + +static int s_cli_srv_xchange(int a_argc, char **a_argv, char **a_str_reply); +static int s_callback_requested(dap_chain_net_srv_t *a_srv, uint32_t a_usage_id, dap_chain_net_srv_client_remote_t *a_srv_client, const void *a_data, size_t a_data_size); +static int s_callback_response_success(dap_chain_net_srv_t *a_srv, uint32_t a_usage_id, dap_chain_net_srv_client_remote_t *a_srv_client, const void *a_data, size_t a_data_size); +static int s_callback_response_error(dap_chain_net_srv_t *a_srv, uint32_t a_usage_id, dap_chain_net_srv_client_remote_t *a_srv_client, const void *a_data, size_t a_data_size); +static int s_callback_receipt_next_success(dap_chain_net_srv_t *a_srv, uint32_t a_usage_id, dap_chain_net_srv_client_remote_t *a_srv_client, const void *a_data, size_t a_data_size); +static dap_chain_net_srv_xchange_price_t *s_xchange_db_load(char *a_key, uint8_t *a_item); + +static dap_chain_net_srv_xchange_t *s_srv_xchange; + +/** + * @brief dap_stream_ch_vpn_init Init actions for VPN stream channel + * @param vpn_addr Zero if only client mode. Address if the node shares its local VPN + * @param vpn_mask Zero if only client mode. Mask if the node shares its local VPN + * @return 0 if everything is okay, lesser then zero if errors + */ +int dap_chain_net_srv_xchange_init() +{ + dap_chain_node_cli_cmd_item_create("srv_xchange", s_cli_srv_xchange, "eXchange service commands", + "srv_xchange price create -net_sell <net_name> -token_sell <token ticker> -net_buy <net_name> -token_buy <token ticker> -wallet <name> -coins <value> -rate <value>\n" + "\tCreate a new price with specified amount of datoshi to exchange with specified rate (buy / sell)\n" + "srv_xchange price remove -net_sell <net_name> -token_sell <token ticker> -net_buy <net_name> -token_buy <token ticker>\n" + "\tRemove price with specified tickers within specified net names\n" + "srv_xchange price list\n" + "\tList all active prices\n" + "srv_xchange price update -net_sell <net_name> -token_sell <token ticker> -net_buy <net_name> -token_buy <token ticker> {-coins <value> | -rate <value> | -wallet <name>}\n" + "\tUpdate price with specified tickers within specified net names\n" + "srv_xchange orders -net <net_name>\n" + "\tGet the exchange orders list within specified net name\n" + "srv_xchange purchase -order <order hash> -net <net_name> -wallet <wallet_name> -coins <value>\n" + "\tExchange tokens with specified order within specified net name. Specify how datoshies to buy\n" + + "srv_xchange tx_list -net <net name> [-time_from <yymmdd> -time_to <yymmdd>]" + "[[-addr <wallet_addr> [-status closed | open] ]\n" /* @RRL: #6294 */ + "\tList of exchange transactions\n" + + "srv_xchange token_pair -net <net name> list all\n" + "\tList of all token pairs\n" + "srv_xchange token_pair -net <net name> price average -token1 <token 1> -token2 <token 2> [-time_from <From time>] [-time_to <To time>] \n" + "\tGet average price for token pair <token 1>:<token 2> from <From time> to <To time> \n" + "\tAll times are in RFC822\n" + "srv_xchange token_pair -net <net name> price history -token1 <token 1> -token2 <token 2> [-time_from <From time>] [-time_to <To time>] \n" + "\tPrint price history for token pair <token 1>:<token 2> from <From time> to <To time>\n" + "\tAll times are in RFC822\n" + + "srv_xchange enable\n" + "\tEnable eXchange service\n" + "srv_xchange disable\n" + "\tDisable eXchange service\n" + ); + dap_chain_net_srv_uid_t l_uid = { .uint64 = DAP_CHAIN_NET_SRV_XCHANGE_ID }; + dap_chain_net_srv_t* l_srv = dap_chain_net_srv_add(l_uid, "srv_xchange", s_callback_requested, + s_callback_response_success, s_callback_response_error, + s_callback_receipt_next_success, NULL); + s_srv_xchange = DAP_NEW_Z(dap_chain_net_srv_xchange_t); + l_srv->_internal = s_srv_xchange; + s_srv_xchange->parent = l_srv; + s_srv_xchange->enabled = false; + size_t l_prices_count = 0; + dap_global_db_obj_t *l_prices = dap_global_db_get_all_sync(GROUP_LOCAL_XCHANGE, &l_prices_count); + for (size_t i = 0; i < l_prices_count; i++) { + dap_chain_net_srv_xchange_price_t *l_price = s_xchange_db_load(l_prices[i].key, l_prices[i].value); + HASH_ADD_KEYPTR(hh, s_srv_xchange->pricelist, l_price->key_ptr, strlen(l_price->key_ptr), l_price); + } + dap_global_db_objs_delete(l_prices, l_prices_count); + return 1; +} + +void dap_chain_net_srv_xchange_deinit() +{ + if(!s_srv_xchange) + return; + dap_chain_net_srv_xchange_price_t *l_price = NULL, *l_tmp; + HASH_ITER(hh, s_srv_xchange->pricelist, l_price, l_tmp) { + // Clang bug at this, l_price should change at every loop cycle + HASH_DEL(s_srv_xchange->pricelist, l_price); + DAP_DELETE(l_price->wallet_str); + DAP_DELETE(l_price->key_ptr); + DAP_DELETE(l_price); + } + dap_chain_net_srv_del(s_srv_xchange->parent); + DAP_DELETE(s_srv_xchange); +} + +bool dap_chain_net_srv_xchange_verificator(dap_chain_tx_out_cond_t *a_cond, dap_chain_datum_tx_t *a_tx, bool a_owner) +{ + if (a_owner) + return true; + /* Check the condition for verification success + * a_cond.srv_xchange.rate (a_cond->header.value / a_cond->subtype.srv_xchange.buy_value) >= + * a_tx.out.rate ((a_cond->header.value - l_back_val) / l_out_val) + */ + dap_list_t *l_list_out = dap_chain_datum_tx_items_get(a_tx, TX_ITEM_TYPE_OUT_EXT, NULL); + uint256_t l_out_val = {}, l_back_val = {}; + char *l_ticker_ctrl = NULL; + for (dap_list_t *l_list_tmp = l_list_out; l_list_tmp; l_list_tmp = l_list_tmp->next) { + dap_chain_tx_out_ext_t *l_tx_out = (dap_chain_tx_out_ext_t *)l_list_tmp->data; + if (memcmp(&l_tx_out->addr, &a_cond->params, sizeof(dap_chain_addr_t))) { + continue; + } + if (strcmp(l_tx_out->token, a_cond->subtype.srv_xchange.buy_token)) { + if (l_ticker_ctrl && strcmp(l_ticker_ctrl, l_tx_out->token)) { + return false; // too many tokens + } + l_ticker_ctrl = l_tx_out->token; + SUM_256_256(l_back_val, l_tx_out->header.value, &l_back_val); + } else { // buying token + SUM_256_256(l_out_val, l_tx_out->header.value, &l_out_val); + } + } + //long double l_buyer_rate = (a_cond->header.value - l_back_val) / (long double)l_out_val; + //long double l_seller_rate = + uint256_t l_buyer_val = {}, l_buyer_mul = {}, l_seller_mul = {}; + SUBTRACT_256_256(a_cond->header.value, l_back_val, &l_buyer_val); + MULT_256_256(l_buyer_val, a_cond->subtype.srv_xchange.buy_value, &l_buyer_mul); + MULT_256_256(l_out_val, a_cond->header.value, &l_seller_mul); + if (compare256(l_seller_mul, l_buyer_mul) == -1) { + return false; // wrong changing rate + } + return true; +} + + + +static dap_chain_datum_tx_receipt_t *s_xchage_receipt_create(dap_chain_net_srv_xchange_price_t *a_price, uint256_t a_datoshi_buy) +{ + uint32_t l_ext_size = sizeof(uint256_t) + DAP_CHAIN_TICKER_SIZE_MAX; + uint8_t *l_ext = DAP_NEW_STACK_SIZE(byte_t, l_ext_size); + memcpy(l_ext, &a_datoshi_buy, sizeof(uint256_t)); + strcpy((char *)&l_ext[sizeof(uint256_t)], a_price->token_buy); + dap_chain_net_srv_price_unit_uid_t l_unit = { .uint32 = SERV_UNIT_UNDEFINED}; + dap_chain_net_srv_uid_t l_uid = { .uint64 = DAP_CHAIN_NET_SRV_XCHANGE_ID }; + uint256_t l_datoshi_sell = {}; + DIV_256_COIN(a_datoshi_buy, a_price->rate, &l_datoshi_sell); + dap_chain_datum_tx_receipt_t *l_receipt = dap_chain_datum_tx_receipt_create(l_uid, l_unit, 0, l_datoshi_sell, + l_ext, l_ext_size); + return l_receipt; +} + +static dap_chain_datum_tx_t *s_xchange_tx_create_request(dap_chain_net_srv_xchange_price_t *a_price, dap_chain_wallet_t *a_wallet) +{ + if (!a_price || !a_price->net_sell || !a_price->net_buy || !*a_price->token_sell || !*a_price->token_buy || !a_wallet) { + return NULL; + } + + // create empty transaction + dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create(); + + dap_ledger_t *l_ledger = dap_chain_ledger_by_net_name(a_price->net_sell->pub.name); + dap_chain_addr_t *l_seller_addr = (dap_chain_addr_t *)dap_chain_wallet_get_addr(a_wallet, a_price->net_sell->pub.id); + dap_enc_key_t *l_seller_key = dap_chain_wallet_get_key(a_wallet, 0); + uint256_t l_value_sell = {}; // how many coins to transfer + // list of transaction with 'out' items to sell + dap_list_t *l_list_used_out = dap_chain_ledger_get_list_tx_outs_with_val(l_ledger, a_price->token_sell, + l_seller_addr, a_price->datoshi_sell, &l_value_sell); + if(!l_list_used_out) { + dap_chain_datum_tx_delete(l_tx); + DAP_DELETE(l_seller_addr); + log_it(L_WARNING, "Nothing to change (not enough funds)"); + return NULL; + } + + // add 'in' items to sell + uint256_t l_value_to_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_used_out); + dap_list_free_full(l_list_used_out, free); + if (compare256(l_value_to_items, l_value_sell) != 0) { + dap_chain_datum_tx_delete(l_tx); + DAP_DELETE(l_seller_addr); + log_it(L_ERROR, "Can't compose the transaction input"); + return NULL; + } + + // add 'out_cond' & 'out' items + { + uint256_t l_datoshi_buy = uint256_0; + MULT_256_COIN(a_price->datoshi_sell, a_price->rate, &l_datoshi_buy); + dap_chain_net_srv_uid_t l_uid = { .uint64 = DAP_CHAIN_NET_SRV_XCHANGE_ID }; + dap_chain_tx_out_cond_t *l_tx_out = dap_chain_datum_tx_item_out_cond_create_srv_xchange(l_uid, a_price->net_sell->pub.id, a_price->datoshi_sell, + a_price->net_buy->pub.id, a_price->token_buy, l_datoshi_buy, + (void *)l_seller_addr, sizeof(dap_chain_addr_t)); + if (!l_tx_out) { + dap_chain_datum_tx_delete(l_tx); + DAP_DELETE(l_seller_addr); + log_it(L_ERROR, "Can't compose the transaction conditional output"); + return NULL; + } + dap_chain_datum_tx_add_item(&l_tx, (const uint8_t *)l_tx_out); + DAP_DELETE(l_tx_out); + // coin back + uint256_t l_value_back = {}; + SUBTRACT_256_256(l_value_sell, a_price->datoshi_sell, &l_value_back); + if (!IS_ZERO_256(l_value_back)) { + if (dap_chain_datum_tx_add_out_item(&l_tx, l_seller_addr, l_value_back) != 1) { + dap_chain_datum_tx_delete(l_tx); + DAP_DELETE(l_seller_addr); + log_it(L_ERROR, "Cant add coin back output"); + return NULL; + } + } + } + DAP_DELETE(l_seller_addr); + + // add 'sign' item + if(dap_chain_datum_tx_add_sign_item(&l_tx, l_seller_key) != 1) { + dap_chain_datum_tx_delete(l_tx); + log_it(L_ERROR, "Can't add sign output"); + return NULL; + } + + return l_tx; +} + +static dap_chain_datum_tx_t *s_xchange_tx_create_exchange(dap_chain_net_srv_xchange_price_t *a_price, + dap_chain_wallet_t *a_wallet, uint256_t a_datoshi_buy) +{ + if (!a_price || !a_price->net_sell || !a_price->net_buy || !*a_price->token_sell || !*a_price->token_buy || !a_wallet) { + return NULL; + } + + // create empty transaction + dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create(); + + dap_ledger_t *l_ledger = dap_chain_ledger_by_net_name(a_price->net_buy->pub.name); + dap_chain_addr_t *l_seller_addr = (dap_chain_addr_t *)dap_chain_wallet_get_addr(a_wallet, a_price->net_buy->pub.id); + dap_enc_key_t *l_seller_key = dap_chain_wallet_get_key(a_wallet, 0); + uint256_t l_value_buy = {}; // how many coins to transfer + // list of transaction with 'out' items to sell + dap_list_t *l_list_used_out = dap_chain_ledger_get_list_tx_outs_with_val(l_ledger, a_price->token_buy, + l_seller_addr, a_datoshi_buy, &l_value_buy); + if(!l_list_used_out) { + dap_chain_datum_tx_delete(l_tx); + log_it(L_WARNING, "Nothing to change (not enough funds)"); + return NULL; + } + + // create and add reciept + dap_chain_datum_tx_receipt_t *l_receipt = s_xchage_receipt_create(a_price, a_datoshi_buy); + dap_chain_datum_tx_add_item(&l_tx, (byte_t *)l_receipt); + DAP_DELETE(l_receipt); + // add 'in' items to sell + uint256_t l_value_to_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_used_out); + dap_list_free_full(l_list_used_out, NULL); + if (compare256(l_value_to_items, l_value_buy) != 0) { + dap_chain_datum_tx_delete(l_tx); + DAP_DELETE(l_seller_addr); + log_it(L_ERROR, "Can't compose the transaction input"); + return NULL; + } + // add 'in' item to buy from conditional transaction + dap_chain_datum_tx_t *l_cond_tx = dap_chain_ledger_tx_find_by_hash(l_ledger, &a_price->tx_hash); + if (!l_cond_tx) { + log_it(L_WARNING, "Requested conditional transaction not found"); + return NULL; + } + int l_prev_cond_idx = 0; + dap_chain_tx_out_cond_t *l_tx_out_cond = dap_chain_datum_tx_out_cond_get(l_cond_tx, &l_prev_cond_idx); + if (dap_chain_ledger_tx_hash_is_used_out_item(l_ledger, &a_price->tx_hash, l_prev_cond_idx)) { + log_it(L_WARNING, "Requested conditional transaction is already used out"); + return false; + } + dap_chain_datum_tx_add_in_cond_item(&l_tx, &a_price->tx_hash, l_prev_cond_idx, 0); + // add 'out' items + { + // transfer selling coins + const dap_chain_addr_t *l_buyer_addr = (dap_chain_addr_t *)l_tx_out_cond->params; + if (dap_chain_datum_tx_add_out_ext_item(&l_tx, l_buyer_addr, a_datoshi_buy, a_price->token_buy) == -1) { + dap_chain_datum_tx_delete(l_tx); + DAP_DELETE(l_seller_addr); + log_it(L_ERROR, "Can't add selling coins output"); + return NULL; + } + // coin back + uint256_t l_value_back = {}; + SUBTRACT_256_256(l_value_buy, a_datoshi_buy, &l_value_back); + if (!IS_ZERO_256(l_value_back)) { + if (dap_chain_datum_tx_add_out_ext_item(&l_tx, l_seller_addr, l_value_back, a_price->token_buy) == -1) { + dap_chain_datum_tx_delete(l_tx); + DAP_DELETE(l_seller_addr); + log_it(L_ERROR, "Can't add selling coins back output"); + return NULL; + } + } + //transfer buying coins + if (dap_chain_datum_tx_add_out_ext_item(&l_tx, l_seller_addr, a_price->datoshi_sell, a_price->token_sell) == -1) { + dap_chain_datum_tx_delete(l_tx); + DAP_DELETE(l_seller_addr); + log_it(L_ERROR, "Can't add buying coins output"); + return NULL; + } + DAP_DELETE(l_seller_addr); + //transfer unbuying coins (partial exchange) + SUBTRACT_256_256(l_tx_out_cond->header.value, a_price->datoshi_sell, &l_value_back); + if (!IS_ZERO_256(l_value_back)) { + if (dap_chain_datum_tx_add_out_ext_item(&l_tx, l_buyer_addr, l_value_back, a_price->token_sell) == -1) { + log_it(L_WARNING, "Can't add buying coins back output (cashback)"); + return NULL; + } + } + } + + // add 'sign' items + if(dap_chain_datum_tx_add_sign_item(&l_tx, l_seller_key) != 1) { + dap_chain_datum_tx_delete(l_tx); + log_it( L_ERROR, "Can't add sign output"); + return NULL; + } + + return l_tx; +} + + + +// Put the transaction to mempool or directly to chains & write transaction's hash to the price +static bool s_xchange_tx_put(dap_chain_datum_tx_t *a_tx, dap_chain_net_t *a_net) +{ + // Put the transaction to mempool or directly to chains + size_t l_tx_size = dap_chain_datum_tx_get_size(a_tx); + dap_chain_datum_t *l_datum = dap_chain_datum_create(DAP_CHAIN_DATUM_TX, a_tx, l_tx_size); + DAP_DELETE(a_tx); + dap_chain_t *l_chain = dap_chain_net_get_chain_by_chain_type(a_net, CHAIN_TYPE_TX); + if (!l_chain) { + DAP_DELETE(l_datum); + return false; + } + // Processing will be made according to autoprocess policy + char *l_ret = dap_chain_mempool_datum_add(l_datum, l_chain); + + DAP_DELETE(l_datum); + + if ( !l_ret ) + return false; + + DAP_DELETE(l_ret); + + return true; +} + +static bool s_xchage_tx_invalidate(dap_chain_net_srv_xchange_price_t *a_price, dap_chain_wallet_t *a_wallet) +{ + // create empty transaction + dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create(); + + dap_ledger_t *l_ledger = dap_chain_ledger_by_net_name(a_price->net_buy->pub.name); + dap_chain_addr_t *l_seller_addr = (dap_chain_addr_t *)dap_chain_wallet_get_addr(a_wallet, a_price->net_buy->pub.id); + dap_enc_key_t *l_seller_key = dap_chain_wallet_get_key(a_wallet, 0); + + // create and add reciept + dap_chain_datum_tx_receipt_t *l_receipt = s_xchage_receipt_create(a_price, uint256_0); + dap_chain_datum_tx_add_item(&l_tx, (byte_t *)l_receipt); + DAP_DELETE(l_receipt); + + // add 'in' item to buy from conditional transaction + dap_chain_datum_tx_t *l_cond_tx = dap_chain_ledger_tx_find_by_hash(l_ledger, &a_price->tx_hash); + if (!l_cond_tx) { + log_it(L_WARNING, "Requested conditional transaction not found"); + return false; + } + int l_prev_cond_idx; + dap_chain_tx_out_cond_t *l_tx_out_cond = dap_chain_datum_tx_out_cond_get(l_cond_tx, &l_prev_cond_idx); + if (dap_chain_ledger_tx_hash_is_used_out_item(l_ledger, &a_price->tx_hash, l_prev_cond_idx)) { + log_it(L_WARNING, "Requested conditional transaction is already used out"); + return false; + } + dap_chain_datum_tx_add_in_cond_item(&l_tx, &a_price->tx_hash, l_prev_cond_idx, 0); + + // add 'out' item + const dap_chain_addr_t *l_buyer_addr = (dap_chain_addr_t *)l_tx_out_cond->params; + if (memcmp(l_seller_addr->data.hash, l_buyer_addr->data.hash, sizeof(dap_chain_hash_fast_t))) { + log_it(L_WARNING, "Only owner can invalidate exchange transaction"); + return false; + } + if (dap_chain_datum_tx_add_out_item(&l_tx, l_seller_addr, l_tx_out_cond->header.value) == -1) { + dap_chain_datum_tx_delete(l_tx); + DAP_DELETE(l_seller_addr); + log_it(L_ERROR, "Cant add returning coins output"); + return false; + } + DAP_DELETE(l_seller_addr); + + // add 'sign' items + if(dap_chain_datum_tx_add_sign_item(&l_tx, l_seller_key) != 1) { + dap_chain_datum_tx_delete(l_tx); + log_it( L_ERROR, "Can't add sign output"); + return false; + } + if (!s_xchange_tx_put(l_tx, a_price->net_buy)) { + return false; + } + return true; +} + + +char *s_xchange_order_create(dap_chain_net_srv_xchange_price_t *a_price, dap_chain_datum_tx_t *a_tx) +{ + dap_chain_hash_fast_t l_tx_hash = {}; + dap_hash_fast(a_tx, dap_chain_datum_tx_get_size(a_tx), &l_tx_hash); + memcpy(&a_price->tx_hash, &l_tx_hash, sizeof(dap_chain_hash_fast_t)); + dap_srv_xchange_order_ext_t l_ext; + l_ext.net_sell_id = a_price->net_sell->pub.id.uint64; + l_ext.datoshi_sell = a_price->datoshi_sell; + strncpy(l_ext.token_sell, a_price->token_sell, DAP_CHAIN_TICKER_SIZE_MAX); + uint32_t l_ext_size = sizeof(dap_srv_xchange_order_ext_t); + dap_chain_node_addr_t *l_node_addr = dap_chain_net_get_cur_addr(a_price->net_sell); + dap_chain_net_srv_price_unit_uid_t l_unit = { .uint32 = SERV_UNIT_UNDEFINED}; + dap_chain_net_srv_uid_t l_uid = { .uint64 = DAP_CHAIN_NET_SRV_XCHANGE_ID }; + uint256_t l_datoshi_buy = uint256_0; + MULT_256_COIN(a_price->datoshi_sell, a_price->rate, &l_datoshi_buy); + + char *l_order_hash_str = dap_chain_net_srv_order_create(a_price->net_buy, SERV_DIR_BUY, l_uid, *l_node_addr, + l_tx_hash, l_datoshi_buy, l_unit, a_price->token_buy, 0, + (uint8_t *)&l_ext, l_ext_size, NULL, 0, a_price->wallet_key); + return l_order_hash_str; +} + + + +dap_chain_net_srv_xchange_price_t *s_xchange_price_from_order(dap_chain_net_t *a_net, dap_chain_net_srv_order_t *a_order) +{ + dap_chain_net_srv_xchange_price_t *l_price = DAP_NEW_Z(dap_chain_net_srv_xchange_price_t); + dap_srv_xchange_order_ext_t *l_ext = (dap_srv_xchange_order_ext_t *)a_order->ext_n_sign; + dap_chain_net_id_t l_net_sell_id = { .uint64 = l_ext->net_sell_id }; + l_price->net_sell = dap_chain_net_by_id(l_net_sell_id); + l_price->datoshi_sell = l_ext->datoshi_sell; + strcpy(l_price->token_sell, l_ext->token_sell); + l_price->net_buy = a_net; + strcpy(l_price->token_buy, a_order->price_ticker); + DIV_256_COIN(a_order->price, l_price->datoshi_sell, &l_price->rate); + memcpy(&l_price->tx_hash, &a_order->tx_cond_hash, sizeof(dap_chain_hash_fast_t)); + return l_price; +} + +static bool s_xchange_db_add(dap_chain_net_srv_xchange_price_t *a_price) +{ + int rc; + + size_t l_size = sizeof(dap_chain_net_srv_xchange_db_item_t) + strlen(a_price->wallet_str) + 1; + dap_chain_net_srv_xchange_db_item_t *l_item = DAP_NEW_Z_SIZE(dap_chain_net_srv_xchange_db_item_t, l_size); + strcpy(l_item->token_sell, a_price->token_sell); + strcpy(l_item->token_buy, a_price->token_buy); + l_item->net_sell_id = a_price->net_sell->pub.id.uint64; + l_item->net_buy_id = a_price->net_buy->pub.id.uint64; + l_item->datoshi_sell = a_price->datoshi_sell; + l_item->rate = a_price->rate; + memcpy(&l_item->tx_hash, &a_price->tx_hash, sizeof(dap_chain_hash_fast_t)); + memcpy(&l_item->order_hash, &a_price->order_hash, sizeof(dap_chain_hash_fast_t)); + strcpy(l_item->wallet_str, a_price->wallet_str); + + rc = dap_global_db_set_sync(GROUP_LOCAL_XCHANGE, a_price->key_ptr, l_item, l_size, false); + DAP_DELETE(l_item); + + return rc; +} + +static dap_chain_net_srv_xchange_price_t *s_xchange_db_load(char *a_key, uint8_t *a_item) +{ + dap_chain_net_srv_xchange_db_item_t *l_item = (dap_chain_net_srv_xchange_db_item_t *)a_item; + dap_chain_net_srv_xchange_price_t *l_price = DAP_NEW_Z(dap_chain_net_srv_xchange_price_t); + l_price->key_ptr = dap_strdup(a_key); + strcpy(l_price->token_sell, l_item->token_sell); + strcpy(l_price->token_buy, l_item->token_buy); + dap_chain_net_id_t l_id = { .uint64 = l_item->net_sell_id}; + l_price->net_sell = dap_chain_net_by_id(l_id); + l_id.uint64 = l_item->net_buy_id; + l_price->net_buy = dap_chain_net_by_id(l_id); + l_price->datoshi_sell = l_item->datoshi_sell; + l_price->rate = l_item->rate; + memcpy(&l_price->tx_hash, &l_item->tx_hash, sizeof(dap_chain_hash_fast_t)); + memcpy(&l_price->order_hash, &l_item->order_hash, sizeof(dap_chain_hash_fast_t)); + l_price->wallet_str = dap_strdup(l_item->wallet_str); + return l_price; +} + +static int s_cli_srv_xchange_price(int a_argc, char **a_argv, int a_arg_index, char **a_str_reply) +{ + enum { + CMD_NONE, CMD_CREATE, CMD_REMOVE, CMD_LIST, CMD_UPDATE + }; + int l_cmd_num = CMD_NONE; + if(dap_chain_node_cli_find_option_val(a_argv, a_arg_index, min(a_argc, a_arg_index + 1), "create", NULL)) { + l_cmd_num = CMD_CREATE; + } + else if(dap_chain_node_cli_find_option_val(a_argv, a_arg_index, min(a_argc, a_arg_index + 1), "remove", NULL)) { + l_cmd_num = CMD_REMOVE; + } + else if(dap_chain_node_cli_find_option_val(a_argv, a_arg_index, min(a_argc, a_arg_index + 1), "list", NULL)) { + l_cmd_num = CMD_LIST; + } + else if(dap_chain_node_cli_find_option_val(a_argv, a_arg_index, min(a_argc, a_arg_index + 1), "update", NULL)) { + l_cmd_num = CMD_UPDATE; + } + int l_arg_index = a_arg_index + 1; + const char *l_net_sell_str = NULL, *l_net_buy_str = NULL; + const char *l_token_sell_str = NULL, *l_token_buy_str = NULL; + dap_chain_net_t *l_net_sell = NULL, *l_net_buy = NULL; + char *l_strkey; + if (l_cmd_num == CMD_CREATE || l_cmd_num == CMD_REMOVE || l_cmd_num == CMD_UPDATE) { + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-net_sell", &l_net_sell_str); + if (!l_net_sell_str) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'price %s' required parameter -net_sell", + l_cmd_num == CMD_CREATE ? "create" : (l_cmd_num == CMD_REMOVE ? "remove" : "update")); + return -2; + } + l_net_sell = dap_chain_net_by_name(l_net_sell_str); + if (!l_net_sell) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Network %s not found", l_net_sell_str); + return -3; + } + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-net_buy", &l_net_buy_str); + if (!l_net_buy_str) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'price %s' required parameter -net_buy", + l_cmd_num == CMD_CREATE ? "create" : (l_cmd_num == CMD_REMOVE ? "remove" : "update")); + return -2; + } + l_net_buy = dap_chain_net_by_name(l_net_buy_str); + if (!l_net_buy) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Network %s not found", l_net_buy_str); + return -3; + } + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-token_sell", &l_token_sell_str); + if (!l_token_sell_str) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'price %s' required parameter -token_sell", + l_cmd_num == CMD_CREATE ? "create" : (l_cmd_num == CMD_REMOVE ? "remove" : "update")); + return -5; + } + if (!dap_chain_ledger_token_ticker_check(l_net_sell->pub.ledger, l_token_sell_str)) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Token ticker %s not found", l_token_sell_str); + return -6; + } + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-token_buy", &l_token_buy_str); + if (!l_token_buy_str) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'price %s' required parameter -token_buy", + l_cmd_num == CMD_CREATE ? "create" : (l_cmd_num == CMD_REMOVE ? "remove" : "update")); + return -5; + } + if (!dap_chain_ledger_token_ticker_check(l_net_buy->pub.ledger, l_token_buy_str)) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Token ticker %s not found", l_token_buy_str); + return -6; + } + l_strkey = DAP_NEW_SIZE(char, dap_strlen(l_token_sell_str) + dap_strlen(l_net_sell_str) + + dap_strlen(l_token_buy_str) + dap_strlen(l_net_buy_str) + 1); + dap_stpcpy(l_strkey, l_token_sell_str); + strcat(l_strkey, l_net_sell_str); + strcat(l_strkey, l_token_buy_str); + strcat(l_strkey, l_net_buy_str); + } + switch (l_cmd_num) { + case CMD_CREATE: { + dap_chain_net_srv_xchange_price_t *l_price = NULL; + +#if 0 /* Disabled on behalf of GD */ + HASH_FIND_STR(s_srv_xchange->pricelist, l_strkey, l_price); + + if (l_price) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Price with provided pair of token ticker + net name already exist"); + return -7; + } +#endif + + + const char *l_val_sell_str = NULL, *l_val_rate_str = NULL, *l_wallet_str = NULL; + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-coins", &l_val_sell_str); + if (!l_val_sell_str) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'price create' required parameter -coins"); + return -8; + } + uint256_t l_datoshi_sell = dap_chain_balance_scan(l_val_sell_str); + if (IS_ZERO_256(l_datoshi_sell)) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Format -coins <unsigned long long>"); + return -9; + } + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-rate", &l_val_rate_str); + if (!l_val_rate_str) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'price create' required parameter -rate"); + return -8; + } + uint256_t l_rate = dap_chain_coins_to_balance(l_val_rate_str); + if (!compare256(l_rate, uint256_0)) { // if (l_rate == 0) + dap_chain_node_cli_set_reply_text(a_str_reply, "Format -rate n.n = sell / buy (eg: 1.0, 1.135)"); + return -9; + } + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-wallet", &l_wallet_str); + if (!l_wallet_str) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'price create' required parameter -wallet"); + return -10; + } + dap_chain_wallet_t *l_wallet = dap_chain_wallet_open(l_wallet_str, dap_chain_wallet_get_path(g_config)); + if (!l_wallet) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Specified wallet not found"); + return -11; + } + uint256_t l_value = dap_chain_wallet_get_balance(l_wallet, l_net_sell->pub.id, l_token_sell_str); + if (compare256(l_value, l_datoshi_sell) == -1) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Not enough cash in specified wallet"); + dap_chain_wallet_close(l_wallet); + return -12; + } + // Create the price + l_price = DAP_NEW_Z(dap_chain_net_srv_xchange_price_t); + l_price->wallet_str = dap_strdup(l_wallet_str); + dap_stpcpy(l_price->token_sell, l_token_sell_str); + l_price->net_sell = l_net_sell; + dap_stpcpy(l_price->token_buy, l_token_buy_str); + l_price->net_buy = l_net_buy; + l_price->key_ptr = l_strkey; + l_price->datoshi_sell = l_datoshi_sell; + l_price->rate = l_rate; + // Create conditional transaction + dap_chain_datum_tx_t *l_tx = s_xchange_tx_create_request(l_price, l_wallet); + if (!l_tx) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Can't compose the conditional transaction"); + DAP_DELETE(l_price->key_ptr); + DAP_DELETE(l_price->wallet_str); + DAP_DELETE(l_price); + dap_chain_wallet_close(l_wallet); + return -14; + } + // Create the order & put it to GDB + l_price->wallet_key = dap_chain_wallet_get_key(l_wallet, 0); + char *l_order_hash_str = s_xchange_order_create(l_price, l_tx); + dap_chain_wallet_close(l_wallet); + if (l_order_hash_str) { + dap_chain_hash_fast_from_str(l_order_hash_str, &l_price->order_hash); + if(!s_xchange_tx_put(l_tx, l_net_buy)) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Can't put transaction to mempool"); + dap_chain_net_srv_order_delete_by_hash_str_sync(l_net_buy, l_order_hash_str); + DAP_DELETE(l_order_hash_str); + DAP_DELETE(l_price->key_ptr); + DAP_DELETE(l_price->wallet_str); + DAP_DELETE(l_price); + return -15; + } + if (!s_xchange_db_add(l_price)) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Can't save price in database"); + dap_chain_net_srv_order_delete_by_hash_str_sync(l_net_buy, l_order_hash_str); + DAP_DELETE(l_order_hash_str); + DAP_DELETE(l_price->key_ptr); + DAP_DELETE(l_price->wallet_str); + DAP_DELETE(l_price); + return -16; + } + dap_chain_node_cli_set_reply_text(a_str_reply, "Successfully created order %s", l_order_hash_str); + DAP_DELETE(l_order_hash_str); + // Add active price to pricelist + HASH_ADD_KEYPTR(hh, s_srv_xchange->pricelist, l_price->key_ptr, strlen(l_price->key_ptr), l_price); + } else { + dap_chain_node_cli_set_reply_text(a_str_reply, "Can't compose the order"); + DAP_DELETE(l_price->key_ptr); + DAP_DELETE(l_price->wallet_str); + DAP_DELETE(l_price); + return -18; + } + } break; + case CMD_REMOVE: + case CMD_UPDATE: { + + dap_chain_net_srv_xchange_price_t *l_price = NULL; + HASH_FIND_STR(s_srv_xchange->pricelist, l_strkey, l_price); + if (!l_price) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Price with provided pair of token ticker + net name is not exist"); + return -1; + } + + if (l_cmd_num == CMD_REMOVE) { + dap_string_t *l_str_reply = dap_string_new(""); + HASH_DEL(s_srv_xchange->pricelist, l_price); + dap_global_db_del_sync(GROUP_LOCAL_XCHANGE, l_price->key_ptr); + dap_chain_wallet_t *l_wallet = dap_chain_wallet_open(l_price->wallet_str, dap_chain_wallet_get_path(g_config)); + bool l_ret = s_xchage_tx_invalidate(l_price, l_wallet); + dap_chain_wallet_close(l_wallet); + if (!l_ret) { + char *l_tx_hash_str = dap_chain_hash_fast_to_str_new(&l_price->tx_hash); + dap_string_append_printf(l_str_reply, "Can't invalidate transaction %s\n", l_tx_hash_str); + DAP_DELETE(l_tx_hash_str); + } + char *l_order_hash_str = dap_chain_hash_fast_to_str_new(&l_price->order_hash); + if (dap_chain_net_srv_order_delete_by_hash_str_sync(l_price->net_buy, l_order_hash_str)) { + dap_string_append_printf(l_str_reply, "Can't remove order %s\n", l_order_hash_str); + } + DAP_DELETE(l_order_hash_str); + DAP_DELETE(l_price->wallet_str); + DAP_DELETE(l_price); + if (!l_str_reply->len) { + dap_string_append(l_str_reply, "Price successfully removed"); + } + *a_str_reply = dap_string_free(l_str_reply, false); + } else { // CMD_UPDATE + const char *l_val_sell_str = NULL, *l_val_rate_str = NULL, *l_wallet_str = NULL, *l_new_wallet_str = NULL; + uint256_t l_datoshi_sell = {}; + uint256_t l_rate = uint256_0; + dap_chain_wallet_t *l_wallet = NULL; + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-coins", &l_val_sell_str); + if (l_val_sell_str) { + l_datoshi_sell = dap_chain_balance_scan(l_val_sell_str); + if (IS_ZERO_256(l_datoshi_sell)) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Format -coins <unsigned long long>"); + return -9; + } + } + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-rate", &l_val_rate_str); + if (l_val_rate_str) { + l_rate = dap_chain_coins_to_balance(l_val_rate_str); + if (!compare256(l_rate, uint256_0)) { // if (l_rate == 0) + dap_chain_node_cli_set_reply_text(a_str_reply, "Format -rate <long double> = sell / buy"); + return -9; + } + } + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-wallet", &l_new_wallet_str); + l_wallet_str = l_new_wallet_str ? l_new_wallet_str : l_price->wallet_str; + l_wallet = dap_chain_wallet_open(l_wallet_str, dap_chain_wallet_get_path(g_config)); + if (!l_wallet) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Specified wallet not found"); + return -11; + } + if (!l_val_sell_str && !l_val_rate_str && !l_wallet_str) { + dap_chain_node_cli_set_reply_text(a_str_reply, "At least one of updating parameters is mandatory"); + return -13; + } + uint256_t l_value = dap_chain_wallet_get_balance(l_wallet, l_net_sell->pub.id, l_token_sell_str); + if (!IS_ZERO_256(l_datoshi_sell) && compare256(l_value, l_datoshi_sell) == -1) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Not enough cash in specified wallet"); + dap_chain_wallet_close(l_wallet); + return -12; + } + if (l_val_sell_str) { + l_price->datoshi_sell = l_datoshi_sell; + } + if (l_val_rate_str) { + l_price->rate = l_rate; + } + // Update the transaction + dap_chain_datum_tx_t *l_tx = s_xchange_tx_create_request(l_price, l_wallet); + if (l_new_wallet_str) { + dap_chain_wallet_close(l_wallet); + l_wallet = dap_chain_wallet_open(l_price->wallet_str, dap_chain_wallet_get_path(g_config)); + DAP_DELETE(l_price->wallet_str); + l_price->wallet_str = dap_strdup(l_new_wallet_str); + } + if (!l_tx) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Can't compose the conditional transaction"); + return -14; + } + HASH_DEL(s_srv_xchange->pricelist, l_price); + dap_global_db_del_sync(GROUP_LOCAL_XCHANGE, l_price->key_ptr); + bool l_ret = s_xchage_tx_invalidate(l_price, l_wallet); // may be changed to old price later + dap_chain_wallet_close(l_wallet); + if (!l_ret) { + char *l_tx_hash_str = dap_chain_hash_fast_to_str_new(&l_price->tx_hash); + dap_chain_node_cli_set_reply_text(a_str_reply, "Can't invalidate transaction %s\n", l_tx_hash_str); + DAP_DELETE(l_tx_hash_str); + return -17; + } + // Update the order + char *l_order_hash_str = dap_chain_hash_fast_to_str_new(&l_price->order_hash); + dap_chain_net_srv_order_delete_by_hash_str_sync(l_price->net_buy, l_order_hash_str); + DAP_DELETE(l_order_hash_str); + l_order_hash_str = s_xchange_order_create(l_price, l_tx); + if (l_order_hash_str) { + dap_chain_hash_fast_from_str(l_order_hash_str, &l_price->order_hash); + if(!s_xchange_tx_put(l_tx, l_net_buy)) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Can't put transaction to mempool"); + dap_chain_net_srv_order_delete_by_hash_str_sync(l_net_buy, l_order_hash_str); + DAP_DELETE(l_order_hash_str); + return -15; + } + if (!s_xchange_db_add(l_price)) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Can't save price in database"); + dap_chain_net_srv_order_delete_by_hash_str_sync(l_net_buy, l_order_hash_str); + DAP_DELETE(l_order_hash_str); + return -16; + } + dap_chain_node_cli_set_reply_text(a_str_reply, "Successfully created order %s", l_order_hash_str); + DAP_DELETE(l_order_hash_str); + } else { + dap_chain_node_cli_set_reply_text(a_str_reply, "Can't compose the order"); + DAP_DELETE(l_price->wallet_str); + DAP_DELETE(l_price->key_ptr); + DAP_DELETE(l_price); + return -18; + } + // Update the pricelist + HASH_ADD_KEYPTR(hh, s_srv_xchange->pricelist, l_price->key_ptr, strlen(l_price->key_ptr), l_price); + } + } break; + case CMD_LIST: { + dap_chain_net_srv_xchange_price_t *l_price = NULL, *l_tmp; + dap_string_t *l_reply_str = dap_string_new(""); + HASH_ITER(hh, s_srv_xchange->pricelist, l_price, l_tmp) { + char *l_order_hash_str = dap_chain_hash_fast_to_str_new(&l_price->order_hash); + dap_string_append_printf(l_reply_str, "%s %s %s %s %s %s %s %s\n", l_order_hash_str, l_price->token_sell, + l_price->net_sell->pub.name, l_price->token_buy, l_price->net_buy->pub.name, + dap_chain_balance_print(l_price->datoshi_sell), dap_chain_balance_print(l_price->rate), l_price->wallet_str); + DAP_DELETE(l_order_hash_str); + } + if (!l_reply_str->len) { + dap_string_append(l_reply_str, "Pricelist is empty"); + } + *a_str_reply = dap_string_free(l_reply_str, false); + } break; + default: { + dap_chain_node_cli_set_reply_text(a_str_reply, "Subcommand %s not recognized", a_argv[a_arg_index]); + return -4; + } + } + return 0; +} + +// Filter for find tx with DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_XCHANGE +static bool s_filter_tx_list(dap_chain_datum_t *a_datum, dap_chain_t *a_chain, void *a_filter_func_param) +{ + UNUSED(a_chain); + // Datum type filter -> only tx + if(!a_datum || a_datum->header.type_id != DAP_CHAIN_DATUM_TX) + return false; + dap_chain_datum_tx_t *l_datum_tx = (dap_chain_datum_tx_t*) a_datum->data; + // Get time from parameters + dap_time_t *l_time_mass = (dap_time_t*) a_filter_func_param; + dap_time_t l_time_begin = 0; + dap_time_t l_time_end = 0; + if(l_time_mass) { + l_time_begin = l_time_mass[0]; + l_time_end = l_time_mass[1]; + } + // Time filter + if(l_time_begin && l_datum_tx->header.ts_created < l_time_begin) + return false; + if(l_time_end && l_datum_tx->header.ts_created > l_time_end) + return false; + // Item filter -> if present tx_out_cond with subtype == SRV_XCHANGE + dap_chain_tx_out_cond_t *l_out_cond_item = NULL; + int l_item_idx = 0; + do { + l_out_cond_item = (dap_chain_tx_out_cond_t*) dap_chain_datum_tx_item_get(l_datum_tx, &l_item_idx, TX_ITEM_TYPE_OUT_COND, NULL); + l_item_idx++; + if(l_out_cond_item && l_out_cond_item->header.subtype == DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_XCHANGE) { + return true; + } + + } + while(l_out_cond_item); + return false; +} + + +static int s_cli_srv_xchange_tx_list_addr ( + dap_chain_net_t *a_net, + dap_time_t a_after, + dap_time_t a_before, + dap_chain_addr_t *a_addr, + int a_status, + char **a_str_reply + ) +{ +char l_hash_str [DAP_CHAIN_HASH_FAST_STR_SIZE + 8] = {0}; +dap_chain_hash_fast_t l_tx_first_hash = {0}; +dap_chain_datum_tx_t *l_datum_tx; +size_t l_datum_tx_size, l_tx_total, l_tx_count; +int l_item_idx, l_rc; +dap_string_t *l_reply_str; +dap_hash_fast_t l_hash; +dap_chain_tx_out_cond_t *l_out_cond_item; + + + if ( !(l_reply_str = dap_string_new("")) ) /* Prepare output string discriptor*/ + return log_it(L_ERROR, "Cannot allocate a memory, errno=%d", errno), -ENOMEM; + + memset(&l_tx_first_hash, 0, sizeof(dap_chain_hash_fast_t)); /* Initial hash == zero */ + + + for ( l_tx_count = l_tx_total = 0; + (l_datum_tx = dap_chain_ledger_tx_find_by_addr(a_net->pub.ledger, NULL, a_addr, &l_tx_first_hash)); + l_tx_total++) + { + /* Check time range (if need ) */ + if ( !(l_datum_tx->header.ts_created > a_after) ) + continue; + + if ( a_before && (l_datum_tx->header.ts_created > a_before) ) + continue; + + + /* TX hash */ + l_datum_tx_size = dap_chain_datum_tx_get_size(l_datum_tx); + + if ( !dap_hash_fast(l_datum_tx, l_datum_tx_size, &l_hash) ) + { /* Never must be happend, but ... */ + log_it(L_ERROR, "dap_hash_fast(..., %zu octets) return error", l_datum_tx_size); + dump_it("l_datum_tx", l_datum_tx, l_datum_tx_size); + continue; + } + + dap_chain_hash_fast_to_str(&l_hash, l_hash_str, DAP_CHAIN_HASH_FAST_STR_SIZE + 1); + dap_string_append_printf(l_reply_str, "Hash: %s\n", l_hash_str); + + /* Find SRV_XCHANGE out_cond item */ + for (l_out_cond_item = NULL, l_item_idx = 0; + (l_out_cond_item = (dap_chain_tx_out_cond_t *) dap_chain_datum_tx_item_get(l_datum_tx, &l_item_idx, TX_ITEM_TYPE_OUT_COND, NULL)); + l_item_idx++) + { + if ( l_out_cond_item->header.subtype != DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_XCHANGE ) + continue; + + + + + if (a_status) /* 1 - closed, 2 - open */ + { + l_rc = dap_chain_ledger_tx_hash_is_used_out_item(a_net->pub.ledger, &l_hash, l_item_idx); + + if ( (a_status == 1) && !l_rc ) + continue; + + if ( (a_status == 2) && l_rc ) + continue; + } + + const char *l_tx_input_ticker = dap_chain_ledger_tx_get_token_ticker_by_hash(a_net->pub.ledger, &l_hash); + + uint256_t l_value_to = l_out_cond_item->subtype.srv_xchange.buy_value; + uint256_t l_tx_input_values = dap_chain_net_get_tx_total_value(a_net, l_datum_tx); + + char *l_tx_input_values_str = dap_cvt_uint256_to_str(l_tx_input_values); + char *l_value_from_str = dap_cvt_uint256_to_str(l_tx_input_values); + char *l_value_to_str = dap_cvt_uint256_to_str(l_value_to); + + dap_string_append_printf(l_reply_str, "Status: is %s used out", l_rc ? "" : "NOT"); + + dap_string_append_printf(l_reply_str, " From: %s %s ", l_tx_input_values_str, l_tx_input_ticker); + dap_string_append_printf(l_reply_str, " To: %s %s\n", l_value_to_str, l_out_cond_item->subtype.srv_xchange.buy_token); + + DAP_DELETE(l_value_from_str); + DAP_DELETE(l_value_to_str); + } + + + dap_string_append(l_reply_str, "\n"); + } + + + *a_str_reply = dap_string_free(l_reply_str, false); /* Free string descriptor, but keep ASCIZ buffer itself */ + return 0; +} + + + + +static int s_cli_srv_xchange(int a_argc, char **a_argv, char **a_str_reply) +{ + enum {CMD_NONE = 0, CMD_PRICE, CMD_ORDERS, CMD_PURCHASE, CMD_ENABLE, CMD_DISABLE, CMD_TX_LIST, CMD_TOKEN_PAIR }; + int l_arg_index = 1, l_cmd_num = CMD_NONE, l_rc; + dap_chain_hash_fast_t l_hash; + + if(dap_chain_node_cli_find_option_val(a_argv, l_arg_index, min(a_argc, l_arg_index + 1), "price", NULL)) { + l_cmd_num = CMD_PRICE; + } + else if(dap_chain_node_cli_find_option_val(a_argv, l_arg_index, min(a_argc, l_arg_index + 1), "orders", NULL)) { + l_cmd_num = CMD_ORDERS; + } + else if(dap_chain_node_cli_find_option_val(a_argv, l_arg_index, min(a_argc, l_arg_index + 1), "purchase", NULL)) { + l_cmd_num = CMD_PURCHASE; + } + else if(dap_chain_node_cli_find_option_val(a_argv, l_arg_index, min(a_argc, l_arg_index + 1), "enable", NULL)) { + l_cmd_num = CMD_ENABLE; + } + else if(dap_chain_node_cli_find_option_val(a_argv, l_arg_index, min(a_argc, l_arg_index + 1), "disable", NULL)) { + l_cmd_num = CMD_DISABLE; + } + else if(dap_chain_node_cli_find_option_val(a_argv, l_arg_index, min(a_argc, l_arg_index + 1), "tx_list", NULL)) { + l_cmd_num = CMD_TX_LIST; + } + else if(dap_chain_node_cli_find_option_val(a_argv, l_arg_index, min(a_argc, l_arg_index + 1), "token_pair", NULL)) { + l_cmd_num = CMD_TOKEN_PAIR; + } + + + switch (l_cmd_num) { + case CMD_PRICE: + return s_cli_srv_xchange_price(a_argc, a_argv, l_arg_index + 1, a_str_reply); + case CMD_ORDERS: { + const char *l_net_str = NULL; + l_arg_index++; + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-net", &l_net_str); + if (!l_net_str) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'purchase' required parameter -net"); + return -2; + } + dap_chain_net_t *l_net = dap_chain_net_by_name(l_net_str); + if (!l_net) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Network %s not found", l_net_str); + return -3; + } + + char * l_gdb_group_str = dap_chain_net_srv_order_get_gdb_group(l_net); + + size_t l_orders_count = 0; + dap_global_db_obj_t * l_orders = dap_global_db_get_all_sync(l_gdb_group_str, &l_orders_count); + dap_chain_net_srv_xchange_price_t *l_price; + dap_string_t *l_reply_str = dap_string_new(""); + + + for (size_t i = 0; i < l_orders_count; i++) + { + dap_chain_net_srv_order_t *l_order = (dap_chain_net_srv_order_t *)l_orders[i].value; + + if (l_order->srv_uid.uint64 != DAP_CHAIN_NET_SRV_XCHANGE_ID) + continue; + + // TODO add filters to list (tokens, network, etc.) + l_price = s_xchange_price_from_order(l_net, l_order); + uint256_t l_datoshi_buy; + char *l_cp1, *l_cp2, *l_cp3; + + MULT_256_COIN(l_price->datoshi_sell, l_price->rate, &l_datoshi_buy); /* sell/buy computation */ + + dap_string_append_printf(l_reply_str, "orderHash: %s tokSel: %s, netSel: %s, tokBuy: %s, netBuy: %s, sell: %s, buy: %s buy/sell: %s\n", l_orders[i].key, + l_price->token_sell, l_price->net_sell->pub.name, + l_price->token_buy, l_price->net_buy->pub.name, + l_cp1 = dap_chain_balance_print(l_price->datoshi_sell), l_cp2 = dap_chain_balance_print(l_datoshi_buy), + l_cp3 = dap_chain_balance_to_coins(l_price->rate)); /* RRL: dap_chain_balance_print(l_rate) ); */ + + DAP_DEL_Z(l_cp1); + DAP_DEL_Z(l_cp2); + DAP_DEL_Z(l_cp3); + DAP_DEL_Z(l_price); + } + dap_global_db_objs_delete(l_orders, l_orders_count); + DAP_DELETE( l_gdb_group_str); + if (!l_reply_str->len) { + dap_string_append(l_reply_str, "No orders found"); + } + *a_str_reply = dap_string_free(l_reply_str, false); + } break; + + + case CMD_PURCHASE: { + const char *l_net_str = NULL, *l_wallet_str = NULL, *l_order_hash_str = NULL, *l_val_buy_str = NULL; + l_arg_index++; + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-net", &l_net_str); + if (!l_net_str) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'purchase' required parameter -net"); + return -2; + } + dap_chain_net_t *l_net = dap_chain_net_by_name(l_net_str); + if (!l_net) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Network %s not found", l_net_str); + return -3; + } + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-wallet", &l_wallet_str); + if (!l_wallet_str) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'purchase' required parameter -wallet"); + return -10; + } + dap_chain_wallet_t *l_wallet = dap_chain_wallet_open(l_wallet_str, dap_chain_wallet_get_path(g_config)); + if (!l_wallet) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Specified wallet not found"); + return -11; + } + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-order", &l_order_hash_str); + if (!l_order_hash_str) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'purchase' required parameter -order"); + return -12; + } + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-coins", &l_val_buy_str); + if (!l_val_buy_str) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'purchase' required parameter -coins"); + return -8; + } + uint256_t l_datoshi_buy = dap_chain_balance_scan(l_val_buy_str); + if (IS_ZERO_256(l_datoshi_buy)) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Format -coins <unsigned long long>"); + return -9; + } + dap_chain_net_srv_order_t *l_order = dap_chain_net_srv_order_find_by_hash_str(l_net, l_order_hash_str); + if (l_order) { + dap_chain_net_srv_xchange_price_t *l_price = s_xchange_price_from_order(l_net, l_order); + // Create conditional transaction + dap_chain_datum_tx_t *l_tx = s_xchange_tx_create_exchange(l_price, l_wallet, l_datoshi_buy); + if (l_tx && s_xchange_tx_put(l_tx, l_net)) { + // TODO send request to seller to update / delete order & price + dap_chain_net_srv_order_delete_by_hash_str_sync(l_price->net_buy, l_order_hash_str); + } + DAP_DELETE(l_price); + DAP_DELETE(l_order); + dap_chain_node_cli_set_reply_text(a_str_reply, l_tx ? "Exchange transaction has done" : + "Exchange transaction error"); + } else { + dap_chain_node_cli_set_reply_text(a_str_reply, "Specified order not found"); + return -13; + } + } break; + case CMD_ENABLE: { + s_srv_xchange->enabled = true; + } break; + case CMD_DISABLE: { + s_srv_xchange->enabled = false; + } break; + case CMD_TX_LIST: { + const char *l_net_str = NULL, *l_time_begin_str = NULL, *l_time_end_str = NULL; + const char *l_status_str = NULL, *l_addr_str = NULL; /* @RRL: #6294 */ + int l_status; + dap_chain_addr_t *l_addr; + + l_arg_index++; + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-net", &l_net_str); + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-time_from", &l_time_begin_str); + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-time_to", &l_time_end_str); + + /* + * @RRL: #6294: [[-addr <addr> [-status closed | open]] + * we should check for valid combination of the status and addr options + */ + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-addr", &l_addr_str); + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-status", &l_status_str); + + /* Validate input arguments ... */ + if(!l_net_str) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'tx_list' required parameter -net"); + return -3; + } + dap_chain_net_t *l_net = dap_chain_net_by_name(l_net_str); + if(!l_net) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Network %s not found", l_net_str); + return -4; + } + dap_time_t l_time[2]; + l_time[0] = dap_time_from_str_rfc822(l_time_begin_str); + l_time[1] = dap_time_from_str_rfc822(l_time_end_str); + + if ( l_status_str && !l_addr_str ) + return dap_chain_node_cli_set_reply_text(a_str_reply, "Parameter -status require -addr"), -EINVAL; + + /* Dispatch request processing to ... */ + if ( l_addr_str ) + { + if ( !(l_addr = dap_chain_addr_from_str(l_addr_str)) ) + return dap_chain_node_cli_set_reply_text(a_str_reply, "Cannot convert -addr '%s' to internal representative", l_addr_str), -EINVAL; + + l_status = 0; /* 0 - all */ + + if ( l_status_str ) + { + /* 1 - closed, 2 - open */ + l_status = !dap_strncmp (l_status_str, "close", 5); + l_status += !dap_strncmp (l_status_str, "open", 4); + } + + return s_cli_srv_xchange_tx_list_addr (l_net, l_time[0], l_time[1], l_addr, l_status, a_str_reply); + } + + + + // Prepare output string + dap_string_t *l_reply_str = dap_string_new(""); + + // Find transactions using filter function s_filter_tx_list() + dap_list_t *l_datum_list0 = dap_chain_datum_list(l_net, NULL, s_filter_tx_list, l_time); + size_t l_datum_num = dap_list_length(l_datum_list0); + + if(l_datum_num > 0) { + dap_string_append_printf(l_reply_str, "Found %zu transactions:\n", l_datum_num); + dap_list_t *l_datum_list = l_datum_list0; + + char l_hash_str [DAP_CHAIN_HASH_FAST_STR_SIZE + 8] = {0}; + + while(l_datum_list) { + dap_chain_datum_tx_t *l_datum_tx = (dap_chain_datum_tx_t*) ((dap_chain_datum_t*) l_datum_list->data)->data; + size_t l_datum_tx_size = dap_chain_datum_tx_get_size(l_datum_tx); + + // Delimiter between tx + if(l_datum_list != l_datum_list0) { + dap_string_append(l_reply_str, "\n\n"); + } + + // Tx hash + dap_hash_fast_t l_tx_hash = {0}; + + dap_hash_fast(l_datum_tx, l_datum_tx_size, &l_tx_hash); + dap_chain_hash_fast_to_str(&l_tx_hash, l_hash_str, DAP_CHAIN_HASH_FAST_STR_SIZE + 1); + dap_string_append_printf(l_reply_str, "Hash: %s\n", l_hash_str); + + // Get input token ticker + const char * l_tx_input_ticker = dap_chain_ledger_tx_get_token_ticker_by_hash( + l_net->pub.ledger, &l_tx_hash); + // Calc inputs + uint256_t l_tx_input_values = dap_chain_net_get_tx_total_value(l_net, l_datum_tx); + + + // Find SRV_XCHANGE out_cond item + dap_chain_tx_out_cond_t *l_out_cond_item = NULL; + int l_item_idx = 0; + do { + l_out_cond_item = (dap_chain_tx_out_cond_t*) dap_chain_datum_tx_item_get(l_datum_tx, &l_item_idx, TX_ITEM_TYPE_OUT_COND, NULL); + l_item_idx++; + if(l_out_cond_item && l_out_cond_item->header.subtype == DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_XCHANGE) { + uint256_t value = l_out_cond_item->subtype.srv_xchange.buy_value; + char *l_value_to_str = dap_cvt_uint256_to_str(value); + char * l_value_from_str = dap_cvt_uint256_to_str(l_tx_input_values); + + l_rc = dap_chain_ledger_tx_hash_is_used_out_item(l_net->pub.ledger, &l_hash, l_item_idx); + + dap_string_append_printf(l_reply_str, "Status: is %s used out", l_rc ? "" : "NOT"); + + dap_string_append_printf(l_reply_str, " From: %s %s ", l_value_from_str, l_tx_input_ticker); + dap_string_append_printf(l_reply_str, " To: %s %s\n", l_value_to_str, l_out_cond_item->subtype.srv_xchange.buy_token); + + DAP_DELETE(l_value_from_str); + DAP_DELETE(l_value_to_str); + } + } + while(l_out_cond_item); + + l_datum_list = dap_list_next(l_datum_list); + } + } + else{ + dap_string_append(l_reply_str, "Transactions not found"); + } + dap_list_free(l_datum_list0); + *a_str_reply = dap_string_free(l_reply_str, false); + } break; + // Token pair control + case CMD_TOKEN_PAIR: { + + // Find and check the network + const char *l_net_str = NULL; + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-net", &l_net_str); + if(!l_net_str) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Command 'token_pair' required parameter -net"); + return -3; + } + dap_chain_net_t *l_net = dap_chain_net_by_name(l_net_str); + if(!l_net) { + dap_chain_node_cli_set_reply_text(a_str_reply, "Network %s not found", l_net_str); + return -4; + } + + + // Select subcommands + + // check for price subcommand + const char * l_price_subcommand = NULL; + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "price", &l_price_subcommand); + + // check for get subcommand + if ( l_price_subcommand ){ + // Check for token1 + const char * l_token1 = NULL; + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-token1", &l_token1); + if(!l_token1){ + dap_chain_node_cli_set_reply_text(a_str_reply,"No argument '-token1'"); + return -5; + } + dap_chain_datum_token_t * l_token1_datum = dap_chain_ledger_token_ticker_check( l_net->pub.ledger, l_token1); + if(!l_token1_datum){ + dap_chain_node_cli_set_reply_text(a_str_reply,"Can't find \"%s\" token in network \"%s\" for argument '-token1' ", l_token1, l_net->pub.name); + return -6; + } + + // Check for token2 + const char * l_token2 = NULL; + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-token2", &l_token2); + if(!l_token2){ + dap_chain_node_cli_set_reply_text(a_str_reply,"No argument '-token2'"); + return -5; + } + dap_chain_datum_token_t * l_token2_datum = dap_chain_ledger_token_ticker_check( l_net->pub.ledger, l_token2); + if(!l_token2_datum){ + dap_chain_node_cli_set_reply_text(a_str_reply,"Can't find \"%s\" token in network \"%s\" for argument '-token2' ", l_token2, l_net->pub.name); + return -6; + } + + // Read time_from + dap_time_t l_time_from = 0; + const char * l_time_from_str = NULL; + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-time_from", &l_time_from_str); + l_time_from = dap_time_from_str_rfc822(l_time_from_str); + + // Read time_to + dap_time_t l_time_to = 0; + const char * l_time_to_str = NULL; + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-time_to", &l_time_to_str); + l_time_to = dap_time_from_str_rfc822(l_time_to_str); + + + // Check for price subcommand + if (strcmp(l_price_subcommand,"average") == 0){ + dap_string_t *l_reply_str = dap_string_new(""); + + dap_list_t *l_tx_cond_list = dap_chain_net_get_tx_cond_all_by_srv_uid(l_net, c_dap_chain_net_srv_xchange_uid, + l_time_from,l_time_to,TX_SEARCH_TYPE_NET ); + dap_list_t * l_cur = l_tx_cond_list; + uint256_t l_total_rates = {0}; + uint256_t l_total_rates_count = {0}; + while(l_cur){ + dap_chain_datum_tx_t * l_tx =(dap_chain_datum_tx_t *) l_cur->data; + if(l_tx){ + dap_hash_fast_t * l_tx_hash = dap_chain_node_datum_tx_calc_hash(l_tx); + + // Get input token ticker + const char * l_tx_input_ticker = dap_chain_ledger_tx_get_token_ticker_by_hash( + l_net->pub.ledger, l_tx_hash); + + DAP_DEL_Z(l_tx_hash); + // Compare with token1 and token2 + if( dap_strcmp(l_tx_input_ticker, l_token1) != 0 && + dap_strcmp(l_tx_input_ticker, l_token2) != 0) + continue; + + dap_chain_tx_out_cond_t *l_out_cond_item = NULL; + int l_item_idx = 0; + do { + l_out_cond_item = (dap_chain_tx_out_cond_t*) dap_chain_datum_tx_item_get(l_tx, &l_item_idx, TX_ITEM_TYPE_OUT_COND, NULL); + l_item_idx++; + if(l_out_cond_item && l_out_cond_item->header.subtype == DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_XCHANGE) { + //const char *token = l_out_cond_item->subtype.srv_xchange.token; + uint256_t value = l_out_cond_item->subtype.srv_xchange.buy_value; + uint256_t l_total_rates_old = l_total_rates; + if(SUM_256_256(value,l_total_rates_old, &l_total_rates )!= 0) + log_it(L_ERROR, "Overflow on avarage price calculation (summing)"); + INCR_256(&l_total_rates_count); + } + } + while(l_out_cond_item); + + } + l_cur = dap_list_next(l_cur); + } + dap_list_free(l_tx_cond_list); + uint256_t l_rate_average = {0}; + if( compare256(l_total_rates_count, uint256_0) != 0 ) + DIV_256(l_total_rates,l_total_rates_count,&l_rate_average); + char *l_rate_average_str = dap_cvt_uint256_to_str(l_rate_average); + dap_string_append_printf(l_reply_str,"Average rate: %s",l_rate_average_str); + DAP_DELETE(l_rate_average_str); + + *a_str_reply = dap_string_free(l_reply_str, false); + break; + }else if (strcmp(l_price_subcommand,"history") == 0){ + dap_string_t *l_reply_str = dap_string_new(""); + + dap_list_t *l_tx_cond_list = dap_chain_net_get_tx_cond_all_by_srv_uid(l_net, c_dap_chain_net_srv_xchange_uid, + l_time_from,l_time_to,TX_SEARCH_TYPE_NET_SPENT ); + dap_list_t * l_cur = l_tx_cond_list; + while(l_cur){ + dap_chain_datum_tx_t * l_tx =(dap_chain_datum_tx_t *) l_cur->data; + if(l_tx){ + dap_hash_fast_t * l_tx_hash = dap_chain_node_datum_tx_calc_hash(l_tx); + + // Get input token ticker + const char * l_tx_input_ticker = dap_chain_ledger_tx_get_token_ticker_by_hash( + l_net->pub.ledger, l_tx_hash); + + // Compare with token1 and token2 + if( dap_strcmp(l_tx_input_ticker, l_token1) != 0 && + dap_strcmp(l_tx_input_ticker, l_token2) != 0) + continue; + + char * l_tx_hash_str = dap_chain_hash_fast_to_str_new(l_tx_hash);\ + + char l_tx_ts_created_str[72] = {0}; + + dap_time_to_str_rfc822(l_tx_ts_created_str,sizeof(l_tx_ts_created_str),l_tx->header.ts_created); + dap_string_append_printf(l_reply_str,"Tx hash: %s\n", l_tx_hash_str); + dap_string_append_printf(l_reply_str,"\tts_created: %s\n", l_tx_ts_created_str); + DAP_DEL_Z(l_tx_hash); + DAP_DEL_Z(l_tx_hash_str); + + // Calc inputs + uint256_t l_tx_input_values = dap_chain_net_get_tx_total_value(l_net, l_tx); + + // Find output + int l_item_idx = 0; + dap_chain_tx_out_cond_t *l_out_cond_item = NULL; + do { + l_out_cond_item = (dap_chain_tx_out_cond_t*) dap_chain_datum_tx_item_get(l_tx, &l_item_idx, TX_ITEM_TYPE_OUT_COND, NULL); + l_item_idx++; + if(l_out_cond_item && l_out_cond_item->header.subtype == DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_XCHANGE) { + //const char *token = l_out_cond_item->subtype.srv_xchange.token; + uint256_t l_value_to = l_out_cond_item->subtype.srv_xchange.buy_value; + char * l_value_from_str = dap_cvt_uint256_to_str(l_tx_input_values); + char * l_value_to_str = dap_cvt_uint256_to_str(l_value_to); + + dap_string_append_printf(l_reply_str, "From: : %s %s ", l_tx_input_ticker,l_value_from_str); + dap_string_append_printf(l_reply_str, "To: %s %s", l_value_to_str, l_out_cond_item->subtype.srv_xchange.buy_token ); + + DAP_DELETE(l_value_from_str); + DAP_DELETE(l_value_to_str); + } + } + while(l_out_cond_item); + + } + // Delimiter between tx + dap_string_append_printf(l_reply_str,"\n\n"); + + l_cur = dap_list_next(l_cur); + } + + dap_list_free(l_tx_cond_list); + + *a_str_reply = dap_string_free(l_reply_str, false); + break; + + }break; + } + + const char * l_list_subcommand = NULL; + dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "list", &l_list_subcommand); + if( l_list_subcommand ){ + if (strcmp(l_list_subcommand,"all") == 0){ + dap_string_t *l_reply_str = dap_string_new(""); + char ** l_tickers = NULL; + size_t l_tickers_count = 0; + dap_chain_ledger_addr_get_token_ticker_all( l_net->pub.ledger,NULL,&l_tickers,&l_tickers_count); + + size_t l_pairs_count = 0; + if(l_tickers){ + for(size_t i = 0; i< l_tickers_count; i++){ + for(size_t j = i+1; j< l_tickers_count; j++){ + if(l_tickers[i] && l_tickers[j]){ + dap_string_append_printf(l_reply_str,"%s:%s ", l_tickers[i], l_tickers[j]); + l_pairs_count++; + } + } + + } + + // Free tickers array + for(size_t i = 0; i< l_tickers_count; i++){ + DAP_DELETE(l_tickers[i]); + } + DAP_DELETE(l_tickers); + } + dap_string_prepend_printf( l_reply_str,"Tokens count pair: %zd\n", l_pairs_count); + *a_str_reply = dap_string_free(l_reply_str, false); + break; + } + } + + // No subcommand selected + dap_chain_node_cli_set_reply_text(a_str_reply,"Command 'token pair' required proper subcommand, please read its manual with command 'help srv_xchange'"); + + + } break; + + default: { + dap_chain_node_cli_set_reply_text(a_str_reply, "Command %s not recognized", a_argv[l_arg_index]); + return -1; + } + } + return 0; +} + +static int s_callback_requested(dap_chain_net_srv_t *a_srv, uint32_t a_usage_id, dap_chain_net_srv_client_remote_t *a_srv_client, const void *a_data, size_t a_data_size) +{ + return 0; +} + +static int s_callback_response_success(dap_chain_net_srv_t *a_srv, uint32_t a_usage_id, dap_chain_net_srv_client_remote_t *a_srv_client, const void *a_data, size_t a_data_size) +{ + return 0; +} + +static int s_callback_response_error(dap_chain_net_srv_t *a_srv, uint32_t a_usage_id, dap_chain_net_srv_client_remote_t *a_srv_client, const void *a_data, size_t a_data_size) +{ + return 0; +} + +static int s_callback_receipt_next_success(dap_chain_net_srv_t *a_srv, uint32_t a_usage_id, dap_chain_net_srv_client_remote_t *a_srv_client, const void *a_data, size_t a_data_size) +{ + return 0; +}