diff --git a/pkg/loop/internal/pb/ccipocr3/models.pb.go b/pkg/loop/internal/pb/ccipocr3/models.pb.go index bfa258edae..99bb91fa55 100644 --- a/pkg/loop/internal/pb/ccipocr3/models.pb.go +++ b/pkg/loop/internal/pb/ccipocr3/models.pb.go @@ -346,7 +346,8 @@ func (x *Message) GetTokenAmounts() []*RampTokenAmount { // BigInt represents a [big.Int]. type BigInt struct { state protoimpl.MessageState `protogen:"open.v1"` - Value []byte `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` + Negative bool `protobuf:"varint,1,opt,name=negative,proto3" json:"negative,omitempty"` + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -381,6 +382,13 @@ func (*BigInt) Descriptor() ([]byte, []int) { return file_models_proto_rawDescGZIP(), []int{3} } +func (x *BigInt) GetNegative() bool { + if x != nil { + return x.Negative + } + return false +} + func (x *BigInt) GetValue() []byte { if x != nil { return x.Value @@ -993,9 +1001,10 @@ const file_models_proto_rawDesc = "" + "\tfee_token\x18\x06 \x01(\fR\bfeeToken\x12K\n" + "\x10fee_token_amount\x18\a \x01(\v2!.loop.internal.pb.ccipocr3.BigIntR\x0efeeTokenAmount\x12I\n" + "\x0ffee_value_juels\x18\b \x01(\v2!.loop.internal.pb.ccipocr3.BigIntR\rfeeValueJuels\x12O\n" + - "\rtoken_amounts\x18\t \x03(\v2*.loop.internal.pb.ccipocr3.RampTokenAmountR\ftokenAmounts\"\x1e\n" + - "\x06BigInt\x12\x14\n" + - "\x05value\x18\x01 \x01(\fR\x05value\"^\n" + + "\rtoken_amounts\x18\t \x03(\v2*.loop.internal.pb.ccipocr3.RampTokenAmountR\ftokenAmounts\":\n" + + "\x06BigInt\x12\x1a\n" + + "\bnegative\x18\x01 \x01(\bR\bnegative\x12\x14\n" + + "\x05value\x18\x02 \x01(\fR\x05value\"^\n" + "\vTokenAmount\x12\x14\n" + "\x05token\x18\x01 \x01(\tR\x05token\x129\n" + "\x06amount\x18\x02 \x01(\v2!.loop.internal.pb.ccipocr3.BigIntR\x06amount\"\xf7\x02\n" + diff --git a/pkg/loop/internal/pb/ccipocr3/models.proto b/pkg/loop/internal/pb/ccipocr3/models.proto index b943f03016..f920e5aa38 100644 --- a/pkg/loop/internal/pb/ccipocr3/models.proto +++ b/pkg/loop/internal/pb/ccipocr3/models.proto @@ -75,7 +75,8 @@ message Message { // BigInt represents a [big.Int]. message BigInt { - bytes value = 1; + bool negative = 1; + bytes value = 2; } // TokenAmount is a helper type that defines a token and an amount. diff --git a/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/ccip_provider.go b/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/ccip_provider.go index a8315bdc91..cbb21d2ee5 100644 --- a/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/ccip_provider.go +++ b/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/ccip_provider.go @@ -29,6 +29,7 @@ type CCIPProviderClient struct { executePluginCodec ccipocr3.ExecutePluginCodec tokenDataEncoder ccipocr3.TokenDataEncoder sourceChainExtraDataCodec ccipocr3.SourceChainExtraDataCodec + messageHasher ccipocr3.MessageHasher } func NewCCIPProviderClient(b *net.BrokerExt, cc grpc.ClientConnInterface) *CCIPProviderClient { @@ -48,6 +49,7 @@ func NewCCIPProviderClient(b *net.BrokerExt, cc grpc.ClientConnInterface) *CCIPP c.executePluginCodec = NewExecutePluginCodecClient(b.WithName("ExecutePluginCodec"), cc) c.tokenDataEncoder = NewTokenDataEncoderClient(b.WithName("TokenDataEncoder"), cc) c.sourceChainExtraDataCodec = NewSourceChainExtraDataCodecClient(b.WithName("SourceChainExtraDataCodec"), cc) + c.messageHasher = NewMessageHasherClient(b.WithName("MessageHasher"), cc) return c } @@ -76,6 +78,7 @@ func (p *CCIPProviderClient) Codec() ccipocr3.Codec { ExecutePluginCodec: p.executePluginCodec, TokenDataEncoder: p.tokenDataEncoder, SourceChainExtraDataCodec: p.sourceChainExtraDataCodec, + MessageHasher: p.messageHasher, } } @@ -104,4 +107,5 @@ func RegisterProviderServices(s *grpc.Server, provider types.CCIPProvider) { ccipocr3pb.RegisterExecutePluginCodecServer(s, NewExecutePluginCodecServer(codec.ExecutePluginCodec)) ccipocr3pb.RegisterTokenDataEncoderServer(s, NewTokenDataEncoderServer(codec.TokenDataEncoder)) ccipocr3pb.RegisterSourceChainExtraDataCodecServer(s, NewSourceChainExtraDataCodecServer(codec.SourceChainExtraDataCodec)) + ccipocr3pb.RegisterMsgHasherServer(s, NewMessageHasherServer(codec.MessageHasher)) } diff --git a/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/codec.go b/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/codec.go index 4a4f823e81..769ab2eea4 100644 --- a/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/codec.go +++ b/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/codec.go @@ -666,3 +666,53 @@ func (s *extraDataCodecBundleServer) DecodeTokenAmountDestExecData(ctx context.C DecodedMap: pbMap, }, nil } + +// MessageHasher client +var _ ccipocr3.MessageHasher = (*messageHasherClient)(nil) + +type messageHasherClient struct { + *net.BrokerExt + grpc ccipocr3pb.MsgHasherClient +} + +func NewMessageHasherClient(broker *net.BrokerExt, cc grpc.ClientConnInterface) ccipocr3.MessageHasher { + return &messageHasherClient{ + BrokerExt: broker, + grpc: ccipocr3pb.NewMsgHasherClient(cc), + } +} + +func (c *messageHasherClient) Hash(ctx context.Context, message ccipocr3.Message) (ccipocr3.Bytes32, error) { + resp, err := c.grpc.HashMsg(ctx, &ccipocr3pb.HashMsgInput{ + Msg: messageToPb(message), + }) + if err != nil { + return ccipocr3.Bytes32{}, err + } + var hash ccipocr3.Bytes32 + copy(hash[:], resp.Hash) + return hash, nil +} + +// MessageHasher server +var _ ccipocr3pb.MsgHasherServer = (*messageHasherServer)(nil) + +type messageHasherServer struct { + ccipocr3pb.UnimplementedMsgHasherServer + impl ccipocr3.MessageHasher +} + +func NewMessageHasherServer(impl ccipocr3.MessageHasher) ccipocr3pb.MsgHasherServer { + return &messageHasherServer{impl: impl} +} + +func (s *messageHasherServer) HashMsg(ctx context.Context, req *ccipocr3pb.HashMsgInput) (*ccipocr3pb.HashMsgOutput, error) { + message := pbToMessage(req.Msg) + hash, err := s.impl.Hash(ctx, message) + if err != nil { + return nil, err + } + return &ccipocr3pb.HashMsgOutput{ + Hash: hash[:], + }, nil +} diff --git a/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/convert.go b/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/convert.go index 55218151dd..e8179dde45 100644 --- a/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/convert.go +++ b/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/convert.go @@ -17,14 +17,15 @@ func pbBigIntToInt(b *ccipocr3pb.BigInt) *big.Int { return nil } - if b.Value == nil { - return nil - } - if len(b.Value) == 0 { return big.NewInt(0) } - return new(big.Int).SetBytes(b.Value) + + i := new(big.Int).SetBytes(b.Value) + if b.Negative { + i = i.Neg(i) + } + return i } // Helper function to convert protobuf BigInt to ccipocr3.BigInt, preserving nil @@ -33,14 +34,15 @@ func pbToBigInt(b *ccipocr3pb.BigInt) ccipocr3.BigInt { return ccipocr3.BigInt{Int: nil} } - if b.Value == nil { - return ccipocr3.BigInt{Int: nil} - } - if len(b.Value) == 0 { return ccipocr3.BigInt{Int: big.NewInt(0)} } - return ccipocr3.NewBigInt(new(big.Int).SetBytes(b.Value)) + + i := new(big.Int).SetBytes(b.Value) + if b.Negative { + i = i.Neg(i) + } + return ccipocr3.NewBigInt(i) } // Helper function to convert big.Int to protobuf BigInt @@ -48,7 +50,10 @@ func intToPbBigInt(i *big.Int) *ccipocr3pb.BigInt { if i == nil { return nil } - return &ccipocr3pb.BigInt{Value: i.Bytes()} + return &ccipocr3pb.BigInt{ + Negative: i.Sign() < 0, + Value: i.Bytes(), + } } // Helper function to convert ConfidenceLevel to protobuf uint32 @@ -864,6 +869,9 @@ func pbToTokenUpdatesUnix(pbUpdates map[string]*ccipocr3pb.TimestampedUnixBig) m var value *big.Int if pbUpdate.Value != nil && len(pbUpdate.Value.Value) > 0 { value = new(big.Int).SetBytes(pbUpdate.Value.Value) + if pbUpdate.Value.Negative { + value = value.Neg(value) + } } else { value = big.NewInt(0) } diff --git a/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/convert_test.go b/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/convert_test.go index 20ea32f6d5..b57d463a90 100644 --- a/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/convert_test.go +++ b/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/convert_test.go @@ -43,11 +43,11 @@ func TestMessageProtobufFlattening(t *testing.T) { FeeValueJuels: ccipocr3.NewBigInt(big.NewInt(2000)), TokenAmounts: []ccipocr3.RampTokenAmount{ { - SourcePoolAddress: []byte("source-pool"), - DestTokenAddress: []byte("dest-token"), - ExtraData: []byte("token-extra"), - Amount: ccipocr3.NewBigInt(big.NewInt(500)), - DestExecData: []byte("dest-exec-data"), + SourcePoolAddress: ccipocr3.UnknownAddress("0x1111111111111111111111111111111111111111"), + DestTokenAddress: ccipocr3.UnknownAddress("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + ExtraData: ccipocr3.Bytes("extra-token-data-1"), + Amount: ccipocr3.NewBigInt(big.NewInt(1)), + DestExecData: ccipocr3.Bytes("dest-exec-data-1"), }, }, }, @@ -857,7 +857,7 @@ func TestMessageTokenIDMapErrorHandling(t *testing.T) { SourcePoolAddress: []byte("test"), DestTokenAddress: []byte("test"), ExtraData: []byte("test"), - Amount: &ccipocr3pb.BigInt{Value: []byte{0x01}}, + Amount: &ccipocr3pb.BigInt{Negative: false, Value: []byte{0x01}}, }, } @@ -1229,32 +1229,27 @@ func TestPbBigIntToInt(t *testing.T) { }, { name: "empty value bytes", // empty bytes are treated as zero - input: &ccipocr3pb.BigInt{Value: []byte{}}, + input: &ccipocr3pb.BigInt{Negative: false, Value: []byte{}}, expected: big.NewInt(0), }, - { - name: "nil value bytes", - input: &ccipocr3pb.BigInt{Value: nil}, - expected: nil, - }, { name: "zero value", - input: &ccipocr3pb.BigInt{Value: big.NewInt(0).Bytes()}, + input: &ccipocr3pb.BigInt{Negative: false, Value: big.NewInt(0).Bytes()}, expected: big.NewInt(0), }, { name: "positive small integer", - input: &ccipocr3pb.BigInt{Value: big.NewInt(42).Bytes()}, + input: &ccipocr3pb.BigInt{Negative: false, Value: big.NewInt(42).Bytes()}, expected: big.NewInt(42), }, { name: "positive large integer", - input: &ccipocr3pb.BigInt{Value: big.NewInt(1234567890).Bytes()}, + input: &ccipocr3pb.BigInt{Negative: false, Value: big.NewInt(1234567890).Bytes()}, expected: big.NewInt(1234567890), }, { name: "very large positive integer", - input: &ccipocr3pb.BigInt{Value: func() []byte { + input: &ccipocr3pb.BigInt{Negative: false, Value: func() []byte { val := new(big.Int) val.SetString("999999999999999999999999999999", 10) return val.Bytes() @@ -1267,7 +1262,7 @@ func TestPbBigIntToInt(t *testing.T) { }, { name: "maximum uint64 value", - input: &ccipocr3pb.BigInt{Value: func() []byte { + input: &ccipocr3pb.BigInt{Negative: false, Value: func() []byte { val := new(big.Int) val.SetUint64(^uint64(0)) // max uint64 return val.Bytes() @@ -1280,7 +1275,7 @@ func TestPbBigIntToInt(t *testing.T) { }, { name: "256-bit integer (32 bytes)", - input: &ccipocr3pb.BigInt{Value: func() []byte { + input: &ccipocr3pb.BigInt{Negative: false, Value: func() []byte { // Create a 256-bit integer (all bits set) bytes := make([]byte, 32) for i := range bytes { @@ -1298,19 +1293,48 @@ func TestPbBigIntToInt(t *testing.T) { }, { name: "single byte value", - input: &ccipocr3pb.BigInt{Value: []byte{0xFF}}, + input: &ccipocr3pb.BigInt{Negative: false, Value: []byte{0xFF}}, expected: big.NewInt(255), }, { name: "two byte value", - input: &ccipocr3pb.BigInt{Value: []byte{0x01, 0x00}}, + input: &ccipocr3pb.BigInt{Negative: false, Value: []byte{0x01, 0x00}}, expected: big.NewInt(256), }, { name: "leading zero bytes (should be handled correctly)", - input: &ccipocr3pb.BigInt{Value: []byte{0x00, 0x00, 0x01, 0x00}}, + input: &ccipocr3pb.BigInt{Negative: false, Value: []byte{0x00, 0x00, 0x01, 0x00}}, expected: big.NewInt(256), }, + // Negative number test cases + { + name: "negative small integer", + input: &ccipocr3pb.BigInt{Negative: true, Value: big.NewInt(42).Bytes()}, + expected: big.NewInt(-42), + }, + { + name: "negative large integer", + input: &ccipocr3pb.BigInt{Negative: true, Value: big.NewInt(1234567890).Bytes()}, + expected: big.NewInt(-1234567890), + }, + { + name: "negative very large integer", + input: &ccipocr3pb.BigInt{Negative: true, Value: func() []byte { + val := new(big.Int) + val.SetString("999999999999999999999999999999", 10) + return val.Bytes() + }()}, + expected: func() *big.Int { + val := new(big.Int) + val.SetString("-999999999999999999999999999999", 10) + return val + }(), + }, + { + name: "negative single byte value", + input: &ccipocr3pb.BigInt{Negative: true, Value: []byte{0xFF}}, + expected: big.NewInt(-255), + }, } for _, tc := range testCases { @@ -1343,32 +1367,27 @@ func TestPbToBigInt(t *testing.T) { }, { name: "empty value bytes should return zero BigInt", - input: &ccipocr3pb.BigInt{Value: []byte{}}, + input: &ccipocr3pb.BigInt{Negative: false, Value: []byte{}}, expected: ccipocr3.BigInt{Int: big.NewInt(0)}, }, - { - name: "nil value bytes should preserve nil", - input: &ccipocr3pb.BigInt{Value: nil}, - expected: ccipocr3.BigInt{Int: nil}, - }, { name: "zero value", - input: &ccipocr3pb.BigInt{Value: big.NewInt(0).Bytes()}, + input: &ccipocr3pb.BigInt{Negative: false, Value: big.NewInt(0).Bytes()}, expected: ccipocr3.NewBigInt(big.NewInt(0)), }, { name: "positive small integer", - input: &ccipocr3pb.BigInt{Value: big.NewInt(123).Bytes()}, + input: &ccipocr3pb.BigInt{Negative: false, Value: big.NewInt(123).Bytes()}, expected: ccipocr3.NewBigInt(big.NewInt(123)), }, { name: "positive large integer", - input: &ccipocr3pb.BigInt{Value: big.NewInt(9876543210).Bytes()}, + input: &ccipocr3pb.BigInt{Negative: false, Value: big.NewInt(9876543210).Bytes()}, expected: ccipocr3.NewBigInt(big.NewInt(9876543210)), }, { name: "very large positive integer", - input: &ccipocr3pb.BigInt{Value: func() []byte { + input: &ccipocr3pb.BigInt{Negative: false, Value: func() []byte { val := new(big.Int) val.SetString("123456789012345678901234567890", 10) return val.Bytes() @@ -1381,7 +1400,7 @@ func TestPbToBigInt(t *testing.T) { }, { name: "maximum uint64 value", - input: &ccipocr3pb.BigInt{Value: func() []byte { + input: &ccipocr3pb.BigInt{Negative: false, Value: func() []byte { val := new(big.Int) val.SetUint64(^uint64(0)) // max uint64 return val.Bytes() @@ -1394,7 +1413,7 @@ func TestPbToBigInt(t *testing.T) { }, { name: "256-bit integer (32 bytes)", - input: &ccipocr3pb.BigInt{Value: func() []byte { + input: &ccipocr3pb.BigInt{Negative: false, Value: func() []byte { // Create a 256-bit integer bytes := make([]byte, 32) for i := range bytes { @@ -1412,17 +1431,17 @@ func TestPbToBigInt(t *testing.T) { }, { name: "single byte maximum value", - input: &ccipocr3pb.BigInt{Value: []byte{0xFF}}, + input: &ccipocr3pb.BigInt{Negative: false, Value: []byte{0xFF}}, expected: ccipocr3.NewBigInt(big.NewInt(255)), }, { name: "two byte value", - input: &ccipocr3pb.BigInt{Value: []byte{0xFF, 0xFF}}, + input: &ccipocr3pb.BigInt{Negative: false, Value: []byte{0xFF, 0xFF}}, expected: ccipocr3.NewBigInt(big.NewInt(65535)), }, { name: "ethereum wei amount (18 decimals)", - input: &ccipocr3pb.BigInt{Value: func() []byte { + input: &ccipocr3pb.BigInt{Negative: false, Value: func() []byte { // 1 ETH in wei = 10^18 val := new(big.Int) val.SetString("1000000000000000000", 10) @@ -1434,6 +1453,35 @@ func TestPbToBigInt(t *testing.T) { return ccipocr3.NewBigInt(val) }(), }, + // Negative number test cases + { + name: "negative small integer", + input: &ccipocr3pb.BigInt{Negative: true, Value: big.NewInt(123).Bytes()}, + expected: ccipocr3.NewBigInt(big.NewInt(-123)), + }, + { + name: "negative large integer", + input: &ccipocr3pb.BigInt{Negative: true, Value: big.NewInt(9876543210).Bytes()}, + expected: ccipocr3.NewBigInt(big.NewInt(-9876543210)), + }, + { + name: "negative very large integer", + input: &ccipocr3pb.BigInt{Negative: true, Value: func() []byte { + val := new(big.Int) + val.SetString("123456789012345678901234567890", 10) + return val.Bytes() + }()}, + expected: func() ccipocr3.BigInt { + val := new(big.Int) + val.SetString("-123456789012345678901234567890", 10) + return ccipocr3.NewBigInt(val) + }(), + }, + { + name: "negative single byte value", + input: &ccipocr3pb.BigInt{Negative: true, Value: []byte{0xFF}}, + expected: ccipocr3.NewBigInt(big.NewInt(-255)), + }, } for _, tc := range testCases { @@ -1494,6 +1542,39 @@ func TestPbBigIntRoundTrip(t *testing.T) { } } +func TestGetChainFeePriceUpdateFakeRoundTrip(t *testing.T) { + mapWithZeroValues := map[ccipocr3.ChainSelector]ccipocr3.TimestampedUnixBig{ + 1: {Value: big.NewInt(0), Timestamp: 2}, + 2: {Value: big.NewInt(0), Timestamp: 3}, + 3: {Value: nil, Timestamp: 4}, + } + + // (s *chainAccessorServer) GetChainFeePriceUpdate() ... + pbUpdates := make(map[uint64]*ccipocr3pb.TimestampedUnixBig) + for chainSel, update := range mapWithZeroValues { + fmt.Println("value:", update.Value, "timestamp:", update.Timestamp) + fmt.Println("intToPbBigInt", intToPbBigInt(update.Value)) + pbUpdates[uint64(chainSel)] = &ccipocr3pb.TimestampedUnixBig{ + Value: intToPbBigInt(update.Value), + Timestamp: update.Timestamp, + } + } + + // (c *ChainAccessorClient) GetChainFeePriceUpdate() ... + gRPCResponse := &ccipocr3pb.GetChainFeePriceUpdateResponse{ + FeePriceUpdates: pbUpdates, + } + result := make(map[ccipocr3.ChainSelector]ccipocr3.TimestampedUnixBig) + for chainSel, timestampedUnixBig := range gRPCResponse.FeePriceUpdates { + result[ccipocr3.ChainSelector(chainSel)] = ccipocr3.TimestampedUnixBig{ + Value: pbToBigInt(timestampedUnixBig.Value).Int, + Timestamp: timestampedUnixBig.Timestamp, + } + } + + assert.Equal(t, result, mapWithZeroValues) +} + // TestPbToBigIntRoundTrip tests round-trip conversion between protobuf BigInt and ccipocr3.BigInt func TestPbToBigIntRoundTrip(t *testing.T) { testValues := []ccipocr3.BigInt{ @@ -1515,6 +1596,15 @@ func TestPbToBigIntRoundTrip(t *testing.T) { val.SetUint64(^uint64(0)) // max uint64 return ccipocr3.NewBigInt(val) }(), + // Test negative values + ccipocr3.NewBigInt(big.NewInt(-1)), + ccipocr3.NewBigInt(big.NewInt(-42)), + ccipocr3.NewBigInt(big.NewInt(-255)), + func() ccipocr3.BigInt { + val := new(big.Int) + val.SetString("-123456789012345678901234567890", 10) + return ccipocr3.NewBigInt(val) + }(), // Test nil value specially ccipocr3.BigInt{Int: nil}, } @@ -1540,7 +1630,7 @@ func TestPbToBigIntRoundTrip(t *testing.T) { // TestPbBigIntEdgeCases tests edge cases and error conditions func TestPbBigIntEdgeCases(t *testing.T) { t.Run("empty bytes should not panic", func(t *testing.T) { - input := &ccipocr3pb.BigInt{Value: []byte{}} + input := &ccipocr3pb.BigInt{Negative: false, Value: []byte{}} // Should not panic result1 := pbBigIntToInt(input) @@ -1551,7 +1641,7 @@ func TestPbBigIntEdgeCases(t *testing.T) { }) t.Run("single zero byte should equal zero", func(t *testing.T) { - input := &ccipocr3pb.BigInt{Value: []byte{0x00}} + input := &ccipocr3pb.BigInt{Negative: false, Value: []byte{0x00}} result1 := pbBigIntToInt(input) result2 := pbToBigInt(input) @@ -1561,7 +1651,7 @@ func TestPbBigIntEdgeCases(t *testing.T) { }) t.Run("multiple zero bytes should equal zero", func(t *testing.T) { - input := &ccipocr3pb.BigInt{Value: []byte{0x00, 0x00, 0x00, 0x00}} + input := &ccipocr3pb.BigInt{Negative: false, Value: []byte{0x00, 0x00, 0x00, 0x00}} result1 := pbBigIntToInt(input) result2 := pbToBigInt(input) @@ -1575,7 +1665,7 @@ func TestPbBigIntEdgeCases(t *testing.T) { bytes := make([]byte, 64) bytes[0] = 0x01 // Set the most significant bit to 1 - input := &ccipocr3pb.BigInt{Value: bytes} + input := &ccipocr3pb.BigInt{Negative: false, Value: bytes} result1 := pbBigIntToInt(input) result2 := pbToBigInt(input) @@ -1591,15 +1681,15 @@ func TestPbBigIntEdgeCases(t *testing.T) { func TestPbBigIntConsistency(t *testing.T) { testInputs := []*ccipocr3pb.BigInt{ nil, - {Value: nil}, - {Value: []byte{}}, - {Value: []byte{0x00}}, - {Value: []byte{0x01}}, - {Value: []byte{0xFF}}, - {Value: []byte{0x01, 0x00}}, - {Value: []byte{0xFF, 0xFF}}, - {Value: big.NewInt(42).Bytes()}, - {Value: big.NewInt(1234567890).Bytes()}, + {Negative: false, Value: nil}, + {Negative: false, Value: []byte{}}, + {Negative: false, Value: []byte{0x00}}, + {Negative: false, Value: []byte{0x01}}, + {Negative: false, Value: []byte{0xFF}}, + {Negative: false, Value: []byte{0x01, 0x00}}, + {Negative: false, Value: []byte{0xFF, 0xFF}}, + {Negative: false, Value: big.NewInt(42).Bytes()}, + {Negative: false, Value: big.NewInt(1234567890).Bytes()}, } for i, input := range testInputs { diff --git a/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/test/codec.go b/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/test/codec.go index c8dbe0fef9..9b4fffe5a7 100644 --- a/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/test/codec.go +++ b/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/test/codec.go @@ -27,6 +27,7 @@ func Codec(lggr logger.Logger) ccipocr3.Codec { ExecutePluginCodec: ExecutePluginCodec(lggr), TokenDataEncoder: TokenDataEncoder(lggr), SourceChainExtraDataCodec: SourceChainExtraDataCodec(lggr), + MessageHasher: MessageHasher(lggr), } } @@ -38,6 +39,7 @@ func CodecEvaluator(lggr logger.Logger) codecEvaluator { executePluginCodec: ExecutePluginCodec(lggr), tokenDataEncoder: TokenDataEncoder(lggr), sourceChainExtraDataCodec: SourceChainExtraDataCodec(lggr), + messageHasher: MessageHasher(lggr), } } @@ -47,6 +49,7 @@ type codecEvaluator struct { executePluginCodec ExecutePluginCodecTester tokenDataEncoder TokenDataEncoderTester sourceChainExtraDataCodec SourceChainExtraDataCodecTester + messageHasher MessageHasherTester } // Evaluate implements CodecEvaluator. @@ -81,6 +84,12 @@ func (s codecEvaluator) Evaluate(ctx context.Context, other ccipocr3.Codec) erro return fmt.Errorf("SourceChainExtraDataCodec evaluation failed: %w", err) } + // Test MessageHasher + err = s.messageHasher.Evaluate(ctx, other.MessageHasher) + if err != nil { + return fmt.Errorf("MessageHasher evaluation failed: %w", err) + } + return nil } diff --git a/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/test/codec_components.go b/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/test/codec_components.go index 47e81cf526..b41028755a 100644 --- a/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/test/codec_components.go +++ b/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/test/codec_components.go @@ -3,6 +3,7 @@ package test import ( "context" "fmt" + "math/big" "testing" "github.com/stretchr/testify/assert" @@ -669,3 +670,93 @@ func (s staticSourceChainExtraDataCodec) AssertEqual(ctx context.Context, t *tes assert.NoError(t, s.Evaluate(ctx, other)) }) } + +// MessageHasher implementation + +type MessageHasherEvaluator interface { + ccipocr3.MessageHasher + testtypes.Evaluator[ccipocr3.MessageHasher] +} + +type MessageHasherTester interface { + ccipocr3.MessageHasher + testtypes.Evaluator[ccipocr3.MessageHasher] + testtypes.AssertEqualer[ccipocr3.MessageHasher] +} + +func MessageHasher(lggr logger.Logger) staticMessageHasher { + return newStaticMessageHasher(lggr, staticMessageHasherConfig{ + hash: ccipocr3.Bytes32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}, + }) +} + +var _ MessageHasherTester = staticMessageHasher{} + +type staticMessageHasherConfig struct { + hash ccipocr3.Bytes32 +} + +type staticMessageHasher struct { + staticMessageHasherConfig +} + +func newStaticMessageHasher(lggr logger.Logger, cfg staticMessageHasherConfig) staticMessageHasher { + lggr = logger.Named(lggr, "staticMessageHasher") + return staticMessageHasher{ + staticMessageHasherConfig: cfg, + } +} + +func (s staticMessageHasher) Hash(ctx context.Context, message ccipocr3.Message) (ccipocr3.Bytes32, error) { + return s.hash, nil +} + +func (s staticMessageHasher) Evaluate(ctx context.Context, other ccipocr3.MessageHasher) error { + testMessage := ccipocr3.Message{ + Header: ccipocr3.RampMessageHeader{ + MessageID: ccipocr3.Bytes32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}, + SourceChainSelector: ccipocr3.ChainSelector(1), + DestChainSelector: ccipocr3.ChainSelector(2), + SequenceNumber: ccipocr3.SeqNum(100), + Nonce: 42, + TxHash: "0x1234567890abcdef", + OnRamp: ccipocr3.UnknownAddress("0xabcdef1234567890"), + }, + Sender: ccipocr3.UnknownAddress("0xsender"), + Data: ccipocr3.Bytes("test-data"), + Receiver: ccipocr3.UnknownAddress("0xreceiver"), + ExtraArgs: ccipocr3.Bytes("extra-args"), + FeeToken: ccipocr3.UnknownAddress("0xfeetoken"), + FeeTokenAmount: ccipocr3.NewBigInt(big.NewInt(1000)), + FeeValueJuels: ccipocr3.NewBigInt(big.NewInt(2000)), + TokenAmounts: []ccipocr3.RampTokenAmount{ + { + SourcePoolAddress: ccipocr3.UnknownAddress("0x1111111111111111111111111111111111111111"), + DestTokenAddress: ccipocr3.UnknownAddress("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + ExtraData: ccipocr3.Bytes("extra-token-data-1"), + Amount: ccipocr3.NewBigInt(big.NewInt(1)), + DestExecData: ccipocr3.Bytes("dest-exec-data-1"), + }, + }, + } + + otherHash, err := other.Hash(ctx, testMessage) + if err != nil { + return fmt.Errorf("MessageHasher other Hash failed: %w", err) + } + myHash, err := s.Hash(ctx, testMessage) + if err != nil { + return fmt.Errorf("MessageHasher Hash failed: %w", err) + } + if otherHash != myHash { + return fmt.Errorf("MessageHasher Hash mismatch: got %x, expected %x", otherHash, myHash) + } + + return nil +} + +func (s staticMessageHasher) AssertEqual(ctx context.Context, t *testing.T, other ccipocr3.MessageHasher) { + t.Run("MessageHasher", func(t *testing.T) { + assert.NoError(t, s.Evaluate(ctx, other)) + }) +} diff --git a/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/test/provider_test.go b/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/test/provider_test.go index 1ae982b396..9209a0cc02 100644 --- a/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/test/provider_test.go +++ b/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/test/provider_test.go @@ -2,6 +2,7 @@ package test import ( "context" + "math/big" "testing" "github.com/stretchr/testify/assert" @@ -81,6 +82,39 @@ func TestCCIPProvider(t *testing.T) { destExecData, err := codec.SourceChainExtraDataCodec.DecodeDestExecDataToMap([]byte("test-dest-exec-data")) assert.NoError(t, err) assert.NotNil(t, destExecData) + + // Test MessageHasher + testMessage := ccipocr3.Message{ + Header: ccipocr3.RampMessageHeader{ + MessageID: ccipocr3.Bytes32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}, + SourceChainSelector: ccipocr3.ChainSelector(1), + DestChainSelector: ccipocr3.ChainSelector(2), + SequenceNumber: ccipocr3.SeqNum(100), + Nonce: 42, + TxHash: "0x1234567890abcdef", + OnRamp: ccipocr3.UnknownAddress("0xabcdef1234567890"), + }, + Sender: ccipocr3.UnknownAddress("0xsender"), + Data: ccipocr3.Bytes("test-data"), + Receiver: ccipocr3.UnknownAddress("0xreceiver"), + ExtraArgs: ccipocr3.Bytes("extra-args"), + FeeToken: ccipocr3.UnknownAddress("0xfeetoken"), + FeeTokenAmount: ccipocr3.NewBigInt(big.NewInt(1000)), + FeeValueJuels: ccipocr3.NewBigInt(big.NewInt(2000)), + TokenAmounts: []ccipocr3.RampTokenAmount{ + { + SourcePoolAddress: ccipocr3.UnknownAddress("0x1111111111111111111111111111111111111111"), + DestTokenAddress: ccipocr3.UnknownAddress("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + ExtraData: ccipocr3.Bytes("extra-token-data-1"), + Amount: ccipocr3.NewBigInt(big.NewInt(1)), + DestExecData: ccipocr3.Bytes("dest-exec-data-1"), + }, + }, + } + + hash, err := codec.MessageHasher.Hash(ctx, testMessage) + assert.NoError(t, err) + assert.NotNil(t, hash) } func TestCCIPProviderEvaluate(t *testing.T) { diff --git a/pkg/types/ccipocr3/plugincodec.go b/pkg/types/ccipocr3/plugincodec.go index ce62b8c105..9313c35fe6 100644 --- a/pkg/types/ccipocr3/plugincodec.go +++ b/pkg/types/ccipocr3/plugincodec.go @@ -13,6 +13,7 @@ type Codec struct { ExecutePluginCodec TokenDataEncoder SourceChainExtraDataCodec + MessageHasher } // ChainSpecificAddressCodec is an interface that defines the methods for encoding and decoding addresses for a specific chain