Skip to content
Closed
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
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package datafeeds

import (
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"math/big"
"strconv"
"strings"

ocrcommon "github.com/smartcontractkit/libocr/commontypes"
ocr2types "github.com/smartcontractkit/libocr/offchainreporting2/types"
Expand Down Expand Up @@ -38,6 +39,7 @@ type chainSelector uint64
type SecureMintAggregatorConfig struct {
// TargetChainSelector is the chain selector to look for
TargetChainSelector chainSelector `mapstructure:"targetChainSelector"`
DataID [16]byte `mapstructure:"dataID"`
}

// ToMap converts the SecureMintAggregatorConfig to a values.Map, which is suitable for the
Expand Down Expand Up @@ -172,12 +174,6 @@ func (a *SecureMintAggregator) createOutcome(lggr logger.Logger, report *secureM
lggr = logger.Named(lggr, "SecureMintAggregator")
lggr.Debugw("createOutcome called", "report", report)

// Convert chain selector to bytes for data ID
// Secure Mint dataID: 0x04 + chain selector as bytes + right padded with 0s
var chainSelectorAsDataID [16]byte
chainSelectorAsDataID[0] = 0x04
binary.BigEndian.PutUint64(chainSelectorAsDataID[1:], uint64(a.config.TargetChainSelector))

smReportAsAnswer, err := packSecureMintReportIntoUint224ForEVM(report.Mintable, report.Block)
if err != nil {
return nil, fmt.Errorf("failed to pack secure mint report for evm into uint224: %w", err)
Expand All @@ -188,9 +184,9 @@ func (a *SecureMintAggregator) createOutcome(lggr logger.Logger, report *secureM
// abi: "(bytes16 dataId, uint32 timestamp, uint224 answer)[] Reports"
toWrap := []any{
map[EVMEncoderKey]any{
DataIDOutputFieldName: chainSelectorAsDataID,
DataIDOutputFieldName: a.config.DataID,
AnswerOutputFieldName: smReportAsAnswer,
TimestampOutputFieldName: int64(report.Block),
TimestampOutputFieldName: uint32(report.Block), // TODO: check if this change from int64 to uint32 is correct
},
}

Expand Down Expand Up @@ -221,6 +217,7 @@ func (a *SecureMintAggregator) createOutcome(lggr logger.Logger, report *secureM
func parseSecureMintConfig(config values.Map) (SecureMintAggregatorConfig, error) {
type rawConfig struct {
TargetChainSelector string `mapstructure:"targetChainSelector"`
DataID string `mapstructure:"dataID"`
}

var rawCfg rawConfig
Expand All @@ -237,8 +234,25 @@ func parseSecureMintConfig(config values.Map) (SecureMintAggregatorConfig, error
return SecureMintAggregatorConfig{}, fmt.Errorf("invalid chain selector: %w", err)
}

if rawCfg.DataID == "" {
return SecureMintAggregatorConfig{}, errors.New("dataID is required")
}

// strip 0x prefix if present
dataID := strings.TrimPrefix(rawCfg.DataID, "0x")

decodedDataID, err := hex.DecodeString(dataID)
if err != nil {
return SecureMintAggregatorConfig{}, fmt.Errorf("invalid dataID: %v %w", dataID, err)
}

if len(decodedDataID) != 16 {
return SecureMintAggregatorConfig{}, fmt.Errorf("dataID must be 16 bytes, got %d", len(decodedDataID))
}

parsedConfig := SecureMintAggregatorConfig{
TargetChainSelector: chainSelector(sel),
DataID: [16]byte(decodedDataID),
}

return parsedConfig, nil
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package datafeeds

import (
"encoding/binary"
"encoding/hex"
"encoding/json"
"math/big"
"testing"
Expand All @@ -28,19 +28,20 @@ func TestSecureMintAggregator_Aggregate(t *testing.T) {
lggr := logger.Test(t)

tests := []struct {
name string
config *values.Map
previousOutcome *types.AggregationOutcome
observations map[ocrcommon.OracleID][]values.Value
f int
expectedShouldReport bool
expectedChainSelector chainSelector
expectError bool
errorContains string
name string
chainSelector string
dataID string
previousOutcome *types.AggregationOutcome
observations map[ocrcommon.OracleID][]values.Value
f int
expectedShouldReport bool
expectError bool
errorContains string
}{
{
name: "successful eth report extraction",
config: configWithChainSelector(t, "16015286601757825753"),
name: "successful eth report extraction",
chainSelector: "16015286601757825753", // eth-testnet-sepolia
dataID: "0x01c508f42b0201320000000000000000", // test data id for a secure mint feed
observations: createSecureMintObservations(t, []ocrTriggerEventData{
{
chainSelector: ethSepoliaChainSelector,
Expand All @@ -63,14 +64,14 @@ func TestSecureMintAggregator_Aggregate(t *testing.T) {
},
},
}),
f: 1,
expectedShouldReport: true,
expectedChainSelector: ethSepoliaChainSelector,
expectError: false,
f: 1,
expectedShouldReport: true,
expectError: false,
},
{
name: "no matching chain selector found",
config: configWithChainSelector(t, "16015286601757825753"),
name: "no matching chain selector found",
chainSelector: "16015286601757825753", // eth-testnet-sepolia
dataID: "0x01c508f42b0201320000000000000000", // test data id for a secure mint feed
observations: createSecureMintObservations(t, []ocrTriggerEventData{
{
chainSelector: bnbTestnetChainSelector,
Expand All @@ -89,7 +90,8 @@ func TestSecureMintAggregator_Aggregate(t *testing.T) {
},
{
name: "no observations",
config: configWithChainSelector(t, "16015286601757825753"),
chainSelector: "16015286601757825753", // eth-testnet-sepolia
dataID: "0x01c508f42b0201320000000000000000", // test data id for a secure mint feed
observations: map[ocrcommon.OracleID][]values.Value{},
f: 1,
expectError: true,
Expand All @@ -100,28 +102,33 @@ func TestSecureMintAggregator_Aggregate(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
// Create aggregator
aggregator, err := NewSecureMintAggregator(*tc.config)
configMap, err := values.WrapMap(map[string]any{
"targetChainSelector": tc.chainSelector,
"dataID": tc.dataID,
})
require.NoError(t, err)
aggregator, err := NewSecureMintAggregator(*configMap)
require.NoError(t, err)

// Run aggregation
outcome, err := aggregator.Aggregate(lggr, tc.previousOutcome, tc.observations, tc.f)

// Check error expectations
if tc.expectError {
require.Error(t, err)
assert.Error(t, err)
if tc.errorContains != "" {
require.Contains(t, err.Error(), tc.errorContains)
assert.Contains(t, err.Error(), tc.errorContains)
}
return
}

require.NoError(t, err)
require.Equal(t, tc.expectedShouldReport, outcome.ShouldReport)
assert.Equal(t, tc.expectedShouldReport, outcome.ShouldReport)

if outcome.ShouldReport {
// Verify the output structure matches the feeds aggregator format
val, err := values.FromMapValueProto(outcome.EncodableOutcome)
require.NoError(t, err)
assert.NoError(t, err)

topLevelMap, err := val.Unwrap()
require.NoError(t, err)
Expand All @@ -130,95 +137,128 @@ func TestSecureMintAggregator_Aggregate(t *testing.T) {

// Check that we have the expected reports
reportsList, ok := mm[TopLevelListOutputFieldName].([]any)
require.True(t, ok)
require.Len(t, reportsList, 1)
assert.True(t, ok)
assert.Len(t, reportsList, 1)

// Check the first (and only) report
report, ok := reportsList[0].(map[string]any)
require.True(t, ok)
assert.True(t, ok)

// Verify dataID
dataIDBytes, ok := report[DataIDOutputFieldName].([]byte)
require.True(t, ok)
// Should be 0x04 + chain selector as bytes + right padded with 0s
var expectedChainSelectorBytes [16]byte
expectedChainSelectorBytes[0] = 0x04
binary.BigEndian.PutUint64(expectedChainSelectorBytes[1:], uint64(tc.expectedChainSelector))
require.Equal(t, expectedChainSelectorBytes[:], dataIDBytes)
t.Logf("Data ID: 0x%x", dataIDBytes)
assert.True(t, ok, "expected dataID to be []byte but got %T", report[DataIDOutputFieldName])
assert.Len(t, dataIDBytes, 16)
assert.Equal(t, tc.dataID, "0x"+hex.EncodeToString(dataIDBytes))

// Verify other fields exist
answer, ok := report[AnswerOutputFieldName].(*big.Int)
require.True(t, ok)
require.NotNil(t, answer)
assert.True(t, ok)
assert.NotNil(t, answer)

timestamp := report[TimestampOutputFieldName].(int64)
require.Equal(t, int64(1000), timestamp)
assert.Equal(t, int64(1000), timestamp)
}
})
}
}

func configWithChainSelector(t *testing.T, chainSelector string) *values.Map {
m, err := values.NewMap(map[string]any{
"targetChainSelector": chainSelector,
})
require.NoError(t, err)
return m
}

func TestSecureMintAggregatorConfig_Validation(t *testing.T) {
tests := []struct {
name string
chainSelector string
expected chainSelector
expectError bool
errorMsg string
name string
chainSelector string
dataID string
expectedChainSelector chainSelector
expectedDataID [16]byte
expectError bool
errorMsg string
}{
{
name: "valid chain selector",
chainSelector: "1",
expected: 1,
expectError: false,
name: "valid chain selector and dataID",
chainSelector: "1",
dataID: "0x01c508f42b0201320000000000000000",
expectedChainSelector: 1,
expectedDataID: [16]byte{0x01, 0xc5, 0x08, 0xf4, 0x2b, 0x02, 0x01, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
expectError: false,
},
{
name: "large chain selector",
chainSelector: "16015286601757825753", // ethereum-testnet-sepolia
dataID: "0x01c508f42b0201320000000000000000",
expectedChainSelector: 16015286601757825753,
expectedDataID: [16]byte{0x01, 0xc5, 0x08, 0xf4, 0x2b, 0x02, 0x01, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
expectError: false,
},
{
name: "dataID without 0x prefix",
chainSelector: "1",
dataID: "01c508f42b0201320000000000000000",
expectedChainSelector: 1,
expectedDataID: [16]byte{0x01, 0xc5, 0x08, 0xf4, 0x2b, 0x02, 0x01, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
expectError: false,
},
{
name: "invalid chain selector",
chainSelector: "invalid",
expectError: true,
errorMsg: "invalid chain selector",
},
{
name: "large chain selector",
chainSelector: "16015286601757825753", // ethereum-testnet-sepolia
expected: 16015286601757825753,
expectError: false,
},
{
name: "negative chain selector",
chainSelector: "-1",
dataID: "0x01c508f42b0201320000000000000000",
expectError: true,
errorMsg: "invalid chain selector",
},
{
name: "invalid dataID",
chainSelector: "1",
dataID: "invalid_data_id",
expectError: true,
errorMsg: "invalid dataID",
},
{
name: "dataID too short",
chainSelector: "1",
dataID: "0x0000",
expectError: true,
errorMsg: "dataID must be 16 bytes",
},
{
name: "dataID with odd length",
chainSelector: "1",
dataID: "0x0",
expectError: true,
errorMsg: "odd length hex string",
},
{
name: "dataID too long",
chainSelector: "1",
dataID: "0x01111111111111111111111111111111111111111111",
expectError: true,
errorMsg: "dataID must be 16 bytes",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
configMap, err := values.WrapMap(map[string]any{
"targetChainSelector": tt.chainSelector,
"dataID": tt.dataID,
})
require.NoError(t, err)

aggregator, err := NewSecureMintAggregator(*configMap)
if tt.expectError {
require.Error(t, err)
assert.Error(t, err)
if tt.errorMsg != "" {
require.Contains(t, err.Error(), tt.errorMsg)
assert.Contains(t, err.Error(), tt.errorMsg)
}
return
}

require.NoError(t, err)
assert.Equal(t, tt.expected, aggregator.(*SecureMintAggregator).config.TargetChainSelector)
assert.Equal(t, tt.expectedChainSelector, aggregator.(*SecureMintAggregator).config.TargetChainSelector)
assert.Equal(t, tt.expectedDataID, aggregator.(*SecureMintAggregator).config.DataID)
})
}
}
Expand Down
Loading