Skip to content

Commit 7e19741

Browse files
committed
ReportFormatEVMABIEncodeUnpackedExpr add check for expected number of stream values
1 parent c69ea27 commit 7e19741

File tree

2 files changed

+5
-255
lines changed

2 files changed

+5
-255
lines changed

llo/reportcodecs/evm/report_codec_evm_abi_encode_unpacked_expr.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,6 @@ func (r ReportCodecEVMABIEncodeUnpackedExpr) Encode(report llo.Report, cd llotyp
2929
if report.Specimen {
3030
return nil, errors.New("ReportCodecEVMABIEncodeUnpackedExpr does not support encoding specimen reports")
3131
}
32-
if len(report.Values) < 2 {
33-
return nil, fmt.Errorf("ReportCodecEVMABIEncodeUnpackedExpr requires at least 2 values (NativePrice, LinkPrice, ...); got report.Values: %v", report.Values)
34-
}
3532
nativePrice, err := extractPrice(report.Values[0])
3633
if err != nil {
3734
return nil, fmt.Errorf("ReportCodecEVMABIEncodeUnpackedExpr failed to extract native price: %w", err)
@@ -49,6 +46,10 @@ func (r ReportCodecEVMABIEncodeUnpackedExpr) Encode(report llo.Report, cd llotyp
4946
return nil, fmt.Errorf("failed to decode opts; got: '%s'; %w", cd.Opts, err)
5047
}
5148

49+
if len(report.Values) < len(cd.Streams)+len(opts.ABI) {
50+
return nil, fmt.Errorf("ReportCodecEVMABIEncodeUnpackedExpr missing values for calculated streams; expected total: %d, got: %d", len(cd.Streams)+len(opts.ABI), len(report.Values))
51+
}
52+
5253
validAfter := ConvertTimestamp(report.ValidAfterNanoseconds, opts.TimestampPrecision)
5354
observationTimestamp := ConvertTimestamp(report.ObservationTimestampNanoseconds, opts.TimestampPrecision)
5455

llo/reportcodecs/evm/report_codec_evm_abi_encode_unpacked_expr_test.go

Lines changed: 1 addition & 252 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,16 @@ package evm
22

33
import (
44
"fmt"
5-
"math/big"
65
"testing"
76

8-
"github.com/ethereum/go-ethereum/accounts/abi"
9-
"github.com/ethereum/go-ethereum/common"
107
"github.com/leanovate/gopter"
118
"github.com/leanovate/gopter/gen"
12-
"github.com/leanovate/gopter/prop"
139
"github.com/shopspring/decimal"
1410
"github.com/stretchr/testify/assert"
1511
"github.com/stretchr/testify/require"
1612

1713
"github.com/smartcontractkit/libocr/offchainreporting2/types"
1814

19-
ubig "github.com/smartcontractkit/chainlink-data-streams/llo/reportcodecs/evm/utils"
20-
2115
llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo"
2216
"github.com/smartcontractkit/chainlink-data-streams/llo"
2317
)
@@ -74,252 +68,7 @@ func TestReportCodecEVMABIEncodeUnpackedExpr_Encode(t *testing.T) {
7468
codec := ReportCodecEVMABIEncodeUnpackedExpr{}
7569
_, err = codec.Encode(report, cd)
7670
require.Error(t, err)
77-
assert.Contains(t, err.Error(), "ABI and values length mismatch")
78-
})
79-
80-
t.Run("DEX-based asset schema example", func(t *testing.T) {
81-
expectedDEXBasedAssetSchema := abi.Arguments([]abi.Argument{
82-
{Name: "feedId", Type: mustNewABIType("bytes32")},
83-
{Name: "validFromTimestamp", Type: mustNewABIType("uint32")},
84-
{Name: "observationsTimestamp", Type: mustNewABIType("uint32")},
85-
{Name: "nativeFee", Type: mustNewABIType("uint192")},
86-
{Name: "linkFee", Type: mustNewABIType("uint192")},
87-
{Name: "expiresAt", Type: mustNewABIType("uint32")},
88-
{Name: "price", Type: mustNewABIType("int192")},
89-
{Name: "baseMarketDepth", Type: mustNewABIType("int192")},
90-
{Name: "quoteMarketDepth", Type: mustNewABIType("int192")},
91-
})
92-
93-
properties := gopter.NewProperties(nil)
94-
95-
runTest := func(sampleFeedID common.Hash, sampleObservationTimestampNanoseconds, sampleValidAfterNanoseconds uint64, sampleExpirationWindow uint32, priceMultiplier, marketDepthMultiplier *ubig.Big, sampleBaseUSDFee, sampleLinkBenchmarkPrice, sampleNativeBenchmarkPrice, sampleDexBasedAssetPrice, sampleBaseMarketDepth, sampleQuoteMarketDepth decimal.Decimal) bool {
96-
report := llo.Report{
97-
ConfigDigest: types.ConfigDigest{0x01},
98-
SeqNr: 0x02,
99-
ChannelID: llotypes.ChannelID(0x03),
100-
ValidAfterNanoseconds: sampleValidAfterNanoseconds,
101-
ObservationTimestampNanoseconds: sampleObservationTimestampNanoseconds,
102-
Values: []llo.StreamValue{
103-
&llo.Quote{Bid: decimal.NewFromFloat(6.1), Benchmark: sampleLinkBenchmarkPrice, Ask: decimal.NewFromFloat(8.2332)}, // Link price
104-
&llo.Quote{Bid: decimal.NewFromFloat(9.4), Benchmark: sampleNativeBenchmarkPrice, Ask: decimal.NewFromFloat(11.33)}, // Native price
105-
llo.ToDecimal(sampleDexBasedAssetPrice), // DEX-based asset price
106-
llo.ToDecimal(sampleBaseMarketDepth), // Base market depth
107-
llo.ToDecimal(sampleQuoteMarketDepth), // Quote market depth
108-
},
109-
Specimen: false,
110-
}
111-
112-
opts := ReportFormatEVMABIEncodeOpts{
113-
BaseUSDFee: sampleBaseUSDFee,
114-
ExpirationWindow: sampleExpirationWindow,
115-
FeedID: sampleFeedID,
116-
TimestampPrecision: PrecisionSeconds,
117-
ABI: []ABIEncoder{
118-
// benchmark price
119-
newSingleABIEncoder("int192", priceMultiplier),
120-
// base market depth
121-
newSingleABIEncoder("int192", marketDepthMultiplier),
122-
// quote market depth
123-
newSingleABIEncoder("int192", marketDepthMultiplier),
124-
},
125-
}
126-
serializedOpts, err := opts.Encode()
127-
require.NoError(t, err)
128-
129-
cd := llotypes.ChannelDefinition{
130-
ReportFormat: llotypes.ReportFormatEVMABIEncodeUnpackedExpr,
131-
Streams: []llotypes.Stream{
132-
{
133-
Aggregator: llotypes.AggregatorMedian,
134-
},
135-
{
136-
Aggregator: llotypes.AggregatorMedian,
137-
},
138-
{
139-
Aggregator: llotypes.AggregatorQuote,
140-
},
141-
{
142-
Aggregator: llotypes.AggregatorMedian,
143-
},
144-
{
145-
Aggregator: llotypes.AggregatorMedian,
146-
},
147-
},
148-
Opts: serializedOpts,
149-
}
150-
151-
codec := ReportCodecEVMABIEncodeUnpackedExpr{}
152-
encoded, err := codec.Encode(report, cd)
153-
require.NoError(t, err)
154-
155-
values, err := expectedDEXBasedAssetSchema.Unpack(encoded)
156-
require.NoError(t, err)
157-
158-
require.Len(t, values, len(expectedDEXBasedAssetSchema))
159-
160-
// doesn't crash if values are nil
161-
for i := range report.Values {
162-
report.Values[i] = nil
163-
}
164-
_, err = codec.Encode(report, cd)
165-
require.Error(t, err)
166-
167-
return true
168-
}
169-
170-
properties.Property("Encodes values", prop.ForAll(
171-
runTest,
172-
genFeedID(),
173-
genObservationTimestampNanoseconds(),
174-
genValidAfterNanoseconds(),
175-
genExpirationWindow(),
176-
genMultiplier(),
177-
genMultiplier(),
178-
genBaseUSDFee(),
179-
genLinkBenchmarkPrice(),
180-
genNativeBenchmarkPrice(),
181-
genDexBasedAssetPrice(),
182-
genMarketDepth(),
183-
genMarketDepth(),
184-
))
185-
186-
properties.TestingRun(t)
187-
})
188-
189-
t.Run("varying timestamp precision schemas", func(t *testing.T) {
190-
runTest := func(sampleFeedID common.Hash, sampleObservationTimestampNanoseconds, sampleValidAfterNanoseconds uint64, sampleExpirationWindow uint32, priceMultiplier, marketDepthMultiplier *ubig.Big, sampleBaseUSDFee, sampleLinkBenchmarkPrice, sampleNativeBenchmarkPrice, sampleDexBasedAssetPrice, sampleBaseMarketDepth, sampleQuoteMarketDepth decimal.Decimal, sampleTimestampPrecision TimestampPrecision) bool {
191-
// Determine timestamp type based on precision
192-
timestampType := "uint64"
193-
if sampleTimestampPrecision == PrecisionSeconds {
194-
timestampType = "uint32"
195-
}
196-
197-
schema := abi.Arguments([]abi.Argument{
198-
{Name: "feedId", Type: mustNewABIType("bytes32")},
199-
{Name: "validFromTimestamp", Type: mustNewABIType(timestampType)},
200-
{Name: "observationsTimestamp", Type: mustNewABIType(timestampType)},
201-
{Name: "nativeFee", Type: mustNewABIType("uint192")},
202-
{Name: "linkFee", Type: mustNewABIType("uint192")},
203-
{Name: "expiresAt", Type: mustNewABIType(timestampType)},
204-
{Name: "price", Type: mustNewABIType("int192")},
205-
{Name: "baseMarketDepth", Type: mustNewABIType("int192")},
206-
{Name: "quoteMarketDepth", Type: mustNewABIType("int192")},
207-
})
208-
209-
report := llo.Report{
210-
ConfigDigest: types.ConfigDigest{0x01},
211-
SeqNr: 0x02,
212-
ChannelID: llotypes.ChannelID(0x03),
213-
ValidAfterNanoseconds: sampleValidAfterNanoseconds,
214-
ObservationTimestampNanoseconds: sampleObservationTimestampNanoseconds,
215-
Values: []llo.StreamValue{
216-
&llo.Quote{Bid: decimal.NewFromFloat(9.4), Benchmark: sampleNativeBenchmarkPrice, Ask: decimal.NewFromFloat(11.33)}, // Native price
217-
&llo.Quote{Bid: decimal.NewFromFloat(6.1), Benchmark: sampleLinkBenchmarkPrice, Ask: decimal.NewFromFloat(8.2332)}, // Link price
218-
llo.ToDecimal(sampleDexBasedAssetPrice), // DEX-based asset price
219-
llo.ToDecimal(sampleBaseMarketDepth), // Base market depth
220-
llo.ToDecimal(sampleQuoteMarketDepth), // Quote market depth
221-
},
222-
Specimen: false,
223-
}
224-
225-
opts := ReportFormatEVMABIEncodeOpts{
226-
BaseUSDFee: sampleBaseUSDFee,
227-
ExpirationWindow: sampleExpirationWindow,
228-
FeedID: sampleFeedID,
229-
TimestampPrecision: sampleTimestampPrecision,
230-
ABI: []ABIEncoder{
231-
// benchmark price
232-
newSingleABIEncoder("int192", priceMultiplier),
233-
// base market depth
234-
newSingleABIEncoder("int192", marketDepthMultiplier),
235-
// quote market depth
236-
newSingleABIEncoder("int192", marketDepthMultiplier),
237-
},
238-
}
239-
serializedOpts, err := opts.Encode()
240-
require.NoError(t, err)
241-
242-
cd := llotypes.ChannelDefinition{
243-
ReportFormat: llotypes.ReportFormatEVMABIEncodeUnpackedExpr,
244-
Streams: []llotypes.Stream{
245-
{
246-
Aggregator: llotypes.AggregatorMedian,
247-
},
248-
{
249-
Aggregator: llotypes.AggregatorMedian,
250-
},
251-
{
252-
Aggregator: llotypes.AggregatorQuote,
253-
},
254-
{
255-
Aggregator: llotypes.AggregatorMedian,
256-
},
257-
{
258-
Aggregator: llotypes.AggregatorMedian,
259-
},
260-
},
261-
Opts: serializedOpts,
262-
}
263-
264-
codec := ReportCodecEVMABIEncodeUnpackedExpr{}
265-
encoded, err := codec.Encode(report, cd)
266-
require.NoError(t, err)
267-
268-
values, err := schema.Unpack(encoded)
269-
require.NoError(t, err)
270-
require.Len(t, values, len(schema))
271-
272-
expectedLinkFee := CalculateFee(sampleLinkBenchmarkPrice, sampleBaseUSDFee)
273-
expectedNativeFee := CalculateFee(sampleNativeBenchmarkPrice, sampleBaseUSDFee)
274-
275-
checks := []bool{
276-
assert.Equal(t, sampleFeedID, (common.Hash)(values[0].([32]byte))), //nolint:testifylint // false positive
277-
assert.Equal(t, sampleDexBasedAssetPrice.Mul(decimal.NewFromBigInt(priceMultiplier.ToInt(), 0)).BigInt(), values[6].(*big.Int)),
278-
assert.Equal(t, sampleBaseMarketDepth.Mul(decimal.NewFromBigInt(marketDepthMultiplier.ToInt(), 0)).BigInt(), values[7].(*big.Int)),
279-
assert.Equal(t, sampleQuoteMarketDepth.Mul(decimal.NewFromBigInt(marketDepthMultiplier.ToInt(), 0)).BigInt(), values[8].(*big.Int)),
280-
assert.Equal(t, expectedNativeFee.String(), values[3].(*big.Int).String()),
281-
assert.Equal(t, expectedLinkFee.String(), values[4].(*big.Int).String()),
282-
}
283-
284-
// Verify timestamps per precision type
285-
expectedValidFrom := ConvertTimestamp(sampleValidAfterNanoseconds, sampleTimestampPrecision) + 1
286-
expectedObservationTimestamp := ConvertTimestamp(sampleObservationTimestampNanoseconds, sampleTimestampPrecision)
287-
expectedExpiresAt := expectedObservationTimestamp + uint64(sampleExpirationWindow)
288-
if timestampType == "uint32" {
289-
checks = append(checks,
290-
assert.Equal(t, uint32(expectedValidFrom), values[1].(uint32)),
291-
assert.Equal(t, uint32(expectedObservationTimestamp), values[2].(uint32)),
292-
assert.Equal(t, uint32(expectedExpiresAt), values[5].(uint32)),
293-
)
294-
} else {
295-
checks = append(checks,
296-
assert.Equal(t, expectedValidFrom, values[1].(uint64)),
297-
assert.Equal(t, expectedObservationTimestamp, values[2].(uint64)),
298-
assert.Equal(t, expectedExpiresAt, values[5].(uint64)),
299-
)
300-
}
301-
302-
return AllTrue(checks)
303-
}
304-
305-
properties := gopter.NewProperties(nil)
306-
properties.Property("Encodes values", prop.ForAll(
307-
runTest,
308-
genFeedID(),
309-
genObservationTimestampNanoseconds(),
310-
genValidAfterNanoseconds(),
311-
genExpirationWindow(),
312-
genMultiplier(),
313-
genMultiplier(),
314-
genBaseUSDFee(),
315-
genLinkBenchmarkPrice(),
316-
genNativeBenchmarkPrice(),
317-
genDexBasedAssetPrice(),
318-
genMarketDepth(),
319-
genMarketDepth(),
320-
genTimestampPrecision(),
321-
))
322-
properties.TestingRun(t)
71+
assert.Contains(t, err.Error(), "missing values for calculated streams")
32372
})
32473
}
32574

0 commit comments

Comments
 (0)