Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Codec interface tests #967

Merged
merged 31 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e723593
Connect codec interface tests and refactor codec to interface like EV…
ilija42 Dec 12, 2024
0250594
progress
ilija42 Dec 12, 2024
aaaee4b
Fully implement Codec interface tests
ilija42 Dec 13, 2024
1573a7f
Run codec tests in loop
ilija42 Dec 13, 2024
d028a40
Prettify codec and codec tests
ilija42 Dec 13, 2024
8fa6889
Refactor codec nil encoding handling
ilija42 Dec 13, 2024
9349fdb
Revert accidental changes to testIDL.json
ilija42 Dec 14, 2024
86baca9
Add sonar exclusion for codec test utils
ilija42 Dec 14, 2024
55f16db
Add sq exclusion for duplications in testutils, add decoder unit tests
ilija42 Dec 14, 2024
630d291
Add encoder unit test
ilija42 Dec 16, 2024
fa9efdd
Fix lint and rename codec to solanacodec to avoid types name collision
ilija42 Dec 16, 2024
3585398
Solana codec entry improvements
ilija42 Dec 16, 2024
7f4fd28
Fix Solana codec field casing
ilija42 Dec 16, 2024
17d3474
minor err messages improvements
ilija42 Dec 16, 2024
d795ae4
Code improvements
ilija42 Dec 16, 2024
4103493
Fix encoder unit tests
ilija42 Dec 16, 2024
68d704e
Fix sonar exclusions
ilija42 Dec 16, 2024
eca38ed
lint
ilija42 Dec 16, 2024
bfb7bbe
Reorder methods in Solana codec
ilija42 Dec 16, 2024
870f95a
Fix CR integration tests config
ilija42 Dec 17, 2024
c15c0d4
Revert TestNewIDLCodec_WithModifiers deletion
ilija42 Dec 17, 2024
5328645
Merge branch 'develop' into codec-interface
ilija42 Dec 17, 2024
aa89442
Add comments for codec entry includeDiscriminator option
ilija42 Dec 18, 2024
6bc1d31
Add discriminator value check in codec entry Decode
ilija42 Dec 18, 2024
44ce6ff
Reuse utils from interface tests for Solana codec interface tests
ilija42 Dec 18, 2024
3314b11
Merge branch 'develop' into codec-interface
ilija42 Dec 18, 2024
794787c
Fix comment
ilija42 Dec 18, 2024
52bbcb6
Fix comment
ilija42 Dec 18, 2024
546bddd
[Non-EVM-1062] Solana Codec events support, Hookup Fuzz tests and cle…
ilija42 Dec 19, 2024
07950d7
Merge branch 'develop' into codec-interface
ilija42 Dec 19, 2024
4f20c81
Merge branch 'develop' into codec-interface
jadepark-dev Dec 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ require (
github.com/stretchr/testify v1.9.0
go.uber.org/zap v1.27.0
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
golang.org/x/sync v0.8.0
golang.org/x/text v0.18.0
golang.org/x/sync v0.10.0
golang.org/x/text v0.21.0
)

