Skip to content

Commit 61182c2

Browse files
committed
move TokenInfo to cl-common and add to GetFeedPricesUSD args
1 parent 7670c41 commit 61182c2

File tree

9 files changed

+398
-111
lines changed

9 files changed

+398
-111
lines changed

pkg/loop/internal/pb/ccipocr3/chainaccessor.pb.go

Lines changed: 183 additions & 101 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/loop/internal/pb/ccipocr3/chainaccessor.proto

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ message MessagesByTokenIDResponse {
360360
// PriceReader request/response messages
361361
message GetFeedPricesUSDRequest {
362362
repeated string tokens = 1; // UnknownEncodedAddress
363+
map<string, TokenInfo> token_info = 2; // key is UnknownEncodedAddress
363364
}
364365

365366
message GetFeedPricesUSDResponse {
@@ -379,4 +380,10 @@ message GetFeeQuoterTokenUpdatesResponse {
379380
message MessageTokenID {
380381
uint64 seq_nr = 1;
381382
int32 index = 2;
383+
}
384+
385+
message TokenInfo {
386+
string aggregator_address = 1; // UnknownEncodedAddress
387+
BigInt deviation_ppb = 2;
388+
uint32 decimals = 3;
382389
}

pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/chainaccessor.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -302,14 +302,15 @@ func (c *chainAccessorClient) MessagesByTokenID(ctx context.Context, source, des
302302
}
303303

304304
// PriceReader methods
305-
func (c *chainAccessorClient) GetFeedPricesUSD(ctx context.Context, tokens []ccipocr3.UnknownEncodedAddress) (ccipocr3.TokenPriceMap, error) {
305+
func (c *chainAccessorClient) GetFeedPricesUSD(ctx context.Context, tokens []ccipocr3.UnknownEncodedAddress, tokenInfo map[ccipocr3.UnknownEncodedAddress]ccipocr3.TokenInfo) (ccipocr3.TokenPriceMap, error) {
306306
var tokenStrs []string
307307
for _, token := range tokens {
308308
tokenStrs = append(tokenStrs, string(token))
309309
}
310310

311311
resp, err := c.grpc.GetFeedPricesUSD(ctx, &ccipocr3pb.GetFeedPricesUSDRequest{
312-
Tokens: tokenStrs,
312+
Tokens: tokenStrs,
313+
TokenInfo: tokenInfoMapToPb(tokenInfo),
313314
})
314315
if err != nil {
315316
return nil, err
@@ -630,8 +631,9 @@ func (s *chainAccessorServer) GetFeedPricesUSD(ctx context.Context, req *ccipocr
630631
for _, tokenStr := range req.Tokens {
631632
tokens = append(tokens, ccipocr3.UnknownEncodedAddress(tokenStr))
632633
}
634+
tokenInfo := pbToTokenInfoMap(req.TokenInfo)
633635

634-
prices, err := s.impl.GetFeedPricesUSD(ctx, tokens)
636+
prices, err := s.impl.GetFeedPricesUSD(ctx, tokens, tokenInfo)
635637
if err != nil {
636638
return nil, err
637639
}

pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/convert.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,3 +884,44 @@ func tokenUpdatesUnixToPb(updates map[ccipocr3.UnknownEncodedAddress]ccipocr3.Ti
884884
}
885885
return result
886886
}
887+
888+
func pbToTokenInfo(pb *ccipocr3pb.TokenInfo) ccipocr3.TokenInfo {
889+
if pb == nil {
890+
return ccipocr3.TokenInfo{}
891+
}
892+
return ccipocr3.TokenInfo{
893+
AggregatorAddress: ccipocr3.UnknownEncodedAddress(pb.AggregatorAddress),
894+
DeviationPPB: pbToBigInt(pb.DeviationPpb),
895+
Decimals: uint8(pb.Decimals),
896+
}
897+
}
898+
899+
func tokenInfoToPb(info ccipocr3.TokenInfo) *ccipocr3pb.TokenInfo {
900+
return &ccipocr3pb.TokenInfo{
901+
AggregatorAddress: string(info.AggregatorAddress),
902+
DeviationPpb: intToPbBigInt(info.DeviationPPB.Int),
903+
Decimals: uint32(info.Decimals),
904+
}
905+
}
906+
907+
func pbToTokenInfoMap(pbMap map[string]*ccipocr3pb.TokenInfo) map[ccipocr3.UnknownEncodedAddress]ccipocr3.TokenInfo {
908+
if pbMap == nil {
909+
return nil
910+
}
911+
result := make(map[ccipocr3.UnknownEncodedAddress]ccipocr3.TokenInfo)
912+
for token, pbInfo := range pbMap {
913+
result[ccipocr3.UnknownEncodedAddress(token)] = pbToTokenInfo(pbInfo)
914+
}
915+
return result
916+
}
917+
918+
func tokenInfoMapToPb(infoMap map[ccipocr3.UnknownEncodedAddress]ccipocr3.TokenInfo) map[string]*ccipocr3pb.TokenInfo {
919+
if infoMap == nil {
920+
return nil
921+
}
922+
result := make(map[string]*ccipocr3pb.TokenInfo)
923+
for token, info := range infoMap {
924+
result[string(token)] = tokenInfoToPb(info)
925+
}
926+
return result
927+
}

pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/convert_test.go

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1404,7 +1404,6 @@ func TestPbToBigIntRoundTrip(t *testing.T) {
14041404

14051405
// Handle nil case specially
14061406
if originalValue.Int == nil {
1407-
// When original is nil, intToPbBigInt now returns nil protobuf which pbToBigInt preserves as nil
14081407
assert.Nil(t, convertedValue.Int, "nil should round-trip to nil")
14091408
} else {
14101409
assert.NotNil(t, convertedValue.Int, "converted value should not be nil for non-nil input")
@@ -1496,3 +1495,102 @@ func TestPbBigIntConsistency(t *testing.T) {
14961495
})
14971496
}
14981497
}
1498+
1499+
// TestTokenInfoConversion tests the TokenInfo conversion functions
1500+
func TestTokenInfoConversion(t *testing.T) {
1501+
testCases := []struct {
1502+
name string
1503+
info ccipocr3.TokenInfo
1504+
expected ccipocr3.TokenInfo
1505+
}{
1506+
{
1507+
name: "complete TokenInfo",
1508+
info: ccipocr3.TokenInfo{
1509+
AggregatorAddress: ccipocr3.UnknownEncodedAddress("0x1234567890123456789012345678901234567890"),
1510+
DeviationPPB: ccipocr3.NewBigInt(big.NewInt(1000000000)), // 1%
1511+
Decimals: 18,
1512+
},
1513+
expected: ccipocr3.TokenInfo{
1514+
AggregatorAddress: ccipocr3.UnknownEncodedAddress("0x1234567890123456789012345678901234567890"),
1515+
DeviationPPB: ccipocr3.NewBigInt(big.NewInt(1000000000)),
1516+
Decimals: 18,
1517+
},
1518+
},
1519+
{
1520+
name: "minimal TokenInfo",
1521+
info: ccipocr3.TokenInfo{
1522+
AggregatorAddress: ccipocr3.UnknownEncodedAddress("0xabc"),
1523+
DeviationPPB: ccipocr3.NewBigInt(big.NewInt(1)),
1524+
Decimals: 6,
1525+
},
1526+
expected: ccipocr3.TokenInfo{
1527+
AggregatorAddress: ccipocr3.UnknownEncodedAddress("0xabc"),
1528+
DeviationPPB: ccipocr3.NewBigInt(big.NewInt(1)),
1529+
Decimals: 6,
1530+
},
1531+
},
1532+
}
1533+
1534+
for _, tc := range testCases {
1535+
t.Run(tc.name, func(t *testing.T) {
1536+
// Convert to protobuf and back
1537+
pbInfo := tokenInfoToPb(tc.info)
1538+
convertedInfo := pbToTokenInfo(pbInfo)
1539+
1540+
assert.Equal(t, tc.expected.AggregatorAddress, convertedInfo.AggregatorAddress)
1541+
assert.Equal(t, tc.expected.DeviationPPB.String(), convertedInfo.DeviationPPB.String())
1542+
assert.Equal(t, tc.expected.Decimals, convertedInfo.Decimals)
1543+
})
1544+
}
1545+
}
1546+
1547+
// TestTokenInfoMapConversion tests the TokenInfo map conversion functions
1548+
func TestTokenInfoMapConversion(t *testing.T) {
1549+
testMap := map[ccipocr3.UnknownEncodedAddress]ccipocr3.TokenInfo{
1550+
"token1": {
1551+
AggregatorAddress: ccipocr3.UnknownEncodedAddress("0x1111111111111111111111111111111111111111"),
1552+
DeviationPPB: ccipocr3.NewBigInt(big.NewInt(2000000000)), // 2%
1553+
Decimals: 18,
1554+
},
1555+
"token2": {
1556+
AggregatorAddress: ccipocr3.UnknownEncodedAddress("0x2222222222222222222222222222222222222222"),
1557+
DeviationPPB: ccipocr3.NewBigInt(big.NewInt(5000000000)), // 5%
1558+
Decimals: 6,
1559+
},
1560+
}
1561+
1562+
// Convert to protobuf and back
1563+
pbMap := tokenInfoMapToPb(testMap)
1564+
convertedMap := pbToTokenInfoMap(pbMap)
1565+
1566+
assert.Len(t, convertedMap, 2)
1567+
1568+
for token, expectedInfo := range testMap {
1569+
convertedInfo, exists := convertedMap[token]
1570+
assert.True(t, exists, "Token %s should exist in converted map", token)
1571+
assert.Equal(t, expectedInfo.AggregatorAddress, convertedInfo.AggregatorAddress)
1572+
assert.Equal(t, expectedInfo.DeviationPPB.String(), convertedInfo.DeviationPPB.String())
1573+
assert.Equal(t, expectedInfo.Decimals, convertedInfo.Decimals)
1574+
}
1575+
}
1576+
1577+
// TestTokenInfoMapNilHandling tests nil handling for TokenInfo map conversion
1578+
func TestTokenInfoMapNilHandling(t *testing.T) {
1579+
t.Run("nil map to protobuf", func(t *testing.T) {
1580+
result := tokenInfoMapToPb(nil)
1581+
assert.Nil(t, result)
1582+
})
1583+
1584+
t.Run("nil protobuf map to Go", func(t *testing.T) {
1585+
result := pbToTokenInfoMap(nil)
1586+
assert.Nil(t, result)
1587+
})
1588+
1589+
t.Run("empty map round-trip", func(t *testing.T) {
1590+
emptyMap := make(map[ccipocr3.UnknownEncodedAddress]ccipocr3.TokenInfo)
1591+
pbMap := tokenInfoMapToPb(emptyMap)
1592+
convertedMap := pbToTokenInfoMap(pbMap)
1593+
assert.NotNil(t, convertedMap)
1594+
assert.Len(t, convertedMap, 0)
1595+
})
1596+
}

pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/test/chain_accessor.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ func (s staticChainAccessor) MessagesByTokenID(ctx context.Context, source, dest
393393
}
394394

395395
// PriceReader implementation
396-
func (s staticChainAccessor) GetFeedPricesUSD(ctx context.Context, tokens []ccipocr3.UnknownEncodedAddress) (ccipocr3.TokenPriceMap, error) {
396+
func (s staticChainAccessor) GetFeedPricesUSD(ctx context.Context, tokens []ccipocr3.UnknownEncodedAddress, tokenInfo map[ccipocr3.UnknownEncodedAddress]ccipocr3.TokenInfo) (ccipocr3.TokenPriceMap, error) {
397397
// Return static test prices
398398
result := make(ccipocr3.TokenPriceMap)
399399
for i, token := range tokens {
@@ -989,12 +989,19 @@ func (s staticChainAccessor) evaluateMessagesByTokenID(ctx context.Context, othe
989989

990990
func (s staticChainAccessor) evaluateGetFeedPricesUSD(ctx context.Context, other ccipocr3.ChainAccessor) error {
991991
tokens := []ccipocr3.UnknownEncodedAddress{"token1", "token2", "token3"}
992+
tokenInfo := map[ccipocr3.UnknownEncodedAddress]ccipocr3.TokenInfo{
993+
"token1": {
994+
AggregatorAddress: ccipocr3.UnknownEncodedAddress("0x1234567890123456789012345678901234567890"),
995+
DeviationPPB: ccipocr3.NewBigInt(big.NewInt(1000000000)), // 1%
996+
Decimals: 18,
997+
},
998+
}
992999

993-
otherPrices, err := other.GetFeedPricesUSD(ctx, tokens)
1000+
otherPrices, err := other.GetFeedPricesUSD(ctx, tokens, tokenInfo)
9941001
if err != nil {
9951002
return fmt.Errorf("GetFeedPricesUSD failed: %w", err)
9961003
}
997-
myPrices, err := s.GetFeedPricesUSD(ctx, tokens)
1004+
myPrices, err := s.GetFeedPricesUSD(ctx, tokens, tokenInfo)
9981005
if err != nil {
9991006
return fmt.Errorf("GetFeedPricesUSD failed: %w", err)
10001007
}

pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/test/chain_accessor_test.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,14 @@ func TestChainAccessor(t *testing.T) {
134134
// PriceReader tests
135135
t.Run("GetFeedPricesUSD", func(t *testing.T) {
136136
tokens := []ccipocr3.UnknownEncodedAddress{"token1", "token2", "token3"}
137-
prices, err := chainAccessor.GetFeedPricesUSD(ctx, tokens)
137+
tokenInfo := map[ccipocr3.UnknownEncodedAddress]ccipocr3.TokenInfo{
138+
"token1": {
139+
AggregatorAddress: ccipocr3.UnknownEncodedAddress("0x1234567890123456789012345678901234567890"),
140+
DeviationPPB: ccipocr3.NewBigInt(big.NewInt(1000000000)), // 1%
141+
Decimals: 18,
142+
},
143+
}
144+
prices, err := chainAccessor.GetFeedPricesUSD(ctx, tokens, tokenInfo)
138145
assert.NoError(t, err)
139146
assert.NotNil(t, prices)
140147
assert.Len(t, prices, 3)

pkg/types/ccipocr3/chainaccessor.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,11 @@ type PriceReader interface {
210210
// 1 ETH = 2,000 USD per full token, each full token is 1e18 units -> 2000 * 1e18 * 1e18 / 1e18 = 2_000e18
211211
// 1 LINK = 5.00 USD per full token, each full token is 1e18 units -> 5 * 1e18 * 1e18 / 1e18 = 5e18
212212
// The order of the returned prices corresponds to the order of the provided tokens.
213-
GetFeedPricesUSD(ctx context.Context,
214-
tokens []UnknownEncodedAddress) (TokenPriceMap, error)
213+
GetFeedPricesUSD(
214+
ctx context.Context,
215+
tokens []UnknownEncodedAddress,
216+
tokenInfo map[UnknownEncodedAddress]TokenInfo,
217+
) (TokenPriceMap, error)
215218

216219
// GetFeeQuoterTokenUpdates returns the latest token prices from the FeeQuoter on the specified chain
217220
GetFeeQuoterTokenUpdates(

pkg/types/ccipocr3/generic_types.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package ccipocr3
22

33
import (
4+
"encoding/hex"
45
"encoding/json"
6+
"errors"
57
"fmt"
68
"math"
79
"math/big"
810
"sort"
911
"strconv"
12+
"strings"
1013
"time"
1114
)
1215

@@ -38,6 +41,43 @@ func NewTokenPrice(tokenID UnknownEncodedAddress, price *big.Int) TokenPrice {
3841
}
3942
}
4043

44+
type TokenInfo struct {
45+
// AggregatorAddress is the address of the price feed TOKEN/USD aggregator on the feed chain.
46+
AggregatorAddress UnknownEncodedAddress `json:"aggregatorAddress"`
47+
48+
// DeviationPPB is the deviation in parts per billion that the price feed is allowed to deviate
49+
// from the last written price on-chain before we write a new price.
50+
DeviationPPB BigInt `json:"deviationPPB"`
51+
52+
// Decimals is the number of decimals for the token (NOT the feed).
53+
Decimals uint8 `json:"decimals"`
54+
}
55+
56+
func (a TokenInfo) Validate() error {
57+
if a.AggregatorAddress == "" {
58+
return errors.New("aggregatorAddress not set")
59+
}
60+
61+
// aggregator must be an ethereum address
62+
decoded, err := hex.DecodeString(strings.ToLower(strings.TrimPrefix(string(a.AggregatorAddress), "0x")))
63+
if err != nil {
64+
return fmt.Errorf("aggregatorAddress must be a valid ethereum address (i.e hex encoded 20 bytes): %w", err)
65+
}
66+
if len(decoded) != 20 {
67+
return fmt.Errorf("aggregatorAddress must be a valid ethereum address, got %d bytes expected 20", len(decoded))
68+
}
69+
70+
if a.DeviationPPB.Int.Cmp(big.NewInt(0)) <= 0 {
71+
return errors.New("deviationPPB not set or negative, must be positive")
72+
}
73+
74+
if a.Decimals == 0 {
75+
return fmt.Errorf("tokenDecimals can't be zero")
76+
}
77+
78+
return nil
79+
}
80+
4181
type GasPriceChain struct {
4282
ChainSel ChainSelector `json:"chainSel"`
4383
GasPrice BigInt `json:"gasPrice"`

0 commit comments

Comments
 (0)