mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-18 13:08:28 +02:00
staticaddr: unit test CalculateWithdrawalTxValues
This commit is contained in:
parent
880694c8df
commit
780584993b
1 changed files with 238 additions and 0 deletions
|
|
@ -4,11 +4,19 @@ import (
|
|||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightninglabs/loop/staticaddr/deposit"
|
||||
"github.com/lightninglabs/loop/swapserverrpc"
|
||||
"github.com/lightninglabs/loop/test"
|
||||
"github.com/lightningnetwork/lnd/funding"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
|
@ -368,3 +376,233 @@ func TestSignMusig2Tx_MissingOutpointInDepositMap(t *testing.T) {
|
|||
// Expect an error indicating the missing outpoint.
|
||||
require.ErrorContains(t, err, "tx outpoint not in deposit index map")
|
||||
}
|
||||
|
||||
// TestCalculateWithdrawalTxValues tests various edge cases in withdrawal
|
||||
// transaction value calculations.
|
||||
func TestCalculateWithdrawalTxValues(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a taproot address for withdrawal.
|
||||
taprootAddr, err := btcutil.NewAddressTaproot(
|
||||
make([]byte, 32), &chaincfg.RegressionNetParams,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Standard fee rate for testing.
|
||||
feeRate := chainfee.SatPerKWeight(1000)
|
||||
|
||||
// Helper to create deposits.
|
||||
createDeposit := func(value btcutil.Amount, idx uint32) *deposit.Deposit {
|
||||
hash := chainhash.Hash{}
|
||||
hash[0] = byte(idx)
|
||||
return &deposit.Deposit{
|
||||
OutPoint: wire.OutPoint{
|
||||
Hash: hash,
|
||||
Index: idx,
|
||||
},
|
||||
Value: value,
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
deposits []*deposit.Deposit
|
||||
localAmount btcutil.Amount
|
||||
feeRate chainfee.SatPerKWeight
|
||||
withdrawAddr btcutil.Address
|
||||
commitmentType lnrpc.CommitmentType
|
||||
expectedErr string
|
||||
expectDustFee bool // change is dust, given to miners
|
||||
}{
|
||||
{
|
||||
name: "neither address nor commitment type specified",
|
||||
deposits: []*deposit.Deposit{
|
||||
createDeposit(100000, 0),
|
||||
},
|
||||
localAmount: 0,
|
||||
feeRate: feeRate,
|
||||
withdrawAddr: nil,
|
||||
commitmentType: lnrpc.CommitmentType_UNKNOWN_COMMITMENT_TYPE,
|
||||
expectedErr: "either address or commitment type must be specified",
|
||||
},
|
||||
{
|
||||
name: "change is dust - given to miners",
|
||||
deposits: []*deposit.Deposit{
|
||||
createDeposit(100000, 0),
|
||||
},
|
||||
// Set localAmount such that change after feeWithChange
|
||||
// would be dust, but change after feeWithoutChange >= 0.
|
||||
// This triggers case: change-feeWithoutChange >= 0
|
||||
localAmount: 99300, // Leaves ~700 sats which is dust
|
||||
feeRate: feeRate,
|
||||
withdrawAddr: taprootAddr,
|
||||
commitmentType: lnrpc.CommitmentType_UNKNOWN_COMMITMENT_TYPE,
|
||||
expectedErr: "",
|
||||
expectDustFee: true,
|
||||
},
|
||||
{
|
||||
name: "insufficient funds after dust and fee",
|
||||
deposits: []*deposit.Deposit{
|
||||
createDeposit(1000, 0),
|
||||
},
|
||||
localAmount: 900,
|
||||
feeRate: feeRate,
|
||||
withdrawAddr: taprootAddr,
|
||||
commitmentType: lnrpc.CommitmentType_UNKNOWN_COMMITMENT_TYPE,
|
||||
expectedErr: "doesn't cover for fees",
|
||||
},
|
||||
{
|
||||
name: "negative change after fees",
|
||||
deposits: []*deposit.Deposit{
|
||||
createDeposit(10000, 0),
|
||||
},
|
||||
localAmount: 15000,
|
||||
feeRate: feeRate,
|
||||
withdrawAddr: taprootAddr,
|
||||
commitmentType: lnrpc.CommitmentType_UNKNOWN_COMMITMENT_TYPE,
|
||||
expectedErr: "doesn't cover for fees",
|
||||
},
|
||||
{
|
||||
name: "min channel size guard - below minimum",
|
||||
deposits: []*deposit.Deposit{
|
||||
createDeposit(funding.MinChanFundingSize-10, 0),
|
||||
},
|
||||
localAmount: 0,
|
||||
feeRate: feeRate,
|
||||
withdrawAddr: nil,
|
||||
commitmentType: lnrpc.CommitmentType_SIMPLE_TAPROOT,
|
||||
expectedErr: "is lower than the minimum channel " +
|
||||
"funding size",
|
||||
},
|
||||
{
|
||||
name: "min channel size guard - exactly minimum",
|
||||
deposits: []*deposit.Deposit{
|
||||
createDeposit(funding.MinChanFundingSize+1000, 0),
|
||||
},
|
||||
localAmount: 0,
|
||||
feeRate: feeRate,
|
||||
withdrawAddr: nil,
|
||||
commitmentType: lnrpc.CommitmentType_SIMPLE_TAPROOT,
|
||||
expectedErr: "",
|
||||
},
|
||||
{
|
||||
name: "withdrawal amount below dust limit",
|
||||
deposits: []*deposit.Deposit{
|
||||
createDeposit(400, 0),
|
||||
},
|
||||
localAmount: 0,
|
||||
feeRate: feeRate,
|
||||
withdrawAddr: taprootAddr,
|
||||
commitmentType: lnrpc.CommitmentType_UNKNOWN_COMMITMENT_TYPE,
|
||||
expectedErr: "below dust limit",
|
||||
},
|
||||
{
|
||||
name: "change higher than input value",
|
||||
deposits: []*deposit.Deposit{
|
||||
createDeposit(10000, 0),
|
||||
createDeposit(5000, 1),
|
||||
},
|
||||
localAmount: 5000,
|
||||
feeRate: chainfee.SatPerKWeight(100),
|
||||
withdrawAddr: taprootAddr,
|
||||
commitmentType: lnrpc.CommitmentType_UNKNOWN_COMMITMENT_TYPE,
|
||||
expectedErr: "change amount",
|
||||
},
|
||||
{
|
||||
name: "successful withdrawal with change",
|
||||
deposits: []*deposit.Deposit{
|
||||
createDeposit(100000, 0),
|
||||
},
|
||||
localAmount: 50000,
|
||||
feeRate: feeRate,
|
||||
withdrawAddr: taprootAddr,
|
||||
commitmentType: lnrpc.CommitmentType_UNKNOWN_COMMITMENT_TYPE,
|
||||
expectedErr: "",
|
||||
},
|
||||
{
|
||||
name: "successful withdrawal no change",
|
||||
deposits: []*deposit.Deposit{
|
||||
createDeposit(100000, 0),
|
||||
},
|
||||
localAmount: 0,
|
||||
feeRate: feeRate,
|
||||
withdrawAddr: taprootAddr,
|
||||
commitmentType: lnrpc.CommitmentType_UNKNOWN_COMMITMENT_TYPE,
|
||||
expectedErr: "",
|
||||
},
|
||||
{
|
||||
name: "successful channel open above min size",
|
||||
deposits: []*deposit.Deposit{
|
||||
createDeposit(funding.MinChanFundingSize*2, 0),
|
||||
},
|
||||
localAmount: 0,
|
||||
feeRate: feeRate,
|
||||
withdrawAddr: nil,
|
||||
commitmentType: lnrpc.CommitmentType_SIMPLE_TAPROOT,
|
||||
expectedErr: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
withdrawAmt, changeAmt, err := CalculateWithdrawalTxValues(
|
||||
tc.deposits, tc.localAmount, tc.feeRate,
|
||||
tc.withdrawAddr, tc.commitmentType,
|
||||
)
|
||||
|
||||
if tc.expectedErr != "" {
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, tc.expectedErr)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, withdrawAmt, btcutil.Amount(0))
|
||||
require.GreaterOrEqual(t, changeAmt, btcutil.Amount(0))
|
||||
|
||||
// Verify that withdrawal amount meets dust threshold.
|
||||
dustLimit := lnwallet.DustLimitForSize(input.P2TRSize)
|
||||
require.GreaterOrEqual(t, withdrawAmt, dustLimit)
|
||||
|
||||
// If this is a channel open, verify min channel size.
|
||||
if tc.commitmentType != lnrpc.CommitmentType_UNKNOWN_COMMITMENT_TYPE {
|
||||
require.GreaterOrEqual(
|
||||
t, withdrawAmt, funding.MinChanFundingSize,
|
||||
)
|
||||
}
|
||||
|
||||
// If expecting dust to be given to miners, verify
|
||||
// changeAmt is 0.
|
||||
if tc.expectDustFee {
|
||||
require.Equal(t, btcutil.Amount(0), changeAmt,
|
||||
"change should be 0 when dust is given to miners")
|
||||
}
|
||||
|
||||
// Verify total accounting: inputs = withdrawal + change + fees.
|
||||
totalInputs := btcutil.Amount(0)
|
||||
for _, d := range tc.deposits {
|
||||
totalInputs += d.Value
|
||||
}
|
||||
|
||||
hasChange := changeAmt > 0
|
||||
weight, err := WithdrawalTxWeight(
|
||||
len(tc.deposits), tc.withdrawAddr,
|
||||
tc.commitmentType, hasChange,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
fee := tc.feeRate.FeeForWeight(weight)
|
||||
|
||||
// When dust is given to miners, the "fee" includes both
|
||||
// the transaction fee and the dust amount.
|
||||
if tc.expectDustFee {
|
||||
// Total should equal withdrawal + implicit fee (including dust)
|
||||
implicitFee := totalInputs - withdrawAmt - changeAmt
|
||||
require.Greater(t, implicitFee, fee,
|
||||
"implicit fee should be greater than tx fee when dust is given to miners")
|
||||
} else {
|
||||
require.Equal(t, totalInputs, withdrawAmt+changeAmt+fee)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue