From 7bafd2866c70db92831f693a88ba1f0972d07c0a Mon Sep 17 00:00:00 2001 From: Some Random Crypto Guy Date: Wed, 27 Mar 2024 14:50:31 +0000 Subject: [PATCH] a number of fixes; disabled return_payment as the functionality is changing to NOT require returning the same output --- src/cryptonote_config.h | 16 +- src/cryptonote_core/blockchain.cpp | 5 +- src/cryptonote_core/cryptonote_tx_utils.cpp | 76 +++++-- src/cryptonote_core/cryptonote_tx_utils.h | 10 +- src/simplewallet/simplewallet.cpp | 232 ++++++++++++++++++-- 5 files changed, 303 insertions(+), 36 deletions(-) diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 82dfc5ecc..8c6872410 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -264,6 +264,8 @@ namespace config uint32_t const GENESIS_NONCE = 10000; const uint64_t YIELD_LOCK_PERIOD = 5;//30*24*30; + + std::string const TREASURY_ADDRESS = "fuLMowH85abK8nz9BBMEem7MAfUbQu4aSHHUV9j5Z86o6Go9Lv2U5ZQiJCWPY9R9HA8p5idburazjAhCqDngLo7fYPCD9ciM9ee1A"; // Hash domain separators const char HASH_KEY_BULLETPROOF_EXPONENT[] = "bulletproof"; @@ -342,6 +344,8 @@ namespace config "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5YBxWx1AZCA9jTUk8Pr2uZ9jpfRt\n" "KWv3Vo1/Gny+1vfaxsXhBQiG1KlHkafNGarzoL0WHW4ocqaaqF5iv8i35A==\n" "-----END PUBLIC KEY-----\n"; + + std::string const TREASURY_ADDRESS = "fuLMowH85abK8nz9BBMEem7MAfUbQu4aSHHUV9j5Z86o6Go9Lv2U5ZQiJCWPY9R9HA8p5idburazjAhCqDngLo7fYPCD9ciM9ee1A"; } namespace stagenet @@ -366,6 +370,8 @@ namespace config "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5YBxWx1AZCA9jTUk8Pr2uZ9jpfRt\n" "KWv3Vo1/Gny+1vfaxsXhBQiG1KlHkafNGarzoL0WHW4ocqaaqF5iv8i35A==\n" "-----END PUBLIC KEY-----\n"; + + std::string const TREASURY_ADDRESS = "fuLMowH85abK8nz9BBMEem7MAfUbQu4aSHHUV9j5Z86o6Go9Lv2U5ZQiJCWPY9R9HA8p5idburazjAhCqDngLo7fYPCD9ciM9ee1A"; } } @@ -393,6 +399,7 @@ namespace cryptonote std::array const ORACLE_URLS; std::string const ORACLE_PUBLIC_KEY; uint64_t YIELD_LOCK_PERIOD; + std::string TREASURY_ADDRESS; }; inline const config_t& get_config(network_type nettype) { @@ -408,7 +415,8 @@ namespace cryptonote ::config::GENESIS_NONCE, ::config::ORACLE_URLS, ::config::ORACLE_PUBLIC_KEY, - ::config::YIELD_LOCK_PERIOD + ::config::YIELD_LOCK_PERIOD, + ::config::TREASURY_ADDRESS }; static const config_t testnet = { ::config::testnet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX, @@ -422,7 +430,8 @@ namespace cryptonote ::config::testnet::GENESIS_NONCE, ::config::testnet::ORACLE_URLS, ::config::testnet::ORACLE_PUBLIC_KEY, - ::config::testnet::YIELD_LOCK_PERIOD + ::config::testnet::YIELD_LOCK_PERIOD, + ::config::testnet::TREASURY_ADDRESS }; static const config_t stagenet = { ::config::stagenet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX, @@ -436,7 +445,8 @@ namespace cryptonote ::config::stagenet::GENESIS_NONCE, ::config::stagenet::ORACLE_URLS, ::config::stagenet::ORACLE_PUBLIC_KEY, - ::config::stagenet::YIELD_LOCK_PERIOD + ::config::stagenet::YIELD_LOCK_PERIOD, + ::config::stagenet::TREASURY_ADDRESS }; switch (nettype) { diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 4eff4efce..765135d09 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1898,7 +1898,10 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, // Time to construct the protocol_tx uint64_t protocol_fee = 0; - ok = construct_protocol_tx(height, protocol_fee, b.protocol_tx, protocol_entries, circ_supply, pr, b.major_version); + address_parse_info treasury_address_info; + ok = cryptonote::get_account_address_from_str(treasury_address_info, m_nettype, get_config(m_nettype).TREASURY_ADDRESS); + CHECK_AND_ASSERT_MES(ok, false, "Failed to obtain treasury address info"); + ok = construct_protocol_tx(height, protocol_fee, b.protocol_tx, protocol_entries, circ_supply, pr, miner_address, treasury_address_info.address, b.major_version); CHECK_AND_ASSERT_MES(ok, false, "Failed to construct protocol tx"); pool_cookie = m_tx_pool.cookie(); diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index ca025bc0f..34a545dc9 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -197,18 +197,12 @@ namespace cryptonote // Apply slippage to the burnt amount amount_slippage = amount_burnt >> 5; // (1/32) - if (hf_version >= HF_VERSION_SLIPPAGE_YIELD) { - - // Apply slippage to the burnt amount - amount_slippage = amount_burnt >> 5; // (1/32) - - // Check that the slippage is acceptable - if (amount_slippage > amount_slippage_limit) { - // Bail out with no conversion - LOG_PRINT_L1("Unable to convert - slippage limit was too low"); - amount_minted = 0; - return true; - } + // Check that the slippage is acceptable + if (amount_slippage > amount_slippage_limit) { + // Bail out with no conversion + LOG_PRINT_L1("Unable to convert - slippage limit was too low"); + amount_minted = 0; + return true; } // Work out the converted amount @@ -224,11 +218,13 @@ namespace cryptonote std::vector& protocol_data, std::map circ_supply, const oracle::pricing_record& pr, + const account_public_address &miner_address, + const account_public_address &treasury_address, const uint8_t hf_version) { // A vector to contain all of the additional _tx_secret_keys_ //std::vector& additional_tx_keys; - + // Clear the TX contents tx.set_null(); tx.type = cryptonote::transaction_type::PROTOCOL; @@ -255,6 +251,7 @@ namespace cryptonote // Calculate the slippage for the output amounts LOG_PRINT_L2("Creating protocol_tx..."); + uint64_t slippage_total = 0; std::vector additional_tx_public_keys; for (auto const& entry: protocol_data) { if (entry.destination_asset == "BURN") { @@ -337,6 +334,19 @@ namespace cryptonote // CONVERTED LOG_PRINT_L2("Conversion TX submitted - converted " << print_money(entry.amount_burnt) << " " << entry.source_asset << " to " << print_money(amount_minted) << " " << entry.destination_asset << "(slippage " << print_money(amount_slippage) << ")"); txin_gen_totals[entry.destination_asset] += amount_minted; + + // Add the slippage to our total for the block + if (entry.source_asset == "FULM") { + slippage_total += amount_slippage; + } else { + // Convert the slippage into a FULM amount so we can pay a proportion to the miner + uint64_t conversion_rate = 0, amount_slippage_converted = 0; + ok = get_conversion_rate(pr, entry.source_asset, entry.destination_asset, conversion_rate); + CHECK_AND_ASSERT_MES(ok, false, "Failed to get conversion rate for miner payout"); + ok = get_converted_amount(conversion_rate, amount_slippage, amount_slippage_converted); + CHECK_AND_ASSERT_MES(ok, false, "Failed to get converted slippage amount for miner payout"); + slippage_total += amount_slippage_converted; + } // Create the TX output for this conversion tx_out out; @@ -359,9 +369,46 @@ namespace cryptonote tx.vout.push_back(out); } } - + // Add in all of the additional TX pubkeys we need to process the payments add_additional_tx_pub_keys_to_extra(tx.extra, additional_tx_public_keys); + + if (slippage_total > 0) { + + // Add a payout for the miner + uint64_t slippage_miner = slippage_total / 5; + + crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); + crypto::public_key out_eph_public_key = AUTO_VAL_INIT(out_eph_public_key); + bool r = crypto::generate_key_derivation(miner_address.m_view_public_key, txkey.sec, derivation); + CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to generate_key_derivation(" << miner_address.m_view_public_key << ", " << txkey.sec << ")"); + + // Calculate the uniqueness + crypto::key_image k_image; + ec_scalar uniqueness; + CHECK_AND_ASSERT_MES(calculate_uniqueness(tx.type, k_image, height, ((size_t)-1), uniqueness), false, "while constructing miner_tx: failed to calculate uniqueness"); + + r = crypto::derive_public_key(derivation, uniqueness, miner_address.m_spend_public_key, out_eph_public_key); + CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to derive_public_key(" << derivation << ", " << 0 << ", "<< miner_address.m_spend_public_key << ")"); + + tx_out out_miner; + cryptonote::set_tx_out(slippage_miner, "FULM", 0, out_eph_public_key, false, crypto::view_tag{}, out_miner); + tx.vout.push_back(out_miner); + + // Add a payout for the treasury + crypto::key_derivation derivation_treasury = AUTO_VAL_INIT(derivation_treasury); + crypto::public_key out_eph_public_key_treasury = AUTO_VAL_INIT(out_eph_public_key_treasury); + r = crypto::generate_key_derivation(treasury_address.m_view_public_key, txkey.sec, derivation_treasury); + CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to generate_key_derivation(" << treasury_address.m_view_public_key << ", " << txkey.sec << ")"); + + r = crypto::derive_public_key(derivation_treasury, uniqueness, treasury_address.m_spend_public_key, out_eph_public_key_treasury); + CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to derive_public_key(" << derivation << ", " << 0 << ", "<< miner_address.m_spend_public_key << ")"); + + uint64_t slippage_treasury = slippage_miner >> 1; + tx_out out_treasury; + cryptonote::set_tx_out(slippage_treasury, "FULM", 0, out_eph_public_key_treasury, false, crypto::view_tag{}, out_treasury); + tx.vout.push_back(out_treasury); + } // Create the txin_gen now txin_gen in; @@ -930,6 +977,7 @@ namespace cryptonote // Do not create outputs that are for the destination asset type - discard them as unused if (dst_entr.asset_type == dest_asset) { tx.amount_burnt += dst_entr.amount; + tx.amount_slippage_limit = dst_entr.slippage_limit; continue; } } else if (tx_type == cryptonote::transaction_type::YIELD) { diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index 462db65b4..41e8ffbc8 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -65,7 +65,7 @@ namespace cryptonote crypto::public_key return_pubkey; }; - bool construct_protocol_tx(const size_t height, uint64_t& protocol_fee, transaction& tx, std::vector& protocol_data, std::map circ_supply, const oracle::pricing_record& pr, const uint8_t hf_version); + bool construct_protocol_tx(const size_t height, uint64_t& protocol_fee, transaction& tx, std::vector& protocol_data, std::map circ_supply, const oracle::pricing_record& pr, const account_public_address &miner_address, const account_public_address &treasury_address, const uint8_t hf_version); //--------------------------------------------------------------- bool construct_miner_tx(size_t height, size_t median_weight, uint64_t already_generated_coins, size_t current_block_weight, uint64_t fee, const account_public_address &miner_address, transaction& tx, const blobdata& extra_nonce = blobdata(), size_t max_outs = 999, uint8_t hard_fork_version = 1); bool construct_protocol_tx(size_t height, transaction& tx, size_t max_outs = 999, uint8_t hard_fork_version = 1); @@ -111,15 +111,16 @@ namespace cryptonote { std::string original; uint64_t amount; //money + uint64_t slippage_limit; //percentage of slippage permitted account_public_address addr; //destination address std::string asset_type; bool is_subaddress; bool is_integrated; bool is_change; - tx_destination_entry() : amount(0), addr(AUTO_VAL_INIT(addr)), asset_type("FULM"), is_subaddress(false), is_integrated(false), is_change(false) { } - tx_destination_entry(uint64_t a, const account_public_address &ad, bool is_subaddress) : amount(a), addr(ad), is_subaddress(is_subaddress), is_integrated(false), is_change(false) { } - tx_destination_entry(const std::string &o, uint64_t a, const account_public_address &ad, bool is_subaddress) : original(o), amount(a), addr(ad), is_subaddress(is_subaddress), is_integrated(false) { } + tx_destination_entry() : amount(0), slippage_limit(0), addr(AUTO_VAL_INIT(addr)), asset_type("FULM"), is_subaddress(false), is_integrated(false), is_change(false) { } + tx_destination_entry(uint64_t a, const account_public_address &ad, bool is_subaddress) : amount(a), slippage_limit(0), addr(ad), is_subaddress(is_subaddress), is_integrated(false), is_change(false) { } + tx_destination_entry(const std::string &o, uint64_t a, const account_public_address &ad, bool is_subaddress) : original(o), amount(a), slippage_limit(0), addr(ad), is_subaddress(is_subaddress), is_integrated(false) { } std::string address(network_type nettype, const crypto::hash &payment_id) const { @@ -139,6 +140,7 @@ namespace cryptonote BEGIN_SERIALIZE_OBJECT() FIELD(original) VARINT_FIELD(amount) + VARINT_FIELD(slippage_limit) FIELD(addr) FIELD(asset_type) FIELD(is_subaddress) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 0fd688b71..19fcb5901 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -6707,6 +6707,7 @@ bool simple_wallet::transfer_main( vector dsts_info; vector dsts; + double slippage_limit = 0.0; for (size_t i = 0; i < local_args.size(); ) { dsts_info.emplace_back(); @@ -6737,15 +6738,27 @@ bool simple_wallet::transfer_main( } else if (i + 1 < local_args.size()) { - r = cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), local_args[i], oa_prompter); - bool ok = cryptonote::parse_amount(de.amount, local_args[i + 1]); - if(!ok || 0 == de.amount) - { - fail_msg_writer() << tr("amount is wrong: ") << local_args[i] << ' ' << local_args[i + 1] << - ", " << tr("expected number from 0 to ") << print_money(std::numeric_limits::max()); - return false; + if (transfer_type == Convert) { + bool ok = cryptonote::parse_amount(de.amount, local_args[i]); + if(!ok || 0 == de.amount) + { + fail_msg_writer() << tr("amount is wrong: ") << local_args[i] << ", " << tr("expected number from 0 to ") << print_money(std::numeric_limits::max()); + return false; + } + double slippage_limit = std::stod(local_args[i+1]); // Already validated in simple_wallet::convert() + de.slippage_limit = ((uint64_t)((double)de.amount * slippage_limit)) / 100; + } else { + r = cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), local_args[i], oa_prompter); + bool ok = cryptonote::parse_amount(de.amount, local_args[i + 1]); + if(!ok || 0 == de.amount) + { + fail_msg_writer() << tr("amount is wrong: ") << local_args[i] << ' ' << local_args[i + 1] << + ", " << tr("expected number from 0 to ") << print_money(std::numeric_limits::max()); + return false; + } + de.original = local_args[i]; + de.slippage_limit = 0; } - de.original = local_args[i]; i += 2; } else @@ -6759,6 +6772,7 @@ bool simple_wallet::transfer_main( return false; } de.asset_type = source_asset; + de.slippage_limit = 0; ++i; } else { if (boost::starts_with(local_args[i], "fulmo:")) @@ -7871,6 +7885,9 @@ bool simple_wallet::sweep_below(const std::vector &args_) //---------------------------------------------------------------------------------------------------- bool simple_wallet::return_payment(const std::vector &args_) { + if (!try_connect_to_daemon()) + return true; + // TODO: add locked versions if (args_.size() != 1) { @@ -7891,11 +7908,13 @@ bool simple_wallet::return_payment(const std::vector &args_) // Get the TX details tools::wallet2::transfer_container transfers; + crypto::key_image ki; + bool found_ki = false; m_wallet->get_transfers(transfers); for (const auto& td: transfers) { // Skip entries we don't care about if (td.m_txid != txid) continue; - + // Found the specified entry - make sure we can return it if (td.m_tx.type != cryptonote::transaction_type::TRANSFER) { fail_msg_writer() << tr("incorrect TX type for txid ") << local_args[0]; @@ -7908,9 +7927,158 @@ bool simple_wallet::return_payment(const std::vector &args_) return true; } - // Create the destination address...somehow - //construct_tx_with_tx_key( + // Check that we have the key image information, and that it is usable + if (!td.m_key_image_known || td.m_key_image_partial || td.m_spent || td.m_frozen) { + fail_msg_writer() << tr("key image is unavailable (partial / unknown / spent / frozen) for txid ") << local_args[0]; + return true; + } + + ki = td.m_key_image; + found_ki = true; } + + // Check we have a valid key_image + if (!found_ki) { + fail_msg_writer() << tr("key image is unavailable (partial / unknown / spent / frozen) for txid ") << local_args[0]; + return true; + } + + // This is nonsense, really - priority isn't really a problem for Fulmo + uint32_t priority = 0; + priority = m_wallet->adjust_priority(priority); + + // This should be fixed by the protocol - any changes would stand out like a sore thumb + size_t fake_outs_count = m_wallet->get_min_ring_size() - 1; + uint64_t adjusted_fake_outs_count = m_wallet->adjust_mixin(fake_outs_count); + if (adjusted_fake_outs_count > fake_outs_count) + { + fail_msg_writer() << (boost::format(tr("ring size %u is too small, minimum is %u")) % (fake_outs_count+1) % (adjusted_fake_outs_count+1)).str(); + return true; + } + if (adjusted_fake_outs_count < fake_outs_count) + { + fail_msg_writer() << (boost::format(tr("ring size %u is too large, maximum is %u")) % (fake_outs_count+1) % (adjusted_fake_outs_count+1)).str(); + return true; + } + + // We will only use one output - the one we are returning - to pay for the transaction fully + size_t outputs = 1; + + std::vector extra; + + SCOPED_WALLET_UNLOCK(); + /* + try + { + // figure out what tx will be necessary + auto ptx_vector = m_wallet->create_transactions_single(ki, info.address, info.is_subaddress, outputs, fake_outs_count, 0, priority, extra); + + if (ptx_vector.empty()) + { + fail_msg_writer() << tr("No outputs found"); + return true; + } + if (ptx_vector.size() > 1) + { + fail_msg_writer() << tr("Multiple transactions are created, which is not supposed to happen"); + return true; + } + if (ptx_vector[0].selected_transfers.size() != 1) + { + fail_msg_writer() << tr("The transaction uses multiple or no inputs, which is not supposed to happen"); + return true; + } + + // give user total and fee, and prompt to confirm + uint64_t total_fee = ptx_vector[0].fee; + uint64_t total_sent = m_wallet->get_transfer_details(ptx_vector[0].selected_transfers.front()).amount(); + std::ostringstream prompt; + if (!process_ring_members(ptx_vector, prompt, m_wallet->print_ring_members())) + return true; + prompt << boost::format(tr("Sweeping %s for a total fee of %s. Is this okay?")) % + print_money(total_sent) % + print_money(total_fee); + std::string accepted = input_line(prompt.str(), true); + if (std::cin.eof()) + return true; + if (!command_line::is_yes(accepted)) + { + fail_msg_writer() << tr("transaction cancelled."); + return true; + } + + // actually commit the transactions + if (m_wallet->multisig()) + { + CHECK_MULTISIG_ENABLED(); + bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); + if (!r) + { + fail_msg_writer() << tr("Failed to write transaction(s) to file"); + } + else + { + success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "multisig_monero_tx"; + } + } + else if (m_wallet->get_account().get_device().has_tx_cold_sign()) + { + try + { + tools::wallet2::signed_tx_set signed_tx; + std::vector dsts_info; + dsts_info.push_back(info); + + if (!cold_sign_tx(ptx_vector, signed_tx, dsts_info, [&](const tools::wallet2::signed_tx_set &tx){ return accept_loaded_tx(tx); })){ + fail_msg_writer() << tr("Failed to cold sign transaction with HW wallet"); + return true; + } + + commit_or_save(signed_tx.ptx, m_do_not_relay); + success_msg_writer(true) << tr("Money successfully sent, transaction: ") << get_transaction_hash(ptx_vector[0].tx); + } + catch (const std::exception& e) + { + handle_transfer_exception(std::current_exception(), m_wallet->is_trusted_daemon()); + } + catch (...) + { + LOG_ERROR("Unknown error"); + fail_msg_writer() << tr("unknown error"); + } + } + else if (m_wallet->watch_only()) + { + bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx"); + if (!r) + { + fail_msg_writer() << tr("Failed to write transaction(s) to file"); + } + else + { + success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "unsigned_monero_tx"; + } + } + else + { + m_wallet->commit_tx(ptx_vector[0]); + success_msg_writer(true) << tr("Money successfully sent, transaction: ") << get_transaction_hash(ptx_vector[0].tx); + } + + } + catch (const std::exception& e) + { + handle_transfer_exception(std::current_exception(), m_wallet->is_trusted_daemon()); + } + catch (...) + { + LOG_ERROR("unknown error"); + fail_msg_writer() << tr("unknown error"); + } + */ + + fail_msg_writer() << tr("return_payment() is not implemented"); + return true; fail_msg_writer() << tr("failed to locate txid ") << local_args[0]; return true; @@ -7947,18 +8115,49 @@ bool simple_wallet::burn(const std::vector &args_) bool simple_wallet::convert(const std::vector &args_) { // TODO: add locked versions - if (args_.size() != 3) + if (args_.size() not_eq 3 and args_.size() not_eq 4) { fail_msg_writer() << tr("missing / extraneous argument(s)"); PRINT_USAGE(USAGE_CONVERT); return true; } - + std::vector local_args = args_; + std::string strLastArg = local_args.back(); + + // Check to see if the last arg is "slippage_limit" (a decimal number) + double slippage_limit = 0.0; + if (args_.size() == 4) { + // Expect to be able to parse the last arg as a double + try + { + slippage_limit = std::stod(strLastArg); + if (slippage_limit < 0.0 or slippage_limit > 90.0) { + fail_msg_writer() << tr("invalid slippage_limit value : expected 0.0-90.0, got ") << slippage_limit; + PRINT_USAGE(USAGE_CONVERT); + return true; + } + + // Update the last arg value + local_args.pop_back(); + strLastArg = local_args.back(); + } + catch (std::invalid_argument const& ex) + { + fail_msg_writer() << tr("invalid argument(s): ") << ex.what(); + PRINT_USAGE(USAGE_CONVERT); + return true; + } + catch (std::out_of_range const& ex) + { + fail_msg_writer() << tr("out of range for slippage limit: ") << ex.what(); + PRINT_USAGE(USAGE_CONVERT); + return true; + } + } // Get the destination asset type std::string source_asset, dest_asset; - std::string strLastArg = local_args.back(); std::transform(strLastArg.begin(), strLastArg.end(), strLastArg.begin(), ::toupper); if (strLastArg not_eq "FULM" and strLastArg not_eq "FUSD") { fail_msg_writer() << tr("invalid destination asset_type"); @@ -7984,6 +8183,11 @@ bool simple_wallet::convert(const std::vector &args_) PRINT_USAGE(USAGE_CONVERT); return true; } + + // Check for slippage_limit again + if (slippage_limit != 0.0) { + local_args.push_back(std::to_string(slippage_limit)); + } transfer_main(Convert, source_asset, dest_asset, local_args, false); return true;