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