mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
assets: validate accepted RFQ asset rates
Validate the bid rate before returning an accepted asset sell quote. This prevents malformed rates from reaching downstream quote arithmetic, where nil or non-positive values can panic. Cover valid and malformed responses with table-driven tests.
This commit is contained in:
parent
cfbade1239
commit
9e97992e18
2 changed files with 106 additions and 3 deletions
|
|
@ -158,11 +158,18 @@ func (c *TapdClient) GetRfqForAsset(ctx context.Context,
|
||||||
rfq.GetRejectedQuote())
|
rfq.GetRejectedQuote())
|
||||||
}
|
}
|
||||||
|
|
||||||
if rfq.GetAcceptedQuote() != nil {
|
acceptedQuote := rfq.GetAcceptedQuote()
|
||||||
return rfq.GetAcceptedQuote(), nil
|
if acceptedQuote == nil {
|
||||||
|
return nil, fmt.Errorf("no accepted quote")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("no accepted quote")
|
_, err = unmarshalAssetRate(acceptedQuote.BidAssetRate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid accepted quote asset rate: %w",
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return acceptedQuote, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAssetName returns the human-readable name of the asset.
|
// GetAssetName returns the human-readable name of the asset.
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,19 @@ func (b *blockingUniverseClient) QueryAssetStats(context.Context,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type staticRfqClient struct {
|
||||||
|
rfqrpc.RfqClient
|
||||||
|
|
||||||
|
response *rfqrpc.AddAssetSellOrderResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *staticRfqClient) AddAssetSellOrder(context.Context,
|
||||||
|
*rfqrpc.AddAssetSellOrderRequest, ...grpc.CallOption) (
|
||||||
|
*rfqrpc.AddAssetSellOrderResponse, error) {
|
||||||
|
|
||||||
|
return s.response, nil
|
||||||
|
}
|
||||||
|
|
||||||
// TestDefaultTapdConfig tests that the default tapd connection paths match
|
// TestDefaultTapdConfig tests that the default tapd connection paths match
|
||||||
// tapd's mainnet defaults.
|
// tapd's mainnet defaults.
|
||||||
func TestDefaultTapdConfig(t *testing.T) {
|
func TestDefaultTapdConfig(t *testing.T) {
|
||||||
|
|
@ -168,6 +181,89 @@ func TestGetAssetNameCachedLookupNotBlocked(t *testing.T) {
|
||||||
require.NoError(t, <-queryResult)
|
require.NoError(t, <-queryResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestGetRfqForAssetValidatesRate verifies that malformed accepted quote rates
|
||||||
|
// are rejected before they reach downstream RFQ arithmetic.
|
||||||
|
func TestGetRfqForAssetValidatesRate(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
assetRate *rfqrpc.FixedPoint
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid",
|
||||||
|
assetRate: &rfqrpc.FixedPoint{
|
||||||
|
Coefficient: "100000", Scale: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil",
|
||||||
|
assetRate: nil,
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "malformed coefficient",
|
||||||
|
assetRate: &rfqrpc.FixedPoint{
|
||||||
|
Coefficient: "not-a-number", Scale: 0,
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero coefficient",
|
||||||
|
assetRate: &rfqrpc.FixedPoint{
|
||||||
|
Coefficient: "0", Scale: 0,
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negative coefficient",
|
||||||
|
assetRate: &rfqrpc.FixedPoint{
|
||||||
|
Coefficient: "-1", Scale: 0,
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "scale overflow",
|
||||||
|
assetRate: &rfqrpc.FixedPoint{
|
||||||
|
Coefficient: "1", Scale: 256,
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
acceptedQuote := &rfqrpc.PeerAcceptedSellQuote{
|
||||||
|
BidAssetRate: test.assetRate,
|
||||||
|
}
|
||||||
|
acceptedResponse :=
|
||||||
|
&rfqrpc.AddAssetSellOrderResponse_AcceptedQuote{
|
||||||
|
AcceptedQuote: acceptedQuote,
|
||||||
|
}
|
||||||
|
client := &TapdClient{
|
||||||
|
RfqClient: &staticRfqClient{
|
||||||
|
response: &rfqrpc.AddAssetSellOrderResponse{
|
||||||
|
Response: acceptedResponse,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rfqTimeoutSeconds: 60,
|
||||||
|
}
|
||||||
|
|
||||||
|
quote, err := client.GetRfqForAsset(
|
||||||
|
context.Background(), 1000, []byte{1}, []byte{2},
|
||||||
|
time.Now().Add(time.Minute).Unix(), 1,
|
||||||
|
)
|
||||||
|
if test.expectError {
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Nil(t, quote)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Same(t, acceptedQuote, quote)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetPaymentMaxAmount(t *testing.T) {
|
func TestGetPaymentMaxAmount(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
satAmount btcutil.Amount
|
satAmount btcutil.Amount
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue