mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-15 12:50:28 +02:00
Add resolveMaxSwapFee helper that computes an effective maximum swap fee from --max_swap_fee_sat and --max_swap_fee_ppm CLI flags. When both are set the tighter (lower) cap wins. The helper also rejects early if the server-quoted fee already exceeds the resolved cap.
213 lines
4.6 KiB
Go
213 lines
4.6 KiB
Go
package main
|
|
|
|
import (
|
|
"math"
|
|
"testing"
|
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
|
"github.com/lightninglabs/loop/looprpc"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestResolveMaxSwapFee tests the fee cap resolution logic for static address
|
|
// loop-ins covering sat-only, ppm-only, combined caps, and edge cases.
|
|
func TestResolveMaxSwapFee(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
reqAmt int64
|
|
quotedAmt int64
|
|
quotedFee int64
|
|
satIsSet bool
|
|
maxSat uint64
|
|
ppmIsSet bool
|
|
maxPpm uint64
|
|
wantFee btcutil.Amount
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "no override uses quoted fee",
|
|
reqAmt: 500_000,
|
|
quotedFee: 1_000,
|
|
wantFee: 1_000,
|
|
},
|
|
{
|
|
name: "sat cap above quote forwards cap",
|
|
reqAmt: 500_000,
|
|
quotedFee: 1_000,
|
|
satIsSet: true,
|
|
maxSat: 2_000,
|
|
wantFee: 2_000,
|
|
},
|
|
{
|
|
name: "sat cap below quote returns error",
|
|
reqAmt: 500_000,
|
|
quotedFee: 1_000,
|
|
satIsSet: true,
|
|
maxSat: 500,
|
|
wantErr: "quoted swap fee 1000 sat exceeds maximum " +
|
|
"allowed 500 sat",
|
|
},
|
|
{
|
|
name: "ppm uses QuotedAmt when req amt is 0",
|
|
quotedAmt: 1_000_000,
|
|
quotedFee: 800,
|
|
ppmIsSet: true,
|
|
maxPpm: 1_000,
|
|
wantFee: 1_000,
|
|
},
|
|
{
|
|
name: "both flags set picks tighter sat cap",
|
|
reqAmt: 1_000_000,
|
|
quotedFee: 500,
|
|
satIsSet: true,
|
|
maxSat: 600,
|
|
ppmIsSet: true,
|
|
maxPpm: 2_000, // = 2000 sat
|
|
wantFee: 600,
|
|
},
|
|
{
|
|
name: "both flags set picks tighter ppm cap",
|
|
reqAmt: 1_000_000,
|
|
quotedFee: 500,
|
|
satIsSet: true,
|
|
maxSat: 5_000,
|
|
ppmIsSet: true,
|
|
maxPpm: 1_000, // = 1000 sat
|
|
wantFee: 1_000,
|
|
},
|
|
{
|
|
name: "both flags set quote exceeds tighter cap",
|
|
reqAmt: 1_000_000,
|
|
quotedFee: 700,
|
|
satIsSet: true,
|
|
maxSat: 5_000,
|
|
ppmIsSet: true,
|
|
maxPpm: 500, // = 500 sat
|
|
wantErr: "quoted swap fee 700 sat exceeds maximum " +
|
|
"allowed 500 sat",
|
|
},
|
|
{
|
|
name: "ppm cap rounds to zero returns error",
|
|
reqAmt: 100,
|
|
quotedFee: 1,
|
|
ppmIsSet: true,
|
|
maxPpm: 1,
|
|
wantErr: "ppm cap rounds to 0 sat for swap amount " +
|
|
"100; use --max_swap_fee_sat instead",
|
|
},
|
|
{
|
|
name: "explicit zero sat cap rejected",
|
|
reqAmt: 500_000,
|
|
satIsSet: true,
|
|
maxSat: 0,
|
|
wantErr: "--max_swap_fee_sat must be positive",
|
|
},
|
|
{
|
|
name: "explicit zero ppm cap rejected",
|
|
reqAmt: 500_000,
|
|
ppmIsSet: true,
|
|
maxPpm: 0,
|
|
wantErr: "--max_swap_fee_ppm must be positive",
|
|
},
|
|
{
|
|
name: "sat cap above hard limit rejected",
|
|
reqAmt: 500_000,
|
|
satIsSet: true,
|
|
maxSat: 10_000_001,
|
|
wantErr: "--max_swap_fee_sat must be <= 10000000",
|
|
},
|
|
{
|
|
name: "max ppm on huge amount defers to tighter " +
|
|
"sat cap",
|
|
reqAmt: math.MaxInt64,
|
|
quotedFee: 1_000,
|
|
satIsSet: true,
|
|
maxSat: 2_000,
|
|
ppmIsSet: true,
|
|
maxPpm: feePPMBase,
|
|
wantFee: 2_000,
|
|
},
|
|
{
|
|
name: "ppm above hard limit rejected",
|
|
reqAmt: math.MaxInt64,
|
|
ppmIsSet: true,
|
|
maxPpm: feePPMBase + 1,
|
|
wantErr: "--max_swap_fee_ppm must be <= 1000000",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
quoteReq := &looprpc.QuoteRequest{
|
|
Amt: tc.reqAmt,
|
|
}
|
|
quote := &looprpc.InQuoteResponse{
|
|
SwapFeeSat: tc.quotedFee,
|
|
QuotedAmt: tc.quotedAmt,
|
|
}
|
|
|
|
got, err := resolveMaxSwapFee(
|
|
quoteReq, quote,
|
|
tc.satIsSet, tc.maxSat,
|
|
tc.ppmIsSet, tc.maxPpm,
|
|
)
|
|
if tc.wantErr != "" {
|
|
require.ErrorContains(t, err, tc.wantErr)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.wantFee, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestPPMCapForSwapAmount checks that ppm-to-sat conversion rounds down as
|
|
// expected and remains correct near the int64 swap-amount limit.
|
|
func TestPPMCapForSwapAmount(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
swapAmt int64
|
|
maxFeePpm uint64
|
|
want uint64
|
|
}{
|
|
{
|
|
name: "one sat at 100 percent",
|
|
swapAmt: 1,
|
|
maxFeePpm: feePPMBase,
|
|
want: 1,
|
|
},
|
|
{
|
|
name: "rounds down fractional sat",
|
|
swapAmt: 100,
|
|
maxFeePpm: 1,
|
|
want: 0,
|
|
},
|
|
{
|
|
name: "simple proportional cap",
|
|
swapAmt: 1_000_000,
|
|
maxFeePpm: 1_000,
|
|
want: 1_000,
|
|
},
|
|
{
|
|
name: "max int64 at 100 percent",
|
|
swapAmt: math.MaxInt64,
|
|
maxFeePpm: feePPMBase,
|
|
want: uint64(math.MaxInt64),
|
|
},
|
|
{
|
|
name: "max int64 at one ppm",
|
|
swapAmt: math.MaxInt64,
|
|
maxFeePpm: 1,
|
|
want: uint64(math.MaxInt64) / feePPMBase,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
require.Equal(
|
|
t, tc.want,
|
|
ppmCapForSwapAmount(tc.swapAmt, tc.maxFeePpm),
|
|
)
|
|
})
|
|
}
|
|
}
|