diff --git a/doc/lightning-invoice.7 b/doc/lightning-invoice.7 index 1a2d6c8e2d90..e7275fa97efe 100644 --- a/doc/lightning-invoice.7 +++ b/doc/lightning-invoice.7 @@ -16,7 +16,7 @@ invoice, if any exists\. The \fImsatoshi\fR parameter can be the string "any", which creates an -invoice that can be paid with any amount\. Otherwise it is in +invoice that can be paid with any amount\. Otherwise it is a positive value in millisatoshi precision; it can be a whole number, or a whole number ending in \fImsat\fR or \fIsat\fR, or a number with three decimal places ending in \fIsat\fR, or a number with 1 to 11 decimal places ending in \fIbtc\fR\. diff --git a/doc/lightning-invoice.7.md b/doc/lightning-invoice.7.md index 8721ddeef0cd..c0c9b8e0f8fb 100644 --- a/doc/lightning-invoice.7.md +++ b/doc/lightning-invoice.7.md @@ -17,7 +17,7 @@ lightning daemon can use to pay this invoice. This token includes a invoice, if any exists. The *msatoshi* parameter can be the string "any", which creates an -invoice that can be paid with any amount. Otherwise it is in +invoice that can be paid with any amount. Otherwise it is a positive value in millisatoshi precision; it can be a whole number, or a whole number ending in *msat* or *sat*, or a number with three decimal places ending in *sat*, or a number with 1 to 11 decimal places ending in *btc*. diff --git a/lightningd/invoice.c b/lightningd/invoice.c index f91fe9386f8f..5196b98423c6 100644 --- a/lightningd/invoice.c +++ b/lightningd/invoice.c @@ -826,22 +826,24 @@ static struct route_info **unpack_routes(const tal_t *ctx, } #endif /* DEVELOPER */ -static struct command_result *param_msat_or_any(struct command *cmd, - const char *name, - const char *buffer, - const jsmntok_t *tok, - struct amount_msat **msat) +static struct command_result *param_positive_msat_or_any(struct command *cmd, + const char *name, + const char *buffer, + const jsmntok_t *tok, + struct amount_msat **msat) { if (json_tok_streq(buffer, tok, "any")) { *msat = NULL; return NULL; } *msat = tal(cmd, struct amount_msat); - if (parse_amount_msat(*msat, buffer + tok->start, tok->end - tok->start)) + if (parse_amount_msat(*msat, buffer + tok->start, tok->end - tok->start) + && !amount_msat_eq(**msat, AMOUNT_MSAT(0))) return NULL; return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "'%s' should be millisatoshis or 'any', not '%.*s'", + "'%s' should be positive millisatoshis or 'any'," + " not '%.*s'", name, tok->end - tok->start, buffer + tok->start); @@ -963,7 +965,7 @@ static struct command_result *json_invoice(struct command *cmd, info->cmd = cmd; if (!param(cmd, buffer, params, - p_req("msatoshi", param_msat_or_any, &msatoshi_val), + p_req("msatoshi", param_positive_msat_or_any, &msatoshi_val), p_req("label", param_label, &info->label), p_req("description", param_escaped_string, &desc_val), p_opt_def("expiry", param_time, &expiry, 3600*24*7), diff --git a/tests/test_invoices.py b/tests/test_invoices.py index 83809e7dc491..ca8a92c70bee 100644 --- a/tests/test_invoices.py +++ b/tests/test_invoices.py @@ -59,6 +59,26 @@ def test_invoice(node_factory, chainparams): l2.rpc.invoice(4294967295, 'inv3', '?') +def test_invoice_zeroval(node_factory): + """A zero value invoice is unpayable, did you mean 'any'?""" + l1 = node_factory.get_node() + + with pytest.raises(RpcError, match=r"positive .* not '0'"): + l1.rpc.invoice(0, 'inv', '?') + + with pytest.raises(RpcError, match=r"positive .* not '0msat'"): + l1.rpc.invoice('0msat', 'inv', '?') + + with pytest.raises(RpcError, match=r"positive .* not '0sat'"): + l1.rpc.invoice('0sat', 'inv', '?') + + with pytest.raises(RpcError, match=r"positive .* not '0.00000000btc'"): + l1.rpc.invoice('0.00000000btc', 'inv', '?') + + with pytest.raises(RpcError, match=r"positive .* not '0.00000000000btc'"): + l1.rpc.invoice('0.00000000000btc', 'inv', '?') + + def test_invoice_weirdstring(node_factory): l1 = node_factory.get_node()