mirror of https://github.com/oxen-io/oxen-core.git
Merge pull request #1530 from jagerman/prepare-reg-disp-fix
Prepare registration fixes and improvements
This commit is contained in:
commit
9a6c4a93a0
|
@ -353,69 +353,54 @@ namespace cryptonote
|
|||
return true;
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
uint64_t power_integral(uint64_t a, uint64_t b)
|
||||
{
|
||||
if(b == 0)
|
||||
return 1;
|
||||
uint64_t total = a;
|
||||
for(uint64_t i = 1; i != b; i++)
|
||||
total *= a;
|
||||
return total;
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
bool parse_amount(uint64_t& amount, std::string_view str_amount)
|
||||
std::optional<uint64_t> parse_amount(std::string_view str_amount)
|
||||
{
|
||||
uint64_t amount;
|
||||
tools::trim(str_amount);
|
||||
|
||||
auto parts = tools::split(str_amount, "."sv);
|
||||
if (parts.size() > 2)
|
||||
return false; // 123.456.789 no thanks.
|
||||
return std::nullopt; // 123.456.789 no thanks.
|
||||
|
||||
if (parts.size() == 2 && parts[1].empty())
|
||||
parts.pop_back(); // allow "123." (treat it as as "123")
|
||||
|
||||
if (parts[0].find_first_not_of("0123456789"sv) != std::string::npos)
|
||||
return false; // whole part contains non-digit
|
||||
return std::nullopt; // whole part contains non-digit
|
||||
|
||||
if (parts[0].empty()) {
|
||||
// Only allow an empty whole number part if there is a fractional part.
|
||||
if (parts.size() == 1)
|
||||
return false;
|
||||
return std::nullopt;
|
||||
amount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!tools::parse_int(parts[0], amount))
|
||||
return false;
|
||||
return std::nullopt;
|
||||
|
||||
// Scale up the number (e.g. 12 from "12.45") to atomic units.
|
||||
//
|
||||
// TODO: get rid of the user-configurable default_decimal_point nonsense and just multiply
|
||||
// this value by the `COIN` constant.
|
||||
for (size_t i = 0; i < CRYPTONOTE_DISPLAY_DECIMAL_POINT; i++)
|
||||
{
|
||||
if (amount > std::numeric_limits<uint64_t>::max() / 10)
|
||||
return false; // would overflow
|
||||
amount *= 10;
|
||||
}
|
||||
if (amount > std::numeric_limits<uint64_t>::max() / COIN)
|
||||
return std::nullopt; // would overflow
|
||||
amount *= COIN;
|
||||
}
|
||||
|
||||
if (parts.size() == 1)
|
||||
return true;
|
||||
return amount;
|
||||
|
||||
if (parts[1].find_first_not_of("0123456789"sv) != std::string::npos)
|
||||
return false; // fractional part contains non-digit
|
||||
return std::nullopt; // fractional part contains non-digit
|
||||
|
||||
// If too long, but with insignificant 0's, trim them off
|
||||
while (parts[1].size() > CRYPTONOTE_DISPLAY_DECIMAL_POINT && parts[1].back() == '0')
|
||||
parts[1].remove_suffix(1);
|
||||
|
||||
if (parts[1].size() > CRYPTONOTE_DISPLAY_DECIMAL_POINT)
|
||||
return false; // fractional part has too many significant digits
|
||||
return std::nullopt; // fractional part has too many significant digits
|
||||
|
||||
uint64_t fractional;
|
||||
if (!tools::parse_int(parts[1], fractional))
|
||||
return false;
|
||||
return std::nullopt;
|
||||
|
||||
// Scale up the value if it wasn't a full fractional value, e.g. if we have "10.45" then we
|
||||
// need to convert the 45 we just parsed to 450'000'000.
|
||||
|
@ -423,10 +408,10 @@ namespace cryptonote
|
|||
fractional *= 10;
|
||||
|
||||
if (fractional > std::numeric_limits<uint64_t>::max() - amount)
|
||||
return false; // would overflow
|
||||
return std::nullopt; // would overflow
|
||||
|
||||
amount += fractional;
|
||||
return true;
|
||||
return amount;
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
uint64_t get_transaction_weight(const transaction &tx, size_t blob_size)
|
||||
|
@ -1047,39 +1032,33 @@ namespace cryptonote
|
|||
cn_fast_hash(blob.data(), blob.size(), res);
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
std::string get_unit(unsigned int decimal_point)
|
||||
std::string print_money(uint64_t amount, bool strip_zeros)
|
||||
{
|
||||
if (decimal_point == (unsigned int)-1)
|
||||
decimal_point = CRYPTONOTE_DISPLAY_DECIMAL_POINT;
|
||||
switch (decimal_point)
|
||||
{
|
||||
case 9:
|
||||
return "oxen";
|
||||
case 6:
|
||||
return "megarok";
|
||||
case 3:
|
||||
return "kilorok";
|
||||
case 0:
|
||||
return "rok";
|
||||
default:
|
||||
ASSERT_MES_AND_THROW("Invalid decimal point specification: " << decimal_point);
|
||||
}
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
std::string print_money(uint64_t amount, unsigned int decimal_point)
|
||||
{
|
||||
if (decimal_point == (unsigned int)-1)
|
||||
decimal_point = CRYPTONOTE_DISPLAY_DECIMAL_POINT;
|
||||
constexpr unsigned int decimal_point = CRYPTONOTE_DISPLAY_DECIMAL_POINT;
|
||||
std::string s = std::to_string(amount);
|
||||
if(s.size() < decimal_point+1)
|
||||
{
|
||||
s.insert(0, decimal_point+1 - s.size(), '0');
|
||||
}
|
||||
if (decimal_point > 0)
|
||||
s.insert(s.size() - decimal_point, ".");
|
||||
s.insert(s.size() - decimal_point, ".");
|
||||
if (strip_zeros)
|
||||
{
|
||||
while (s.back() == '0')
|
||||
s.pop_back();
|
||||
if (s.back() == '.')
|
||||
s.pop_back();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
std::string format_money(uint64_t amount, bool strip_zeros)
|
||||
{
|
||||
auto value = print_money(amount, strip_zeros);
|
||||
value += ' ';
|
||||
value += get_unit();
|
||||
return value;
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
std::string print_tx_verification_context(tx_verification_context const &tvc, transaction const *tx)
|
||||
{
|
||||
std::ostringstream os;
|
||||
|
|
|
@ -202,7 +202,7 @@ namespace cryptonote
|
|||
uint64_t get_outs_money_amount(const transaction& tx);
|
||||
bool check_inputs_types_supported(const transaction& tx);
|
||||
bool check_outs_valid(const transaction& tx);
|
||||
bool parse_amount(uint64_t& amount, std::string_view str_amount);
|
||||
std::optional<uint64_t> parse_amount(std::string_view str_amount);
|
||||
uint64_t get_transaction_weight(const transaction &tx);
|
||||
uint64_t get_transaction_weight(const transaction &tx, size_t blob_size);
|
||||
uint64_t get_pruned_transaction_weight(const transaction &tx);
|
||||
|
@ -213,8 +213,12 @@ namespace cryptonote
|
|||
uint64_t get_block_height(const block& b);
|
||||
std::vector<uint64_t> relative_output_offsets_to_absolute(const std::vector<uint64_t>& off);
|
||||
std::vector<uint64_t> absolute_output_offsets_to_relative(const std::vector<uint64_t>& off);
|
||||
std::string get_unit(unsigned int decimal_point = -1);
|
||||
std::string print_money(uint64_t amount, unsigned int decimal_point = -1);
|
||||
constexpr std::string_view get_unit() { return "OXEN"sv; }
|
||||
// Returns a monetary value with a decimal point; optionally strips insignificant trailing 0s.
|
||||
std::string print_money(uint64_t amount, bool strip_zeros = false);
|
||||
// Returns a formatted monetary value including the unit, e.g. "1.234567 OXEN"; strips
|
||||
// insignificant trailing 0s by default (unlike the above) but can be overridden to not do that.
|
||||
std::string format_money(uint64_t amount, bool strip_zeros = true);
|
||||
|
||||
std::string print_tx_verification_context (tx_verification_context const &tvc, transaction const *tx = nullptr);
|
||||
std::string print_vote_verification_context(vote_verification_context const &vvc, service_nodes::quorum_vote_t const *vote = nullptr);
|
||||
|
|
|
@ -61,21 +61,20 @@ namespace daemonize {
|
|||
namespace {
|
||||
enum class input_line_result { yes, no, cancel, back, };
|
||||
|
||||
std::string input_line(std::string const &prompt)
|
||||
template <typename... Args>
|
||||
std::string input_line(Args&&... prompt)
|
||||
{
|
||||
std::cout << prompt << std::flush;
|
||||
std::string result;
|
||||
rdln::suspend_readline pause_readline;
|
||||
(std::cout << ... << prompt) << std::flush;
|
||||
std::string result;
|
||||
std::cin >> result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
input_line_result input_line_yes_no_back_cancel(char const *msg)
|
||||
input_line_result input_line_ask(std::string_view msg)
|
||||
{
|
||||
std::string prompt = std::string(msg);
|
||||
prompt += " (Y/Yes/N/No/B/Back/C/Cancel): ";
|
||||
std::string input = input_line(prompt);
|
||||
auto input = input_line(msg, " (Y/Yes/N/No/B/Back/C/Cancel): ");
|
||||
|
||||
if (command_line::is_yes(input)) return input_line_result::yes;
|
||||
if (command_line::is_no(input)) return input_line_result::no;
|
||||
|
@ -83,27 +82,20 @@ namespace {
|
|||
return input_line_result::cancel;
|
||||
}
|
||||
|
||||
input_line_result input_line_yes_no_cancel(char const *msg)
|
||||
std::pair<input_line_result, std::string> input_line_value(std::string_view msg, bool back = true)
|
||||
{
|
||||
std::string prompt = msg;
|
||||
prompt += " (Y/Yes/N/No/C/Cancel): ";
|
||||
std::string input = input_line(prompt);
|
||||
std::string_view end = ""sv;
|
||||
if (msg.back() == '\n') {
|
||||
end = "\n"sv;
|
||||
msg.remove_suffix(1);
|
||||
}
|
||||
auto input = input_line(msg, back ? " (B/Back/C/Cancel): " : " (C/Cancel): ", end);
|
||||
|
||||
if (command_line::is_yes(input)) return input_line_result::yes;
|
||||
if (command_line::is_no(input)) return input_line_result::no;
|
||||
return input_line_result::cancel;
|
||||
}
|
||||
|
||||
|
||||
input_line_result input_line_back_cancel_get_input(char const *msg, std::string &input)
|
||||
{
|
||||
std::string prompt = msg;
|
||||
prompt += " (B/Back/C/Cancel): ";
|
||||
input = input_line(prompt);
|
||||
|
||||
if (command_line::is_back(input)) return input_line_result::back;
|
||||
if (command_line::is_cancel(input)) return input_line_result::cancel;
|
||||
return input_line_result::yes;
|
||||
return {
|
||||
back && command_line::is_back(input) ? input_line_result::back :
|
||||
command_line::is_cancel(input) ? input_line_result::cancel :
|
||||
input_line_result::yes,
|
||||
input};
|
||||
}
|
||||
|
||||
void print_peer(std::string const & prefix, GET_PEER_LIST::peer const & peer, bool pruned_only, bool publicrpc_only)
|
||||
|
@ -186,15 +178,11 @@ namespace {
|
|||
|
||||
std::string get_time_hms(time_t t)
|
||||
{
|
||||
unsigned int hours, minutes, seconds;
|
||||
char buffer[24];
|
||||
hours = t / 3600;
|
||||
unsigned int hours = t / 3600;
|
||||
t %= 3600;
|
||||
minutes = t / 60;
|
||||
t %= 60;
|
||||
seconds = t;
|
||||
snprintf(buffer, sizeof(buffer), "%02u:%02u:%02u", hours, minutes, seconds);
|
||||
return std::string(buffer);
|
||||
unsigned int minutes = t / 60;
|
||||
unsigned int seconds = t % 60;
|
||||
return fmt::format("{:02}:{:02}:{:02}", hours, minutes, seconds);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -605,7 +593,7 @@ bool rpc_command_executor::mining_status() {
|
|||
if (!mining_busy && mres.active && mres.speed > 0 && mres.block_target > 0 && mres.difficulty > 0)
|
||||
{
|
||||
uint64_t daily = 86400 / (double)mres.difficulty * mres.speed * mres.block_reward;
|
||||
tools::msg_writer() << "Expected: " << cryptonote::print_money(daily) << " OXEN daily, " << cryptonote::print_money(7*daily) << " weekly";
|
||||
tools::msg_writer() << "Expected: " << cryptonote::format_money(daily) << " daily, " << cryptonote::format_money(7*daily) << " weekly";
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -1899,18 +1887,6 @@ bool rpc_command_executor::print_sn_key()
|
|||
return true;
|
||||
}
|
||||
|
||||
// Returns lowest x such that (STAKING_PORTIONS * x/amount) >= portions
|
||||
static uint64_t get_amount_to_make_portions(uint64_t amount, uint64_t portions)
|
||||
{
|
||||
uint64_t lo, hi, resulthi, resultlo;
|
||||
lo = mul128(amount, portions, &hi);
|
||||
if (lo > UINT64_MAX - (STAKING_PORTIONS - 1))
|
||||
hi++;
|
||||
lo += STAKING_PORTIONS-1;
|
||||
div128_64(hi, lo, STAKING_PORTIONS, &resulthi, &resultlo);
|
||||
return resultlo;
|
||||
}
|
||||
|
||||
static uint64_t get_actual_amount(uint64_t amount, uint64_t portions)
|
||||
{
|
||||
uint64_t lo, hi, resulthi, resultlo;
|
||||
|
@ -1919,6 +1895,21 @@ static uint64_t get_actual_amount(uint64_t amount, uint64_t portions)
|
|||
return resultlo;
|
||||
}
|
||||
|
||||
// Returns an error message on invalid, nullopt if good
|
||||
static std::optional<std::string_view> is_invalid_staking_address(
|
||||
std::string_view addr,
|
||||
const cryptonote::network_type nettype) {
|
||||
cryptonote::address_parse_info info;
|
||||
bool valid = get_account_address_from_str(info, nettype, addr);
|
||||
if (!valid)
|
||||
return "Invalid OXEN address"sv;
|
||||
if (info.is_subaddress)
|
||||
return "Staking from subaddresses is not supported"sv;
|
||||
if (info.has_payment_id)
|
||||
return "Staking with a payment id/integrated address is not supported"sv;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool rpc_command_executor::prepare_registration(bool force_registration)
|
||||
{
|
||||
// RAII-style class to temporarily clear categories and restore upon destruction (i.e. upon returning).
|
||||
|
@ -2003,31 +1994,32 @@ bool rpc_command_executor::prepare_registration(bool force_registration)
|
|||
std::max(service_nodes::get_staking_requirement(nettype, block_height),
|
||||
service_nodes::get_staking_requirement(nettype, block_height + 30 * 24)); // allow 1 day
|
||||
|
||||
auto highlight_money = [](uint64_t amount) {
|
||||
return fmt::format("\x1b[36;1m{}\x1b[0m", cryptonote::format_money(amount));
|
||||
};
|
||||
|
||||
// anything less than DUST will be added to operator stake
|
||||
const uint64_t DUST = MAX_NUMBER_OF_CONTRIBUTORS;
|
||||
std::cout << "Current staking requirement: " << cryptonote::print_money(staking_requirement) << " " << cryptonote::get_unit() << std::endl;
|
||||
std::cout << "Current staking requirement: " << highlight_money(staking_requirement) << std::endl;
|
||||
|
||||
enum struct register_step
|
||||
{
|
||||
ask_is_solo_stake = 0,
|
||||
is_solo_stake__operator_address_to_reserve,
|
||||
ask_address,
|
||||
ask_is_solo_stake,
|
||||
|
||||
get_operator_fee,
|
||||
do_you_want_to_reserve_other_contributors,
|
||||
how_many_more_contributors,
|
||||
staker_amount_to_reserve,
|
||||
summary_info,
|
||||
|
||||
is_open_stake__get_operator_fee,
|
||||
is_open_stake__do_you_want_to_reserve_other_contributors,
|
||||
is_open_stake__how_many_more_contributors,
|
||||
is_open_stake__operator_amount_to_reserve,
|
||||
is_open_stake__operator_address_to_reserve,
|
||||
is_open_stake__contributor_address_to_reserve,
|
||||
is_open_stake__contributor_amount_to_reserve,
|
||||
is_open_stake__summary_info,
|
||||
final_summary,
|
||||
cancelled_by_user,
|
||||
};
|
||||
|
||||
struct prepare_registration_state
|
||||
{
|
||||
register_step prev_step = register_step::ask_is_solo_stake;
|
||||
bool is_solo_stake;
|
||||
register_step prev_step = register_step::ask_address;
|
||||
size_t num_participants = 1;
|
||||
uint64_t operator_fee_portions = STAKING_PORTIONS;
|
||||
uint64_t portions_remaining = STAKING_PORTIONS;
|
||||
|
@ -2036,329 +2028,236 @@ bool rpc_command_executor::prepare_registration(bool force_registration)
|
|||
std::vector<uint64_t> contributions;
|
||||
};
|
||||
|
||||
std::string operator_address;
|
||||
|
||||
prepare_registration_state state = {};
|
||||
std::stack<prepare_registration_state> state_stack;
|
||||
state_stack.push(state);
|
||||
|
||||
|
||||
|
||||
bool finished = false;
|
||||
register_step step = register_step::ask_is_solo_stake;
|
||||
for (input_line_result last_input_result = input_line_result::yes; !finished;)
|
||||
bool go_back = false;
|
||||
auto step = register_step::ask_address;
|
||||
|
||||
auto next_step = [&](register_step next)
|
||||
{
|
||||
if (last_input_result == input_line_result::back)
|
||||
state.prev_step = step;
|
||||
step = next;
|
||||
state_stack.push(state);
|
||||
std::cout << std::endl;
|
||||
};
|
||||
auto check_cancel_back = [&](input_line_result result) -> bool {
|
||||
switch (result) {
|
||||
case input_line_result::cancel:
|
||||
step = register_step::cancelled_by_user;
|
||||
return true;
|
||||
case input_line_result::back:
|
||||
go_back = true;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
while (!finished)
|
||||
{
|
||||
if (go_back)
|
||||
{
|
||||
step = state.prev_step;
|
||||
state_stack.pop();
|
||||
state = state_stack.top();
|
||||
go_back = false;
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
switch(step)
|
||||
{
|
||||
case register_step::ask_address:
|
||||
{
|
||||
bool is_operator = state.addresses.empty();
|
||||
auto [result, address_str] = input_line_value(fmt::format(
|
||||
"Enter the OXEN address of {}\n",
|
||||
is_operator ? "the Service Node operator" : fmt::format("contributor {}", state.contributions.size())),
|
||||
/*back=*/ !is_operator);
|
||||
|
||||
if (check_cancel_back(result))
|
||||
break;
|
||||
|
||||
if (auto bad = is_invalid_staking_address(address_str, nettype))
|
||||
{
|
||||
tools::fail_msg_writer() << *bad << std::endl;
|
||||
break;
|
||||
}
|
||||
if (std::find(state.addresses.begin(), state.addresses.end(), address_str) != state.addresses.end())
|
||||
{
|
||||
tools::fail_msg_writer() << "Invalid OXEN address: you cannot provide the same address twice" << std::endl;
|
||||
break;
|
||||
}
|
||||
state.addresses.push_back(std::move(address_str));
|
||||
next_step(is_operator
|
||||
? register_step::ask_is_solo_stake
|
||||
: register_step::staker_amount_to_reserve);
|
||||
break;
|
||||
}
|
||||
|
||||
case register_step::ask_is_solo_stake:
|
||||
{
|
||||
last_input_result = input_line_yes_no_cancel("Will the operator contribute the entire stake?");
|
||||
if(last_input_result == input_line_result::cancel)
|
||||
{
|
||||
step = register_step::cancelled_by_user;
|
||||
continue;
|
||||
}
|
||||
auto result = input_line_ask("Will the operator contribute the entire stake?");
|
||||
if (check_cancel_back(result))
|
||||
break;
|
||||
|
||||
state.is_solo_stake = (last_input_result == input_line_result::yes);
|
||||
if (state.is_solo_stake)
|
||||
if (result == input_line_result::yes)
|
||||
{
|
||||
std::cout << std::endl;
|
||||
step = register_step::is_solo_stake__operator_address_to_reserve;
|
||||
state.contributions.push_back(STAKING_PORTIONS);
|
||||
state.portions_remaining = 0;
|
||||
state.total_reserved_contributions += staking_requirement;
|
||||
|
||||
next_step(register_step::final_summary);
|
||||
}
|
||||
else
|
||||
{
|
||||
step = register_step::is_open_stake__get_operator_fee;
|
||||
}
|
||||
|
||||
state_stack.push(state);
|
||||
continue;
|
||||
next_step(register_step::staker_amount_to_reserve);
|
||||
break;
|
||||
}
|
||||
|
||||
case register_step::is_solo_stake__operator_address_to_reserve:
|
||||
case register_step::get_operator_fee:
|
||||
{
|
||||
std::string address_str;
|
||||
last_input_result = input_line_back_cancel_get_input("Enter the oxen address for the solo staker", address_str);
|
||||
if (last_input_result == input_line_result::back)
|
||||
continue;
|
||||
auto [result, operator_fee_str] = input_line_value(
|
||||
"Enter operator fee as a percentage of earned rewards [0-100]%");
|
||||
|
||||
if (last_input_result == input_line_result::cancel)
|
||||
{
|
||||
step = register_step::cancelled_by_user;
|
||||
continue;
|
||||
}
|
||||
|
||||
state.addresses.push_back(address_str); // the addresses will be validated later down the line
|
||||
state.contributions.push_back(STAKING_PORTIONS);
|
||||
state.portions_remaining = 0;
|
||||
state.total_reserved_contributions += staking_requirement;
|
||||
state.prev_step = step;
|
||||
step = register_step::final_summary;
|
||||
state_stack.push(state);
|
||||
continue;
|
||||
}
|
||||
|
||||
case register_step::is_open_stake__get_operator_fee:
|
||||
{
|
||||
std::string operator_fee_str;
|
||||
last_input_result = input_line_back_cancel_get_input("Enter operator fee as a percentage of the total staking reward [0-100]%", operator_fee_str);
|
||||
|
||||
if (last_input_result == input_line_result::back)
|
||||
continue;
|
||||
|
||||
if (last_input_result == input_line_result::cancel)
|
||||
{
|
||||
step = register_step::cancelled_by_user;
|
||||
continue;
|
||||
}
|
||||
if (check_cancel_back(result))
|
||||
break;
|
||||
|
||||
if (!service_nodes::get_portions_from_percent_str(operator_fee_str, state.operator_fee_portions))
|
||||
{
|
||||
std::cout << "Invalid value: " << operator_fee_str << ". Should be between [0-100]" << std::endl;
|
||||
continue;
|
||||
tools::fail_msg_writer() << "Invalid value: " << operator_fee_str << ". Should be between [0-100]" << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
step = register_step::is_open_stake__do_you_want_to_reserve_other_contributors;
|
||||
state_stack.push(state);
|
||||
continue;
|
||||
next_step(register_step::do_you_want_to_reserve_other_contributors);
|
||||
break;
|
||||
}
|
||||
|
||||
case register_step::is_open_stake__do_you_want_to_reserve_other_contributors:
|
||||
case register_step::do_you_want_to_reserve_other_contributors:
|
||||
{
|
||||
last_input_result = input_line_yes_no_back_cancel("Do you want to reserve portions of the stake for other specific contributors?");
|
||||
if (last_input_result == input_line_result::back)
|
||||
continue;
|
||||
auto result = input_line_ask("Do you want to reserve stakes for specific contributors?");
|
||||
if (check_cancel_back(result))
|
||||
break;
|
||||
|
||||
if (last_input_result == input_line_result::cancel)
|
||||
{
|
||||
step = register_step::cancelled_by_user;
|
||||
continue;
|
||||
}
|
||||
|
||||
state.prev_step = step;
|
||||
if(last_input_result == input_line_result::yes)
|
||||
{
|
||||
step = register_step::is_open_stake__how_many_more_contributors;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << std::endl;
|
||||
step = register_step::is_open_stake__operator_address_to_reserve;
|
||||
}
|
||||
|
||||
state_stack.push(state);
|
||||
continue;
|
||||
next_step(result == input_line_result::yes
|
||||
? register_step::how_many_more_contributors
|
||||
: register_step::summary_info);
|
||||
break;
|
||||
}
|
||||
|
||||
case register_step::is_open_stake__how_many_more_contributors:
|
||||
case register_step::how_many_more_contributors:
|
||||
{
|
||||
std::string prompt = "Number of additional contributors [1-" + std::to_string(MAX_NUMBER_OF_CONTRIBUTORS - 1) + "]";
|
||||
std::string input;
|
||||
last_input_result = input_line_back_cancel_get_input(prompt.c_str(), input);
|
||||
auto [result, input] = input_line_value(
|
||||
"Number of additional contributors [1-" + std::to_string(MAX_NUMBER_OF_CONTRIBUTORS - 1) + "]");
|
||||
|
||||
if (last_input_result == input_line_result::back)
|
||||
continue;
|
||||
if (check_cancel_back(result))
|
||||
break;
|
||||
|
||||
if (last_input_result == input_line_result::cancel)
|
||||
size_t additional_contributors;
|
||||
if (!tools::parse_int(input, additional_contributors) ||
|
||||
additional_contributors < 1 || additional_contributors > (MAX_NUMBER_OF_CONTRIBUTORS - 1))
|
||||
{
|
||||
step = register_step::cancelled_by_user;
|
||||
continue;
|
||||
tools::fail_msg_writer() << "Invalid value; must be between 1 and " << (MAX_NUMBER_OF_CONTRIBUTORS - 1) << "." << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
long additional_contributors = strtol(input.c_str(), NULL, 10 /*base 10*/);
|
||||
if(additional_contributors < 1 || additional_contributors > (MAX_NUMBER_OF_CONTRIBUTORS - 1))
|
||||
{
|
||||
std::cout << "Invalid value. Should be between [1-" << (MAX_NUMBER_OF_CONTRIBUTORS - 1) << "]" << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
state.num_participants += static_cast<size_t>(additional_contributors);
|
||||
state.prev_step = step;
|
||||
step = register_step::is_open_stake__operator_address_to_reserve;
|
||||
state_stack.push(state);
|
||||
continue;
|
||||
state.num_participants += additional_contributors;
|
||||
next_step(register_step::ask_address);
|
||||
break;
|
||||
}
|
||||
|
||||
case register_step::is_open_stake__operator_address_to_reserve:
|
||||
case register_step::staker_amount_to_reserve:
|
||||
{
|
||||
std::string address_str;
|
||||
last_input_result = input_line_back_cancel_get_input("Enter the oxen address for the operator", address_str);
|
||||
if (last_input_result == input_line_result::back)
|
||||
continue;
|
||||
bool is_operator = state.total_reserved_contributions == 0;
|
||||
uint64_t amount_left = staking_requirement - state.total_reserved_contributions;
|
||||
uint64_t min_contribution_portions = service_nodes::get_min_node_contribution_in_portions(
|
||||
hf_version, staking_requirement, state.total_reserved_contributions, state.contributions.size());
|
||||
uint64_t min_contribution = service_nodes::portions_to_amount(staking_requirement, min_contribution_portions);
|
||||
|
||||
if (last_input_result == input_line_result::cancel)
|
||||
{
|
||||
step = register_step::cancelled_by_user;
|
||||
continue;
|
||||
}
|
||||
auto [result, contribution_str] = input_line_value(fmt::format(
|
||||
"The {} contribution must be between {} and {} to meet the staking requirements.\n\n"
|
||||
"How much OXEN does {} want to stake?",
|
||||
is_operator ? "operator" : "next",
|
||||
highlight_money(min_contribution),
|
||||
highlight_money(amount_left),
|
||||
is_operator ? "the operator" : fmt::format("contributor {}", state.contributions.size())));
|
||||
|
||||
state.addresses.push_back(address_str); // the addresses will be validated later down the line
|
||||
state.prev_step = step;
|
||||
step = register_step::is_open_stake__operator_amount_to_reserve;
|
||||
state_stack.push(state);
|
||||
continue;
|
||||
}
|
||||
|
||||
case register_step::is_open_stake__operator_amount_to_reserve:
|
||||
{
|
||||
uint64_t min_contribution_portions = service_nodes::get_min_node_contribution_in_portions(hf_version, staking_requirement, 0, 0);
|
||||
const uint64_t min_contribution = get_amount_to_make_portions(staking_requirement, min_contribution_portions);
|
||||
std::cout << "Minimum amount that can be reserved: " << cryptonote::print_money(min_contribution) << " " << cryptonote::get_unit() << std::endl;
|
||||
|
||||
std::string contribution_str;
|
||||
last_input_result = input_line_back_cancel_get_input("How much oxen does the operator want to reserve in the stake?", contribution_str);
|
||||
if (last_input_result == input_line_result::back)
|
||||
continue;
|
||||
|
||||
if (last_input_result == input_line_result::cancel)
|
||||
{
|
||||
step = register_step::cancelled_by_user;
|
||||
continue;
|
||||
}
|
||||
if (check_cancel_back(result))
|
||||
break;
|
||||
|
||||
uint64_t contribution;
|
||||
if(!cryptonote::parse_amount(contribution, contribution_str))
|
||||
{
|
||||
std::cout << "Invalid amount." << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint64_t portions = service_nodes::get_portions_to_make_amount(staking_requirement, contribution);
|
||||
if(portions < min_contribution_portions)
|
||||
{
|
||||
std::cout << "The operator needs to contribute at least 25% of the stake requirement (" << cryptonote::print_money(min_contribution) << " " << cryptonote::get_unit() << "). Aborted." << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(portions > state.portions_remaining)
|
||||
{
|
||||
std::cout << "The operator contribution is higher than the staking requirement. Any excess contribution will be locked for the staking duration, but won't yield any additional reward." << std::endl;
|
||||
portions = state.portions_remaining;
|
||||
}
|
||||
|
||||
state.contributions.push_back(portions);
|
||||
state.portions_remaining -= portions;
|
||||
state.total_reserved_contributions += get_actual_amount(staking_requirement, portions);
|
||||
state.prev_step = step;
|
||||
|
||||
if (state.num_participants > 1)
|
||||
{
|
||||
step = register_step::is_open_stake__contributor_address_to_reserve;
|
||||
}
|
||||
if (auto c = cryptonote::parse_amount(contribution_str))
|
||||
contribution = *c;
|
||||
else
|
||||
{
|
||||
step = register_step::is_open_stake__summary_info;
|
||||
tools::fail_msg_writer() << "Invalid amount." << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
state_stack.push(state);
|
||||
continue;
|
||||
}
|
||||
|
||||
case register_step::is_open_stake__contributor_address_to_reserve:
|
||||
{
|
||||
std::string const prompt = "Enter the oxen address for contributor " + std::to_string(state.contributions.size() + 1);
|
||||
std::string address_str;
|
||||
last_input_result = input_line_back_cancel_get_input(prompt.c_str(), address_str);
|
||||
if (last_input_result == input_line_result::back)
|
||||
continue;
|
||||
|
||||
if (last_input_result == input_line_result::cancel)
|
||||
if (contribution > amount_left)
|
||||
{
|
||||
step = register_step::cancelled_by_user;
|
||||
continue;
|
||||
}
|
||||
|
||||
// the addresses will be validated later down the line
|
||||
state.addresses.push_back(address_str);
|
||||
state.prev_step = step;
|
||||
step = register_step::is_open_stake__contributor_amount_to_reserve;
|
||||
state_stack.push(state);
|
||||
continue;
|
||||
}
|
||||
|
||||
case register_step::is_open_stake__contributor_amount_to_reserve:
|
||||
{
|
||||
const uint64_t amount_left = staking_requirement - state.total_reserved_contributions;
|
||||
uint64_t min_contribution_portions = service_nodes::get_min_node_contribution_in_portions(hf_version, staking_requirement, state.total_reserved_contributions, state.contributions.size());
|
||||
const uint64_t min_contribution = service_nodes::portions_to_amount(staking_requirement, min_contribution_portions);
|
||||
|
||||
std::cout << "The minimum amount possible to contribute is " << cryptonote::print_money(min_contribution) << " " << cryptonote::get_unit() << std::endl;
|
||||
std::cout << "There is " << cryptonote::print_money(amount_left) << " " << cryptonote::get_unit() << " left to meet the staking requirement." << std::endl;
|
||||
|
||||
std::string contribution_str;
|
||||
std::string const prompt = "How much oxen does contributor " + std::to_string(state.contributions.size() + 1) + " want to reserve in the stake?";
|
||||
last_input_result = input_line_back_cancel_get_input(prompt.c_str(), contribution_str);
|
||||
if (last_input_result == input_line_result::back)
|
||||
continue;
|
||||
|
||||
if (last_input_result == input_line_result::cancel)
|
||||
{
|
||||
step = register_step::cancelled_by_user;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint64_t contribution;
|
||||
if (!cryptonote::parse_amount(contribution, contribution_str))
|
||||
{
|
||||
std::cout << "Invalid amount." << std::endl;
|
||||
continue;
|
||||
tools::fail_msg_writer() << fmt::format(
|
||||
"Invalid amount: The contribution exceeds the remaining staking requirement ({}).\n",
|
||||
highlight_money(amount_left));
|
||||
break;
|
||||
}
|
||||
|
||||
uint64_t portions = service_nodes::get_portions_to_make_amount(staking_requirement, contribution);
|
||||
if (portions < min_contribution_portions)
|
||||
{
|
||||
std::cout << "The amount is too small." << std::endl;
|
||||
continue;
|
||||
tools::fail_msg_writer() << "Invalid amount\n";
|
||||
break;
|
||||
}
|
||||
|
||||
if (portions > state.portions_remaining)
|
||||
portions = state.portions_remaining;
|
||||
|
||||
state.contributions.push_back(portions);
|
||||
state.portions_remaining -= portions;
|
||||
state.total_reserved_contributions += get_actual_amount(staking_requirement, portions);
|
||||
state.prev_step = step;
|
||||
|
||||
if (state.contributions.size() == state.num_participants)
|
||||
step = register_step::is_open_stake__summary_info;
|
||||
else
|
||||
step = register_step::is_open_stake__contributor_address_to_reserve;
|
||||
|
||||
std::cout << std::endl;
|
||||
state_stack.push(state);
|
||||
continue;
|
||||
next_step(
|
||||
is_operator ? register_step::get_operator_fee :
|
||||
state.num_participants > state.contributions.size()
|
||||
? register_step::ask_address
|
||||
: register_step::summary_info);
|
||||
break;
|
||||
}
|
||||
|
||||
case register_step::is_open_stake__summary_info:
|
||||
case register_step::summary_info:
|
||||
{
|
||||
uint64_t open_spots = MAX_NUMBER_OF_CONTRIBUTORS - state.contributions.size();
|
||||
const uint64_t amount_left = staking_requirement - state.total_reserved_contributions;
|
||||
std::cout << "Total staking contributions reserved: " << cryptonote::print_money(state.total_reserved_contributions) << " " << cryptonote::get_unit() << std::endl;
|
||||
fmt::print("Total reserved contributions: {}\n", highlight_money(state.total_reserved_contributions));
|
||||
if (amount_left > DUST)
|
||||
{
|
||||
std::cout << "Your total reservations do not equal the staking requirement." << std::endl;
|
||||
std::cout << "You will leave the remaining portion of " << cryptonote::print_money(amount_left) << " " << cryptonote::get_unit() << " open to contributions from anyone, and the Service Node will not activate until the full staking requirement is filled." << std::endl;
|
||||
fmt::print(
|
||||
"\nThe total reserved amount ({}) is less than the required full stake ({}).\n"
|
||||
"The remaining stake ({}) will be open to contribution from {}.\n"
|
||||
"The Service Node will not activate until the full contribution has been filled.\n\n",
|
||||
highlight_money(state.total_reserved_contributions),
|
||||
highlight_money(staking_requirement),
|
||||
highlight_money(amount_left),
|
||||
open_spots > 1 ? fmt::format("1-{} public contributors", open_spots) : "1 public contributor"
|
||||
);
|
||||
|
||||
last_input_result = input_line_yes_no_back_cancel("Is this ok?\n");
|
||||
if(last_input_result == input_line_result::no || last_input_result == input_line_result::cancel)
|
||||
{
|
||||
step = register_step::cancelled_by_user;
|
||||
continue;
|
||||
}
|
||||
auto result = input_line_ask("Is this acceptable?");
|
||||
if (result == input_line_result::no)
|
||||
result = input_line_result::cancel;
|
||||
if (check_cancel_back(result))
|
||||
break;
|
||||
|
||||
if(last_input_result == input_line_result::back)
|
||||
continue;
|
||||
|
||||
state_stack.push(state);
|
||||
state.prev_step = step;
|
||||
next_step(register_step::final_summary);
|
||||
break;
|
||||
}
|
||||
|
||||
// Not calling next_step here because we have no state change to push
|
||||
step = register_step::final_summary;
|
||||
continue;
|
||||
std::cout << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
case register_step::final_summary:
|
||||
|
@ -2366,51 +2265,65 @@ bool rpc_command_executor::prepare_registration(bool force_registration)
|
|||
assert(state.addresses.size() == state.contributions.size());
|
||||
const uint64_t amount_left = staking_requirement - state.total_reserved_contributions;
|
||||
|
||||
std::cout << "Summary:" << std::endl;
|
||||
std::cout << "Operating costs as % of reward: " << (state.operator_fee_portions * 100.0 / static_cast<double>(STAKING_PORTIONS)) << "%" << std::endl;
|
||||
printf("%-16s%-9s%-19s%-s\n", "Contributor", "Address", "Contribution", "Contribution(%)");
|
||||
printf("%-16s%-9s%-19s%-s\n", "___________", "_______", "____________", "_______________");
|
||||
std::cout << "\nRegistration Summary:\n" << std::endl;
|
||||
|
||||
if (amount_left > 0 || state.addresses.size() > 1)
|
||||
fmt::print("Operator fee (as % of Service Node rewards): \x1b[33;1m{}%\x1b[0m\n\n",
|
||||
state.operator_fee_portions * 100.0 / static_cast<double>(STAKING_PORTIONS));
|
||||
|
||||
constexpr auto row = "{:^14} {:^13} {:>17} {:>8}\n"sv;
|
||||
fmt::print(row, "Contributor", "Address", "Contribution", "Contr. %");
|
||||
fmt::print(row, "_____________", "_____________", "_________________", "________");
|
||||
fmt::print("\n");
|
||||
|
||||
for (size_t i = 0; i < state.num_participants; ++i)
|
||||
{
|
||||
const std::string participant_name = (i==0) ? "Operator" : "Contributor " + std::to_string(i);
|
||||
uint64_t amount = get_actual_amount(staking_requirement, state.contributions[i]);
|
||||
if (amount_left <= DUST && i == 0)
|
||||
amount += amount_left; // add dust to the operator.
|
||||
printf("%-16s%-9s%-19s%-.9f\n", participant_name.c_str(), state.addresses[i].substr(0,6).c_str(), cryptonote::print_money(amount).c_str(), (double)state.contributions[i] * 100 / (double)STAKING_PORTIONS);
|
||||
const auto& addr = state.addresses[i];
|
||||
fmt::print(row,
|
||||
(i==0) ? "Operator" : "Contributor " + std::to_string(i),
|
||||
addr.substr(0, 9) + ".." + addr.substr(addr.size() - 2),
|
||||
cryptonote::print_money(amount),
|
||||
fmt::format("{:.2f}%", state.contributions[i] * 100.0 / STAKING_PORTIONS));
|
||||
}
|
||||
|
||||
if (amount_left > DUST)
|
||||
{
|
||||
printf("%-16s%-9s%-19s%-.2f\n", "(open)", "", cryptonote::print_money(amount_left).c_str(), amount_left * 100.0 / staking_requirement);
|
||||
size_t open_spots = MAX_NUMBER_OF_CONTRIBUTORS - state.contributions.size();
|
||||
for (size_t i = 0; i < open_spots; i++) {
|
||||
fmt::print(row,
|
||||
"(open)",
|
||||
"(any)",
|
||||
i == 0 && open_spots == 1 ? cryptonote::print_money(amount_left) :
|
||||
i == 0 ? ">=" + cryptonote::print_money((amount_left + open_spots - 1) / open_spots) :
|
||||
"",
|
||||
i == 0 && open_spots == 1 ? fmt::format("{:.2f}%", amount_left * 100.0 / staking_requirement) :
|
||||
i == 0 ? fmt::format(">={:.2f}%", amount_left * 100.0 / staking_requirement / open_spots) :
|
||||
"");
|
||||
}
|
||||
}
|
||||
else if (amount_left > 0)
|
||||
{
|
||||
std::cout << "\nActual amounts may differ slightly from specification. This is due to\n" << std::endl;
|
||||
std::cout << "limitations on the way fractions are represented internally.\n" << std::endl;
|
||||
std::cout <<
|
||||
"\nActual amounts may differ slightly from specification because of limitations on"
|
||||
"\nthe way fractions are represented on the blockchain\n\n";
|
||||
}
|
||||
|
||||
std::cout << "\nBecause the actual requirement will depend on the time that you register, the\n";
|
||||
std::cout << "amounts shown here are used as a guide only, and the percentages will remain\n";
|
||||
std::cout << "the same." << std::endl << std::endl;
|
||||
|
||||
last_input_result = input_line_yes_no_back_cancel("Do you confirm the information above is correct?");
|
||||
if(last_input_result == input_line_result::no || last_input_result == input_line_result::cancel)
|
||||
{
|
||||
step = register_step::cancelled_by_user;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(last_input_result == input_line_result::back)
|
||||
continue;
|
||||
auto result = input_line_ask("\nIs the staking information above correct?");
|
||||
if (result == input_line_result::no)
|
||||
result = input_line_result::cancel;
|
||||
if (check_cancel_back(result))
|
||||
break;
|
||||
|
||||
finished = true;
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
|
||||
case register_step::cancelled_by_user:
|
||||
{
|
||||
std::cout << "Cancel requested in prepare registration. Aborting." << std::endl;
|
||||
tools::fail_msg_writer() << "Registration preparation cancelled." << std::endl;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -2425,18 +2338,6 @@ bool rpc_command_executor::prepare_registration(bool force_registration)
|
|||
args.push_back(std::to_string(state.contributions[i]));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < state.addresses.size(); i++)
|
||||
{
|
||||
for (size_t j = 0; j < i; j++)
|
||||
{
|
||||
if (state.addresses[i] == state.addresses[j])
|
||||
{
|
||||
std::cout << "Must not provide the same address twice" << std::endl;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scoped_log_cats.reset();
|
||||
|
||||
{
|
||||
|
@ -2447,8 +2348,9 @@ bool rpc_command_executor::prepare_registration(bool force_registration)
|
|||
req.make_friendly = true;
|
||||
req.staking_requirement = staking_requirement;
|
||||
|
||||
if (!invoke<GET_SERVICE_NODE_REGISTRATION_CMD_RAW>(std::move(req), res, "Failed to validate registration arguments; "
|
||||
"check the addresses and registration parameters and that the Daemon is running with the '--service-node' flag"))
|
||||
if (!invoke<GET_SERVICE_NODE_REGISTRATION_CMD_RAW>(std::move(req), res,
|
||||
"Failed to validate registration arguments; check the addresses and registration parameters "
|
||||
"and make sure oxend is running as a service node."))
|
||||
return false;
|
||||
|
||||
tools::success_msg_writer() << res.registration_cmd;
|
||||
|
|
|
@ -2302,8 +2302,8 @@ bool simple_wallet::set_min_output_count(const std::vector<std::string> &args/*
|
|||
|
||||
bool simple_wallet::set_min_output_value(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
|
||||
{
|
||||
uint64_t value;
|
||||
if (!cryptonote::parse_amount(value, args[1]))
|
||||
auto value = cryptonote::parse_amount(args[1]);
|
||||
if (!value)
|
||||
{
|
||||
fail_msg_writer() << tr("invalid value");
|
||||
return true;
|
||||
|
@ -2312,7 +2312,7 @@ bool simple_wallet::set_min_output_value(const std::vector<std::string> &args/*
|
|||
const auto pwd_container = get_and_verify_password();
|
||||
if (pwd_container)
|
||||
{
|
||||
m_wallet->set_min_output_value(value);
|
||||
m_wallet->set_min_output_value(*value);
|
||||
m_wallet->rewrite(m_wallet_file, pwd_container->password());
|
||||
}
|
||||
return true;
|
||||
|
@ -2424,15 +2424,15 @@ bool simple_wallet::set_ignore_outputs_above(const std::vector<std::string> &arg
|
|||
const auto pwd_container = get_and_verify_password();
|
||||
if (pwd_container)
|
||||
{
|
||||
uint64_t amount;
|
||||
if (!cryptonote::parse_amount(amount, args[1]))
|
||||
auto amount = cryptonote::parse_amount(args[1]);
|
||||
if (!amount)
|
||||
{
|
||||
fail_msg_writer() << tr("Invalid amount");
|
||||
return true;
|
||||
}
|
||||
if (amount == 0)
|
||||
if (*amount == 0)
|
||||
amount = MONEY_SUPPLY;
|
||||
m_wallet->ignore_outputs_above(amount);
|
||||
m_wallet->ignore_outputs_above(*amount);
|
||||
m_wallet->rewrite(m_wallet_file, pwd_container->password());
|
||||
}
|
||||
return true;
|
||||
|
@ -2443,13 +2443,13 @@ bool simple_wallet::set_ignore_outputs_below(const std::vector<std::string> &arg
|
|||
const auto pwd_container = get_and_verify_password();
|
||||
if (pwd_container)
|
||||
{
|
||||
uint64_t amount;
|
||||
if (!cryptonote::parse_amount(amount, args[1]))
|
||||
auto amount = cryptonote::parse_amount(args[1]);
|
||||
if (!amount)
|
||||
{
|
||||
fail_msg_writer() << tr("Invalid amount");
|
||||
return true;
|
||||
}
|
||||
m_wallet->ignore_outputs_below(amount);
|
||||
m_wallet->ignore_outputs_below(*amount);
|
||||
m_wallet->rewrite(m_wallet_file, pwd_container->password());
|
||||
}
|
||||
return true;
|
||||
|
@ -5806,9 +5806,13 @@ bool simple_wallet::transfer_main(Transfer transfer_type, const std::vector<std:
|
|||
static constexpr auto BURN_PREFIX = "burn="sv;
|
||||
uint64_t burn_amount = 0;
|
||||
std::string burn_amount_str = eat_named_argument(local_args, BURN_PREFIX);
|
||||
if (!burn_amount_str.empty() && !cryptonote::parse_amount(burn_amount, burn_amount_str)) {
|
||||
fail_msg_writer() << tr("Invalid amount");
|
||||
return true;
|
||||
if (!burn_amount_str.empty()) {
|
||||
if (auto b = cryptonote::parse_amount(burn_amount_str))
|
||||
burn_amount = *b;
|
||||
else {
|
||||
fail_msg_writer() << tr("Invalid amount");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t priority = 0;
|
||||
|
@ -5885,8 +5889,9 @@ bool simple_wallet::transfer_main(Transfer transfer_type, const std::vector<std:
|
|||
return false;
|
||||
}
|
||||
|
||||
bool ok = cryptonote::parse_amount(de.amount, local_args[i + 1]);
|
||||
if(!ok || 0 == de.amount)
|
||||
if (auto a = cryptonote::parse_amount(local_args[i + 1]); a && *a > 0)
|
||||
de.amount = *a;
|
||||
else
|
||||
{
|
||||
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<uint64_t>::max());
|
||||
|
@ -6112,7 +6117,9 @@ bool simple_wallet::stake(const std::vector<std::string> &args_)
|
|||
}
|
||||
else
|
||||
{
|
||||
if (!cryptonote::parse_amount(amount, local_args[1]) || amount == 0)
|
||||
if (auto a = cryptonote::parse_amount(local_args[1]); a && *a > 0)
|
||||
amount = *a;
|
||||
else
|
||||
{
|
||||
fail_msg_writer() << tr("amount is wrong: ") << local_args[1] <<
|
||||
", " << tr("expected number from ") << print_money(1) << " to " << print_money(std::numeric_limits<uint64_t>::max());
|
||||
|
@ -7557,18 +7564,18 @@ bool simple_wallet::sweep_account(const std::vector<std::string> &args_)
|
|||
//----------------------------------------------------------------------------------------------------
|
||||
bool simple_wallet::sweep_below(const std::vector<std::string> &args_)
|
||||
{
|
||||
uint64_t below = 0;
|
||||
if (args_.size() < 1)
|
||||
{
|
||||
fail_msg_writer() << tr("missing threshold amount");
|
||||
return true;
|
||||
}
|
||||
if (!cryptonote::parse_amount(below, args_[0]))
|
||||
auto below = cryptonote::parse_amount(args_[0]);
|
||||
if (!below)
|
||||
{
|
||||
fail_msg_writer() << tr("invalid amount threshold");
|
||||
return true;
|
||||
}
|
||||
return sweep_main(m_current_subaddress_account, below, Transfer::Normal, std::vector<std::string>(++args_.begin(), args_.end()));
|
||||
return sweep_main(m_current_subaddress_account, *below, Transfer::Normal, std::vector<std::string>(++args_.begin(), args_.end()));
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes, const std::function<const wallet::tx_construction_data&(size_t)> &get_tx, const std::string &extra_message)
|
||||
|
@ -8250,7 +8257,9 @@ bool simple_wallet::get_reserve_proof(const std::vector<std::string> &args)
|
|||
{
|
||||
account_minreserve = std::pair<uint32_t, uint64_t>();
|
||||
account_minreserve->first = m_current_subaddress_account;
|
||||
if (!cryptonote::parse_amount(account_minreserve->second, args[0]))
|
||||
if (auto r = cryptonote::parse_amount(args[0]))
|
||||
account_minreserve->second = *r;
|
||||
else
|
||||
{
|
||||
fail_msg_writer() << tr("amount is wrong: ") << args[0];
|
||||
return true;
|
||||
|
@ -8587,7 +8596,9 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_)
|
|||
uint64_t max_amount = std::numeric_limits<uint64_t>::max();
|
||||
if (local_args.size() > 0)
|
||||
{
|
||||
if (!cryptonote::parse_amount(min_amount, local_args[0]))
|
||||
if (auto a = cryptonote::parse_amount(local_args[0]))
|
||||
min_amount = *a;
|
||||
else
|
||||
{
|
||||
fail_msg_writer() << tr("amount is wrong: ") << local_args[0];
|
||||
return true;
|
||||
|
@ -8595,7 +8606,9 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_)
|
|||
local_args.erase(local_args.begin());
|
||||
if (local_args.size() > 0)
|
||||
{
|
||||
if (!cryptonote::parse_amount(max_amount, local_args[0]))
|
||||
if (auto a = cryptonote::parse_amount(local_args[0]))
|
||||
max_amount = *a;
|
||||
else
|
||||
{
|
||||
fail_msg_writer() << tr("amount is wrong: ") << local_args[0];
|
||||
return true;
|
||||
|
|
|
@ -316,9 +316,7 @@ std::string Wallet::displayAmount(uint64_t amount)
|
|||
EXPORT
|
||||
uint64_t Wallet::amountFromString(const std::string &amount)
|
||||
{
|
||||
uint64_t result = 0;
|
||||
cryptonote::parse_amount(result, amount);
|
||||
return result;
|
||||
return cryptonote::parse_amount(amount).value_or(0);
|
||||
}
|
||||
|
||||
EXPORT
|
||||
|
|
|
@ -14457,7 +14457,9 @@ bool wallet2::parse_uri(std::string_view uri, std::string &address, std::string
|
|||
if (key == "tx_amount"sv)
|
||||
{
|
||||
amount = 0;
|
||||
if (!cryptonote::parse_amount(amount, value))
|
||||
if (auto a = cryptonote::parse_amount(value))
|
||||
amount = *a;
|
||||
else
|
||||
{
|
||||
error = "URI has invalid amount: " + value;
|
||||
return false;
|
||||
|
|
|
@ -38,21 +38,20 @@ namespace
|
|||
{
|
||||
void do_pos_test(uint64_t expected, const std::string& str)
|
||||
{
|
||||
uint64_t val;
|
||||
std::string number_str = str;
|
||||
std::replace(number_str.begin(), number_str.end(), '_', '.');
|
||||
number_str.erase(std::remove(number_str.begin(), number_str.end(), '~'), number_str.end());
|
||||
ASSERT_TRUE(parse_amount(val, number_str));
|
||||
ASSERT_EQ(expected, val);
|
||||
auto val = parse_amount(number_str);
|
||||
ASSERT_TRUE(val);
|
||||
ASSERT_EQ(expected, *val);
|
||||
}
|
||||
|
||||
void do_neg_test(const std::string& str)
|
||||
{
|
||||
uint64_t val;
|
||||
std::string number_str = str;
|
||||
std::replace(number_str.begin(), number_str.end(), '_', '.');
|
||||
number_str.erase(std::remove(number_str.begin(), number_str.end(), '~'), number_str.end());
|
||||
ASSERT_FALSE(parse_amount(val, number_str));
|
||||
ASSERT_FALSE(parse_amount(number_str));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -164,46 +164,45 @@ TEST(parse_and_validate_tx_extra, fails_on_wrong_size_in_extra_nonce)
|
|||
}
|
||||
TEST(validate_parse_amount_case, validate_parse_amount)
|
||||
{
|
||||
uint64_t res = 0;
|
||||
bool r = cryptonote::parse_amount(res, "0.0001");
|
||||
ASSERT_TRUE(r);
|
||||
ASSERT_EQ(res, 100000);
|
||||
auto a = cryptonote::parse_amount("0.0001");
|
||||
ASSERT_TRUE(a);
|
||||
ASSERT_EQ(*a, 100000);
|
||||
|
||||
r = cryptonote::parse_amount(res, "100.0001");
|
||||
ASSERT_TRUE(r);
|
||||
ASSERT_EQ(res, 100000100000);
|
||||
a = cryptonote::parse_amount("100.0001");
|
||||
ASSERT_TRUE(a);
|
||||
ASSERT_EQ(*a, 100000100000);
|
||||
|
||||
r = cryptonote::parse_amount(res, "000.0000");
|
||||
ASSERT_TRUE(r);
|
||||
ASSERT_EQ(res, 0);
|
||||
a = cryptonote::parse_amount("000.0000");
|
||||
ASSERT_TRUE(a);
|
||||
ASSERT_EQ(*a, 0);
|
||||
|
||||
r = cryptonote::parse_amount(res, "0");
|
||||
ASSERT_TRUE(r);
|
||||
ASSERT_EQ(res, 0);
|
||||
a = cryptonote::parse_amount("0");
|
||||
ASSERT_TRUE(a);
|
||||
ASSERT_EQ(*a, 0);
|
||||
|
||||
|
||||
r = cryptonote::parse_amount(res, " 100.0001 ");
|
||||
ASSERT_TRUE(r);
|
||||
ASSERT_EQ(res, 100000100000);
|
||||
a = cryptonote::parse_amount(" 100.0001 ");
|
||||
ASSERT_TRUE(a);
|
||||
ASSERT_EQ(*a, 100000100000);
|
||||
|
||||
r = cryptonote::parse_amount(res, " 100.0000 ");
|
||||
ASSERT_TRUE(r);
|
||||
ASSERT_EQ(res, 100000000000);
|
||||
a = cryptonote::parse_amount(" 100.0000 ");
|
||||
ASSERT_TRUE(a);
|
||||
ASSERT_EQ(*a, 100000000000);
|
||||
|
||||
r = cryptonote::parse_amount(res, " 100. 0000 ");
|
||||
ASSERT_FALSE(r);
|
||||
a = cryptonote::parse_amount(" 100. 0000 ");
|
||||
ASSERT_FALSE(a);
|
||||
|
||||
r = cryptonote::parse_amount(res, "100. 0000");
|
||||
ASSERT_FALSE(r);
|
||||
a = cryptonote::parse_amount("100. 0000");
|
||||
ASSERT_FALSE(a);
|
||||
|
||||
r = cryptonote::parse_amount(res, "100 . 0000");
|
||||
ASSERT_FALSE(r);
|
||||
a = cryptonote::parse_amount("100 . 0000");
|
||||
ASSERT_FALSE(a);
|
||||
|
||||
r = cryptonote::parse_amount(res, "100.00 00");
|
||||
ASSERT_FALSE(r);
|
||||
a = cryptonote::parse_amount("100.00 00");
|
||||
ASSERT_FALSE(a);
|
||||
|
||||
r = cryptonote::parse_amount(res, "1 00.00 00");
|
||||
ASSERT_FALSE(r);
|
||||
a = cryptonote::parse_amount("1 00.00 00");
|
||||
ASSERT_FALSE(a);
|
||||
}
|
||||
|
||||
TEST(sort_tx_extra, empty)
|
||||
|
|
Loading…
Reference in New Issue