swaps_service_test.go raw
1 package swaps
2
3 import (
4 "crypto/rand"
5 "crypto/sha256"
6 "encoding/hex"
7 "testing"
8 "time"
9
10 "github.com/btcsuite/btcd/btcec/v2"
11 "github.com/btcsuite/btcd/btcec/v2/ecdsa"
12 "github.com/btcsuite/btcd/chaincfg"
13 "github.com/btcsuite/btcd/chaincfg/chainhash"
14 "github.com/lightningnetwork/lnd/lnwire"
15 "github.com/lightningnetwork/lnd/zpay32"
16 "github.com/stretchr/testify/assert"
17 "github.com/stretchr/testify/require"
18 )
19
20 func makeTestInvoice(t *testing.T, paymentHash [32]byte, amountMsat uint64) string {
21 t.Helper()
22
23 privKey, err := btcec.NewPrivateKey()
24 require.NoError(t, err)
25
26 invoice, err := zpay32.NewInvoice(
27 &chaincfg.MainNetParams,
28 paymentHash,
29 time.Now(),
30 zpay32.Amount(lnwire.MilliSatoshi(amountMsat)),
31 zpay32.Description("test swap invoice"),
32 )
33 require.NoError(t, err)
34
35 encoded, err := invoice.Encode(zpay32.MessageSigner{
36 SignCompact: func(msg []byte) ([]byte, error) {
37 return ecdsa.SignCompact(privKey, chainhash.HashB(msg), true), nil
38 },
39 })
40 require.NoError(t, err)
41
42 return encoded
43 }
44
45 func makeTestPaymentHash(t *testing.T) ([32]byte, string) {
46 t.Helper()
47
48 preimage := make([]byte, 32)
49 _, err := rand.Read(preimage)
50 require.NoError(t, err)
51 paymentHash := sha256.Sum256(preimage)
52 return paymentHash, hex.EncodeToString(paymentHash[:])
53 }
54
55 func TestVerifySwapOutInvoice(t *testing.T) {
56 paymentHash, paymentHashHex := makeTestPaymentHash(t)
57
58 t.Run("accepts invoice with matching payment hash and amount", func(t *testing.T) {
59 invoice := makeTestInvoice(t, paymentHash, 100_000_000)
60
61 sendAmountSat, err := verifySwapOutInvoice(invoice, paymentHashHex, 100_000)
62 require.NoError(t, err)
63 assert.Equal(t, uint64(100_000), sendAmountSat)
64 })
65
66 t.Run("rejects invoice with different payment hash", func(t *testing.T) {
67 otherPaymentHash, _ := makeTestPaymentHash(t)
68 invoice := makeTestInvoice(t, otherPaymentHash, 100_000_000)
69
70 _, err := verifySwapOutInvoice(invoice, paymentHashHex, 100_000)
71 require.Error(t, err)
72 assert.Contains(t, err.Error(), "does not match swap payment hash")
73 })
74
75 t.Run("rejects invoice exceeding maximum amount", func(t *testing.T) {
76 invoice := makeTestInvoice(t, paymentHash, 100_001_000)
77
78 _, err := verifySwapOutInvoice(invoice, paymentHashHex, 100_000)
79 require.Error(t, err)
80 assert.Contains(t, err.Error(), "exceeds maximum expected amount")
81 })
82
83 t.Run("rejects invoice without an amount", func(t *testing.T) {
84 invoice := makeTestInvoice(t, paymentHash, 0)
85
86 _, err := verifySwapOutInvoice(invoice, paymentHashHex, 100_000)
87 require.Error(t, err)
88 assert.Contains(t, err.Error(), "does not have an amount")
89 })
90
91 t.Run("rejects unparseable invoice", func(t *testing.T) {
92 _, err := verifySwapOutInvoice("lnbc1notaninvoice", paymentHashHex, 100_000)
93 require.Error(t, err)
94 })
95 }
96
97 func TestCalculateMaxSwapOutSendAmountSat(t *testing.T) {
98 // 100_000 requested + 300 claim fee + 500 lockup fee = 100_800,
99 // marked up by 0.5% boltz + 1% alby fee on the invoice amount:
100 // ceil(100_800 / 0.985) = 102_336, plus 10 sat tolerance
101 assert.Equal(t, uint64(102_346), calculateMaxSwapOutSendAmountSat(100_000, 0.5, 500, 300))
102
103 // with a 0% boltz fee only the alby fee percentage applies:
104 // ceil(100_800 / 0.99) = 101_819, plus 10 sat tolerance
105 assert.Equal(t, uint64(101_829), calculateMaxSwapOutSendAmountSat(100_000, 0, 500, 300))
106
107 // invalid fee rates of 100% or more are never accepted
108 assert.Equal(t, uint64(0), calculateMaxSwapOutSendAmountSat(100_000, 100, 500, 300))
109 }
110