require (
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -682,8 +682,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand Down Expand Up @@ -746,8 +746,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
Expand Down
4 changes: 2 additions & 2 deletions integration-tests/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ require (
github.com/stretchr/testify v1.9.0
github.com/testcontainers/testcontainers-go v0.34.0
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
golang.org/x/sync v0.8.0
golang.org/x/text v0.19.0
golang.org/x/sync v0.10.0
golang.org/x/text v0.21.0
gopkg.in/guregu/null.v4 v4.0.0
)

Expand Down
8 changes: 4 additions & 4 deletions integration-tests/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1692,8 +1692,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand Down Expand Up @@ -1804,8 +1804,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
Expand Down
6 changes: 3 additions & 3 deletions integration-tests/relayinterface/chain_components_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func (it *SolanaChainComponentsInterfaceTester[T]) Setup(t T) {
Procedure: config.ChainReaderProcedure{
IDLAccount: "DataAccount",
OutputModifications: codec.ModifiersConfig{
&codec.PropertyExtractorConfig{FieldName: "U64value"},
&codec.PropertyExtractorConfig{FieldName: "U64Value"},
},
},
},
Expand All @@ -142,7 +142,7 @@ func (it *SolanaChainComponentsInterfaceTester[T]) Setup(t T) {
Procedure: config.ChainReaderProcedure{
IDLAccount: "DataAccount",
OutputModifications: codec.ModifiersConfig{
&codec.PropertyExtractorConfig{FieldName: "U64slice"},
&codec.PropertyExtractorConfig{FieldName: "U64Slice"},
},
},
},
Expand All @@ -156,7 +156,7 @@ func (it *SolanaChainComponentsInterfaceTester[T]) Setup(t T) {
Procedure: config.ChainReaderProcedure{
IDLAccount: "DataAccount",
OutputModifications: codec.ModifiersConfig{
&codec.PropertyExtractorConfig{FieldName: "U64value"},
&codec.PropertyExtractorConfig{FieldName: "U64Value"},
},
},
},
Expand Down
152 changes: 152 additions & 0 deletions pkg/solana/codec/codec_entry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package codec

import (
"bytes"
"fmt"
"reflect"

"github.com/smartcontractkit/chainlink-common/pkg/codec"
commonencodings "github.com/smartcontractkit/chainlink-common/pkg/codec/encodings"
commontypes "github.com/smartcontractkit/chainlink-common/pkg/types"
)

type Entry interface {
Encode(value any, into []byte) ([]byte, error)
Decode(encoded []byte) (any, []byte, error)
GetCodecType() commonencodings.TypeCodec
GetType() reflect.Type
Modifier() codec.Modifier
Size(numItems int) (int, error)
FixedSize() (int, error)
}

type entry struct {
// TODO this might not be needed in the end, it was handy to make tests simpler
offchainName string
onchainName string
reflectType reflect.Type
typeCodec commonencodings.TypeCodec
mod codec.Modifier
// includeDiscriminator during Encode adds a discriminator to the encoded bytes under an assumption that the provided value didn't have a discriminator.
// During Decode includeDiscriminator removes discriminator from bytes under an assumption that the provided struct doesn't need a discriminator.
includeDiscriminator bool
discriminator Discriminator
}

func NewAccountEntry(offchainName string, idlAccount IdlTypeDef, idlTypes IdlTypeDefSlice, includeDiscriminator bool, mod codec.Modifier, builder commonencodings.Builder) (Entry, error) {
refs := &codecRefs{
builder: builder,
codecs: make(map[string]commonencodings.TypeCodec),
typeDefs: idlTypes,
dependencies: make(map[string][]string),
}

_, accCodec, err := createCodecType(idlAccount, refs, false)
if err != nil {
return nil, err
}

return &entry{
offchainName: offchainName,
onchainName: idlAccount.Name,
reflectType: accCodec.GetType(),
typeCodec: accCodec,
mod: ensureModifier(mod),
includeDiscriminator: includeDiscriminator,
discriminator: *NewDiscriminator(idlAccount.Name),
}, nil
}

func NewInstructionArgsEntry(offChainName string, instructions IdlInstruction, idlTypes IdlTypeDefSlice, mod codec.Modifier, builder commonencodings.Builder) (Entry, error) {
refs := &codecRefs{
builder: builder,
codecs: make(map[string]commonencodings.TypeCodec),
typeDefs: idlTypes,
dependencies: make(map[string][]string),
}

_, instructionCodecArgs, err := asStruct(instructions.Args, refs, instructions.Name, false, true)
if err != nil {
return nil, err
}

return &entry{
offchainName: offChainName,
onchainName: instructions.Name,
typeCodec: instructionCodecArgs,
reflectType: instructionCodecArgs.GetType(),
mod: ensureModifier(mod),
}, nil
}

func (e *entry) Encode(value any, into []byte) ([]byte, error) {
// Special handling for encoding a nil pointer to an empty struct.
t := e.reflectType
if value == nil {
if t.Kind() == reflect.Pointer {
elem := t.Elem()
if elem.Kind() == reflect.Struct && elem.NumField() == 0 {
return []byte{}, nil
}
}
return nil, fmt.Errorf("%w: cannot encode nil value for offchainName: %q, onchainName: %q", commontypes.ErrInvalidType, e.offchainName, e.onchainName)
}

encodedVal, err := e.typeCodec.Encode(value, into)
if err != nil {
return nil, err
}

if e.includeDiscriminator {
var byt []byte
encodedDisc, err := e.discriminator.Encode(&e.discriminator.hashPrefix, byt)
if err != nil {
return nil, err
}
return append(encodedDisc, encodedVal...), nil
}

return encodedVal, nil
}

func (e *entry) Decode(encoded []byte) (any, []byte, error) {
if e.includeDiscriminator {
if len(encoded) < discriminatorLength {
return nil, nil, fmt.Errorf("%w: encoded data too short to contain discriminator for offchainName: %q, onchainName: %q", commontypes.ErrInvalidType, e.offchainName, e.onchainName)
}

if !bytes.Equal(e.discriminator.hashPrefix, encoded[:8]) {
return nil, nil, fmt.Errorf("%w: encoded data has a bad discriminator %v for offchainName: %q, onchainName: %q", commontypes.ErrInvalidType, encoded[:8], e.offchainName, e.onchainName)
}

encoded = encoded[discriminatorLength:]
}
return e.typeCodec.Decode(encoded)
}

func (e *entry) GetCodecType() commonencodings.TypeCodec {
return e.typeCodec
}

func (e *entry) GetType() reflect.Type {
return e.reflectType
}

func (e *entry) Modifier() codec.Modifier {
return e.mod
}

func (e *entry) Size(numItems int) (int, error) {
return e.typeCodec.Size(numItems)
}

func (e *entry) FixedSize() (int, error) {
return e.typeCodec.FixedSize()
}

func ensureModifier(mod codec.Modifier) codec.Modifier {
if mod == nil {
return codec.MultiModifier{}
}
return mod
}
133 changes: 133 additions & 0 deletions pkg/solana/codec/codec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package codec_test

import (
"bytes"
_ "embed"
"slices"
"testing"

bin "github.com/gagliardetto/binary"
"github.com/gagliardetto/solana-go"
ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types"
"github.com/stretchr/testify/require"

commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec"
looptestutils "github.com/smartcontractkit/chainlink-common/pkg/loop/testutils"
clcommontypes "github.com/smartcontractkit/chainlink-common/pkg/types"
. "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" //nolint common practice to import test mods with .
"github.com/smartcontractkit/chainlink-solana/pkg/solana/codec"
"github.com/smartcontractkit/chainlink-solana/pkg/solana/codec/testutils"
)

const anyExtraValue = 3

func TestCodec(t *testing.T) {
tester := &codecInterfaceTester{}
RunCodecInterfaceTests(t, tester)
RunCodecInterfaceTests(t, looptestutils.WrapCodecTesterForLoop(tester))
}

type codecInterfaceTester struct {
TestSelectionSupport
}

func (it *codecInterfaceTester) Setup(_ *testing.T) {}

func (it *codecInterfaceTester) GetAccountBytes(_ int) []byte {
// TODO solana base58 string can be of variable length, this value is always 44, but it should be able to handle any length 32-44
pk := solana.PublicKeyFromBytes([]byte{220, 108, 195, 188, 166, 6, 163, 39, 197, 131, 44, 38, 154, 177, 232, 80, 141, 50, 7, 65, 28, 65, 182, 165, 57, 5, 176, 68, 46, 181, 58, 245})
return pk.Bytes()
}

func (it *codecInterfaceTester) GetAccountString(i int) string {
return solana.PublicKeyFromBytes(it.GetAccountBytes(i)).String()
}

func (it *codecInterfaceTester) EncodeFields(t *testing.T, request *EncodeRequest) []byte {
if request.TestOn == TestItemType {
return encodeFieldsOnItem(t, request)
}

return encodeFieldsOnSliceOrArray(t, request)
}

func encodeFieldsOnItem(t *testing.T, request *EncodeRequest) ocr2types.Report {
buf := new(bytes.Buffer)
if err := testutils.EncodeRequestToTestItemAsAccount(request.TestStructs[0]).MarshalWithEncoder(bin.NewBorshEncoder(buf)); err != nil {
require.NoError(t, err)
}
return buf.Bytes()
}

func encodeFieldsOnSliceOrArray(t *testing.T, request *EncodeRequest) []byte {
var toEncode interface{}
buf := new(bytes.Buffer)
switch request.TestOn {
case TestItemArray1Type:
toEncode = [1]testutils.TestItemAsArgs{testutils.EncodeRequestToTestItemAsArgs(request.TestStructs[0])}
case TestItemArray2Type:
toEncode = [2]testutils.TestItemAsArgs{testutils.EncodeRequestToTestItemAsArgs(request.TestStructs[0]), testutils.EncodeRequestToTestItemAsArgs(request.TestStructs[1])}
default:
// encode TestItemSliceType as instruction args (similar to accounts, but no discriminator) because accounts can't be just a vector
var itemSliceType []testutils.TestItemAsArgs
for _, req := range request.TestStructs {
itemSliceType = append(itemSliceType, testutils.EncodeRequestToTestItemAsArgs(req))
}
toEncode = itemSliceType
}

if err := bin.NewBorshEncoder(buf).Encode(toEncode); err != nil {
require.NoError(t, err)
}
return buf.Bytes()
}

func (it *codecInterfaceTester) GetCodec(t *testing.T) clcommontypes.Codec {
codecConfig := codec.Config{Configs: map[string]codec.ChainConfig{}}
TestItem := CreateTestStruct[*testing.T](0, it)
for offChainName, v := range testutils.CodecDefs {
codecEntryCfg := codecConfig.Configs[offChainName]
codecEntryCfg.IDL = v.IDL
codecEntryCfg.Type = v.ItemType
codecEntryCfg.OnChainName = v.IDLTypeName

if offChainName != NilType {
codecEntryCfg.ModifierConfigs = commoncodec.ModifiersConfig{
&commoncodec.RenameModifierConfig{Fields: map[string]string{"NestedDynamicStruct.Inner.IntVal": "I"}},
&commoncodec.RenameModifierConfig{Fields: map[string]string{"NestedStaticStruct.Inner.IntVal": "I"}},
}
}

if slices.Contains([]string{TestItemType, TestItemSliceType, TestItemArray1Type, TestItemArray2Type, testutils.TestItemWithConfigExtraType}, offChainName) {
addressByteModifier := &commoncodec.AddressBytesToStringModifierConfig{
Fields: []string{"AccountStruct.AccountStr"},
Modifier: codec.SolanaAddressModifier{},
}
codecEntryCfg.ModifierConfigs = append(codecEntryCfg.ModifierConfigs, addressByteModifier)
}

if offChainName == testutils.TestItemWithConfigExtraType {
hardCode := &commoncodec.HardCodeModifierConfig{
OnChainValues: map[string]any{
"BigField": TestItem.BigField.String(),
"AccountStruct.Account": solana.PublicKeyFromBytes(TestItem.AccountStruct.Account),
},
OffChainValues: map[string]any{"ExtraField": anyExtraValue},
}
codecEntryCfg.ModifierConfigs = append(codecEntryCfg.ModifierConfigs, hardCode)
}
codecConfig.Configs[offChainName] = codecEntryCfg
}

c, err := codec.NewCodec(codecConfig)
require.NoError(t, err)

return c
}

func (it *codecInterfaceTester) IncludeArrayEncodingSizeEnforcement() bool {
return true
}
func (it *codecInterfaceTester) Name() string {
return "Solana"
}
Loading
Loading