Skip to content

Commit

Permalink
Merge branch 'develop' into NONEVM-1064/bug-fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
jadepark-dev authored Feb 24, 2025
2 parents de50b20 + 479a5ea commit 949ee33
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 137 deletions.
81 changes: 81 additions & 0 deletions integration-tests/relayinterface/lookups_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,62 @@ func TestLookupTables(t *testing.T) {
require.Equal(t, lookupKeys[i], address.PublicKey)
}
})

t.Run("Resolving optional derived lookup table does not return error", func(t *testing.T) {
// Deployed contract_reader_interface contract
programID := solana.MustPublicKeyFromBase58("6AfuXF6HapDUhQfE4nQG9C1SGtA1YjP3icaJyRfU4RyE")

args := map[string]interface{}{
"seed1": []byte("lookup"),
}

lookupConfig := chainwriter.LookupTables{
DerivedLookupTables: []chainwriter.DerivedLookupTable{
{
Name: "DerivedTable",
Accounts: chainwriter.Lookup{PDALookups: &chainwriter.PDALookups{
Name: "DataAccountPDA",
PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{Name: "WriteTest", Address: programID.String()}},
Seeds: []chainwriter.Seed{
{Dynamic: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{Name: "missing_seed", Location: "missing_seed"}}},
},
IsSigner: false,
IsWritable: false,
InternalField: chainwriter.InternalField{
TypeName: "LookupTableDataAccount",
Location: "LookupTable",
IDL: testContractIDL,
}},
},
Optional: true,
},
},
}

derivedTableMap, _, err := cw.ResolveLookupTables(ctx, args, lookupConfig)
require.NoError(t, err)

pdaWithAccountLookupSeed := chainwriter.Lookup{
PDALookups: &chainwriter.PDALookups{
PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{Address: chainwriter.GetRandomPubKey(t).String()}},
Seeds: []chainwriter.Seed{
{
Dynamic: chainwriter.Lookup{
AccountsFromLookupTable: &chainwriter.AccountsFromLookupTable{
LookupTableName: "DerivedTable",
IncludeIndexes: []int{},
},
},
},
},
},
Optional: true,
}

accounts, err := chainwriter.GetAddresses(ctx, nil, []chainwriter.Lookup{pdaWithAccountLookupSeed}, derivedTableMap, multiClient)
require.NoError(t, err)
require.Empty(t, accounts)
})
}

func TestCreateATAs(t *testing.T) {
Expand Down Expand Up @@ -936,6 +992,31 @@ func TestCreateATAs(t *testing.T) {
require.NoError(t, err)
require.Empty(t, ataInstructions, "No new instructions should be returned if ATAs already exist")
})

t.Run("optional ATA creation does not return error if lookups fail", func(t *testing.T) {
lookups := []chainwriter.ATALookup{
{
Location: "Inner.Address",
WalletAddress: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{
Address: feePayer.String(),
}},
TokenProgram: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{
Address: chainwriter.GetRandomPubKey(t).String(),
}},
MintAddress: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{
Location: "Inner.BadLocation",
}},
Optional: true,
},
}
args := chainwriter.TestArgs{
Inner: []chainwriter.InnerArgs{{Address: chainwriter.GetRandomPubKey(t).Bytes()}},
}

ataInstructions, err := chainwriter.CreateATAs(ctx, args, lookups, nil, multiClient, testContractIDL, feePayer, logger.Test(t))
require.NoError(t, err)
require.Len(t, ataInstructions, 0)
})
}

