Skip to content

Commit

Permalink
Address sharing (#1069)
Browse files Browse the repository at this point in the history
  • Loading branch information
ilija42 authored Feb 12, 2025
1 parent 3ea6680 commit bfc4414
Show file tree
Hide file tree
Showing 7 changed files with 289 additions and 98 deletions.
93 changes: 68 additions & 25 deletions integration-tests/relayinterface/chain_components_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,10 @@ func RunContractReaderTests[T WrappedTestingT[T]](t T, it *SolanaChainComponents

// GetLatestValue method
const (
ContractReaderGetLatestValueUsingMultiReader = "Get latest value using multi reader"
ContractReaderGetLatestValueUsingMultiReaderWithParmsReuse = "Get latest value using multi reader with params reuse"
ContractReaderGetLatestValueGetTokenPrices = "Get latest value handles get token prices edge case"
ContractReaderGetLatestValueUsingMultiReader = "Get latest value using multi reader"
ContractReaderGetLatestValueWithAddressHardcodedIntoResponse = "Get latest value with AddressHardcoded into response"
ContractReaderGetLatestValueUsingMultiReaderWithParmsReuse = "Get latest value using multi reader with params reuse"
ContractReaderGetLatestValueGetTokenPrices = "Get latest value handles get token prices edge case"
)

type TimestampedUnixBig struct {
Expand All @@ -211,6 +212,33 @@ type TimestampedUnixBig struct {
func RunContractReaderInLoopTests[T WrappedTestingT[T]](t T, it ChainComponentsInterfaceTester[T]) {
//RunContractReaderInterfaceTests(t, it, false, true)
testCases := []Testcase[T]{
{
Name: ContractReaderGetLatestValueWithAddressHardcodedIntoResponse,
Test: func(t T) {
cr := it.GetContractReader(t)
bindings := it.GetBindings(t)
ctx := tests.Context(t)

bound := BindingsByName(bindings, AnyContractName)[0]
require.NoError(t, cr.Bind(ctx, bindings))

boundAddress, err := solana.PublicKeyFromBase58(bound.Address)
require.NoError(t, err)

type MultiReadResult struct {
A uint8
B int16
SharedAddress []byte
AddressToShare []byte
}

mRR := MultiReadResult{}
require.NoError(t, cr.GetLatestValue(ctx, bound.ReadIdentifier(ReadWithAddressHardCodedIntoResponse), primitives.Unconfirmed, nil, &mRR))

expectedMRR := MultiReadResult{A: 1, B: 2, SharedAddress: boundAddress.Bytes(), AddressToShare: boundAddress.Bytes()}
require.Equal(t, expectedMRR, mRR)
},
},
{
Name: ContractReaderGetLatestValueUsingMultiReader,
Test: func(t T) {
Expand Down Expand Up @@ -603,9 +631,10 @@ func (h *helper) runInitialize(
}

const (
MultiRead = "MultiRead"
MultiReadWithParamsReuse = "MultiReadWithParamsReuse"
GetTokenPrices = "GetTokenPrices"
MultiRead = "MultiRead"
ReadWithAddressHardCodedIntoResponse = "ReadWithAddressHardCodedIntoResponse"
MultiReadWithParamsReuse = "MultiReadWithParamsReuse"
GetTokenPrices = "GetTokenPrices"
)

func (it *SolanaChainComponentsInterfaceTester[T]) buildContractReaderConfig(t T) config.ContractReader {
Expand All @@ -631,11 +660,43 @@ func (it *SolanaChainComponentsInterfaceTester[T]) buildContractReaderConfig(t T
MethodReturningUint64: uint64ReadDef,
},
}

readWithAddressHardCodedIntoResponseDef := config.ReadDefinition{
ChainSpecificName: "MultiRead1",
ReadType: config.Account,
PDADefinition: codec.PDATypeDef{
Prefix: []byte("multi_read1"),
},
ResponseAddressHardCoder: &commoncodec.HardCodeModifierConfig{
// placeholder values, whatever is put as value gets replaced with a solana pub key anyway
OffChainValues: map[string]any{
"SharedAddress": solana.PublicKey{},
"AddressToShare": solana.PublicKey{},
},
},
OutputModifications: commoncodec.ModifiersConfig{
&commoncodec.HardCodeModifierConfig{
OffChainValues: map[string]any{"U": "", "V": false},
},
},
}

multiReadDef := readWithAddressHardCodedIntoResponseDef
multiReadDef.ResponseAddressHardCoder = nil
multiReadDef.MultiReader = &config.MultiReader{
Reads: []config.ReadDefinition{{
ChainSpecificName: "MultiRead2",
PDADefinition: codec.PDATypeDef{Prefix: []byte("multi_read2")},
ReadType: config.Account,
}},
}

return config.ContractReader{
Namespaces: map[string]config.ChainContractReader{
AnyContractName: {
IDL: mustUnmarshalIDL(t, string(it.Helper.GetPrimaryIDL(t))),
Reads: map[string]config.ReadDefinition{
ReadWithAddressHardCodedIntoResponse: readWithAddressHardCodedIntoResponseDef,
GetTokenPrices: {
ChainSpecificName: "BillingTokenConfigWrapper",
PDADefinition: codec.PDATypeDef{
Expand Down Expand Up @@ -664,25 +725,7 @@ func (it *SolanaChainComponentsInterfaceTester[T]) buildContractReaderConfig(t T
},
ReadType: config.Account,
},
MultiRead: {
ChainSpecificName: "MultiRead1",
PDADefinition: codec.PDATypeDef{
Prefix: []byte("multi_read1"),
},
OutputModifications: commoncodec.ModifiersConfig{
&commoncodec.HardCodeModifierConfig{
OffChainValues: map[string]any{"U": "", "V": false},
},
},
MultiReader: &config.MultiReader{Reads: []config.ReadDefinition{
{
ChainSpecificName: "MultiRead2",
PDADefinition: codec.PDATypeDef{Prefix: []byte("multi_read2")},
ReadType: config.Account,
},
}},
ReadType: config.Account,
},
MultiRead: multiReadDef,
MultiReadWithParamsReuse: {
ChainSpecificName: "MultiRead3",
PDADefinition: codec.PDATypeDef{
Expand Down
82 changes: 58 additions & 24 deletions pkg/solana/chainreader/account_read_binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,38 +11,46 @@ import (
"github.com/smartcontractkit/chainlink-common/pkg/types"
"github.com/smartcontractkit/chainlink-common/pkg/types/query"

"github.com/smartcontractkit/chainlink-solana/pkg/solana/config"

"github.com/smartcontractkit/chainlink-solana/pkg/solana/codec"
)

// accountReadBinding provides decoding and reading Solana Account data using a defined codec.
type accountReadBinding struct {
namespace, genericName string
codec types.RemoteCodec
key solana.PublicKey
isPda bool // flag to signify whether or not the account read is for a PDA
prefix []byte // only used for PDA public key calculation
namespace, genericName string
codec types.RemoteCodec
key solana.PublicKey
isPda bool // flag to signify whether or not the account read is for a PDA
prefix []byte // only used for PDA public key calculation
responseAddressHardCoder *commoncodec.HardCodeModifierConfig
readDefinition config.ReadDefinition
idl codec.IDL
inputIDLType interface{}
outputIDLTypeDef codec.IdlTypeDef
}

func newAccountReadBinding(namespace, genericName string, prefix []byte, isPda bool) *accountReadBinding {
return &accountReadBinding{
namespace: namespace,
genericName: genericName,
prefix: prefix,
isPda: isPda,
func newAccountReadBinding(namespace, genericName string, isPda bool, idl codec.IDL, inputIDLType interface{}, outputIDLTypeDef codec.IdlTypeDef, readDefinition config.ReadDefinition) *accountReadBinding {
rb := &accountReadBinding{
namespace: namespace,
genericName: genericName,
prefix: readDefinition.PDADefinition.Prefix,
isPda: isPda,
readDefinition: readDefinition,
idl: idl,
inputIDLType: inputIDLType,
outputIDLTypeDef: outputIDLTypeDef,
responseAddressHardCoder: nil,
}
}

var _ readBinding = &accountReadBinding{}
if readDefinition.ResponseAddressHardCoder != nil {
rb.responseAddressHardCoder = readDefinition.ResponseAddressHardCoder
}

func (b *accountReadBinding) SetCodec(codec types.RemoteCodec) {
b.codec = codec
return rb
}

func (b *accountReadBinding) SetModifier(commoncodec.Modifier) {}

func (b *accountReadBinding) SetAddress(key solana.PublicKey) {
b.key = key
}
var _ readBinding = &accountReadBinding{}

func (b *accountReadBinding) GetAddress(ctx context.Context, params any) (solana.PublicKey, error) {
// Return the bound key if normal account read
Expand All @@ -61,6 +69,32 @@ func (b *accountReadBinding) GetAddress(ctx context.Context, params any) (solana
return key, nil
}

func (b *accountReadBinding) GetGenericName() string {
return b.genericName
}

func (b *accountReadBinding) GetReadDefinition() config.ReadDefinition {
return b.readDefinition
}

func (b *accountReadBinding) GetIDLInfo() (idl codec.IDL, inputIDLTypeDef interface{}, outputIDLTypeDef codec.IdlTypeDef) {
return b.idl, b.inputIDLType, b.outputIDLTypeDef
}

func (b *accountReadBinding) GetAddressResponseHardCoder() *commoncodec.HardCodeModifierConfig {
return b.responseAddressHardCoder
}

func (b *accountReadBinding) SetAddress(key solana.PublicKey) {
b.key = key
}

func (b *accountReadBinding) SetCodec(codec types.RemoteCodec) {
b.codec = codec
}

func (b *accountReadBinding) SetModifier(commoncodec.Modifier) {}

func (b *accountReadBinding) CreateType(forEncoding bool) (any, error) {
return b.codec.CreateType(codec.WrapItemType(forEncoding, b.namespace, b.genericName), forEncoding)
}
Expand All @@ -69,6 +103,10 @@ func (b *accountReadBinding) Decode(ctx context.Context, bts []byte, outVal any)
return b.codec.Decode(ctx, bts, outVal, codec.WrapItemType(false, b.namespace, b.genericName))
}

func (b *accountReadBinding) QueryKey(_ context.Context, _ query.KeyFilter, _ query.LimitAndSort, _ any) ([]types.Sequence, error) {
return nil, errors.New("unimplemented")
}

// buildSeedsSlice encodes and builds the seedslist to calculate the PDA public key
func (b *accountReadBinding) buildSeedsSlice(ctx context.Context, params any) ([][]byte, error) {
flattenedSeeds := make([]byte, 0, solana.MaxSeeds*solana.MaxSeedLength)
Expand Down Expand Up @@ -104,7 +142,3 @@ func (b *accountReadBinding) buildSeedsSlice(ctx context.Context, params any) ([
}
return seedByteArray, nil
}

func (b *accountReadBinding) QueryKey(_ context.Context, _ query.KeyFilter, _ query.LimitAndSort, _ any) ([]types.Sequence, error) {
return nil, errors.New("unimplemented")
}
29 changes: 22 additions & 7 deletions pkg/solana/chainreader/bindings.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,18 @@ import (
commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec"
"github.com/smartcontractkit/chainlink-common/pkg/types"
"github.com/smartcontractkit/chainlink-common/pkg/types/query"

"github.com/smartcontractkit/chainlink-solana/pkg/solana/codec"
"github.com/smartcontractkit/chainlink-solana/pkg/solana/config"
)

type readBinding interface {
SetAddress(solana.PublicKey)
GetAddress(context.Context, any) (solana.PublicKey, error)
GetGenericName() string
GetReadDefinition() config.ReadDefinition
GetIDLInfo() (idl codec.IDL, inputIDLTypeDef interface{}, outputIDLTypeDef codec.IdlTypeDef)
GetAddressResponseHardCoder() *commoncodec.HardCodeModifierConfig
SetAddress(solana.PublicKey)
SetCodec(types.RemoteCodec)
SetModifier(commoncodec.Modifier)
CreateType(bool) (any, error)
Expand Down Expand Up @@ -47,9 +54,9 @@ func (b *bindingsRegistry) AddReadBinding(namespace, readName string, rBinding r
}

func (b *bindingsRegistry) GetReadBinding(namespace, readName string) (readBinding, error) {
rBindings, nameSpaceExists := b.namespaceBindings[namespace]
if !nameSpaceExists {
return nil, fmt.Errorf("%w: no read binding exists for namespace: %q", types.ErrInvalidConfig, namespace)
rBindings, err := b.GetReadBindings(namespace)
if err != nil {
return nil, err
}

rBinding, rBindingExists := rBindings[readName]
Expand All @@ -60,6 +67,14 @@ func (b *bindingsRegistry) GetReadBinding(namespace, readName string) (readBindi
return rBinding, nil
}

func (b *bindingsRegistry) GetReadBindings(namespace string) (readNameBindings, error) {
rBindings, nameSpaceExists := b.namespaceBindings[namespace]
if !nameSpaceExists {
return nil, fmt.Errorf("%w: no read binding exists for namespace: %q", types.ErrInvalidConfig, namespace)
}
return rBindings, nil
}

func (b *bindingsRegistry) CreateType(namespace, readName string, forEncoding bool) (any, error) {
rBinding, err := b.GetReadBinding(namespace, readName)
if err != nil {
Expand Down Expand Up @@ -112,7 +127,7 @@ func (b *bindingsRegistry) SetModifiers(modifier commoncodec.Modifier) {
}

func (b *bindingsRegistry) handleAddressSharing(boundContract *types.BoundContract) error {
shareGroup, isInAGroup := b.getShareGroup(*boundContract)
shareGroup, isInAGroup := b.getShareGroup(boundContract.Name)
if !isInAGroup {
return nil
}
Expand All @@ -135,8 +150,8 @@ func (b *bindingsRegistry) handleAddressSharing(boundContract *types.BoundContra
return nil
}

func (b *bindingsRegistry) getShareGroup(boundContract types.BoundContract) (*addressShareGroup, bool) {
shareGroup, sharesAddress := b.addressShareGroups[boundContract.Name]
func (b *bindingsRegistry) getShareGroup(nameSpace string) (*addressShareGroup, bool) {
shareGroup, sharesAddress := b.addressShareGroups[nameSpace]
if !sharesAddress {
return nil, false
}
Expand Down
27 changes: 23 additions & 4 deletions pkg/solana/chainreader/bindings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import (
commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec"
"github.com/smartcontractkit/chainlink-common/pkg/types"
"github.com/smartcontractkit/chainlink-common/pkg/types/query"

"github.com/smartcontractkit/chainlink-solana/pkg/solana/codec"
"github.com/smartcontractkit/chainlink-solana/pkg/solana/config"
)

func TestBindings_CreateType(t *testing.T) {
Expand Down Expand Up @@ -48,14 +51,30 @@ type mockBinding struct {
mock.Mock
}

func (_m *mockBinding) SetCodec(_ types.RemoteCodec) {}

func (_m *mockBinding) SetAddress(_ solana.PublicKey) {}

func (_m *mockBinding) GetAddress(_ context.Context, _ any) (solana.PublicKey, error) {
return solana.PublicKey{}, nil
}

func (_m *mockBinding) GetGenericName() string {
return ""
}

func (_m *mockBinding) GetReadDefinition() config.ReadDefinition {
return config.ReadDefinition{}
}

func (_m *mockBinding) GetIDLInfo() (idl codec.IDL, inputIDLTypeDef interface{}, outputIDLTypeDef codec.IdlTypeDef) {
return codec.IDL{}, codec.IdlTypeDef{}, codec.IdlTypeDef{}
}

func (_m *mockBinding) GetAddressResponseHardCoder() *commoncodec.HardCodeModifierConfig {
return &commoncodec.HardCodeModifierConfig{}
}

func (_m *mockBinding) SetAddress(_ solana.PublicKey) {}

func (_m *mockBinding) SetCodec(_ types.RemoteCodec) {}

func (_m *mockBinding) SetModifier(a commoncodec.Modifier) {
_m.Called(a)
}
Expand Down
Loading

0 comments on commit bfc4414

Please sign in to comment.