mirror of
https://github.com/ACINQ/eclair.git
synced 2025-02-23 06:35:11 +01:00
Handle invoice with amounts larger than 1btc (#2684)
For amounts that are multiples of 1btc, we shouldn't use a multiplier and should directly encode this amount.
This commit is contained in:
parent
5ab84712bf
commit
42dfa9f535
2 changed files with 48 additions and 28 deletions
|
@ -477,22 +477,23 @@ object Bolt11Invoice {
|
|||
/**
|
||||
* @return the unit allowing for the shortest representation possible
|
||||
*/
|
||||
def unit(amount: MilliSatoshi): Char = amount.toLong * 10 match { // 1 milli-satoshis == 10 pico-bitcoin
|
||||
case pico if pico % 1000 > 0 => 'p'
|
||||
case pico if pico % 1000000 > 0 => 'n'
|
||||
case pico if pico % 1000000000 > 0 => 'u'
|
||||
case _ => 'm'
|
||||
def unit(amount: MilliSatoshi): Option[Char] = amount.toLong * 10 match { // 1 milli-satoshis == 10 pico-bitcoin
|
||||
case pico if pico % 1_000 > 0 => Some('p')
|
||||
case pico if pico % 1_000_000 > 0 => Some('n')
|
||||
case pico if pico % 1_000_000_000 > 0 => Some('u')
|
||||
case pico if pico % 1_000_000_000_000L > 0 => Some('m')
|
||||
case _ => None
|
||||
}
|
||||
|
||||
def decode(input: String): Try[Option[MilliSatoshi]] =
|
||||
(input match {
|
||||
case "" => Success(None)
|
||||
case a if a.last == 'p' && a.dropRight(1).last != '0' => Failure(new IllegalArgumentException("invalid sub-millisatoshi precision"))
|
||||
case a if a.last == 'p' => Success(Some(MilliSatoshi(a.dropRight(1).toLong / 10L))) // 1 pico-bitcoin == 0.1 milli-satoshis
|
||||
case a if a.last == 'n' => Success(Some(MilliSatoshi(a.dropRight(1).toLong * 100L)))
|
||||
case a if a.last == 'u' => Success(Some(MilliSatoshi(a.dropRight(1).toLong * 100000L)))
|
||||
case a if a.last == 'm' => Success(Some(MilliSatoshi(a.dropRight(1).toLong * 100000000L)))
|
||||
case a => Success(Some(MilliSatoshi(a.toLong * 100000000000L)))
|
||||
case a if a.last == 'p' => Success(Some(MilliSatoshi(a.dropRight(1).toLong / 10))) // 1 pico-bitcoin == 0.1 milli-satoshis
|
||||
case a if a.last == 'n' => Success(Some(MilliSatoshi(a.dropRight(1).toLong * 100)))
|
||||
case a if a.last == 'u' => Success(Some(MilliSatoshi(a.dropRight(1).toLong * 100_000)))
|
||||
case a if a.last == 'm' => Success(Some(MilliSatoshi(a.dropRight(1).toLong * 100_000_000)))
|
||||
case a => Success(Some(MilliSatoshi(a.toLong * 100_000_000_000L)))
|
||||
}).map {
|
||||
case None => None
|
||||
case Some(MilliSatoshi(0)) => None
|
||||
|
@ -500,12 +501,15 @@ object Bolt11Invoice {
|
|||
}
|
||||
|
||||
def encode(amount: Option[MilliSatoshi]): String = {
|
||||
(amount: @unchecked) match {
|
||||
amount match {
|
||||
case None => ""
|
||||
case Some(amt) if unit(amt) == 'p' => s"${amt.toLong * 10L}p" // 1 pico-bitcoin == 0.1 milli-satoshis
|
||||
case Some(amt) if unit(amt) == 'n' => s"${amt.toLong / 100L}n"
|
||||
case Some(amt) if unit(amt) == 'u' => s"${amt.toLong / 100000L}u"
|
||||
case Some(amt) if unit(amt) == 'm' => s"${amt.toLong / 100000000L}m"
|
||||
case Some(amt) => unit(amt) match {
|
||||
case Some('p') => s"${amt.toLong * 10}p" // 1 pico-bitcoin == 0.1 milli-satoshis
|
||||
case Some('n') => s"${amt.toLong / 100}n"
|
||||
case Some('u') => s"${amt.toLong / 100_000}u"
|
||||
case Some('m') => s"${amt.toLong / 100_000_000}m"
|
||||
case _ => s"${amt.toLong / 100_000_000_000L}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,17 +83,20 @@ class Bolt11InvoiceSpec extends AnyFunSuite {
|
|||
}
|
||||
|
||||
test("check minimal unit is used") {
|
||||
assert('p' == Amount.unit(1 msat))
|
||||
assert('p' == Amount.unit(99 msat))
|
||||
assert('n' == Amount.unit(100 msat))
|
||||
assert('p' == Amount.unit(101 msat))
|
||||
assert('n' == Amount.unit((1 sat).toMilliSatoshi))
|
||||
assert('u' == Amount.unit((100 sat).toMilliSatoshi))
|
||||
assert('n' == Amount.unit((101 sat).toMilliSatoshi))
|
||||
assert('u' == Amount.unit((1155400 sat).toMilliSatoshi))
|
||||
assert('m' == Amount.unit((1 millibtc).toMilliSatoshi))
|
||||
assert('m' == Amount.unit((10 millibtc).toMilliSatoshi))
|
||||
assert('m' == Amount.unit((1 btc).toMilliSatoshi))
|
||||
assert(Amount.unit(1 msat).contains('p'))
|
||||
assert(Amount.unit(99 msat).contains('p'))
|
||||
assert(Amount.unit(100 msat).contains('n'))
|
||||
assert(Amount.unit(101 msat).contains('p'))
|
||||
assert(Amount.unit((1 sat).toMilliSatoshi).contains('n'))
|
||||
assert(Amount.unit((100 sat).toMilliSatoshi).contains('u'))
|
||||
assert(Amount.unit((101 sat).toMilliSatoshi).contains('n'))
|
||||
assert(Amount.unit((1155400 sat).toMilliSatoshi).contains('u'))
|
||||
assert(Amount.unit((1 millibtc).toMilliSatoshi).contains('m'))
|
||||
assert(Amount.unit((10 millibtc).toMilliSatoshi).contains('m'))
|
||||
assert(Amount.unit((1 btc).toMilliSatoshi).isEmpty)
|
||||
assert(Amount.unit((1.1 btc).toMilliSatoshi).contains('m'))
|
||||
assert(Amount.unit((2 btc).toMilliSatoshi).isEmpty)
|
||||
assert(Amount.unit((10 btc).toMilliSatoshi).isEmpty)
|
||||
}
|
||||
|
||||
test("decode empty amount") {
|
||||
|
@ -470,13 +473,26 @@ class Bolt11InvoiceSpec extends AnyFunSuite {
|
|||
assert(Bolt11Invoice.fromString(input.toUpperCase()).get.toString == input)
|
||||
}
|
||||
|
||||
test("Pay 1 BTC without multiplier") {
|
||||
test("Pay 1 BTC with multiplier") {
|
||||
val ref = "lnbc1000m1pdkmqhusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5n2ees808r98m0rh4472yyth0c5fptzcxmexcjznrzmq8xald0cgqdqsf4ujqarfwqsxymmccqp2pv37ezvhth477nu0yhhjlcry372eef57qmldhreqnr0kx82jkupp3n7nw42u3kdyyjskdr8jhjy2vugr3skdmy8ersft36969xplkxsp2v7c58"
|
||||
val Success(invoice) = Bolt11Invoice.fromString(ref)
|
||||
assert(invoice.amount_opt.contains(100000000000L msat))
|
||||
assert(invoice.amount_opt.contains(100_000_000_000L msat))
|
||||
assert(features2bits(invoice.features) == BitVector.empty)
|
||||
}
|
||||
|
||||
test("Pay 1 BTC without multiplier") {
|
||||
val testCases = Seq(
|
||||
100_000_000_000L.msat -> "lnbcrt11pj8wdh7sp5p2052f28az75s3eauqjskcwrzrjujf7rfqspsvk6hgppywytrdzspp5670t00mwakdy0l5w3lnw4rhdgnv4ctep974am6jp0zma627fhdfsdqqxqyjw5qcqp29qyysgqh2ce2cmptj33l35a9pt2l603aa34jpj8p35s302l0lhuujmtmkghrmkadv456h3rpsxjpschnpt5ugzltqsjtauvnfy799aufapav6gp202th5",
|
||||
100_000_000_000_000L.msat -> "lnbcrt10001pj8wd3rsp5cv2vayxnm7d4783r0477rstzpkl7n4ftmalgu9v8akzf0nhqrs3qpp5vednenalh0v6gzxpzrdxf9cepv4274vc0tax5389cjq0zv9qvs9sdqqxqyjw5qcqp29qyysgqk5f8um72jlnw9unjltdgxw9e2fvec0cxq05tcwuen2jpu42q4p9pt2djk2ysu62nkpg49km59wrexm0wt3msevz53fr2tfnqxf5sdnqpu8th97",
|
||||
)
|
||||
testCases.foreach { case (amount, ref) =>
|
||||
val Success(invoice) = Bolt11Invoice.fromString(ref)
|
||||
assert(invoice.amount_opt.contains(amount))
|
||||
val encoded = invoice.toString
|
||||
assert(encoded == ref)
|
||||
}
|
||||
}
|
||||
|
||||
test("supported invoice features") {
|
||||
val nodeParams = TestConstants.Alice.nodeParams.copy(features = Features(knownFeatures.map(f => f -> Optional).toMap))
|
||||
case class Result(allowMultiPart: Boolean, requirePaymentSecret: Boolean, areSupported: Boolean) // "supported" is based on the "it's okay to be odd" rule
|
||||
|
|
Loading…
Add table
Reference in a new issue