Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion go/report/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
v11 "github.com/smartcontractkit/data-streams-sdk/go/v2/report/v11"
v12 "github.com/smartcontractkit/data-streams-sdk/go/v2/report/v12"
v13 "github.com/smartcontractkit/data-streams-sdk/go/v2/report/v13"
v14 "github.com/smartcontractkit/data-streams-sdk/go/v2/report/v14"
v2 "github.com/smartcontractkit/data-streams-sdk/go/v2/report/v2"
v3 "github.com/smartcontractkit/data-streams-sdk/go/v2/report/v3"
v4 "github.com/smartcontractkit/data-streams-sdk/go/v2/report/v4"
Expand All @@ -22,7 +23,7 @@ import (

// Data represents the actual report data and attributes
type Data interface {
v1.Data | v2.Data | v3.Data | v4.Data | v5.Data | v6.Data | v7.Data | v8.Data | v9.Data | v10.Data | v11.Data | v12.Data | v13.Data
v1.Data | v2.Data | v3.Data | v4.Data | v5.Data | v6.Data | v7.Data | v8.Data | v9.Data | v10.Data | v11.Data | v12.Data | v13.Data | v14.Data
Schema() abi.Arguments
}

Expand Down Expand Up @@ -76,6 +77,8 @@ func Decode[T Data](fullReport []byte) (r *Report[T], err error) {
data, err = v12.Decode(r.ReportBlob)
case v13.Data:
data, err = v13.Decode(r.ReportBlob)
case v14.Data:
data, err = v14.Decode(r.ReportBlob)
default:
return nil, fmt.Errorf("report: unsupported data type")
}
Expand Down
59 changes: 59 additions & 0 deletions go/report/report_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
v11 "github.com/smartcontractkit/data-streams-sdk/go/v2/report/v11"
v12 "github.com/smartcontractkit/data-streams-sdk/go/v2/report/v12"
v13 "github.com/smartcontractkit/data-streams-sdk/go/v2/report/v13"
v14 "github.com/smartcontractkit/data-streams-sdk/go/v2/report/v14"
v2 "github.com/smartcontractkit/data-streams-sdk/go/v2/report/v2"
v3 "github.com/smartcontractkit/data-streams-sdk/go/v2/report/v3"
v4 "github.com/smartcontractkit/data-streams-sdk/go/v2/report/v4"
Expand Down Expand Up @@ -207,6 +208,20 @@ func TestReport(t *testing.T) {
if !reflect.DeepEqual(v13Report, rv13) {
t.Errorf("expected: %#v, got: %#v", v13Report, rv13)
}

b, err = schema.Pack(v14Report.ReportContext, v14Report.ReportBlob, v14Report.RawRs, v14Report.RawSs, v14Report.RawVs)
if err != nil {
t.Errorf("failed to encode report: %s", err)
}

rv14, err := Decode[v14.Data](b)
if err != nil {
t.Errorf("failed to decode report: %s", err)
}

if !reflect.DeepEqual(v14Report, rv14) {
t.Errorf("expected: %#v, got: %#v", v14Report, rv14)
}
}

var v1Report = &Report[v1.Data]{
Expand Down Expand Up @@ -326,6 +341,15 @@ var v13Report = &Report[v13.Data]{
RawVs: [32]uint8{00, 01, 10, 74, 67, 29, 24, 17, 12, 18, 22, 11, 69, 11, 63, 86, 12, 86, 23, 58, 13, 53, 29, 12, 17, 10, 17, 12, 63, 27, 12, 14},
}

var v14Report = &Report[v14.Data]{
Data: v14Data,
ReportContext: [3][32]uint8{},
ReportBlob: mustPackData(v14Data),
RawRs: [][32]uint8{{00, 01, 10, 74, 67, 29, 24, 17, 12, 18, 22, 11, 69, 11, 63, 86, 12, 86, 23, 58, 13, 53, 29, 12, 17, 10, 17, 12, 63, 27, 12, 14}},
RawSs: [][32]uint8{{01, 02, 10, 73, 65, 19, 14, 27, 42, 48, 52, 18, 39, 116, 67, 85, 13, 82, 33, 48, 23, 33, 49, 32, 67, 50, 37, 32, 63, 77, 14, 64}},
RawVs: [32]uint8{00, 01, 10, 74, 67, 29, 24, 17, 12, 18, 22, 11, 69, 11, 63, 86, 12, 86, 23, 58, 13, 53, 29, 12, 17, 10, 17, 12, 63, 27, 12, 14},
}

var v1Data = v1.Data{
FeedID: [32]uint8{00, 01, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58, 163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114},
ObservationsTimestamp: time.Unix(1700000000, 0),
Expand Down Expand Up @@ -492,6 +516,23 @@ var v13Data = v13.Data{
LastTradedPrice: big.NewInt(76),
}

var v14Data = v14.Data{
FeedID: [32]uint8{00, 14, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58, 163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114},
ValidFromTimestamp: time.Unix(1700000000, 0),
ObservationsTimestamp: time.Unix(1700000000, 0),
NativeFee: big.NewInt(10),
LinkFee: big.NewInt(10),
ExpiresAt: time.Unix(1700000100, 0),
MidPrice: big.NewInt(100),
BidPrice: big.NewInt(99),
AskPrice: big.NewInt(101),
ExpiryTime: time.Unix(0, 1700000010000000000),
FirstDayOfNotice: time.Unix(0, 1700000005000000000),
LastSeenTimestampNs: time.Unix(0, 1700000000000000000),
MarketStatus: common.MarketStatusOpen,
ContractMonth: "F",
}

func mustPackData(d interface{}) []byte {
var args []interface{}
var dataSchema abi.Arguments
Expand Down Expand Up @@ -676,6 +717,24 @@ func mustPackData(d interface{}) []byte {
v.BidVolume,
v.LastTradedPrice,
}
case v14.Data:
dataSchema = v14.Schema()
args = []interface{}{
v.FeedID,
uint64(v.ValidFromTimestamp.Unix()),
uint64(v.ObservationsTimestamp.Unix()),
v.NativeFee,
v.LinkFee,
uint64(v.ExpiresAt.Unix()),
v.MidPrice,
v.BidPrice,
v.AskPrice,
uint64(v.ExpiryTime.UnixNano()),
uint64(v.FirstDayOfNotice.UnixNano()),
uint64(v.LastSeenTimestampNs.UnixNano()),
v.MarketStatus,
v.ContractMonth,
}
default:
panic(fmt.Sprintf("invalid type to pack: %#v", v))
}
Expand Down
2 changes: 2 additions & 0 deletions go/report/v10/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"time"

"github.com/ethereum/go-ethereum/accounts/abi"

"github.com/smartcontractkit/data-streams-sdk/go/v2/feed"
)

Expand All @@ -26,6 +27,7 @@ func Schema() abi.Arguments {
{Name: "observationsTimestamp", Type: mustNewType("uint64")},
{Name: "nativeFee", Type: mustNewType("uint192")},
{Name: "linkFee", Type: mustNewType("uint192")},

{Name: "expiresAt", Type: mustNewType("uint64")},
{Name: "lastUpdateTimestamp", Type: mustNewType("uint64")},
{Name: "price", Type: mustNewType("int192")},
Expand Down
122 changes: 122 additions & 0 deletions go/report/v14/data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package v14

import (
"fmt"
"math/big"
"time"

"github.com/ethereum/go-ethereum/accounts/abi"

"github.com/smartcontractkit/data-streams-sdk/go/v2/feed"
)

var schema = Schema()

// Schema returns this data version schema
func Schema() abi.Arguments {
mustNewType := func(t string) abi.Type {
result, err := abi.NewType(t, "", []abi.ArgumentMarshaling{})
if err != nil {
panic(fmt.Sprintf("Unexpected error during abi.NewType: %s", err))
}
return result
}
return abi.Arguments([]abi.Argument{
{Name: "feedId", Type: mustNewType("bytes32")},
{Name: "validFromTimestamp", Type: mustNewType("uint64")},
{Name: "observationsTimestamp", Type: mustNewType("uint64")},
{Name: "nativeFee", Type: mustNewType("uint192")},
{Name: "linkFee", Type: mustNewType("uint192")},
{Name: "expiresAt", Type: mustNewType("uint64")},

{Name: "midPrice", Type: mustNewType("int192")},
{Name: "bidPrice", Type: mustNewType("int192")},
{Name: "askPrice", Type: mustNewType("int192")},
{Name: "expiryTime", Type: mustNewType("uint64")},
{Name: "firstDayOfNotice", Type: mustNewType("uint64")},
{Name: "lastSeenTimestampNs", Type: mustNewType("uint64")},
{Name: "marketStatus", Type: mustNewType("uint32")},
{Name: "contractMonth", Type: mustNewType("string")},
})
}

// Data is the container for this schema's attributes
type Data struct {
FeedID feed.ID `abi:"feedId"`
ValidFromTimestamp time.Time
ObservationsTimestamp time.Time
NativeFee *big.Int
LinkFee *big.Int
ExpiresAt time.Time

MidPrice *big.Int
BidPrice *big.Int
AskPrice *big.Int
ExpiryTime time.Time // nanoseconds precision
FirstDayOfNotice time.Time // roll_date converted to UNIX timestamp, nanoseconds precision
LastSeenTimestampNs time.Time // Should reflect the timestamp of the last update from the DP, nanoseconds precision
MarketStatus uint32
ContractMonth string // A single capital letter F to Z for Jan to Dec.
}

// rawData is used internally for ABI decoding - types must match ABI schema
type rawData struct {
FeedID feed.ID `abi:"feedId"`
ValidFromTimestamp uint64
ObservationsTimestamp uint64
NativeFee *big.Int
LinkFee *big.Int
ExpiresAt uint64

MidPrice *big.Int
BidPrice *big.Int
AskPrice *big.Int
ExpiryTime uint64
FirstDayOfNotice uint64
LastSeenTimestampNs uint64
MarketStatus uint32
ContractMonth string
}

// Schema returns this data version schema
func (Data) Schema() abi.Arguments {
return Schema()
}

// Decode decodes the serialized data bytes
func Decode(data []byte) (*Data, error) {
values, err := schema.Unpack(data)
if err != nil {
return nil, fmt.Errorf("failed to decode report: %w", err)
}
raw := new(rawData)
if err = schema.Copy(raw, values); err != nil {
return nil, fmt.Errorf("failed to copy report values to struct: %w", err)
}

// contractMonth must be a single letter from F to Z (Jan to Dec).
if len(raw.ContractMonth) != 1 || raw.ContractMonth[0] < 'F' || raw.ContractMonth[0] > 'Z' {
return nil, fmt.Errorf("invalid contractMonth %q: must be a single letter from F to Z", raw.ContractMonth)
}

res := raw.FeedID.Resolution()

decoded := &Data{
FeedID: raw.FeedID,
ValidFromTimestamp: feed.ParseTimestamp(raw.ValidFromTimestamp, res),
ObservationsTimestamp: feed.ParseTimestamp(raw.ObservationsTimestamp, res),
NativeFee: raw.NativeFee,
LinkFee: raw.LinkFee,
ExpiresAt: feed.ParseTimestamp(raw.ExpiresAt, res),
MidPrice: raw.MidPrice,
BidPrice: raw.BidPrice,
AskPrice: raw.AskPrice,
ExpiryTime: time.Unix(0, int64(raw.ExpiryTime)),
FirstDayOfNotice: time.Unix(0, int64(raw.FirstDayOfNotice)),
LastSeenTimestampNs: time.Unix(0, int64(raw.LastSeenTimestampNs)),
MarketStatus: raw.MarketStatus,
ContractMonth: raw.ContractMonth,
}

return decoded, nil
}
126 changes: 126 additions & 0 deletions go/report/v14/data_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package v14

import (
"math/big"
"testing"
"time"
)

func TestData(t *testing.T) {
// Raw values for packing
feedID := [32]uint8{00, 14, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58, 163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114}
validFromTS := uint64(time.Now().Unix())
observationsTS := uint64(time.Now().Unix())
nativeFee := big.NewInt(10)
linkFee := big.NewInt(10)
expiresAt := uint64(time.Now().Unix()) + 100
midPrice := big.NewInt(100)
bidPrice := big.NewInt(99)
askPrice := big.NewInt(101)
expiryTime := uint64(time.Now().UnixNano()) + 100
firstDayOfNotice := uint64(time.Now().UnixNano()) + 50
lastSeenTimestampNs := uint64(time.Now().UnixNano()) - 100
marketStatus := uint32(1)
contractMonth := "N"

b, err := schema.Pack(
feedID,
validFromTS,
observationsTS,
nativeFee,
linkFee,
expiresAt,
midPrice,
bidPrice,
askPrice,
expiryTime,
firstDayOfNotice,
lastSeenTimestampNs,
marketStatus,
contractMonth,
)

if err != nil {
t.Fatalf("failed to serialize report: %s", err)
}

d, err := Decode(b)
if err != nil {
t.Fatalf("failed to deserialize report: %s", err)
}

// Verify decoded values
if d.FeedID != feedID {
t.Errorf("FeedID mismatch: expected %v, got %v", feedID, d.FeedID)
}
if d.ValidFromTimestamp.Unix() != int64(validFromTS) {
t.Errorf("ValidFromTimestamp mismatch: expected %d, got %d", validFromTS, d.ValidFromTimestamp.Unix())
}
if d.ObservationsTimestamp.Unix() != int64(observationsTS) {
t.Errorf("ObservationsTimestamp mismatch: expected %d, got %d", observationsTS, d.ObservationsTimestamp.Unix())
}
if d.NativeFee.Cmp(nativeFee) != 0 {
t.Errorf("NativeFee mismatch: expected %v, got %v", nativeFee, d.NativeFee)
}
if d.LinkFee.Cmp(linkFee) != 0 {
t.Errorf("LinkFee mismatch: expected %v, got %v", linkFee, d.LinkFee)
}
if d.ExpiresAt.Unix() != int64(expiresAt) {
t.Errorf("ExpiresAt mismatch: expected %d, got %d", expiresAt, d.ExpiresAt.Unix())
}
if d.MidPrice.Cmp(midPrice) != 0 {
t.Errorf("MidPrice mismatch: expected %v, got %v", midPrice, d.MidPrice)
}
if d.BidPrice.Cmp(bidPrice) != 0 {
t.Errorf("BidPrice mismatch: expected %v, got %v", bidPrice, d.BidPrice)
}
if d.AskPrice.Cmp(askPrice) != 0 {
t.Errorf("AskPrice mismatch: expected %v, got %v", askPrice, d.AskPrice)
}
if d.ExpiryTime.UnixNano() != int64(expiryTime) {
t.Errorf("ExpiryTime mismatch: expected %d, got %d", expiryTime, d.ExpiryTime.UnixNano())
}
if d.FirstDayOfNotice.UnixNano() != int64(firstDayOfNotice) {
t.Errorf("FirstDayOfNotice mismatch: expected %d, got %d", firstDayOfNotice, d.FirstDayOfNotice.UnixNano())
}
if d.LastSeenTimestampNs.UnixNano() != int64(lastSeenTimestampNs) {
t.Errorf("LastSeenTimestampNs mismatch: expected %d, got %d", lastSeenTimestampNs, d.LastSeenTimestampNs.UnixNano())
}
if d.MarketStatus != marketStatus {
t.Errorf("MarketStatus mismatch: expected %d, got %d", marketStatus, d.MarketStatus)
}
if d.ContractMonth != contractMonth {
t.Errorf("ContractMonth mismatch: expected %s, got %s", contractMonth, d.ContractMonth)
}
}

func TestDecodeInvalidContractMonth(t *testing.T) {
feedID := [32]uint8{00, 14, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58, 163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114}

// Values that are outside the valid single-letter F..Z range must be rejected.
for _, cm := range []string{"", "A", "E", "AB", "n", "1"} {
b, err := schema.Pack(
feedID,
uint64(time.Now().Unix()),
uint64(time.Now().Unix()),
big.NewInt(10),
big.NewInt(10),
uint64(time.Now().Unix())+100,
big.NewInt(100),
big.NewInt(99),
big.NewInt(101),
uint64(time.Now().UnixNano()),
uint64(time.Now().UnixNano()),
uint64(time.Now().UnixNano()),
uint32(1),
cm,
)
if err != nil {
t.Fatalf("failed to serialize report: %s", err)
}

if _, err := Decode(b); err == nil {
t.Errorf("expected error decoding contractMonth %q, got nil", cm)
}
}
}
Loading
Loading