1
1
package liquidity
2
2
3
3
import (
4
+ "context"
5
+ "encoding/hex"
6
+ "encoding/json"
4
7
"testing"
5
8
"time"
6
9
@@ -11,6 +14,7 @@ import (
11
14
"github.com/lightninglabs/loop/loopdb"
12
15
"github.com/lightninglabs/loop/swap"
13
16
"github.com/lightninglabs/loop/test"
17
+ "github.com/lightninglabs/taproot-assets/rfqmsg"
14
18
"github.com/lightningnetwork/lnd/lntypes"
15
19
"github.com/lightningnetwork/lnd/lnwire"
16
20
"github.com/lightningnetwork/lnd/routing/route"
@@ -1520,3 +1524,356 @@ func existingInFromRequest(in *loop.LoopInRequest, initTime time.Time,
1520
1524
},
1521
1525
}
1522
1526
}
1527
+
1528
+ // TestEasyAssetAutoloop tests that the easy asset autoloop logic works as
1529
+ // expected. This involves testing that channels are correctly selected and
1530
+ // that the balance target is successfully met.
1531
+ func TestEasyAssetAutoloop (t * testing.T ) {
1532
+ defer test .Guard (t )
1533
+
1534
+ // Common variables for asset tests.
1535
+ assetId := [32 ]byte {0x01 }
1536
+ assetStr := hex .EncodeToString (assetId [:])
1537
+ addr , err := btcutil .DecodeAddress (p2wkhAddr , nil )
1538
+ require .NoError (t , err )
1539
+
1540
+ // Sub-test 1: Single asset channel.
1541
+ t .Run ("single asset channel" , func (t * testing.T ) {
1542
+ // Prepare a channel with asset custom data.
1543
+ customChanData := rfqmsg.JsonAssetChannel {
1544
+ Assets : []rfqmsg.JsonAssetChanInfo {
1545
+ {
1546
+ AssetInfo : rfqmsg.JsonAssetUtxo {
1547
+ AssetGenesis : rfqmsg.JsonAssetGenesis {
1548
+ AssetID : assetStr ,
1549
+ },
1550
+ },
1551
+ LocalBalance : 950000 ,
1552
+ RemoteBalance : 0 ,
1553
+ Capacity : 100000 ,
1554
+ },
1555
+ },
1556
+ }
1557
+ customChanDataBytes , err := json .Marshal (customChanData )
1558
+ require .NoError (t , err )
1559
+
1560
+ assetChan := lndclient.ChannelInfo {
1561
+ Active : true ,
1562
+ ChannelID : chanID1 .ToUint64 (),
1563
+ PubKeyBytes : peer1 ,
1564
+ LocalBalance : 95000 ,
1565
+ RemoteBalance : 0 ,
1566
+ Capacity : 100000 ,
1567
+ CustomChannelData : customChanDataBytes ,
1568
+ }
1569
+
1570
+ channels := []lndclient.ChannelInfo {assetChan }
1571
+ params := Parameters {
1572
+ Autoloop : true ,
1573
+ DestAddr : addr ,
1574
+ AutoFeeBudget : 36000 ,
1575
+ AutoFeeRefreshPeriod : time .Hour * 3 ,
1576
+ AutoloopBudgetLastRefresh : testBudgetStart ,
1577
+ MaxAutoInFlight : 2 ,
1578
+ FailureBackOff : time .Hour ,
1579
+ SweepConfTarget : 10 ,
1580
+ HtlcConfTarget : defaultHtlcConfTarget ,
1581
+ FeeLimit : defaultFeePortion (),
1582
+ AssetAutoloopParams : map [string ]AssetParams {
1583
+ assetStr : {
1584
+ EnableEasyOut : true ,
1585
+ LocalTargetAssetAmount : 75000 ,
1586
+ },
1587
+ },
1588
+ }
1589
+
1590
+ c := newAutoloopTestCtx (t , params , channels , testRestrictions )
1591
+ // For testing, simply return asset units 1:1 to satoshis.
1592
+ assetPriceFunc := func (ctx context.Context , assetId string ,
1593
+ peerPubkey []byte , assetAmt uint64 , minSatAmt btcutil.Amount ) (
1594
+ btcutil.Amount , error ) {
1595
+
1596
+ return btcutil .Amount (assetAmt ), nil
1597
+ }
1598
+ c .manager .cfg .GetAssetPrice = assetPriceFunc
1599
+ c .start ()
1600
+
1601
+ // In this scenario we expect a swap of maxAmt (here chosen as 50000)
1602
+ // on our single asset channel.
1603
+ maxAmt := 50000
1604
+ chanSwap := & loop.OutRequest {
1605
+ Amount : btcutil .Amount (maxAmt ),
1606
+ DestAddr : addr ,
1607
+ OutgoingChanSet : loopdb.ChannelSet {assetChan .ChannelID },
1608
+ Label : labels .AutoloopLabel (swap .TypeOut ),
1609
+ Initiator : autoloopSwapInitiator ,
1610
+ }
1611
+ quotesOut := []quoteRequestResp {
1612
+ {
1613
+ request : & loop.LoopOutQuoteRequest {
1614
+ Amount : btcutil .Amount (maxAmt ),
1615
+ AssetRFQRequest : & loop.AssetRFQRequest {
1616
+ AssetId : assetId [:],
1617
+ AssetEdgeNode : []byte ("edge" ),
1618
+ },
1619
+ },
1620
+ quote : & loop.LoopOutQuote {
1621
+ SwapFee : 1 ,
1622
+ PrepayAmount : 1 ,
1623
+ MinerFee : 1 ,
1624
+ LoopOutRfq : & loop.LoopOutRfq {
1625
+ PrepayRfqId : []byte ("prepay" ),
1626
+ SwapRfqId : []byte ("swap" ),
1627
+ },
1628
+ },
1629
+ },
1630
+ }
1631
+ expectedOut := []loopOutRequestResp {
1632
+ {
1633
+ request : chanSwap ,
1634
+ response : & loop.LoopOutSwapInfo {
1635
+ SwapHash : lntypes.Hash {1 },
1636
+ },
1637
+ },
1638
+ }
1639
+ step := & easyAutoloopStep {
1640
+ minAmt : 1 ,
1641
+ maxAmt : btcutil .Amount (maxAmt ),
1642
+ quotesOut : quotesOut ,
1643
+ expectedOut : expectedOut ,
1644
+ }
1645
+ c .easyautoloop (step , false )
1646
+ c .stop ()
1647
+ })
1648
+
1649
+ // Sub-test 2: Two asset channels.
1650
+ t .Run ("two asset channels" , func (t * testing.T ) {
1651
+ // Reuse the same custom channel data for both channels.
1652
+ customChanData := rfqmsg.JsonAssetChannel {
1653
+ Assets : []rfqmsg.JsonAssetChanInfo {
1654
+ {
1655
+ AssetInfo : rfqmsg.JsonAssetUtxo {
1656
+ AssetGenesis : rfqmsg.JsonAssetGenesis {
1657
+ AssetID : assetStr ,
1658
+ },
1659
+ },
1660
+ LocalBalance : 950000 ,
1661
+ RemoteBalance : 0 ,
1662
+ Capacity : 100000 ,
1663
+ },
1664
+ },
1665
+ }
1666
+ customChanDataBytes1 , err := json .Marshal (customChanData )
1667
+ require .NoError (t , err )
1668
+
1669
+ customChanData .Assets [0 ].LocalBalance = 1050000
1670
+ customChanDataBytes2 , err := json .Marshal (customChanData )
1671
+ require .NoError (t , err )
1672
+
1673
+ // Create two asset channels with different local balances.
1674
+ assetChan1 := lndclient.ChannelInfo {
1675
+ Active : true ,
1676
+ ChannelID : chanID1 .ToUint64 (),
1677
+ PubKeyBytes : peer1 ,
1678
+ CustomChannelData : customChanDataBytes1 ,
1679
+ }
1680
+ assetChan2 := lndclient.ChannelInfo {
1681
+ Active : true ,
1682
+ ChannelID : chanID2 .ToUint64 (), // different channel ID
1683
+ PubKeyBytes : peer2 ,
1684
+ CustomChannelData : customChanDataBytes2 ,
1685
+ }
1686
+
1687
+ channels := []lndclient.ChannelInfo {assetChan1 , assetChan2 }
1688
+ params := Parameters {
1689
+ Autoloop : true ,
1690
+ DestAddr : addr ,
1691
+ AutoFeeBudget : 36000 ,
1692
+ AutoFeeRefreshPeriod : time .Hour * 3 ,
1693
+ AutoloopBudgetLastRefresh : testBudgetStart ,
1694
+ MaxAutoInFlight : 2 ,
1695
+ FailureBackOff : time .Hour ,
1696
+ SweepConfTarget : 10 ,
1697
+ HtlcConfTarget : defaultHtlcConfTarget ,
1698
+ FeeLimit : defaultFeePortion (),
1699
+ AssetAutoloopParams : map [string ]AssetParams {
1700
+ assetStr : {
1701
+ EnableEasyOut : true ,
1702
+ LocalTargetAssetAmount : 75000 ,
1703
+ },
1704
+ },
1705
+ }
1706
+
1707
+ c := newAutoloopTestCtx (t , params , channels , testRestrictions )
1708
+ assetPriceFunc := func (ctx context.Context , assetId string ,
1709
+ peerPubkey []byte , assetAmt uint64 , minSatAmt btcutil.Amount ) (
1710
+ btcutil.Amount , error ) {
1711
+
1712
+ return btcutil .Amount (assetAmt ), nil
1713
+ }
1714
+ c .manager .cfg .GetAssetPrice = assetPriceFunc
1715
+ c .start ()
1716
+
1717
+ // Expect a swap on the channel with the higher local balance (assetChan2).
1718
+ maxAmt := 40000
1719
+ chanSwap := & loop.OutRequest {
1720
+ Amount : btcutil .Amount (maxAmt ),
1721
+ DestAddr : addr ,
1722
+ OutgoingChanSet : loopdb.ChannelSet {assetChan2 .ChannelID },
1723
+ Label : labels .AutoloopLabel (swap .TypeOut ),
1724
+ Initiator : autoloopSwapInitiator ,
1725
+ }
1726
+ quotesOut := []quoteRequestResp {
1727
+ {
1728
+ request : & loop.LoopOutQuoteRequest {
1729
+ Amount : btcutil .Amount (maxAmt ),
1730
+ AssetRFQRequest : & loop.AssetRFQRequest {
1731
+ AssetId : assetId [:],
1732
+ AssetEdgeNode : []byte ("edge" ),
1733
+ },
1734
+ },
1735
+ quote : & loop.LoopOutQuote {
1736
+ SwapFee : 1 ,
1737
+ PrepayAmount : 1 ,
1738
+ MinerFee : 1 ,
1739
+ LoopOutRfq : & loop.LoopOutRfq {
1740
+ PrepayRfqId : []byte ("prepay" ),
1741
+ SwapRfqId : []byte ("swap" ),
1742
+ },
1743
+ },
1744
+ },
1745
+ }
1746
+ expectedOut := []loopOutRequestResp {
1747
+ {
1748
+ request : chanSwap ,
1749
+ response : & loop.LoopOutSwapInfo {
1750
+ SwapHash : lntypes.Hash {1 },
1751
+ },
1752
+ },
1753
+ }
1754
+ step := & easyAutoloopStep {
1755
+ minAmt : 1 ,
1756
+ maxAmt : btcutil .Amount (maxAmt ),
1757
+ quotesOut : quotesOut ,
1758
+ expectedOut : expectedOut ,
1759
+ }
1760
+ c .easyautoloop (step , false )
1761
+ c .stop ()
1762
+ })
1763
+
1764
+ // Sub-test 3: Mixed asset and non-asset channels.
1765
+ t .Run ("non asset and normal channel" , func (t * testing.T ) {
1766
+ // Create an asset channel with custom asset data.
1767
+ customChanData := rfqmsg.JsonAssetChannel {
1768
+ Assets : []rfqmsg.JsonAssetChanInfo {
1769
+ {
1770
+ AssetInfo : rfqmsg.JsonAssetUtxo {
1771
+ AssetGenesis : rfqmsg.JsonAssetGenesis {
1772
+ AssetID : assetStr ,
1773
+ },
1774
+ },
1775
+ LocalBalance : 950000 ,
1776
+ RemoteBalance : 0 ,
1777
+ Capacity : 100000 ,
1778
+ },
1779
+ },
1780
+ }
1781
+ customChanDataBytes , err := json .Marshal (customChanData )
1782
+ require .NoError (t , err )
1783
+ assetChan := lndclient.ChannelInfo {
1784
+ Active : true ,
1785
+ ChannelID : chanID1 .ToUint64 (),
1786
+ PubKeyBytes : peer1 ,
1787
+ LocalBalance : 95000 ,
1788
+ RemoteBalance : 0 ,
1789
+ Capacity : 100000 ,
1790
+ CustomChannelData : customChanDataBytes ,
1791
+ }
1792
+
1793
+ // Create a normal channel (no custom channel data).
1794
+ normalChan := lndclient.ChannelInfo {
1795
+ Active : true ,
1796
+ ChannelID : chanID2 .ToUint64 (),
1797
+ PubKeyBytes : peer1 ,
1798
+ LocalBalance : 100000 ,
1799
+ RemoteBalance : 0 ,
1800
+ Capacity : 100000 ,
1801
+ }
1802
+
1803
+ channels := []lndclient.ChannelInfo {assetChan , normalChan }
1804
+ params := Parameters {
1805
+ Autoloop : true ,
1806
+ DestAddr : addr ,
1807
+ AutoFeeBudget : 36000 ,
1808
+ AutoFeeRefreshPeriod : time .Hour * 3 ,
1809
+ AutoloopBudgetLastRefresh : testBudgetStart ,
1810
+ MaxAutoInFlight : 2 ,
1811
+ FailureBackOff : time .Hour ,
1812
+ SweepConfTarget : 10 ,
1813
+ HtlcConfTarget : defaultHtlcConfTarget ,
1814
+ FeeLimit : defaultFeePortion (),
1815
+ AssetAutoloopParams : map [string ]AssetParams {
1816
+ assetStr : {
1817
+ EnableEasyOut : true ,
1818
+ LocalTargetAssetAmount : 75000 ,
1819
+ },
1820
+ },
1821
+ }
1822
+
1823
+ c := newAutoloopTestCtx (t , params , channels , testRestrictions )
1824
+ assetPriceFunc := func (ctx context.Context , assetId string ,
1825
+ peerPubkey []byte , assetAmt uint64 , minSatAmt btcutil.Amount ) (
1826
+ btcutil.Amount , error ) {
1827
+
1828
+ return btcutil .Amount (assetAmt ), nil
1829
+ }
1830
+ c .manager .cfg .GetAssetPrice = assetPriceFunc
1831
+ c .start ()
1832
+
1833
+ maxAmtAsset := 50000
1834
+
1835
+ assetSwap := & loop.OutRequest {
1836
+ Amount : btcutil .Amount (maxAmtAsset ),
1837
+ DestAddr : addr ,
1838
+ OutgoingChanSet : loopdb.ChannelSet {assetChan .ChannelID },
1839
+ Label : labels .AutoloopLabel (swap .TypeOut ),
1840
+ Initiator : autoloopSwapInitiator ,
1841
+ }
1842
+ quotesOut := []quoteRequestResp {
1843
+ {
1844
+ request : & loop.LoopOutQuoteRequest {
1845
+ Amount : btcutil .Amount (maxAmtAsset ),
1846
+ AssetRFQRequest : & loop.AssetRFQRequest {
1847
+ AssetId : assetId [:],
1848
+ AssetEdgeNode : []byte ("edge" ),
1849
+ },
1850
+ },
1851
+ quote : & loop.LoopOutQuote {
1852
+ SwapFee : 1 ,
1853
+ PrepayAmount : 1 ,
1854
+ MinerFee : 1 ,
1855
+ LoopOutRfq : & loop.LoopOutRfq {
1856
+ PrepayRfqId : []byte ("prepay" ),
1857
+ SwapRfqId : []byte ("swap" ),
1858
+ },
1859
+ },
1860
+ },
1861
+ }
1862
+ expectedOut := []loopOutRequestResp {
1863
+ {
1864
+ request : assetSwap ,
1865
+ response : & loop.LoopOutSwapInfo {
1866
+ SwapHash : lntypes.Hash {1 },
1867
+ },
1868
+ },
1869
+ }
1870
+ step := & easyAutoloopStep {
1871
+ minAmt : 1 ,
1872
+ maxAmt : 50000 ,
1873
+ quotesOut : quotesOut ,
1874
+ expectedOut : expectedOut ,
1875
+ }
1876
+ c .easyautoloop (step , false )
1877
+ c .stop ()
1878
+ })
1879
+ }
0 commit comments