44 "bytes"
55 "errors"
66 "fmt"
7+ "math"
78 "math/big"
89
910 "github.com/goccy/go-json"
@@ -55,6 +56,10 @@ type ReportFormatEVMABIEncodeOpts struct {
5556 // top-level elements in this ABI array (stream 0 is always the native
5657 // token price and stream 1 is the link token price).
5758 ABI []ABIEncoder `json:"abi"`
59+ // TimestampPrecision is the precision of the timestamps in the report.
60+ // Seconds use uint32 ABI encoding, while milliseconds/microseconds/nanoseconds use uint64.
61+ // Defaults to "s" (seconds) if not specified.
62+ TimestampPrecision TimestampPrecision `json:"timestampPrecision,omitempty"`
5863}
5964
6065func (r * ReportFormatEVMABIEncodeOpts ) Decode (opts []byte ) error {
@@ -69,11 +74,11 @@ func (r *ReportFormatEVMABIEncodeOpts) Encode() ([]byte, error) {
6974
7075type BaseReportFields struct {
7176 FeedID common.Hash
72- ValidFromTimestamp uint32
73- Timestamp uint32
77+ ValidFromTimestamp uint64
78+ Timestamp uint64
7479 NativeFee * big.Int
7580 LinkFee * big.Int
76- ExpiresAt uint32
81+ ExpiresAt uint64
7782}
7883
7984func (r ReportCodecEVMABIEncodeUnpacked ) Encode (report llo.Report , cd llotypes.ChannelDefinition ) ([]byte , error ) {
@@ -100,21 +105,19 @@ func (r ReportCodecEVMABIEncodeUnpacked) Encode(report llo.Report, cd llotypes.C
100105 return nil , fmt .Errorf ("failed to decode opts; got: '%s'; %w" , cd .Opts , err )
101106 }
102107
103- validAfterSeconds , observationTimestampSeconds , err := ExtractTimestamps (report )
104- if err != nil {
105- return nil , fmt .Errorf ("failed to extract timestamps; %w" , err )
106- }
108+ validAfter := ConvertTimestamp (report .ValidAfterNanoseconds , opts .TimestampPrecision )
109+ observationTimestamp := ConvertTimestamp (report .ObservationTimestampNanoseconds , opts .TimestampPrecision )
107110
108111 rf := BaseReportFields {
109112 FeedID : opts .FeedID ,
110- ValidFromTimestamp : validAfterSeconds + 1 ,
111- Timestamp : observationTimestampSeconds ,
113+ ValidFromTimestamp : validAfter + 1 ,
114+ Timestamp : observationTimestamp ,
112115 NativeFee : CalculateFee (nativePrice , opts .BaseUSDFee ),
113116 LinkFee : CalculateFee (linkPrice , opts .BaseUSDFee ),
114- ExpiresAt : observationTimestampSeconds + opts .ExpirationWindow ,
117+ ExpiresAt : observationTimestamp + uint64 ( opts .ExpirationWindow ) ,
115118 }
116119
117- header , err := r .buildHeader (rf )
120+ header , err := r .buildHeader (rf , opts . TimestampPrecision )
118121 if err != nil {
119122 return nil , fmt .Errorf ("failed to build base report; %w" , err )
120123 }
@@ -179,9 +182,12 @@ func (r ReportCodecEVMABIEncodeUnpacked) Verify(cd llotypes.ChannelDefinition) e
179182// EVMABIEncodeUnpacked reports.
180183//
181184// An arbitrary payload will be appended to this.
182- var BaseSchema = getBaseSchema ()
185+ var (
186+ BaseSchemaUint32 = getBaseSchema ("uint32" )
187+ BaseSchemaUint64 = getBaseSchema ("uint64" )
188+ )
183189
184- func getBaseSchema () abi.Arguments {
190+ func getBaseSchema (timestampType string ) abi.Arguments {
185191 mustNewType := func (t string ) abi.Type {
186192 result , err := abi .NewType (t , "" , []abi.ArgumentMarshaling {})
187193 if err != nil {
@@ -191,15 +197,15 @@ func getBaseSchema() abi.Arguments {
191197 }
192198 return abi .Arguments ([]abi.Argument {
193199 {Name : "feedId" , Type : mustNewType ("bytes32" )},
194- {Name : "validFromTimestamp" , Type : mustNewType ("uint32" )},
195- {Name : "observationsTimestamp" , Type : mustNewType ("uint32" )},
200+ {Name : "validFromTimestamp" , Type : mustNewType (timestampType )},
201+ {Name : "observationsTimestamp" , Type : mustNewType (timestampType )},
196202 {Name : "nativeFee" , Type : mustNewType ("uint192" )},
197203 {Name : "linkFee" , Type : mustNewType ("uint192" )},
198- {Name : "expiresAt" , Type : mustNewType ("uint32" )},
204+ {Name : "expiresAt" , Type : mustNewType (timestampType )},
199205 })
200206}
201207
202- func (r ReportCodecEVMABIEncodeUnpacked ) buildHeader (rf BaseReportFields ) ([]byte , error ) {
208+ func (r ReportCodecEVMABIEncodeUnpacked ) buildHeader (rf BaseReportFields , precision TimestampPrecision ) ([]byte , error ) {
203209 var merr error
204210 if rf .LinkFee == nil {
205211 merr = errors .Join (merr , errors .New ("linkFee may not be nil" ))
@@ -214,7 +220,38 @@ func (r ReportCodecEVMABIEncodeUnpacked) buildHeader(rf BaseReportFields) ([]byt
214220 if merr != nil {
215221 return nil , merr
216222 }
217- b , err := BaseSchema .Pack (rf .FeedID , rf .ValidFromTimestamp , rf .Timestamp , rf .NativeFee , rf .LinkFee , rf .ExpiresAt )
223+
224+ var b []byte
225+ var err error
226+ if precision == PrecisionSeconds {
227+ if rf .ValidFromTimestamp > math .MaxUint32 {
228+ return nil , fmt .Errorf ("validFromTimestamp %d exceeds uint32 range" , rf .ValidFromTimestamp )
229+ }
230+ if rf .Timestamp > math .MaxUint32 {
231+ return nil , fmt .Errorf ("timestamp %d exceeds uint32 range" , rf .Timestamp )
232+ }
233+ if rf .ExpiresAt > math .MaxUint32 {
234+ return nil , fmt .Errorf ("expiresAt %d exceeds uint32 range" , rf .ExpiresAt )
235+ }
236+ b , err = BaseSchemaUint32 .Pack (
237+ rf .FeedID ,
238+ uint32 (rf .ValidFromTimestamp ),
239+ uint32 (rf .Timestamp ),
240+ rf .NativeFee ,
241+ rf .LinkFee ,
242+ uint32 (rf .ExpiresAt ),
243+ )
244+ } else {
245+ b , err = BaseSchemaUint64 .Pack (
246+ rf .FeedID ,
247+ rf .ValidFromTimestamp ,
248+ rf .Timestamp ,
249+ rf .NativeFee ,
250+ rf .LinkFee ,
251+ rf .ExpiresAt ,
252+ )
253+ }
254+
218255 if err != nil {
219256 return nil , fmt .Errorf ("failed to pack base report blob; %w" , err )
220257 }
0 commit comments