Skip to content

Commit

Permalink
[BCFR-147][solana] - Add codec chain agnostic modifier for converting…
Browse files Browse the repository at this point in the history
… byte array address to string (#875)

* working tests

* bump common

* bump common

* chain agnostic modifer

* bump common

* fix lint

* addresing comments

* bump common

* manually retype test

* bump common

* inject modifier during runtime

* remove unnecessary skip

* fix integration tests deps

* general name

* refactor helper func

* bump common

* update mercury dep

* update common ref

* integration tests fix
  • Loading branch information
Farber98 authored Oct 18, 2024
1 parent 61df9bc commit 8b009d6
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 22 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
12 changes: 6 additions & 6 deletions integration-tests/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
20 changes: 10 additions & 10 deletions integration-tests/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand All @@ -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=
Expand Down
14 changes: 14 additions & 0 deletions pkg/solana/chainreader/chain_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
55 changes: 52 additions & 3 deletions pkg/solana/chainreader/chain_reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"math/big"
"os"
"strconv"
"strings"
Expand All @@ -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"
Expand Down Expand Up @@ -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"
}
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -652,6 +664,7 @@ func (r *wrappedTestChainReader) GetLatestValue(ctx context.Context, readIdentif

fallthrough
default:

if len(r.testStructQueue) == 0 {
r.test.FailNow()
}
Expand All @@ -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
Expand Down Expand Up @@ -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"}}
]
}
Expand Down
40 changes: 40 additions & 0 deletions pkg/solana/codec/byte_string_modifier.go
Original file line number Diff line number Diff line change
@@ -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
}
57 changes: 57 additions & 0 deletions pkg/solana/codec/byte_string_modifier_test.go
Original file line number Diff line number Diff line change
@@ -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())
})
}

0 comments on commit 8b009d6

Please sign in to comment.