Skip to content

Commit

Permalink
Added bitmap location parsing for IsWritable and IsSignable on Accoun…
Browse files Browse the repository at this point in the history
…tLookups (#1017)

* Added location parsing for IsWritable and IsSignable on AccountLookups

* Updated MetaBool to use a bitmap location rather than a bool location
  • Loading branch information
silaslenihan authored and amit-momin committed Jan 24, 2025
1 parent 774bdfa commit 1bb66cb
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 27 deletions.
145 changes: 138 additions & 7 deletions integration-tests/relayinterface/lookups_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ import (
"github.com/smartcontractkit/chainlink-solana/pkg/solana/utils"
)

type InnerAccountArgs struct {
Accounts []*solana.AccountMeta
Bitmap uint64
}

type TestAccountArgs struct {
Inner InnerAccountArgs
}

func TestAccountContant(t *testing.T) {
t.Run("AccountConstant resolves valid address", func(t *testing.T) {
expectedAddr := chainwriter.GetRandomPubKey(t)
Expand Down Expand Up @@ -62,8 +71,8 @@ func TestAccountLookups(t *testing.T) {
lookupConfig := chainwriter.AccountLookup{
Name: "TestAccount",
Location: "Inner.Address",
IsSigner: true,
IsWritable: true,
IsSigner: chainwriter.MetaBool{Value: true},
IsWritable: chainwriter.MetaBool{Value: true},
}
result, err := lookupConfig.Resolve(ctx, testArgs, nil, nil)
require.NoError(t, err)
Expand Down Expand Up @@ -96,8 +105,8 @@ func TestAccountLookups(t *testing.T) {
lookupConfig := chainwriter.AccountLookup{
Name: "TestAccount",
Location: "Inner.Address",
IsSigner: true,
IsWritable: true,
IsSigner: chainwriter.MetaBool{Value: true},
IsWritable: chainwriter.MetaBool{Value: true},
}
result, err := lookupConfig.Resolve(ctx, testArgs, nil, nil)
require.NoError(t, err)
Expand All @@ -117,12 +126,134 @@ func TestAccountLookups(t *testing.T) {
lookupConfig := chainwriter.AccountLookup{
Name: "InvalidAccount",
Location: "Invalid.Directory",
IsSigner: true,
IsWritable: true,
IsSigner: chainwriter.MetaBool{Value: true},
IsWritable: chainwriter.MetaBool{Value: true},
}
_, err := lookupConfig.Resolve(ctx, testArgs, nil, nil)
require.Error(t, err)
})

t.Run("AccountLookup works with MetaBool bitmap lookups", func(t *testing.T) {
accounts := [3]*solana.AccountMeta{}

for i := 0; i < 3; i++ {
accounts[i] = &solana.AccountMeta{
PublicKey: chainwriter.GetRandomPubKey(t),
IsSigner: (i)%2 == 0,
IsWritable: (i)%2 == 0,
}
}

lookupConfig := chainwriter.AccountLookup{
Name: "InvalidAccount",
Location: "Inner.Accounts.PublicKey",
IsSigner: chainwriter.MetaBool{BitmapLocation: "Inner.Bitmap"},
IsWritable: chainwriter.MetaBool{BitmapLocation: "Inner.Bitmap"},
}

args := TestAccountArgs{
Inner: InnerAccountArgs{
Accounts: accounts[:],
// should be 101... so {true, false, true}
Bitmap: 5,
},
}

result, err := lookupConfig.Resolve(ctx, args, nil, nil)
require.NoError(t, err)

for i, meta := range result {
require.Equal(t, accounts[i], meta)
}
})

t.Run("AccountLookup fails with MetaBool due to an invalid number of bitmaps", func(t *testing.T) {
type TestAccountArgsExtended struct {
Inner InnerAccountArgs
Bitmaps []uint64
}

accounts := [3]*solana.AccountMeta{}

for i := 0; i < 3; i++ {
accounts[i] = &solana.AccountMeta{
PublicKey: chainwriter.GetRandomPubKey(t),
IsWritable: true,
IsSigner: true,
}
}

lookupConfig := chainwriter.AccountLookup{
Name: "InvalidAccount",
Location: "Inner.Accounts.PublicKey",
IsSigner: chainwriter.MetaBool{BitmapLocation: "Bitmaps"},
IsWritable: chainwriter.MetaBool{BitmapLocation: "Bitmaps"},
}

args := TestAccountArgsExtended{
Inner: InnerAccountArgs{
Accounts: accounts[:],
},
Bitmaps: []uint64{5, 3},
}

_, err := lookupConfig.Resolve(ctx, args, nil, nil)
require.Contains(t, err.Error(), "bitmap value is not a single value")
})

t.Run("AccountLookup fails with MetaBool with an Invalid BitmapLocation", func(t *testing.T) {
accounts := [3]*solana.AccountMeta{}

for i := 0; i < 3; i++ {
accounts[i] = &solana.AccountMeta{
PublicKey: chainwriter.GetRandomPubKey(t),
IsWritable: true,
}
}

lookupConfig := chainwriter.AccountLookup{
Name: "InvalidAccount",
Location: "Inner.Accounts.PublicKey",
IsSigner: chainwriter.MetaBool{BitmapLocation: "Invalid.Bitmap"},
IsWritable: chainwriter.MetaBool{BitmapLocation: "Invalid.Bitmap"},
}

args := TestAccountArgs{
Inner: InnerAccountArgs{
Accounts: accounts[:],
},
}

_, err := lookupConfig.Resolve(ctx, args, nil, nil)
require.Contains(t, err.Error(), "error reading bitmap from location")
})

t.Run("AccountLookup fails when MetaBool Bitmap is an invalid type", func(t *testing.T) {
accounts := [3]*solana.AccountMeta{}

for i := 0; i < 3; i++ {
accounts[i] = &solana.AccountMeta{
PublicKey: chainwriter.GetRandomPubKey(t),
IsWritable: true,
}
}

lookupConfig := chainwriter.AccountLookup{
Name: "InvalidAccount",
Location: "Inner.Accounts.PublicKey",
IsSigner: chainwriter.MetaBool{BitmapLocation: "Inner"},
IsWritable: chainwriter.MetaBool{BitmapLocation: "Inner"},
}

args := TestAccountArgs{
Inner: InnerAccountArgs{
Accounts: accounts[:],
},
}

_, err := lookupConfig.Resolve(ctx, args, nil, nil)
require.Contains(t, err.Error(), "invalid value format at path")
})
}

func TestPDALookups(t *testing.T) {
Expand Down Expand Up @@ -435,7 +566,7 @@ func TestLookupTables(t *testing.T) {
Accounts: chainwriter.AccountLookup{
Name: "TestLookupTable",
Location: "Inner.Address",
IsSigner: true,
IsSigner: chainwriter.MetaBool{Value: true},
},
},
},
Expand Down
12 changes: 6 additions & 6 deletions pkg/solana/chainwriter/ccip_example_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ func TestConfig() {
AccountLookup{
Name: "TokenAccount",
Location: "Message.TokenAmounts.DestTokenAddress",
IsSigner: false,
IsWritable: false,
IsSigner: MetaBool{Value: false},
IsWritable: MetaBool{Value: false},
},
// PDA Account Lookup - Based on an account lookup and an address lookup
PDALookups{
Expand All @@ -116,16 +116,16 @@ func TestConfig() {
PublicKey: AccountLookup{
Name: "TokenAccount",
Location: "Message.TokenAmounts.DestTokenAddress",
IsSigner: false,
IsWritable: false,
IsSigner: MetaBool{Value: false},
IsWritable: MetaBool{Value: false},
},
// The seed is the receiver address.
Seeds: []Seed{
{Dynamic: AccountLookup{
Name: "Receiver",
Location: "Message.Receiver",
IsSigner: false,
IsWritable: false,
IsSigner: MetaBool{Value: false},
IsWritable: MetaBool{Value: false},
}},
},
},
Expand Down
8 changes: 4 additions & 4 deletions pkg/solana/chainwriter/chain_writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ func TestChainWriter_GetAddresses(t *testing.T) {
chainwriter.AccountLookup{
Name: "LookupTable",
Location: "LookupTable",
IsSigner: accountLookupMeta.IsSigner,
IsWritable: accountLookupMeta.IsWritable,
IsSigner: chainwriter.MetaBool{Value: accountLookupMeta.IsSigner},
IsWritable: chainwriter.MetaBool{Value: accountLookupMeta.IsWritable},
},
chainwriter.PDALookups{
Name: "DataAccountPDA",
Expand Down Expand Up @@ -463,8 +463,8 @@ func TestChainWriter_SubmitTransaction(t *testing.T) {
chainwriter.AccountLookup{
Name: "LookupTable",
Location: "LookupTable",
IsSigner: false,
IsWritable: false,
IsSigner: chainwriter.MetaBool{Value: false},
IsWritable: chainwriter.MetaBool{Value: false},
},
chainwriter.PDALookups{
Name: "DataAccountPDA",
Expand Down
2 changes: 1 addition & 1 deletion pkg/solana/chainwriter/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func GetValuesAtLocation(args any, location string) ([][]byte, error) {
binary.LittleEndian.PutUint64(buf, num)
vals = append(vals, buf)
} else {
return nil, fmt.Errorf("invalid value format at path: %s", location)
return nil, fmt.Errorf("invalid value format at path: %s, type: %s", location, reflect.TypeOf(value).String())
}
}

Expand Down
73 changes: 64 additions & 9 deletions pkg/solana/chainwriter/lookups.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package chainwriter

import (
"context"
"encoding/binary"
"fmt"
"reflect"

Expand All @@ -27,10 +28,16 @@ type AccountConstant struct {

// AccountLookup dynamically derives an account address from args using a specified location path.
type AccountLookup struct {
Name string
Location string
IsSigner bool
IsWritable bool
Name string
Location string
// IsSigner and IsWritable can either be a constant bool or a location to a bitmap which decides the bools
IsSigner MetaBool
IsWritable MetaBool
}

type MetaBool struct {
Value bool
BitmapLocation string
}

type Seed struct {
Expand Down Expand Up @@ -89,23 +96,71 @@ func (ac AccountConstant) Resolve(_ context.Context, _ any, _ map[string]map[str
}, nil
}

func (al AccountLookup) Resolve(_ context.Context, args any, _ map[string]map[string][]*solana.AccountMeta, _ client.Reader) ([]*solana.AccountMeta, error) {
func (al AccountLookup) Resolve(
_ context.Context,
args any,
_ map[string]map[string][]*solana.AccountMeta,
_ client.Reader,
) ([]*solana.AccountMeta, error) {
derivedValues, err := GetValuesAtLocation(args, al.Location)
if err != nil {
return nil, fmt.Errorf("error getting account from lookup: %w", err)
return nil, fmt.Errorf("error getting account from '%s': %w", al.Location, err)
}

var metas []*solana.AccountMeta
for _, address := range derivedValues {
signerIndexes, err := resolveBitMap(al.IsSigner, args, len(derivedValues))
if err != nil {
return nil, err
}

writerIndexes, err := resolveBitMap(al.IsWritable, args, len(derivedValues))
if err != nil {
return nil, err
}

for i, address := range derivedValues {
// Resolve isSigner for this particular pubkey
isSigner := signerIndexes[i]

// Resolve isWritable
isWritable := writerIndexes[i]

metas = append(metas, &solana.AccountMeta{
PublicKey: solana.PublicKeyFromBytes(address),
IsSigner: al.IsSigner,
IsWritable: al.IsWritable,
IsSigner: isSigner,
IsWritable: isWritable,
})
}

return metas, nil
}

func resolveBitMap(mb MetaBool, args any, length int) ([]bool, error) {
result := make([]bool, length)
if mb.BitmapLocation == "" {
for i := 0; i < length; i++ {
result[i] = mb.Value
}
return result, nil
}

bitmapVals, err := GetValuesAtLocation(args, mb.BitmapLocation)
if err != nil {
return []bool{}, fmt.Errorf("error reading bitmap from location '%s': %w", mb.BitmapLocation, err)
}

if len(bitmapVals) != 1 {
return []bool{}, fmt.Errorf("bitmap value is not a single value: %v, length: %d", bitmapVals, len(bitmapVals))
}

bitmapInt := binary.LittleEndian.Uint64(bitmapVals[0])
for i := 0; i < length; i++ {
result[i] = bitmapInt&(1<<i) > 0
}

return result, nil
}

func (alt AccountsFromLookupTable) Resolve(_ context.Context, _ any, derivedTableMap map[string]map[string][]*solana.AccountMeta, _ client.Reader) ([]*solana.AccountMeta, error) {
// Fetch the inner map for the specified lookup table name
innerMap, ok := derivedTableMap[alt.LookupTableName]
Expand Down

0 comments on commit 1bb66cb

Please sign in to comment.