From 8db6b32d749d343b6eb51c5c082296f65da32556 Mon Sep 17 00:00:00 2001 From: carla Date: Thu, 3 Sep 2020 10:36:42 +0200 Subject: [PATCH] liquidity: add swap suggestions for threshold rule for loop out --- liquidity/balances.go | 4 ++ liquidity/suggestions.go | 26 +++++++++++ liquidity/threshold_rule.go | 28 +++++++++++ liquidity/threshold_rule_test.go | 79 ++++++++++++++++++++++++++++++++ 4 files changed, 137 insertions(+) create mode 100644 liquidity/suggestions.go diff --git a/liquidity/balances.go b/liquidity/balances.go index cadef49..4975380 100644 --- a/liquidity/balances.go +++ b/liquidity/balances.go @@ -2,6 +2,7 @@ package liquidity import ( "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd/lnwire" ) // balances summarizes the state of the balances of a channel. Channel reserve, @@ -15,4 +16,7 @@ type balances struct { // outgoing is the local balance of the channel. outgoing btcutil.Amount + + // channelID is the channel that has these balances. + channelID lnwire.ShortChannelID } diff --git a/liquidity/suggestions.go b/liquidity/suggestions.go new file mode 100644 index 0000000..681d105 --- /dev/null +++ b/liquidity/suggestions.go @@ -0,0 +1,26 @@ +package liquidity + +import ( + "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd/lnwire" +) + +// LoopOutRecommendation contains the information required to recommend a loop +// out. +type LoopOutRecommendation struct { + // Amount is the total amount to swap. + Amount btcutil.Amount + + // Channel is the target outgoing channel. + Channel lnwire.ShortChannelID +} + +// newLoopOutRecommendation creates a new loop out swap. +func newLoopOutRecommendation(amount btcutil.Amount, + channelID lnwire.ShortChannelID) *LoopOutRecommendation { + + return &LoopOutRecommendation{ + Amount: amount, + Channel: channelID, + } +} diff --git a/liquidity/threshold_rule.go b/liquidity/threshold_rule.go index a6b3c6f..559449d 100644 --- a/liquidity/threshold_rule.go +++ b/liquidity/threshold_rule.go @@ -62,6 +62,34 @@ func (r *ThresholdRule) validate() error { return nil } +// suggestSwap suggests a swap based on the liquidity thresholds configured, +// returning nil if no swap is recommended. +func (r *ThresholdRule) suggestSwap(channel *balances, + outRestrictions *Restrictions) *LoopOutRecommendation { + + // Examine our total balance and required ratios to decide whether we + // need to swap. + amount := loopOutSwapAmount( + channel, r.MinimumIncoming, r.MinimumOutgoing, + ) + + // Limit our swap amount by the minimum/maximum thresholds set. + switch { + case amount < outRestrictions.Minimum: + return nil + + case amount > outRestrictions.Maximum: + return newLoopOutRecommendation( + outRestrictions.Maximum, channel.channelID, + ) + + default: + return newLoopOutRecommendation( + amount, channel.channelID, + ) + } +} + // loopOutSwapAmount determines whether we can perform a loop out swap, and // returns the amount we need to swap to reach the desired liquidity balance // specified by the incoming and outgoing thresholds. diff --git a/liquidity/threshold_rule_test.go b/liquidity/threshold_rule_test.go index 4c6b6fb..ea6aa1e 100644 --- a/liquidity/threshold_rule_test.go +++ b/liquidity/threshold_rule_test.go @@ -174,3 +174,82 @@ func TestLoopOutAmount(t *testing.T) { }) } } + +// TestSuggestSwaps tests swap suggestions for the threshold rule. It does not +// many different values because we have separate tests for swap amount +// calculation. +func TestSuggestSwap(t *testing.T) { + tests := []struct { + name string + rule *ThresholdRule + channel *balances + outRestrictions *Restrictions + swap *LoopOutRecommendation + }{ + { + name: "liquidity ok", + rule: NewThresholdRule(10, 10), + outRestrictions: NewRestrictions(10, 100), + channel: &balances{ + capacity: 100, + incoming: 50, + outgoing: 50, + }, + }, + { + name: "loop out", + rule: NewThresholdRule(40, 40), + outRestrictions: NewRestrictions(10, 100), + channel: &balances{ + capacity: 100, + incoming: 0, + outgoing: 100, + }, + swap: &LoopOutRecommendation{Amount: 50}, + }, + { + name: "amount below minimum", + rule: NewThresholdRule(40, 40), + outRestrictions: NewRestrictions(200, 300), + channel: &balances{ + capacity: 100, + incoming: 0, + outgoing: 100, + }, + swap: nil, + }, + { + name: "amount above maximum", + rule: NewThresholdRule(40, 40), + outRestrictions: NewRestrictions(10, 20), + channel: &balances{ + capacity: 100, + incoming: 0, + outgoing: 100, + }, + swap: &LoopOutRecommendation{Amount: 20}, + }, + { + name: "loop in", + rule: NewThresholdRule(10, 10), + outRestrictions: NewRestrictions(10, 100), + channel: &balances{ + capacity: 100, + incoming: 100, + outgoing: 0, + }, + swap: nil, + }, + } + + for _, test := range tests { + test := test + + t.Run(test.name, func(t *testing.T) { + swap := test.rule.suggestSwap( + test.channel, test.outRestrictions, + ) + require.Equal(t, test.swap, swap) + }) + } +}