mirror of
https://github.com/muun/recovery.git
synced 2025-11-12 06:50:18 -05:00
2409 lines
57 KiB
Go
2409 lines
57 KiB
Go
package operation
|
|
|
|
import (
|
|
"math"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/muun/libwallet/fees"
|
|
)
|
|
|
|
var defaultFeeWindow = &FeeWindow{
|
|
TargetedFees: func() map[uint]float64 {
|
|
fees := make(map[uint]float64)
|
|
fees[1] = 10.0
|
|
fees[2] = 5.0
|
|
fees[3] = 1.0
|
|
return fees
|
|
}(),
|
|
}
|
|
|
|
var feeWindowFailureTests = &FeeWindow{
|
|
TargetedFees: func() map[uint]float64 {
|
|
fees := make(map[uint]float64)
|
|
fees[1] = 100.0
|
|
fees[5] = 50.0
|
|
fees[10] = .25
|
|
return fees
|
|
}(),
|
|
}
|
|
|
|
var defaultNTS = &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 1_000_000,
|
|
SizeInVByte: 100,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 0,
|
|
}
|
|
|
|
func TestAnalyzeOnChain(t *testing.T) {
|
|
testCases := []struct {
|
|
desc string
|
|
nts *NextTransactionSize
|
|
payment *PaymentToAddress
|
|
expected *PaymentAnalysis
|
|
err bool
|
|
}{
|
|
{
|
|
desc: "success",
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 10000,
|
|
FeeRateInSatsPerVByte: 1,
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 10000,
|
|
FeeInSat: 100,
|
|
TotalInSat: 10100,
|
|
},
|
|
},
|
|
{
|
|
desc: "take fee from amount",
|
|
payment: &PaymentToAddress{
|
|
AmountInSat: 1_000_000,
|
|
TakeFeeFromAmount: true,
|
|
FeeRateInSatsPerVByte: 1,
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 999_900,
|
|
FeeInSat: 100,
|
|
TotalInSat: 1_000_000,
|
|
},
|
|
},
|
|
{
|
|
desc: "zero amount",
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 0,
|
|
FeeRateInSatsPerVByte: 1,
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusAmountTooSmall,
|
|
},
|
|
},
|
|
{
|
|
desc: "zero amount using TFFA",
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 0,
|
|
FeeRateInSatsPerVByte: 1,
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusAmountTooSmall,
|
|
},
|
|
},
|
|
{
|
|
desc: "sub dust amount",
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 100,
|
|
FeeRateInSatsPerVByte: 1,
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusAmountTooSmall,
|
|
},
|
|
},
|
|
{
|
|
desc: "sub dust amount using TFFA",
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 100,
|
|
FeeRateInSatsPerVByte: 1,
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusAmountTooSmall,
|
|
},
|
|
},
|
|
{
|
|
desc: "amount greater than balance",
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 1_000_000_000,
|
|
FeeRateInSatsPerVByte: 1,
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusAmountGreaterThanBalance,
|
|
TotalInSat: 1_000_000_000,
|
|
},
|
|
},
|
|
{
|
|
desc: "valid amount plus selected fee",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 10_000,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 0,
|
|
},
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 5000,
|
|
FeeRateInSatsPerVByte: 10,
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 5000,
|
|
FeeInSat: 2400,
|
|
TotalInSat: 7400,
|
|
},
|
|
},
|
|
{
|
|
desc: "valid amount but unpayable because of fee",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 10_000,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 0,
|
|
},
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 9000,
|
|
FeeRateInSatsPerVByte: 10,
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusUnpayable,
|
|
AmountInSat: 9000,
|
|
FeeInSat: 2400,
|
|
TotalInSat: 11400,
|
|
},
|
|
},
|
|
{
|
|
desc: "valid amount but unpayable with any fee",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 10_000,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 0,
|
|
},
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 9999,
|
|
FeeRateInSatsPerVByte: 10,
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusUnpayable,
|
|
AmountInSat: 9999,
|
|
FeeInSat: 2400,
|
|
TotalInSat: 12399,
|
|
},
|
|
},
|
|
{
|
|
desc: "valid amount plus fee using TFFA",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 10_000,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 0,
|
|
},
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 10_000,
|
|
FeeRateInSatsPerVByte: 10,
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 7600,
|
|
FeeInSat: 2400,
|
|
TotalInSat: 10_000,
|
|
},
|
|
},
|
|
{
|
|
desc: "invalid amount using TFFA",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 10_000,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 0,
|
|
},
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 9900,
|
|
FeeRateInSatsPerVByte: 10,
|
|
},
|
|
err: true,
|
|
},
|
|
{
|
|
desc: "valid amount but unpayable because of fee using TFFA",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 1_000,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 0,
|
|
},
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 1000,
|
|
FeeRateInSatsPerVByte: 10,
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusUnpayable,
|
|
AmountInSat: 0,
|
|
FeeInSat: 2400,
|
|
TotalInSat: 1000,
|
|
},
|
|
},
|
|
{
|
|
desc: "valid amount but unpayable with any fee using TFFA",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 600,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 0,
|
|
},
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 600,
|
|
FeeRateInSatsPerVByte: 10,
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusUnpayable,
|
|
AmountInSat: 0,
|
|
FeeInSat: 2400,
|
|
TotalInSat: 600,
|
|
},
|
|
},
|
|
{
|
|
desc: "success with debt > 0",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: defaultNTS.SizeProgression,
|
|
ExpectedDebtInSat: 10000,
|
|
},
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 10000,
|
|
FeeRateInSatsPerVByte: 1,
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 10000,
|
|
FeeInSat: 100,
|
|
TotalInSat: 10100,
|
|
},
|
|
},
|
|
{
|
|
desc: "take fee from amount success with debt > 0",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: defaultNTS.SizeProgression,
|
|
ExpectedDebtInSat: 10000,
|
|
},
|
|
payment: &PaymentToAddress{
|
|
AmountInSat: 990_000,
|
|
TakeFeeFromAmount: true,
|
|
FeeRateInSatsPerVByte: 1,
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 989_900,
|
|
FeeInSat: 100,
|
|
TotalInSat: 990_000,
|
|
},
|
|
},
|
|
{
|
|
desc: "amount greater than balance because debt > 0",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: defaultNTS.SizeProgression,
|
|
ExpectedDebtInSat: 10000,
|
|
},
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 999_900,
|
|
FeeRateInSatsPerVByte: 1,
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusAmountGreaterThanBalance,
|
|
TotalInSat: 999_900,
|
|
},
|
|
},
|
|
{
|
|
desc: "unpayable because debt > 0",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: defaultNTS.SizeProgression,
|
|
ExpectedDebtInSat: 10000,
|
|
},
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 989_500,
|
|
FeeRateInSatsPerVByte: 10,
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusUnpayable,
|
|
AmountInSat: 989_500,
|
|
FeeInSat: 1000,
|
|
TotalInSat: 990_500,
|
|
},
|
|
},
|
|
{
|
|
desc: "amount greater than balance using TFFA because debt > 0",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: defaultNTS.SizeProgression,
|
|
ExpectedDebtInSat: 10000,
|
|
},
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 999_900,
|
|
FeeRateInSatsPerVByte: 1,
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusAmountGreaterThanBalance,
|
|
TotalInSat: 999_900,
|
|
},
|
|
},
|
|
{
|
|
desc: "unpayable using TFFA because debt > 0",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 10_000,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 8000,
|
|
},
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 2000,
|
|
FeeRateInSatsPerVByte: 100,
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusUnpayable,
|
|
AmountInSat: 0,
|
|
FeeInSat: 24000,
|
|
TotalInSat: 2000,
|
|
},
|
|
},
|
|
{
|
|
desc: "unpayable using TFFA because amount < DUST",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 10_000,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 7400,
|
|
},
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 2600,
|
|
FeeRateInSatsPerVByte: 10,
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusUnpayable,
|
|
AmountInSat: 200,
|
|
FeeInSat: 2400,
|
|
TotalInSat: 2600,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tC := range testCases {
|
|
t.Run(tC.desc, func(t *testing.T) {
|
|
|
|
var analyzer *PaymentAnalyzer
|
|
if tC.nts != nil {
|
|
analyzer = NewPaymentAnalyzer(defaultFeeWindow, tC.nts)
|
|
} else {
|
|
analyzer = NewPaymentAnalyzer(defaultFeeWindow, defaultNTS)
|
|
}
|
|
|
|
analysis, err := analyzer.ToAddress(tC.payment)
|
|
|
|
if err == nil && tC.err {
|
|
t.Fatal("expected analysis to error")
|
|
}
|
|
if err != nil && !tC.err {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(analysis, tC.expected) {
|
|
t.Fatalf("analysis does not match expected, got %+v, expected %+v", analysis, tC.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAnalyzeOnChainValidAmountButUnpayableWithAnyFee(t *testing.T) {
|
|
analyzer := NewPaymentAnalyzer(defaultFeeWindow, &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 10_000,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 0,
|
|
})
|
|
|
|
analysis, err := analyzer.ToAddress(&PaymentToAddress{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 9999,
|
|
FeeRateInSatsPerVByte: 10,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if analysis.Status != AnalysisStatusUnpayable {
|
|
t.Fatal("expected analysis to be unpayable")
|
|
}
|
|
if analysis.FeeInSat != 2400 {
|
|
t.Fatalf("expected fee to be %v, but got %v", 2400, analysis.FeeInSat)
|
|
}
|
|
if analysis.TotalInSat != 12399 {
|
|
t.Fatalf("expected total to be %v, but got %v", 12399, analysis.TotalInSat)
|
|
}
|
|
|
|
analysis, err = analyzer.ToAddress(&PaymentToAddress{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 9999,
|
|
FeeRateInSatsPerVByte: 0.25,
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if analysis.Status != AnalysisStatusUnpayable {
|
|
t.Fatal("expected analysis to be unpayable")
|
|
}
|
|
if analysis.FeeInSat != 60 {
|
|
t.Fatalf("expected fee to be %v, but got %v", 60, analysis.FeeInSat)
|
|
}
|
|
if analysis.TotalInSat != 10059 {
|
|
t.Fatalf("expected total to be %v, but got %v", 10059, analysis.TotalInSat)
|
|
}
|
|
}
|
|
|
|
func TestAnalyzeOnChainValidAmountButUnpayableWithAnyFeeUsingTFFA(t *testing.T) {
|
|
analyzer := NewPaymentAnalyzer(defaultFeeWindow, &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 600,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 0,
|
|
})
|
|
|
|
analysis, err := analyzer.ToAddress(&PaymentToAddress{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 600,
|
|
FeeRateInSatsPerVByte: 10,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if analysis.Status != AnalysisStatusUnpayable {
|
|
t.Fatal("expected analysis to be unpayable")
|
|
}
|
|
if analysis.FeeInSat != 2400 {
|
|
t.Fatalf("expected fee to be %v, but got %v", 2400, analysis.FeeInSat)
|
|
}
|
|
if analysis.TotalInSat != 600 {
|
|
t.Fatalf("expected total to be %v, but got %v", 600, analysis.TotalInSat)
|
|
}
|
|
|
|
analysis, err = analyzer.ToAddress(&PaymentToAddress{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 600,
|
|
FeeRateInSatsPerVByte: 0.25,
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if analysis.Status != AnalysisStatusUnpayable {
|
|
t.Fatal("expected analysis to be unpayable")
|
|
}
|
|
if analysis.FeeInSat != 60 {
|
|
t.Fatalf("expected fee to be %v, but got %v", 60, analysis.FeeInSat)
|
|
}
|
|
if analysis.AmountInSat != 540 {
|
|
t.Fatalf("expected amount to be %v, but got %v", 540, analysis.TotalInSat)
|
|
}
|
|
if analysis.TotalInSat != 600 {
|
|
t.Fatalf("expected total to be %v, but got %v", 600, analysis.TotalInSat)
|
|
}
|
|
}
|
|
|
|
func TestAnalyzeOffChain(t *testing.T) {
|
|
testCases := []struct {
|
|
desc string
|
|
feeWindow *FeeWindow
|
|
nts *NextTransactionSize
|
|
payment *PaymentToInvoice
|
|
expected *PaymentAnalysis
|
|
err bool
|
|
}{
|
|
{
|
|
desc: "swap with amount too small (zero funds)",
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 0,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 0,
|
|
DebtType: fees.DebtTypeNone,
|
|
DebtAmount: 0,
|
|
RoutingFee: 0,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusAmountTooSmall,
|
|
},
|
|
},
|
|
{
|
|
desc: "swap with amount too small (negative funds)",
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: -10,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 0,
|
|
DebtType: fees.DebtTypeNone,
|
|
DebtAmount: 0,
|
|
RoutingFee: 0,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusAmountTooSmall,
|
|
},
|
|
},
|
|
{
|
|
desc: "swap with amount greater than balance",
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 5_000_000,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 5_000_000,
|
|
DebtType: fees.DebtTypeNone,
|
|
DebtAmount: 0,
|
|
RoutingFee: 0,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusAmountGreaterThanBalance,
|
|
TotalInSat: 5_000_000,
|
|
},
|
|
},
|
|
{
|
|
desc: "swap with integrity error",
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 500_000,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 500_001,
|
|
DebtType: fees.DebtTypeNone,
|
|
DebtAmount: 0,
|
|
RoutingFee: 0,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
err: true,
|
|
},
|
|
{
|
|
desc: "swap COLLECT with integrity error",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 5_000_000,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 1000,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 500_000,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 501_001,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 1000,
|
|
RoutingFee: 0,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
err: true,
|
|
},
|
|
{
|
|
desc: "swap with valid amount with no routing fee",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 10_000,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 0,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 100,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 1100,
|
|
DebtType: fees.DebtTypeNone,
|
|
DebtAmount: 0,
|
|
RoutingFee: 0,
|
|
OutputPadding: 1000,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 100,
|
|
FeeInSat: 240,
|
|
TotalInSat: 1340,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 1100,
|
|
DebtType: fees.DebtTypeNone,
|
|
DebtAmount: 0,
|
|
RoutingFee: 0,
|
|
OutputPadding: 1000,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap with valid amount",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 10_000,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 0,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 100,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 1101,
|
|
DebtType: fees.DebtTypeNone,
|
|
DebtAmount: 0,
|
|
RoutingFee: 1,
|
|
OutputPadding: 1000,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 100,
|
|
FeeInSat: 240,
|
|
TotalInSat: 1341,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 1101,
|
|
DebtType: fees.DebtTypeNone,
|
|
DebtAmount: 0,
|
|
RoutingFee: 1,
|
|
OutputPadding: 1000,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap with valid amount with 1-conf",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 10_000,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 0,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 100,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 1101,
|
|
DebtType: fees.DebtTypeNone,
|
|
DebtAmount: 0,
|
|
RoutingFee: 1,
|
|
OutputPadding: 1000,
|
|
ConfirmationsNeeded: 1,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 100,
|
|
FeeInSat: 2400,
|
|
TotalInSat: 3501,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 1101,
|
|
DebtType: fees.DebtTypeNone,
|
|
DebtAmount: 0,
|
|
RoutingFee: 1,
|
|
OutputPadding: 1000,
|
|
ConfirmationsNeeded: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap with valid FIXED amount using TFFA",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 10_000,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 0,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 10_000,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 10_000,
|
|
DebtType: fees.DebtTypeNone,
|
|
DebtAmount: 0,
|
|
RoutingFee: 0,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
err: true,
|
|
},
|
|
{
|
|
desc: "swap valid amount but unpayable because of fee",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 10_000,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 0,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 500,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 10000,
|
|
DebtType: fees.DebtTypeNone,
|
|
DebtAmount: 0,
|
|
RoutingFee: 1000,
|
|
OutputPadding: 8500,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusUnpayable,
|
|
AmountInSat: 500,
|
|
FeeInSat: 240,
|
|
TotalInSat: 10240,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 10000,
|
|
DebtType: fees.DebtTypeNone,
|
|
DebtAmount: 0,
|
|
RoutingFee: 1000,
|
|
OutputPadding: 8500,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap valid amount but unpayable because of output padding",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 10_000,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 0,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 500,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 11000,
|
|
DebtType: fees.DebtTypeNone,
|
|
DebtAmount: 0,
|
|
RoutingFee: 1000,
|
|
OutputPadding: 9500,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusUnpayable,
|
|
AmountInSat: 500,
|
|
FeeInSat: 240,
|
|
TotalInSat: 11240,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 11000,
|
|
DebtType: fees.DebtTypeNone,
|
|
DebtAmount: 0,
|
|
RoutingFee: 1000,
|
|
OutputPadding: 9500,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap LEND success",
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 100,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 0,
|
|
DebtType: fees.DebtTypeLend,
|
|
DebtAmount: 100,
|
|
RoutingFee: 1,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 100,
|
|
FeeInSat: 0,
|
|
TotalInSat: 101,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 0,
|
|
DebtType: fees.DebtTypeLend,
|
|
DebtAmount: 100,
|
|
RoutingFee: 1,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap LEND success with no routing fee",
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 100,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 0,
|
|
DebtType: fees.DebtTypeLend,
|
|
DebtAmount: 100,
|
|
RoutingFee: 0,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 100,
|
|
FeeInSat: 0,
|
|
TotalInSat: 100,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 0,
|
|
DebtType: fees.DebtTypeLend,
|
|
DebtAmount: 100,
|
|
RoutingFee: 0,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap LEND amount greater than balance",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 1_000,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 900,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 200,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 0,
|
|
DebtType: fees.DebtTypeLend,
|
|
DebtAmount: 200,
|
|
RoutingFee: 0,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusAmountGreaterThanBalance,
|
|
TotalInSat: 200,
|
|
},
|
|
},
|
|
{
|
|
desc: "swap LEND unpayable",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 1_000,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 900,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 100,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 0,
|
|
DebtType: fees.DebtTypeLend,
|
|
DebtAmount: 100,
|
|
RoutingFee: 10,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusUnpayable,
|
|
AmountInSat: 100,
|
|
FeeInSat: 0,
|
|
TotalInSat: 110,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 0,
|
|
DebtType: fees.DebtTypeLend,
|
|
DebtAmount: 100,
|
|
RoutingFee: 10,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap full COLLECT success with no routing fee",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 10401,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 3000,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 5000,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 8000,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 3000,
|
|
RoutingFee: 0,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 5000,
|
|
FeeInSat: 240,
|
|
TotalInSat: 5240,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 8000,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 3000,
|
|
RoutingFee: 0,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap full COLLECT success",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 10401,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 3000,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 5000,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 8001,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 3000,
|
|
RoutingFee: 1,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 5000,
|
|
FeeInSat: 240,
|
|
TotalInSat: 5241,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 8001,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 3000,
|
|
RoutingFee: 1,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap full COLLECT success with 1-conf",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 10401,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 3000,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 5000,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 8001,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 3000,
|
|
RoutingFee: 1,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 1,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 5000,
|
|
FeeInSat: 2400,
|
|
TotalInSat: 7401,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 8001,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 3000,
|
|
RoutingFee: 1,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap COLLECT output amount greater than balance (aka unpayable)",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 10401,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 3000,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 7400,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 10500,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 3000,
|
|
RoutingFee: 100,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusUnpayable,
|
|
AmountInSat: 7400,
|
|
FeeInSat: 240,
|
|
TotalInSat: 7740,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 10500,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 3000,
|
|
RoutingFee: 100,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap COLLECT amount plus fee greater than balance (aka unpayable)",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 10500,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 3000,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 7400,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 10500,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 3000,
|
|
RoutingFee: 100,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusUnpayable,
|
|
AmountInSat: 7400,
|
|
FeeInSat: 240,
|
|
TotalInSat: 7740,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 10500,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 3000,
|
|
RoutingFee: 100,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap partial COLLECT success with no routing fee",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 20401,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 8000,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 12160,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 14160,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 2000,
|
|
RoutingFee: 0,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 12160,
|
|
FeeInSat: 240,
|
|
TotalInSat: 12400,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 14160,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 2000,
|
|
RoutingFee: 0,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap partial COLLECT success",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 20401,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 8000,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 12160,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 14161,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 2000,
|
|
RoutingFee: 1,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 12160,
|
|
FeeInSat: 240,
|
|
TotalInSat: 12401,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 14161,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 2000,
|
|
RoutingFee: 1,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap partial COLLECT for unpayable amount",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 20401,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 8000,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 12401,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 14401,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 2000,
|
|
RoutingFee: 0,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusUnpayable,
|
|
AmountInSat: 12401,
|
|
FeeInSat: 240,
|
|
TotalInSat: 12641,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 14401,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 2000,
|
|
RoutingFee: 0,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap amountless invoice as LEND swap success",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 20401,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 3000,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 12401,
|
|
BestRouteFees: []fees.BestRouteFees{
|
|
{
|
|
MaxCapacity: 100000,
|
|
FeeProportionalMillionth: 1,
|
|
FeeBase: 10,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &fees.FundingOutputPolicies{
|
|
MaximumDebt: 1000000,
|
|
PotentialCollect: 0,
|
|
MaxAmountFor0Conf: 1000000,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 12401,
|
|
FeeInSat: 0,
|
|
TotalInSat: 12411,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 0,
|
|
DebtType: fees.DebtTypeLend,
|
|
DebtAmount: 12411,
|
|
RoutingFee: 10,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap amountless invoice as COLLECT swap success",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 20401,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 3000,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 12401,
|
|
BestRouteFees: []fees.BestRouteFees{
|
|
{
|
|
MaxCapacity: 100000,
|
|
FeeProportionalMillionth: 1,
|
|
FeeBase: 10,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &fees.FundingOutputPolicies{
|
|
MaximumDebt: 100,
|
|
PotentialCollect: 1010,
|
|
MaxAmountFor0Conf: 1000000,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 12401,
|
|
FeeInSat: 240,
|
|
TotalInSat: 12651,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 13421,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 1010,
|
|
RoutingFee: 10,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap amountless invoice as NON DEBT swap success",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 20401,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 3000,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 12401,
|
|
BestRouteFees: []fees.BestRouteFees{
|
|
{
|
|
MaxCapacity: 100000,
|
|
FeeProportionalMillionth: 1,
|
|
FeeBase: 10,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &fees.FundingOutputPolicies{
|
|
MaximumDebt: 0,
|
|
PotentialCollect: 0,
|
|
MaxAmountFor0Conf: 1000000,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 12401,
|
|
FeeInSat: 240,
|
|
TotalInSat: 12651,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 12411,
|
|
DebtType: fees.DebtTypeNone,
|
|
DebtAmount: 0,
|
|
RoutingFee: 10,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap amountless invoice one-conf",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 20401,
|
|
SizeInVByte: 240,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 3000,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 12401,
|
|
BestRouteFees: []fees.BestRouteFees{
|
|
{
|
|
MaxCapacity: 100000,
|
|
FeeProportionalMillionth: 1,
|
|
FeeBase: 10,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &fees.FundingOutputPolicies{
|
|
MaximumDebt: 0,
|
|
PotentialCollect: 0,
|
|
MaxAmountFor0Conf: 0,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 12401,
|
|
FeeInSat: 2400,
|
|
TotalInSat: 14811,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 12411,
|
|
DebtType: fees.DebtTypeNone,
|
|
DebtAmount: 0,
|
|
RoutingFee: 10,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap amountless invoice TFFA one-conf",
|
|
feeWindow: feeWindowFailureTests,
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 369_310,
|
|
SizeInVByte: 253,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 26876,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 342434,
|
|
BestRouteFees: []fees.BestRouteFees{
|
|
{
|
|
MaxCapacity: 1000000,
|
|
FeeProportionalMillionth: 1051,
|
|
FeeBase: 4,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &fees.FundingOutputPolicies{
|
|
MaximumDebt: 123124,
|
|
PotentialCollect: 20686,
|
|
MaxAmountFor0Conf: 0,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 316798,
|
|
FeeInSat: 25300,
|
|
TotalInSat: 342434,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 337820,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 20686,
|
|
RoutingFee: 336,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap amountless invoice TFFA with invalid amount",
|
|
feeWindow: feeWindowFailureTests,
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 36_931,
|
|
SizeInVByte: 253,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 26876,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 10054,
|
|
BestRouteFees: []fees.BestRouteFees{
|
|
{
|
|
MaxCapacity: 100000,
|
|
FeeProportionalMillionth: 1051,
|
|
FeeBase: 4,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &fees.FundingOutputPolicies{
|
|
MaximumDebt: 123124,
|
|
PotentialCollect: 20686,
|
|
MaxAmountFor0Conf: 193437,
|
|
},
|
|
},
|
|
err: true,
|
|
},
|
|
{
|
|
desc: "swap amountless invoice TFFA cant pay combined fee",
|
|
feeWindow: &FeeWindow{
|
|
TargetedFees: func() map[uint]float64 {
|
|
fees := make(map[uint]float64)
|
|
fees[1] = 100.0
|
|
fees[5] = 50.0
|
|
fees[10] = 3.1
|
|
return fees
|
|
}(),
|
|
},
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 800,
|
|
SizeInVByte: 253,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 0,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 800,
|
|
BestRouteFees: []fees.BestRouteFees{
|
|
{
|
|
MaxCapacity: 100000,
|
|
FeeProportionalMillionth: 1051,
|
|
FeeBase: 40,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &fees.FundingOutputPolicies{
|
|
MaximumDebt: 123124,
|
|
PotentialCollect: 20686,
|
|
MaxAmountFor0Conf: 193437,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusUnpayable,
|
|
AmountInSat: 0,
|
|
FeeInSat: 785,
|
|
TotalInSat: 1585,
|
|
},
|
|
},
|
|
{
|
|
desc: "swap amountless invoice TFFA (failure 1)",
|
|
feeWindow: feeWindowFailureTests,
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 36_931,
|
|
SizeInVByte: 253,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 26876,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 10055,
|
|
BestRouteFees: []fees.BestRouteFees{
|
|
{
|
|
MaxCapacity: 100000,
|
|
FeeProportionalMillionth: 1051,
|
|
FeeBase: 4,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &fees.FundingOutputPolicies{
|
|
MaximumDebt: 123124,
|
|
PotentialCollect: 20686,
|
|
MaxAmountFor0Conf: 193437,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 9977,
|
|
FeeInSat: 64,
|
|
TotalInSat: 10055,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 30677,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 20686,
|
|
RoutingFee: 14,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap amountless invoice TFFA (failure 2)",
|
|
feeWindow: feeWindowFailureTests,
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 83880,
|
|
SizeInVByte: 495,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 73590,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 10290,
|
|
BestRouteFees: []fees.BestRouteFees{
|
|
{
|
|
MaxCapacity: 100000,
|
|
FeeProportionalMillionth: 297,
|
|
FeeBase: 4,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &fees.FundingOutputPolicies{
|
|
MaximumDebt: 76410,
|
|
PotentialCollect: 33292,
|
|
MaxAmountFor0Conf: 254235,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 10159,
|
|
FeeInSat: 124,
|
|
TotalInSat: 10290,
|
|
SwapFees: &fees.SwapFees{
|
|
OutputAmount: 43458,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 33292,
|
|
RoutingFee: 7,
|
|
OutputPadding: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap amountless invoice TFFA (failure 3)",
|
|
feeWindow: feeWindowFailureTests,
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 1597,
|
|
SizeInVByte: 391,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 1511,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 86,
|
|
BestRouteFees: []fees.BestRouteFees{
|
|
{
|
|
MaxCapacity: 100000,
|
|
FeeProportionalMillionth: 875,
|
|
FeeBase: 2,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &fees.FundingOutputPolicies{
|
|
MaximumDebt: 148489,
|
|
PotentialCollect: 759,
|
|
MaxAmountFor0Conf: 163704,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusUnpayable,
|
|
FeeInSat: 98,
|
|
TotalInSat: 86,
|
|
},
|
|
},
|
|
{
|
|
desc: "swap amountless invoice TFFA (failure 4)",
|
|
feeWindow: feeWindowFailureTests,
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 1016,
|
|
SizeInVByte: 409,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 776,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 240,
|
|
BestRouteFees: []fees.BestRouteFees{
|
|
{
|
|
MaxCapacity: 100000,
|
|
FeeProportionalMillionth: 468,
|
|
FeeBase: 4,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &fees.FundingOutputPolicies{
|
|
MaximumDebt: 149224,
|
|
PotentialCollect: 246,
|
|
MaxAmountFor0Conf: 243320,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusUnpayable,
|
|
AmountInSat: 133,
|
|
FeeInSat: 103,
|
|
TotalInSat: 240,
|
|
SwapFees: &fees.SwapFees{
|
|
RoutingFee: 4,
|
|
OutputPadding: 163,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 246,
|
|
OutputAmount: 546,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap amountless invoice TFFA (failure 5)",
|
|
feeWindow: feeWindowFailureTests,
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 1843,
|
|
SizeInVByte: 475,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 458,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 1385,
|
|
BestRouteFees: []fees.BestRouteFees{
|
|
{
|
|
MaxCapacity: 100000,
|
|
FeeProportionalMillionth: 1417,
|
|
FeeBase: 2,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &fees.FundingOutputPolicies{
|
|
MaximumDebt: 149542,
|
|
PotentialCollect: 222,
|
|
MaxAmountFor0Conf: 223421,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 1263,
|
|
FeeInSat: 119,
|
|
TotalInSat: 1385,
|
|
SwapFees: &fees.SwapFees{
|
|
RoutingFee: 3,
|
|
OutputPadding: 0,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 222,
|
|
OutputAmount: 1488,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap amountless invoice TFFA (failure 6)",
|
|
feeWindow: feeWindowFailureTests,
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 6658,
|
|
SizeInVByte: 262,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 6539,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 119,
|
|
BestRouteFees: []fees.BestRouteFees{
|
|
{
|
|
MaxCapacity: 100000,
|
|
FeeProportionalMillionth: 1991,
|
|
FeeBase: 1,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &fees.FundingOutputPolicies{
|
|
MaximumDebt: 143461,
|
|
PotentialCollect: 6422,
|
|
MaxAmountFor0Conf: 254856,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 52,
|
|
FeeInSat: 66,
|
|
TotalInSat: 119,
|
|
SwapFees: &fees.SwapFees{
|
|
RoutingFee: 1,
|
|
OutputPadding: 0,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 6422,
|
|
OutputAmount: 6475,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap amountless invoice TFFA (failure 7)",
|
|
feeWindow: feeWindowFailureTests,
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 8169,
|
|
SizeInVByte: 301,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 5619,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 2550,
|
|
BestRouteFees: []fees.BestRouteFees{
|
|
{
|
|
MaxCapacity: 100000,
|
|
FeeProportionalMillionth: 1009,
|
|
FeeBase: 2,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &fees.FundingOutputPolicies{
|
|
MaximumDebt: 144381,
|
|
PotentialCollect: 1140,
|
|
MaxAmountFor0Conf: 210617,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 2470,
|
|
FeeInSat: 76,
|
|
TotalInSat: 2550,
|
|
SwapFees: &fees.SwapFees{
|
|
RoutingFee: 4,
|
|
OutputPadding: 0,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 1140,
|
|
OutputAmount: 3614,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap amountless invoice TFFA (failure 8)",
|
|
feeWindow: feeWindowFailureTests,
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 46785,
|
|
SizeInVByte: 365,
|
|
},
|
|
{
|
|
AmountInSat: 99948,
|
|
SizeInVByte: 857,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 40471,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 59477,
|
|
BestRouteFees: []fees.BestRouteFees{
|
|
{
|
|
MaxCapacity: 100000,
|
|
FeeProportionalMillionth: 655,
|
|
FeeBase: 1,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &fees.FundingOutputPolicies{
|
|
MaximumDebt: 109529,
|
|
PotentialCollect: 13892,
|
|
MaxAmountFor0Conf: 258399,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 59223,
|
|
FeeInSat: 215,
|
|
TotalInSat: 59477,
|
|
SwapFees: &fees.SwapFees{
|
|
RoutingFee: 39,
|
|
OutputPadding: 0,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 13892,
|
|
OutputAmount: 73154,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap amountless invoice TFFA (failure 9)",
|
|
feeWindow: feeWindowFailureTests,
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 10087,
|
|
SizeInVByte: 430,
|
|
},
|
|
{
|
|
AmountInSat: 156784,
|
|
SizeInVByte: 851,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 84117,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 72667,
|
|
BestRouteFees: []fees.BestRouteFees{
|
|
{
|
|
MaxCapacity: 100000,
|
|
FeeProportionalMillionth: 1146,
|
|
FeeBase: 3,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &fees.FundingOutputPolicies{
|
|
MaximumDebt: 65883,
|
|
PotentialCollect: 77393,
|
|
MaxAmountFor0Conf: 240440,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 72369,
|
|
FeeInSat: 213,
|
|
TotalInSat: 72667,
|
|
SwapFees: &fees.SwapFees{
|
|
RoutingFee: 85,
|
|
OutputPadding: 0,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 77393,
|
|
OutputAmount: 149847,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap amountless invoice TFFA (failure 10)",
|
|
feeWindow: feeWindowFailureTests,
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 117197,
|
|
SizeInVByte: 333,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 18225,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 98972,
|
|
BestRouteFees: []fees.BestRouteFees{
|
|
{
|
|
MaxCapacity: 100000,
|
|
FeeProportionalMillionth: 890,
|
|
FeeBase: 1,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &fees.FundingOutputPolicies{
|
|
MaximumDebt: 131775,
|
|
PotentialCollect: 16109,
|
|
MaxAmountFor0Conf: 182743,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 98800,
|
|
FeeInSat: 84,
|
|
TotalInSat: 98972,
|
|
SwapFees: &fees.SwapFees{
|
|
RoutingFee: 88,
|
|
OutputPadding: 0,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 16109,
|
|
OutputAmount: 114997,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap amountless invoice TFFA (failure 11)",
|
|
feeWindow: feeWindowFailureTests,
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 3165,
|
|
SizeInVByte: 402,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 2812,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 353,
|
|
BestRouteFees: []fees.BestRouteFees{
|
|
{
|
|
MaxCapacity: 100000,
|
|
FeeProportionalMillionth: 1461,
|
|
FeeBase: 3,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &fees.FundingOutputPolicies{
|
|
MaximumDebt: 147188,
|
|
PotentialCollect: 222,
|
|
MaxAmountFor0Conf: 164563,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusUnpayable,
|
|
AmountInSat: 249,
|
|
FeeInSat: 101,
|
|
TotalInSat: 353,
|
|
SwapFees: &fees.SwapFees{
|
|
RoutingFee: 3,
|
|
OutputPadding: 72,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 222,
|
|
OutputAmount: 546,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap amountless invoice TFFA (failure 11 bis)",
|
|
feeWindow: feeWindowFailureTests,
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 3165,
|
|
SizeInVByte: 402,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 2740,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 425,
|
|
BestRouteFees: []fees.BestRouteFees{
|
|
{
|
|
MaxCapacity: 100000,
|
|
FeeProportionalMillionth: 1461,
|
|
FeeBase: 3,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &fees.FundingOutputPolicies{
|
|
MaximumDebt: 147188,
|
|
PotentialCollect: 222,
|
|
MaxAmountFor0Conf: 164563,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 321,
|
|
FeeInSat: 101,
|
|
TotalInSat: 425,
|
|
SwapFees: &fees.SwapFees{
|
|
RoutingFee: 3,
|
|
OutputPadding: 0,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 222,
|
|
OutputAmount: 546,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap amountless invoice TFFA (failure 12)",
|
|
feeWindow: feeWindowFailureTests,
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 86343,
|
|
SizeInVByte: 358,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 32639,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 53704,
|
|
BestRouteFees: []fees.BestRouteFees{
|
|
{
|
|
MaxCapacity: 100000,
|
|
FeeProportionalMillionth: 1457,
|
|
FeeBase: 2,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &fees.FundingOutputPolicies{
|
|
MaximumDebt: 117361,
|
|
PotentialCollect: 27161,
|
|
MaxAmountFor0Conf: 287685,
|
|
},
|
|
},
|
|
// In this scenario there's 1 sat that is lost and will be burn in fees to the miner
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusOk,
|
|
AmountInSat: 53534,
|
|
FeeInSat: 90,
|
|
TotalInSat: 53704,
|
|
SwapFees: &fees.SwapFees{
|
|
RoutingFee: 79,
|
|
OutputPadding: 0,
|
|
DebtType: fees.DebtTypeCollect,
|
|
DebtAmount: 27161,
|
|
OutputAmount: 80774,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap amountless invoice TFFA (failure 13)",
|
|
feeWindow: feeWindowFailureTests,
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 500,
|
|
SizeInVByte: 209,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 0,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 500,
|
|
BestRouteFees: []fees.BestRouteFees{
|
|
{
|
|
MaxCapacity: 100000,
|
|
FeeProportionalMillionth: 1000,
|
|
FeeBase: 1,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &fees.FundingOutputPolicies{
|
|
MaximumDebt: 0,
|
|
PotentialCollect: 0,
|
|
MaxAmountFor0Conf: math.MaxInt64,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusUnpayable,
|
|
AmountInSat: 446,
|
|
FeeInSat: 53,
|
|
TotalInSat: 500,
|
|
SwapFees: &fees.SwapFees{
|
|
RoutingFee: 1,
|
|
OutputPadding: 99,
|
|
DebtType: fees.DebtTypeNone,
|
|
DebtAmount: 0,
|
|
OutputAmount: 546,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "swap amountless invoice TFFA with no best route for amount",
|
|
feeWindow: feeWindowFailureTests,
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 117197,
|
|
SizeInVByte: 333,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 18225,
|
|
},
|
|
payment: &PaymentToInvoice{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 98972,
|
|
BestRouteFees: []fees.BestRouteFees{
|
|
{
|
|
MaxCapacity: 90000,
|
|
FeeProportionalMillionth: 890,
|
|
FeeBase: 1,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &fees.FundingOutputPolicies{
|
|
MaximumDebt: 131775,
|
|
PotentialCollect: 16109,
|
|
MaxAmountFor0Conf: 182743,
|
|
},
|
|
},
|
|
expected: &PaymentAnalysis{
|
|
Status: AnalysisStatusUnpayable,
|
|
FeeInSat: 84,
|
|
TotalInSat: 99056,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tC := range testCases {
|
|
t.Run(tC.desc, func(t *testing.T) {
|
|
|
|
nts := defaultNTS
|
|
if tC.nts != nil {
|
|
nts = tC.nts
|
|
}
|
|
|
|
feeWindow := defaultFeeWindow
|
|
if tC.feeWindow != nil {
|
|
feeWindow = tC.feeWindow
|
|
}
|
|
|
|
analyzer := NewPaymentAnalyzer(feeWindow, nts)
|
|
analysis, err := analyzer.ToInvoice(tC.payment)
|
|
|
|
if err == nil && tC.err {
|
|
t.Fatal("expected analysis to error")
|
|
}
|
|
if err != nil && !tC.err {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(analysis, tC.expected) {
|
|
t.Fatalf(
|
|
"analysis does not match expected\n analysis: got %+v, expected %+v\nswapfees: got %+v, expected %+v",
|
|
analysis,
|
|
tC.expected,
|
|
analysis.SwapFees,
|
|
tC.expected.SwapFees,
|
|
)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMaxFeeRate(t *testing.T) {
|
|
testCases := []struct {
|
|
desc string
|
|
nts *NextTransactionSize
|
|
payment *PaymentToAddress
|
|
expected float64
|
|
}{
|
|
{
|
|
desc: "small amount with one coin",
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 10_000,
|
|
},
|
|
expected: 9_900,
|
|
},
|
|
{
|
|
desc: "take fee from amount one coin",
|
|
payment: &PaymentToAddress{
|
|
AmountInSat: 1_000_000,
|
|
TakeFeeFromAmount: true,
|
|
},
|
|
expected: 10_000,
|
|
},
|
|
{
|
|
desc: "zero amount",
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 0,
|
|
},
|
|
expected: 10_000,
|
|
},
|
|
{
|
|
desc: "zero amount using TFFA",
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 0,
|
|
},
|
|
expected: 0,
|
|
},
|
|
{
|
|
desc: "amount greater than balance",
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 1_000_000_000,
|
|
},
|
|
expected: 0,
|
|
},
|
|
{
|
|
desc: "small amount with one coin and debt > 0",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: defaultNTS.SizeProgression,
|
|
ExpectedDebtInSat: 10_000,
|
|
},
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 10_000,
|
|
},
|
|
expected: 9_800,
|
|
},
|
|
{
|
|
desc: "take fee from amount success with debt > 0",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: defaultNTS.SizeProgression,
|
|
ExpectedDebtInSat: 10_000,
|
|
},
|
|
payment: &PaymentToAddress{
|
|
AmountInSat: 990_000,
|
|
TakeFeeFromAmount: true,
|
|
},
|
|
expected: 9_900,
|
|
},
|
|
{
|
|
desc: "amount greater than balance because debt > 0",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: defaultNTS.SizeProgression,
|
|
ExpectedDebtInSat: 10_000,
|
|
},
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 999_900,
|
|
},
|
|
expected: 0,
|
|
},
|
|
{
|
|
desc: "needs 2 coins to spend",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 10_000,
|
|
SizeInVByte: 240,
|
|
},
|
|
{
|
|
AmountInSat: 20_000,
|
|
SizeInVByte: 450,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 0,
|
|
},
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 11_000,
|
|
},
|
|
expected: 20,
|
|
},
|
|
{
|
|
desc: "needs 2 coins to spend with debt",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 10_000,
|
|
SizeInVByte: 240,
|
|
},
|
|
{
|
|
AmountInSat: 20_000,
|
|
SizeInVByte: 400,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 8_000,
|
|
},
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: false,
|
|
AmountInSat: 11_000,
|
|
FeeRateInSatsPerVByte: 0,
|
|
},
|
|
expected: 2.5,
|
|
},
|
|
{
|
|
desc: "TFFA needs 2 coins to spend with debt",
|
|
nts: &NextTransactionSize{
|
|
SizeProgression: []SizeForAmount{
|
|
{
|
|
AmountInSat: 10_000,
|
|
SizeInVByte: 240,
|
|
},
|
|
{
|
|
AmountInSat: 20_000,
|
|
SizeInVByte: 400,
|
|
},
|
|
},
|
|
ExpectedDebtInSat: 8_000,
|
|
},
|
|
payment: &PaymentToAddress{
|
|
TakeFeeFromAmount: true,
|
|
AmountInSat: 12_000,
|
|
},
|
|
expected: 30,
|
|
},
|
|
}
|
|
|
|
for _, tC := range testCases {
|
|
t.Run(tC.desc, func(t *testing.T) {
|
|
|
|
var analyzer *PaymentAnalyzer
|
|
if tC.nts != nil {
|
|
analyzer = NewPaymentAnalyzer(defaultFeeWindow, tC.nts)
|
|
} else {
|
|
analyzer = NewPaymentAnalyzer(defaultFeeWindow, defaultNTS)
|
|
}
|
|
|
|
maxFeeRate := analyzer.MaxFeeRateToAddress(tC.payment)
|
|
if maxFeeRate != tC.expected {
|
|
t.Fatalf("Max fee rate %v != %v", maxFeeRate, tC.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|