Skip to content

Commit

Permalink
ChainWriter unit tests (#948)
Browse files Browse the repository at this point in the history
* Added ChainWriter unit tests for GetFeeComponents and GetTransactionStatus

* Created SubmitTransaction tests

* Created SubmitTransaction tests

* Moved txm utils into own package and generated txm mock

* Updated chain writer tests to use txm mock

* Added GetAddresses unit test and fixed SubmitTransaction unit test

* Fixed linting and removed file read for IDL

* Fixed filter lookup table error case and fixed linting

* Added filter lookup table addresses unit tests

* Added new test case and fixed formatting issues

* Addressed golang lint suggestions

* Cleaned out unused dependency and fixed remaining golang lint errors

* Added derived lookup table indeces unit tests

---------

Co-authored-by: Silas Lenihan <[email protected]>
  • Loading branch information
amit-momin and silaslenihan committed Dec 13, 2024
1 parent d175223 commit 1d74e41
Show file tree
Hide file tree
Showing 22 changed files with 1,364 additions and 256 deletions.
1 change: 1 addition & 0 deletions .mockery.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ packages:
config:
filename: simple_keystore.go
case: underscore
TxManager:
github.com/smartcontractkit/chainlink-solana/pkg/solana/logpoller:
interfaces:
RPCClient:
7 changes: 6 additions & 1 deletion contracts/artifacts/localnet/write_test-keypair.json
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
[26,39,164,161,246,97,149,0,58,187,146,162,53,35,107,2,117,242,83,171,48,7,63,240,69,221,239,45,97,55,112,106,192,228,214,205,123,71,58,23,62,229,166,213,149,122,96,145,35,150,16,156,247,199,242,108,173,80,62,231,39,196,27,192]
[
26, 39, 164, 161, 246, 97, 149, 0, 58, 187, 146, 162, 53, 35, 107, 2, 117,
242, 83, 171, 48, 7, 63, 240, 69, 221, 239, 45, 97, 55, 112, 106, 192, 228,
214, 205, 123, 71, 58, 23, 62, 229, 166, 213, 149, 122, 96, 145, 35, 150, 16,
156, 247, 199, 242, 108, 173, 80, 62, 231, 39, 196, 27, 192
]
3 changes: 1 addition & 2 deletions contracts/programs/write_test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ pub mod write_test {
data.administrator = ctx.accounts.admin.key();
data.pending_administrator = Pubkey::default();
data.lookup_table = lookup_table;

Ok(())
}

}

#[derive(Accounts)]
Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ require (
github.com/smartcontractkit/chainlink-common v0.3.1-0.20241127162636-07aa781ee1f4
github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12
github.com/stretchr/testify v1.9.0
github.com/test-go/testify v1.1.4
go.uber.org/zap v1.27.0
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
golang.org/x/sync v0.8.0
Expand Down
11 changes: 6 additions & 5 deletions pkg/solana/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/smartcontractkit/chainlink-solana/pkg/solana/internal"
"github.com/smartcontractkit/chainlink-solana/pkg/solana/monitor"
"github.com/smartcontractkit/chainlink-solana/pkg/solana/txm"
txmutils "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm/utils"
)

