package loopd import ( "context" "testing" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btclog/v2" "github.com/lightninglabs/loop/looprpc" "github.com/lightninglabs/loop/staticaddr/address" "github.com/lightninglabs/loop/staticaddr/deposit" "github.com/lightninglabs/loop/staticaddr/script" mock_lnd "github.com/lightninglabs/loop/test" "github.com/lightningnetwork/lnd/lnwallet" "github.com/stretchr/testify/require" ) type staticAddrDepositStore struct { allDeposits []*deposit.Deposit byOutpoint map[string]*deposit.Deposit } // CreateDeposit implements deposit.Store for static address server tests. func (s *staticAddrDepositStore) CreateDeposit(context.Context, *deposit.Deposit) error { return nil } // UpdateDeposit implements deposit.Store for static address server tests. func (s *staticAddrDepositStore) UpdateDeposit(context.Context, *deposit.Deposit) error { return nil } // GetDeposit implements deposit.Store for static address server tests. func (s *staticAddrDepositStore) GetDeposit(context.Context, deposit.ID) (*deposit.Deposit, error) { return nil, nil } // DepositForOutpoint returns the deposit for the requested outpoint. func (s *staticAddrDepositStore) DepositForOutpoint(_ context.Context, outpoint string) (*deposit.Deposit, error) { if deposit, ok := s.byOutpoint[outpoint]; ok { return deposit, nil } return nil, deposit.ErrDepositNotFound } // AllDeposits returns all deposits seeded into the test store. func (s *staticAddrDepositStore) AllDeposits(context.Context) ( []*deposit.Deposit, error) { return s.allDeposits, nil } type staticAddrTestAddressManager struct{} func (s *staticAddrTestAddressManager) GetStaticAddressParameters( context.Context) (*script.Parameters, error) { return nil, nil } func (s *staticAddrTestAddressManager) GetStaticAddress( context.Context) (*script.StaticAddress, error) { return nil, nil } func (s *staticAddrTestAddressManager) ListUnspent(context.Context, int32, int32) ([]*lnwallet.Utxo, error) { return nil, nil } func (s *staticAddrTestAddressManager) GetTaprootAddress( *btcec.PublicKey, *btcec.PublicKey, int64) (*btcutil.AddressTaproot, error) { return nil, nil } // newTestDepositManager creates a deposit manager backed by seeded deposits. func newTestDepositManager( deposits ...*deposit.Deposit) *deposit.Manager { byOutpoint := make(map[string]*deposit.Deposit, len(deposits)) for _, deposit := range deposits { byOutpoint[deposit.OutPoint.String()] = deposit } return deposit.NewManager(&deposit.ManagerConfig{ AddressManager: &staticAddrTestAddressManager{}, Store: &staticAddrDepositStore{ allDeposits: deposits, byOutpoint: byOutpoint, }, }) } // newTestStaticAddressContext creates static address test dependencies. func newTestStaticAddressContext(t *testing.T) (*address.Manager, *mock_lnd.LndMockServices) { t.Helper() mock := mock_lnd.NewMockLnd() _, client := mock_lnd.CreateKey(1) _, server := mock_lnd.CreateKey(2) addrStore := &mockAddressStore{ params: []*script.Parameters{{ ClientPubkey: client, ServerPubkey: server, Expiry: 10, PkScript: []byte("pkscript"), }}, } addrMgr, err := address.NewManager(&address.ManagerConfig{ Store: addrStore, WalletKit: mock.WalletKit, ChainParams: mock.ChainParams, }, 1) require.NoError(t, err) return addrMgr, mock } // TestListStaticAddressDepositsReturnsVisibleDeposits verifies normal deposit // listings include visible deposit records. func TestListStaticAddressDepositsReturnsVisibleDeposits(t *testing.T) { t.Parallel() available := &deposit.Deposit{ OutPoint: wire.OutPoint{ Hash: chainhash.Hash{2}, Index: 2, }, } available.SetState(deposit.Deposited) addrMgr, lnd := newTestStaticAddressContext(t) server := &swapClientServer{ depositManager: newTestDepositManager(available), staticAddressManager: addrMgr, lnd: &lnd.LndServices, } resp, err := server.ListStaticAddressDeposits( context.Background(), &looprpc.ListStaticAddressDepositsRequest{}, ) require.NoError(t, err) require.Len(t, resp.FilteredDeposits, 1) require.Equal( t, available.OutPoint.String(), resp.FilteredDeposits[0].Outpoint, ) } // TestGetStaticAddressSummaryTotalsDeposits verifies visible deposits are // included in static address summary totals. func TestGetStaticAddressSummaryTotalsDeposits(t *testing.T) { t.Parallel() unconfirmed := &deposit.Deposit{ OutPoint: wire.OutPoint{ Hash: chainhash.Hash{4}, Index: 4, }, Value: btcutil.Amount(2_000), ConfirmationHeight: 0, } unconfirmed.SetState(deposit.Deposited) confirmed := &deposit.Deposit{ OutPoint: wire.OutPoint{ Hash: chainhash.Hash{5}, Index: 5, }, Value: btcutil.Amount(3_000), ConfirmationHeight: 123, } confirmed.SetState(deposit.Deposited) addrMgr, _ := newTestStaticAddressContext(t) server := &swapClientServer{ depositManager: newTestDepositManager( unconfirmed, confirmed, ), staticAddressManager: addrMgr, } resp, err := server.GetStaticAddressSummary( context.Background(), &looprpc.StaticAddressSummaryRequest{}, ) require.NoError(t, err) require.EqualValues(t, 2, resp.TotalNumDeposits) require.EqualValues(t, 2_000, resp.ValueUnconfirmedSatoshis) require.EqualValues(t, 3_000, resp.ValueDepositedSatoshis) } // TestGetLoopInQuoteRejectsUnavailableSelectedDeposit verifies manual quote // requests fail for selected deposits that are no longer available. func TestGetLoopInQuoteRejectsUnavailableSelectedDeposit(t *testing.T) { t.Parallel() setLogger(btclog.Disabled) locked := &deposit.Deposit{ OutPoint: wire.OutPoint{ Hash: chainhash.Hash{6}, Index: 6, }, Value: btcutil.Amount(5_000), } locked.SetState(deposit.LoopingIn) addrMgr, lnd := newTestStaticAddressContext(t) server := &swapClientServer{ depositManager: newTestDepositManager(locked), staticAddressManager: addrMgr, lnd: &lnd.LndServices, } _, err := server.GetLoopInQuote(context.Background(), &looprpc.QuoteRequest{ DepositOutpoints: []string{locked.OutPoint.String()}, }) require.ErrorContains(t, err, "is not currently available") }