diff --git a/go.mod b/go.mod index 8ad05ef72..891b4de2d 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/jpillora/backoff v1.0.0 github.com/pelletier/go-toml/v2 v2.2.0 github.com/prometheus/client_golang v1.17.0 - github.com/smartcontractkit/chainlink-common v0.3.1-0.20241011160913-5d432bcdc2e8 + github.com/smartcontractkit/chainlink-common v0.3.1-0.20241018163014-a9f995ebb98b github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 diff --git a/go.sum b/go.sum index 4acd944de..492552194 100644 --- a/go.sum +++ b/go.sum @@ -435,8 +435,8 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241011160913-5d432bcdc2e8 h1:S3DuS2Su9Oo+3O1kSqNHxqz+iv5y5ljbikK0xrBp8/E= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241011160913-5d432bcdc2e8/go.mod h1:tsGgeEJc5SUSlfVGSX0wR0EkRU3pM58D6SKF97V68ko= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241018163014-a9f995ebb98b h1:oOtkQzoABBHLKHVN3FafxCKRxJ8W4kxDUKbqGoRDp+I= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241018163014-a9f995ebb98b/go.mod h1:tsGgeEJc5SUSlfVGSX0wR0EkRU3pM58D6SKF97V68ko= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12ijqMM9tvYVEm+nR826WsrNi6zCKpwBhuApq127wHs= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12 h1:NzZGjaqez21I3DU7objl3xExTH4fxYvzTqar8DC6360= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index c0fc19615..8d8c6cc40 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -16,12 +16,12 @@ require ( github.com/lib/pq v1.10.9 github.com/pelletier/go-toml/v2 v2.2.2 github.com/rs/zerolog v1.33.0 - github.com/smartcontractkit/chainlink-common v0.3.1-0.20241017135127-b283b1e14fa6 - github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241010140936-4e1d0ae8315a + github.com/smartcontractkit/chainlink-common v0.3.1-0.20241018163014-a9f995ebb98b + github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241017134533-5459a1034ecd github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.11 github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.1 github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20241017214158-315746307aa7 - github.com/smartcontractkit/chainlink/v2 v2.14.0-mercury-20240807.0.20241017214158-315746307aa7 + github.com/smartcontractkit/chainlink/v2 v2.14.0-mercury-20240807.0.20241018161821-cbfb1fffe09b github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12 github.com/stretchr/testify v1.9.0 github.com/testcontainers/testcontainers-go v0.33.0 @@ -385,11 +385,11 @@ require ( github.com/smartcontractkit/chain-selectors v1.0.27 // indirect github.com/smartcontractkit/chainlink-automation v1.0.5-0.20241009152924-78acf196c332 // indirect github.com/smartcontractkit/chainlink-ccip v0.0.0-20241017140434-6757be193e1c // indirect - github.com/smartcontractkit/chainlink-cosmos v0.5.1 // indirect - github.com/smartcontractkit/chainlink-data-streams v0.1.0 // indirect + github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f // indirect + github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 // indirect - github.com/smartcontractkit/chainlink-starknet/relayer v0.1.0 // indirect + github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8 // indirect github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 // indirect github.com/smartcontractkit/chainlink-testing-framework/wasp v1.50.0 // indirect github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index bacbace9d..00f44a0a9 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1373,18 +1373,18 @@ github.com/smartcontractkit/chainlink-automation v1.0.5-0.20241009152924-78acf19 github.com/smartcontractkit/chainlink-automation v1.0.5-0.20241009152924-78acf196c332/go.mod h1:74ly9zfnQ9EwBtHZH46sIAbxQdOnX56fFjjvSQvn53k= github.com/smartcontractkit/chainlink-ccip v0.0.0-20241017140434-6757be193e1c h1:BvzX0A659a9fShyW69P/jV3iVlA4/wlGbZ/4XXE3pxI= github.com/smartcontractkit/chainlink-ccip v0.0.0-20241017140434-6757be193e1c/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241017135127-b283b1e14fa6 h1:SHwvqXq1gdXOG/f1sQGupOH6ICZRuzMy5QkD3Um/k+8= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241017135127-b283b1e14fa6/go.mod h1:tsGgeEJc5SUSlfVGSX0wR0EkRU3pM58D6SKF97V68ko= -github.com/smartcontractkit/chainlink-cosmos v0.5.1 h1:2xeZWh+4/w7xalTdAu8jqgFuxZ291aYTEwZhlQEv/BY= -github.com/smartcontractkit/chainlink-cosmos v0.5.1/go.mod h1:c1wUtVxXUqW4PzuCQhuHaBDZFv9XAQjhKTqam7GLGIU= -github.com/smartcontractkit/chainlink-data-streams v0.1.0 h1:wcRJRm7eqfbgN+Na+GjAe0/IUn6XwmSagFHqIWHHBGk= -github.com/smartcontractkit/chainlink-data-streams v0.1.0/go.mod h1:lmdRVjg49Do+5tkk9V5iAhi+Jm2kXhjZXWAbzh7xg7o= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241018163014-a9f995ebb98b h1:oOtkQzoABBHLKHVN3FafxCKRxJ8W4kxDUKbqGoRDp+I= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241018163014-a9f995ebb98b/go.mod h1:tsGgeEJc5SUSlfVGSX0wR0EkRU3pM58D6SKF97V68ko= +github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f h1:BwrIaQIx5Iy6eT+DfLhFfK2XqjxRm74mVdlX8gbu4dw= +github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f/go.mod h1:wHtwSR3F1CQSJJZDQKuqaqFYnvkT+kMyget7dl8Clvo= +github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e h1:JiETqdNM0bktAUGMc62COwXIaw3rR3M77Me6bBLG0Fg= +github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e/go.mod h1:iK3BNHKCLgSgkOyiu3iE7sfZ20Qnuk7xwjV/yO/6gnQ= github.com/smartcontractkit/chainlink-feeds v0.1.1 h1:JzvUOM/OgGQA1sOqTXXl52R6AnNt+Wg64sVG+XSA49c= github.com/smartcontractkit/chainlink-feeds v0.1.1/go.mod h1:55EZ94HlKCfAsUiKUTNI7QlE/3d3IwTlsU3YNa/nBb4= github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 h1:PBUaFfPLm+Efq7H9kdfGBivH+QhJ6vB5EZTR/sCZsxI= github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= -github.com/smartcontractkit/chainlink-starknet/relayer v0.1.0 h1:C00zDQ6AQdR9JFrHnOBEhC2TlYVzVSsC7k5AZ7hXwHI= -github.com/smartcontractkit/chainlink-starknet/relayer v0.1.0/go.mod h1:K6cKpFDW2hX4D4F5aq86l13AMJ3jyEz/AjZyGjYlS90= +github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8 h1:B4DFdk6MGcQnoCjjMBCx7Z+GWQpxRWJ4O8W/dVJyWGA= +github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8/go.mod h1:WkBqgBo+g34Gm5vWkDDl8Fh3Mzd7bF5hXp7rryg0t5o= github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.11 h1:JvRVMS6aXMoux9i/xihHo/qZtNwtv4lpbjsxo2O/1gE= github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.11/go.mod h1:c5Is0W7DUUEeV369pWbAOYqEktlGeIBQXefGyIMCzvE= github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 h1:VIxK8u0Jd0Q/VuhmsNm6Bls6Tb31H/sA3A/rbc5hnhg= @@ -1395,8 +1395,8 @@ github.com/smartcontractkit/chainlink-testing-framework/wasp v1.50.0 h1:gfhfTn7H github.com/smartcontractkit/chainlink-testing-framework/wasp v1.50.0/go.mod h1:tqajhpUJA/9OaMCLitghBXjAgqYO4i27St0F4TUO3+M= github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20241017214158-315746307aa7 h1:DcUlvGvDZgqSvEjii96ICQ9QpZYm+uRnDRjBQ3c45+M= github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20241017214158-315746307aa7/go.mod h1:uX3QgJ8hIUfK68mAAK9Hpxizert3wkCW4uUQatY4LVM= -github.com/smartcontractkit/chainlink/v2 v2.14.0-mercury-20240807.0.20241017214158-315746307aa7 h1:UE0dtQZw9euKdz+EnTeOwwoYBazYBNKsL/4G+lvHweo= -github.com/smartcontractkit/chainlink/v2 v2.14.0-mercury-20240807.0.20241017214158-315746307aa7/go.mod h1:uxtnnSRg5VmcT4oz/25YzSNbEfVjyARangiRNts+kPA= +github.com/smartcontractkit/chainlink/v2 v2.14.0-mercury-20240807.0.20241018161821-cbfb1fffe09b h1:7HKwlbZzwS/98Px9QLkkb9hRrXwVRT3Cv7WCD9sN8Yc= +github.com/smartcontractkit/chainlink/v2 v2.14.0-mercury-20240807.0.20241018161821-cbfb1fffe09b/go.mod h1:y4wEKXHwQh2hpQl6iwM9ZoijZzQPXx967HW47M2AZ5g= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12ijqMM9tvYVEm+nR826WsrNi6zCKpwBhuApq127wHs= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12 h1:NzZGjaqez21I3DU7objl3xExTH4fxYvzTqar8DC6360= diff --git a/pkg/solana/chainreader/chain_reader.go b/pkg/solana/chainreader/chain_reader.go index 39845d482..ba0093edc 100644 --- a/pkg/solana/chainreader/chain_reader.go +++ b/pkg/solana/chainreader/chain_reader.go @@ -12,6 +12,7 @@ import ( ag_solana "github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go/rpc" + codeccommon "github.com/smartcontractkit/chainlink-common/pkg/codec" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/types" @@ -288,6 +289,8 @@ func (s *SolanaChainReaderService) init(namespaces map[string]config.ChainReader s.lookup.addReadNameForContract(namespace, methodName) for _, procedure := range method.Procedures { + injectAddressModifier(procedure.OutputModifications) + mod, err := procedure.OutputModifications.ToModifier(codec.DecoderHooks...) if err != nil { return err @@ -311,6 +314,17 @@ func (s *SolanaChainReaderService) init(namespaces map[string]config.ChainReader return nil } +// injectAddressModifier injects AddressModifier into OutputModifications. +// This is necessary because AddressModifier cannot be serialized and must be applied at runtime. +func injectAddressModifier(outputModifications codeccommon.ModifiersConfig) { + for i, modConfig := range outputModifications { + if addrModifierConfig, ok := modConfig.(*codeccommon.AddressBytesToStringModifierConfig); ok { + addrModifierConfig.Modifier = codec.SolanaAddressModifier{} + outputModifications[i] = addrModifierConfig + } + } +} + func createRPCOpts(opts *config.RPCOpts) *rpc.GetAccountInfoOpts { if opts == nil { return nil diff --git a/pkg/solana/chainreader/chain_reader_test.go b/pkg/solana/chainreader/chain_reader_test.go index 5bff4ccc3..f1af2f3d5 100644 --- a/pkg/solana/chainreader/chain_reader_test.go +++ b/pkg/solana/chainreader/chain_reader_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "math/big" "os" "strconv" "strings" @@ -19,6 +20,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/libocr/commontypes" + codeccommon "github.com/smartcontractkit/chainlink-common/pkg/codec" "github.com/smartcontractkit/chainlink-common/pkg/codec/encodings/binary" "github.com/smartcontractkit/chainlink-common/pkg/logger" @@ -426,6 +429,10 @@ func (r *chainReaderInterfaceTester) GetAccountBytes(i int) []byte { return account[:] } +func (r *chainReaderInterfaceTester) GetAccountString(i int) string { + return solana.PublicKeyFromBytes(r.GetAccountBytes(i)).String() +} + func (r *chainReaderInterfaceTester) Name() string { return "Solana" } @@ -495,6 +502,11 @@ func (r *chainReaderInterfaceTester) Setup(t *testing.T) { Procedures: []config.ChainReaderProcedure{ { IDLAccount: "TestStructB", + OutputModifications: codeccommon.ModifiersConfig{ + &codeccommon.AddressBytesToStringModifierConfig{ + Fields: []string{"Accountstr"}, + }, + }, }, { IDLAccount: "TestStructA", @@ -652,6 +664,7 @@ func (r *wrappedTestChainReader) GetLatestValue(ctx context.Context, readIdentif fallthrough default: + if len(r.testStructQueue) == 0 { r.test.FailNow() } @@ -666,9 +679,44 @@ func (r *wrappedTestChainReader) GetLatestValue(ctx context.Context, readIdentif // split into two encoded parts to test the preloading function cdc := makeTestCodec(r.test, fullStructIDL(r.test), config.EncodingTypeBorsh) - bts, err := cdc.Encode(ctx, nextTestStruct, "TestStructB") - if err != nil { - r.test.FailNow() + var bts []byte + var err error + if strings.Contains(r.test.Name(), "wraps_config_with_modifiers_using_its_own_mapstructure_overrides") { + // TODO: This is a temporary solution. We are manually retyping this struct to avoid breaking unrelated tests. + // Once input modifiers are fully implemented, revisit this code and remove this manual struct conversion + tempStruct := struct { + Field *int32 + OracleID commontypes.OracleID + OracleIDs [32]commontypes.OracleID + Account []byte + AccountStr []byte + Accounts [][]byte + DifferentField string + BigField *big.Int + NestedDynamicStruct MidLevelDynamicTestStruct + NestedStaticStruct MidLevelStaticTestStruct + }{ + Field: nextTestStruct.Field, + OracleID: nextTestStruct.OracleID, + OracleIDs: nextTestStruct.OracleIDs, + Account: nextTestStruct.Account, + AccountStr: nextTestStruct.Account, // This test needs AccountStr to be a byte slice + Accounts: nextTestStruct.Accounts, + DifferentField: nextTestStruct.DifferentField, + BigField: nextTestStruct.BigField, + NestedDynamicStruct: nextTestStruct.NestedDynamicStruct, + NestedStaticStruct: nextTestStruct.NestedStaticStruct, + } + + bts, err = cdc.Encode(ctx, tempStruct, "TestStructB") + if err != nil { + r.test.FailNow() + } + } else { + bts, err = cdc.Encode(ctx, nextTestStruct, "TestStructB") + if err != nil { + r.test.FailNow() + } } // make part A return slower than part B @@ -891,6 +939,7 @@ const ( {"name": "oracleID","type": "u8"}, {"name": "oracleIDs","type": {"array": ["u8",32]}}, {"name": "account","type": "bytes"}, + {"name": "accountstr","type": {"array": ["u8",32]}}, {"name": "accounts","type": {"vec": "bytes"}} ] } diff --git a/pkg/solana/codec/byte_string_modifier.go b/pkg/solana/codec/byte_string_modifier.go new file mode 100644 index 000000000..8a30b33d5 --- /dev/null +++ b/pkg/solana/codec/byte_string_modifier.go @@ -0,0 +1,40 @@ +package codec + +import ( + "fmt" + + "github.com/gagliardetto/solana-go" + + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +// SolanaAddressModifier implements the AddressModifier interface for Solana addresses. +// It handles encoding and decoding Solana addresses using Base58 encoding. +type SolanaAddressModifier struct{} + +// EncodeAddress encodes a Solana address (32-byte array) into a Base58 string. +func (s SolanaAddressModifier) EncodeAddress(bytes []byte) (string, error) { + if len(bytes) != s.Length() { + return "", fmt.Errorf("%w: got length %d, expected 32 for bytes %x", commontypes.ErrInvalidType, len(bytes), bytes) + } + return solana.PublicKeyFromBytes(bytes).String(), nil +} + +// DecodeAddress decodes a Base58-encoded Solana address into a 32-byte array. +func (s SolanaAddressModifier) DecodeAddress(str string) ([]byte, error) { + if len(str) != 44 { + return nil, fmt.Errorf("%w: got length %d, expected 44 for address %s", commontypes.ErrInvalidType, len(str), str) + } + + pubkey, err := solana.PublicKeyFromBase58(str) + if err != nil { + return nil, fmt.Errorf("%w: failed to decode Base58 address: %s", commontypes.ErrInvalidType, err) + } + + return pubkey.Bytes(), nil +} + +// Length returns the expected length of a Solana address in bytes (32 bytes). +func (s SolanaAddressModifier) Length() int { + return solana.PublicKeyLength +} diff --git a/pkg/solana/codec/byte_string_modifier_test.go b/pkg/solana/codec/byte_string_modifier_test.go new file mode 100644 index 000000000..83d1bf189 --- /dev/null +++ b/pkg/solana/codec/byte_string_modifier_test.go @@ -0,0 +1,57 @@ +package codec_test + +import ( + "testing" + + "github.com/gagliardetto/solana-go" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + + "github.com/smartcontractkit/chainlink-solana/pkg/solana/codec" +) + +func TestSolanaAddressModifier(t *testing.T) { + modifier := codec.SolanaAddressModifier{} + + // Valid Solana address (32 bytes, Base58 encoded) + validAddressStr := "9nQhQ7iCyY5SgAX2Zm4DtxNh9Ubc4vbiLkiYbX43SDXY" + validAddressBytes := solana.MustPublicKeyFromBase58(validAddressStr).Bytes() + invalidLengthAddressStr := "abc123" + + t.Run("EncodeAddress encodes valid Solana address bytes", func(t *testing.T) { + encoded, err := modifier.EncodeAddress(validAddressBytes) + require.NoError(t, err) + assert.Equal(t, validAddressStr, encoded) + }) + + t.Run("EncodeAddress returns error for invalid byte length", func(t *testing.T) { + invalidBytes := []byte(invalidLengthAddressStr) + _, err := modifier.EncodeAddress(invalidBytes) + assert.Error(t, err) + assert.Contains(t, err.Error(), commontypes.ErrInvalidType.Error()) + }) + + t.Run("DecodeAddress decodes valid Solana address", func(t *testing.T) { + decodedBytes, err := modifier.DecodeAddress(validAddressStr) + require.NoError(t, err) + assert.Equal(t, validAddressBytes, decodedBytes) + }) + + t.Run("DecodeAddress returns error for invalid address length", func(t *testing.T) { + _, err := modifier.DecodeAddress(invalidLengthAddressStr) + assert.Error(t, err) + assert.Contains(t, err.Error(), commontypes.ErrInvalidType.Error()) + }) + + t.Run("DecodeAddress returns error for zero-value address", func(t *testing.T) { + _, err := modifier.DecodeAddress(solana.PublicKey{}.String()) + assert.Error(t, err) + assert.Contains(t, err.Error(), commontypes.ErrInvalidType.Error()) + }) + + t.Run("Length returns 32 for Solana addresses", func(t *testing.T) { + assert.Equal(t, solana.PublicKeyLength, modifier.Length()) + }) +}