func checkIfATAExists(t *testing.T, rpcClient *rpc.Client, ataAddress solana.PublicKey) bool {
Expand Down
14 changes: 10 additions & 4 deletions pkg/solana/chainwriter/chain_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,9 @@ func CreateATAs(ctx context.Context, args any, lookups []ATALookup, derivedTable
}
}
walletAddresses, err := GetAddresses(ctx, args, []Lookup{lookup.WalletAddress}, derivedTableMap, client)
if err != nil {
if lookup.Optional && isIgnorableError(err) {
continue
} else if err != nil {
return nil, fmt.Errorf("error resolving wallet address: %w", err)
}
if len(walletAddresses) != 1 {
Expand All @@ -262,12 +264,16 @@ func CreateATAs(ctx context.Context, args any, lookups []ATALookup, derivedTable
wallet := walletAddresses[0].PublicKey

tokenPrograms, err := GetAddresses(ctx, args, []Lookup{lookup.TokenProgram}, derivedTableMap, client)
if err != nil {
if lookup.Optional && isIgnorableError(err) {
continue
} else if err != nil {
return nil, fmt.Errorf("error resolving token program address: %w", err)
}

mints, err := GetAddresses(ctx, args, []Lookup{lookup.MintAddress}, derivedTableMap, client)
if err != nil {
if lookup.Optional && isIgnorableError(err) {
continue
} else if err != nil {
return nil, fmt.Errorf("error resolving mint address: %w", err)
}
if len(tokenPrograms) != len(mints) {
Expand Down Expand Up @@ -384,7 +390,7 @@ func (s *SolanaChainWriterService) SubmitTransaction(ctx context.Context, contra
return errorWithDebugID(fmt.Errorf("error finding transform function: %w", tfErr), debugID)
}
s.lggr.Debugw("Applying args transformation", "contract", contractName, "method", method)
args, accounts, err = transformFunc(ctx, s, args, accounts, toAddress)
args, accounts, err = transformFunc(ctx, args, accounts, derivedTableMap)
if err != nil {
return errorWithDebugID(fmt.Errorf("error transforming args: %w", err), debugID)
}
Expand Down
47 changes: 42 additions & 5 deletions pkg/solana/chainwriter/chain_writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -779,7 +779,48 @@ func TestChainWriter_CCIPOfframp(t *testing.T) {
},
ChainSpecificName: "execute",
ArgsTransform: "CCIPExecute",
LookupTables: chainwriter.LookupTables{},
LookupTables: chainwriter.LookupTables{
DerivedLookupTables: []chainwriter.DerivedLookupTable{
{
Name: "PoolLookupTable",
Accounts: chainwriter.Lookup{
PDALookups: &chainwriter.PDALookups{
Name: "TokenAdminRegistry",
PublicKey: chainwriter.Lookup{
PDALookups: &chainwriter.PDALookups{
Name: ccipconsts.ContractNameRouter,
PublicKey: chainwriter.Lookup{
AccountConstant: &chainwriter.AccountConstant{Address: offrampAddr.String()},
},
Seeds: []chainwriter.Seed{
{Static: []byte("reference_addresses")},
},
// Reads the address from the reference addresses account
InternalField: chainwriter.InternalField{
TypeName: "ReferenceAddresses",
Location: "Router",
IDL: ccipOfframpIDL,
},
},
},
Seeds: []chainwriter.Seed{
{Static: []byte("token_admin_registry")},
{Dynamic: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{Address: destTokenAddr.String()}}},
},
IsSigner: false,
IsWritable: false,
InternalField: chainwriter.InternalField{
TypeName: "TokenAdminRegistry",
Location: "LookupTable",
// TokenAdminRegistry is in the router program so need to provide the router's IDL
IDL: ccipRouterIDL,
},
},
},
Optional: true, // Lookup table is optional if DestTokenAddress is not present in report
},
},
},
Accounts: []chainwriter.Lookup{
{AccountConstant: &chainwriter.AccountConstant{
Name: "testAcc1",
Expand Down Expand Up @@ -842,10 +883,6 @@ func TestChainWriter_CCIPOfframp(t *testing.T) {
},
IDL: ccipOfframpIDL,
},
// Requires only the IDL for the CCIPArgsTransform to fetch the TokenAdminRegistry
ccipconsts.ContractNameRouter: {
IDL: ccipRouterIDL,
},
},
}

Expand Down
1 change: 1 addition & 0 deletions pkg/solana/chainwriter/lookups.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ type ATALookup struct {
WalletAddress Lookup
TokenProgram Lookup
MintAddress Lookup
Optional bool
}

func (l Lookup) validate() error {
Expand Down
94 changes: 17 additions & 77 deletions pkg/solana/chainwriter/transform_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (

"github.com/gagliardetto/solana-go"
"github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_offramp"
ccipconsts "github.com/smartcontractkit/chainlink-ccip/pkg/consts"
"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
)

Expand All @@ -27,7 +26,7 @@ type ReportPostTransform struct {
TokenIndexes []byte
}

func FindTransform(id string) (func(context.Context, *SolanaChainWriterService, any, solana.AccountMetaSlice, string) (any, solana.AccountMetaSlice, error), error) {
func FindTransform(id string) (func(context.Context, any, solana.AccountMetaSlice, map[string]map[string][]*solana.AccountMeta) (any, solana.AccountMetaSlice, error), error) {
switch id {
case "CCIPExecute":
return CCIPExecuteArgsTransform, nil
Expand All @@ -40,73 +39,26 @@ func FindTransform(id string) (func(context.Context, *SolanaChainWriterService,

// This Transform function looks up the token pool addresses in the accounts slice and augments the args
// with the indexes of the token pool addresses in the accounts slice.
func CCIPExecuteArgsTransform(ctx context.Context, cw *SolanaChainWriterService, args any, accounts solana.AccountMetaSlice, toAddress string) (any, solana.AccountMetaSlice, error) {
// Fetch offramp config to use to fetch the router address
offrampProgramConfig, ok := cw.config.Programs[ccipconsts.ContractNameOffRamp]
func CCIPExecuteArgsTransform(ctx context.Context, args any, accounts solana.AccountMetaSlice, tableMap map[string]map[string][]*solana.AccountMeta) (any, solana.AccountMetaSlice, error) {
argsTyped, ok := args.(ReportPreTransform)
if !ok {
return nil, nil, fmt.Errorf("%s program not found in config", ccipconsts.ContractNameOffRamp)
}
// PDA lookup to fetch router address
routerAddrLookup := PDALookups{
Name: "ReferenceAddresses",
PublicKey: Lookup{AccountConstant: &AccountConstant{
Address: toAddress,
}},
Seeds: []Seed{
{Static: []byte("reference_addresses")},
},
// Reads the router address from the reference addresses PDA
InternalField: InternalField{
TypeName: "ReferenceAddresses",
Location: "Router",
IDL: offrampProgramConfig.IDL,
},
}
accountMetas, err := routerAddrLookup.Resolve(ctx, nil, nil, cw.client)
if err != nil {
return nil, nil, fmt.Errorf("failed to fetch the router program address from the reference addresses account: %w", err)
}
if len(accountMetas) != 1 {
return nil, nil, fmt.Errorf("expect 1 address to be returned for router address, received %d: %w", len(accountMetas), err)
return nil, nil, fmt.Errorf("args is not of type ReportPreTransform")
}

// Fetch router config to use to fetch TokenAdminRegistry
routerProgramConfig, ok := cw.config.Programs[ccipconsts.ContractNameRouter]
if !ok {
return nil, nil, fmt.Errorf("%s program not found in config", ccipconsts.ContractNameRouter)
argsTransformed := ReportPostTransform{
ReportContext: argsTyped.ReportContext,
Report: argsTyped.Report,
AbstractReport: argsTyped.AbstractReport,
Info: argsTyped.Info,
}

routerAddress := accountMetas[0].PublicKey
TokenPoolLookupTable := LookupTables{
DerivedLookupTables: []DerivedLookupTable{
{
Name: "PoolLookupTable",
Accounts: Lookup{PDALookups: &PDALookups{
Name: "TokenAdminRegistry",
PublicKey: Lookup{AccountConstant: &AccountConstant{
Address: routerAddress.String(),
}},
Seeds: []Seed{
{Static: []byte("token_admin_registry")},
{Dynamic: Lookup{AccountLookup: &AccountLookup{Location: "Info.AbstractReports.Messages.TokenAmounts.DestTokenAddress"}}},
},
IsSigner: false,
IsWritable: false,
InternalField: InternalField{
TypeName: "TokenAdminRegistry",
Location: "LookupTable",
IDL: routerProgramConfig.IDL,
},
}},
},
},
registryTables, exists := tableMap["PoolLookupTable"]
// If PoolLookupTable does not exist in the table map, token indexes are not needed
// Return with empty TokenIndexes
if !exists {
argsTransformed.TokenIndexes = []byte{}
return argsTransformed, accounts, nil
}

tableMap, _, err := cw.ResolveLookupTables(ctx, args, TokenPoolLookupTable)
if err != nil {
return nil, nil, err
}
registryTables := tableMap["PoolLookupTable"]
tokenPoolAddresses := []solana.PublicKey{}
for _, table := range registryTables {
tokenPoolAddresses = append(tokenPoolAddresses, table[0].PublicKey)
Expand All @@ -128,24 +80,12 @@ func CCIPExecuteArgsTransform(ctx context.Context, cw *SolanaChainWriterService,
return nil, nil, fmt.Errorf("missing token pools in accounts")
}

argsTyped, ok := args.(ReportPreTransform)
if !ok {
return nil, nil, fmt.Errorf("args is not of type ReportPreTransform")
}

argsTransformed := ReportPostTransform{
ReportContext: argsTyped.ReportContext,
Report: argsTyped.Report,
AbstractReport: argsTyped.AbstractReport,
Info: argsTyped.Info,
TokenIndexes: tokenIndexes,
}

argsTransformed.TokenIndexes = tokenIndexes
return argsTransformed, accounts, nil
}

// This Transform function trims off the GlobalState account from commit transactions if there are no token or gas price updates
func CCIPCommitAccountTransform(ctx context.Context, cw *SolanaChainWriterService, args any, accounts solana.AccountMetaSlice, toAddress string) (any, solana.AccountMetaSlice, error) {
func CCIPCommitAccountTransform(ctx context.Context, args any, accounts solana.AccountMetaSlice, _ map[string]map[string][]*solana.AccountMeta) (any, solana.AccountMetaSlice, error) {
var tokenPriceVals, gasPriceVals [][]byte
var err error
tokenPriceVals, err = GetValuesAtLocation(args, "Info.TokenPrices.TokenID")
Expand Down
Loading

0 comments on commit 949ee33

Please sign in to comment.