type Chain interface {
Expand Down Expand Up @@ -576,12 +577,12 @@ func (c *chain) sendTx(ctx context.Context, from, to string, amount *big.Int, ba

chainTxm := c.TxManager()
err = chainTxm.Enqueue(ctx, "", tx, nil,
txm.SetComputeUnitLimit(500), // reduce from default 200K limit - should only take 450 compute units
txmutils.SetComputeUnitLimit(500), // reduce from default 200K limit - should only take 450 compute units
// no fee bumping and no additional fee - makes validating balance accurate
txm.SetComputeUnitPriceMax(0),
txm.SetComputeUnitPriceMin(0),
txm.SetBaseComputeUnitPrice(0),
txm.SetFeeBumpPeriod(0),
txmutils.SetComputeUnitPriceMax(0),
txmutils.SetComputeUnitPriceMin(0),
txmutils.SetBaseComputeUnitPrice(0),
txmutils.SetFeeBumpPeriod(0),
)
if err != nil {
return fmt.Errorf("transaction failed: %w", err)
Expand Down
6 changes: 3 additions & 3 deletions pkg/solana/chainwriter/ccip_example_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func TestConfig() {
// 3. Lookup Table content - Get all the accounts from a lookup table
// 4. PDA Account Lookup - Based on another account and a seed/s
// Nested PDA Account with seeds from:
// -> input paramters
// -> input parameters
// -> constant
// PDALookups can resolve to multiple addresses if:
// A) The PublicKey lookup resolves to multiple addresses (i.e. multiple token addresses)
Expand All @@ -102,8 +102,8 @@ func TestConfig() {
},
// Lookup Table content - Get the accounts from the derived lookup table above
AccountsFromLookupTable{
LookupTablesName: "RegistryTokenState",
IncludeIndexes: []int{}, // If left empty, all addresses will be included. Otherwise, only the specified indexes will be included.
LookupTableName: "RegistryTokenState",
IncludeIndexes: []int{}, // If left empty, all addresses will be included. Otherwise, only the specified indexes will be included.
},
// Account Lookup - Based on data from input parameters
// In this case, the user wants to add the destination token addresses to the transaction.
Expand Down
56 changes: 28 additions & 28 deletions pkg/solana/chainwriter/chain_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ import (

type SolanaChainWriterService struct {
reader client.Reader
txm txm.Txm
txm txm.TxManager
ge fees.Estimator
config ChainWriterConfig
codecs map[string]types.Codec
}

// nolint // ignoring naming suggestion
type ChainWriterConfig struct {
Programs map[string]ProgramConfig
}
Expand All @@ -46,7 +47,7 @@ type MethodConfig struct {
DebugIDLocation string
}

func NewSolanaChainWriterService(reader client.Reader, txm txm.Txm, ge fees.Estimator, config ChainWriterConfig) (*SolanaChainWriterService, error) {
func NewSolanaChainWriterService(reader client.Reader, txm txm.TxManager, ge fees.Estimator, config ChainWriterConfig) (*SolanaChainWriterService, error) {
codecs, err := parseIDLCodecs(config)
if err != nil {
return nil, fmt.Errorf("failed to parse IDL codecs: %w", err)
Expand All @@ -68,7 +69,7 @@ func parseIDLCodecs(config ChainWriterConfig) (map[string]types.Codec, error) {
if err := json.Unmarshal([]byte(programConfig.IDL), &idl); err != nil {
return nil, fmt.Errorf("failed to unmarshal IDL: %w", err)
}
idlCodec, err := codec.NewIDLAccountCodec(idl, binary.LittleEndian())
idlCodec, err := codec.NewIDLInstructionsCodec(idl, binary.LittleEndian())
if err != nil {
return nil, fmt.Errorf("failed to create codec from IDL: %w", err)
}
Expand All @@ -79,7 +80,7 @@ func parseIDLCodecs(config ChainWriterConfig) (map[string]types.Codec, error) {
return nil, fmt.Errorf("failed to create input modifications: %w", err)
}
// add mods to codec
idlCodec, err = codec.NewNamedModifierCodec(idlCodec, WrapItemType(program, method, true), modConfig)
idlCodec, err = codec.NewNamedModifierCodec(idlCodec, method, modConfig)
if err != nil {
return nil, fmt.Errorf("failed to create named codec: %w", err)
}
Expand All @@ -90,14 +91,6 @@ func parseIDLCodecs(config ChainWriterConfig) (map[string]types.Codec, error) {
return codecs, nil
}

func WrapItemType(programName, itemType string, isParams bool) string {
if isParams {
return fmt.Sprintf("params.%s.%s", programName, itemType)
}

return fmt.Sprintf("return.%s.%s", programName, itemType)
}

/*
GetAddresses resolves account addresses from various `Lookup` configurations to build the required `solana.AccountMeta` list
for Solana transactions. It handles constant addresses, dynamic lookups, program-derived addresses (PDAs), and lookup tables.
Expand Down Expand Up @@ -161,7 +154,7 @@ func (s *SolanaChainWriterService) FilterLookupTableAddresses(
for innerIdentifier, metas := range innerMap {
tableKey, err := solana.PublicKeyFromBase58(innerIdentifier)
if err != nil {
fmt.Errorf("error parsing lookup table key: %w", err)
continue
}

// Collect public keys that are actually used
Expand Down Expand Up @@ -198,18 +191,31 @@ func (s *SolanaChainWriterService) FilterLookupTableAddresses(
}

func (s *SolanaChainWriterService) SubmitTransaction(ctx context.Context, contractName, method string, args any, transactionID string, toAddress string, meta *types.TxMeta, value *big.Int) error {
programConfig := s.config.Programs[contractName]
methodConfig := programConfig.Methods[method]
programConfig, exists := s.config.Programs[contractName]
if !exists {
return fmt.Errorf("failed to find program config for contract name: %s", contractName)
}
methodConfig, exists := programConfig.Methods[method]
if !exists {
return fmt.Errorf("failed to find method config for method: %s", method)
}

// Configure debug ID
debugID := ""
if methodConfig.DebugIDLocation != "" {
debugID, err := GetDebugIDAtLocation(args, methodConfig.DebugIDLocation)
var err error
debugID, err = GetDebugIDAtLocation(args, methodConfig.DebugIDLocation)
if err != nil {
return errorWithDebugID(fmt.Errorf("error getting debug ID from input args: %w", err), debugID)
}
}

codec := s.codecs[contractName]
encodedPayload, err := codec.Encode(ctx, args, method)
if err != nil {
return errorWithDebugID(fmt.Errorf("error encoding transaction payload: %w", err), debugID)
}

// Fetch derived and static table maps
derivedTableMap, staticTableMap, err := s.ResolveLookupTables(ctx, args, methodConfig.LookupTables)
if err != nil {
Expand All @@ -232,7 +238,7 @@ func (s *SolanaChainWriterService) SubmitTransaction(ctx context.Context, contra
}

// Prepare transaction
programId, err := solana.PublicKeyFromBase58(contractName)
programID, err := solana.PublicKeyFromBase58(contractName)
if err != nil {
return errorWithDebugID(fmt.Errorf("error parsing program ID: %w", err), debugID)
}
Expand All @@ -242,15 +248,9 @@ func (s *SolanaChainWriterService) SubmitTransaction(ctx context.Context, contra
return errorWithDebugID(fmt.Errorf("error parsing fee payer address: %w", err), debugID)
}

codec := s.codecs[contractName]
encodedPayload, err := codec.Encode(ctx, args, WrapItemType(contractName, method, true))
if err != nil {
return errorWithDebugID(fmt.Errorf("error encoding transaction payload: %w", err), debugID)
}

tx, err := solana.NewTransaction(
[]solana.Instruction{
solana.NewInstruction(programId, accounts, encodedPayload),
solana.NewInstruction(programID, accounts, encodedPayload),
},
blockhash.Value.Blockhash,
solana.TransactionPayer(feePayer),
Expand All @@ -269,13 +269,13 @@ func (s *SolanaChainWriterService) SubmitTransaction(ctx context.Context, contra
}

var (
_ services.Service = &SolanaChainWriterService{}
_ types.ChainWriter = &SolanaChainWriterService{}
_ services.Service = &SolanaChainWriterService{}
_ types.ContractWriter = &SolanaChainWriterService{}
)

// GetTransactionStatus returns the current status of a transaction in the underlying chain's TXM.
func (s *SolanaChainWriterService) GetTransactionStatus(ctx context.Context, transactionID string) (types.TransactionStatus, error) {
return types.Unknown, nil
return s.txm.GetTransactionStatus(ctx, transactionID)
}

// GetFeeComponents retrieves the associated gas costs for executing a transaction.
Expand All @@ -286,7 +286,7 @@ func (s *SolanaChainWriterService) GetFeeComponents(ctx context.Context) (*types

fee := s.ge.BaseComputeUnitPrice()
return &types.ChainFeeComponents{
ExecutionFee: big.NewInt(int64(fee)),
ExecutionFee: new(big.Int).SetUint64(fee),
DataAvailabilityFee: nil,
}, nil
}
Expand Down
Loading

0 comments on commit 1d74e41

Please sign in to comment.