Repository: smartcontractkit/chainlink-solana
Pull request #1252: Add type checks for safety
Author: login: amit-momin
Created: 2025-05-22T18:25:11Z | Merged: 2025-05-27T21:20:36Z

[NONEVM-1796](https://smartcontract-it.atlassian.net/browse/NONEVM-1796)

Added safer type checks

[NONEVM-1796]: https://smartcontract-it.atlassian.net/browse/NONEVM-1796?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ


Diff:
diff --git a/pkg/solana/chainreader/batch.go b/pkg/solana/chainreader/batch.go
index 03de96744..8a9173ffa 100644
--- a/pkg/solana/chainreader/batch.go
+++ b/pkg/solana/chainreader/batch.go
@@ -146,7 +146,16 @@ func doMethodBatchCall(ctx context.Context, lggr logger.Logger, client MultipleA
 
 		// HACK: workaround for RouterGetWrappedNative
 		if batchCall.ReadName == ccipconsts.MethodNameRouterGetWrappedNative {
-			*(results[idx].returnVal.(*[]byte)) = solana.WrappedSol.Bytes()
+			returnVal, ok := results[idx].returnVal.(*[]byte)
+			if !ok {
+				results[idx].err = fmt.Errorf("returnVal is unexpected type: %T, expected %T", results[idx].returnVal, &[]byte{})
+				continue
+			}
+			if returnVal == nil {
+				results[idx].err = errors.New("returnVal is nil")
+				continue
+			}
+			*returnVal = solana.WrappedSol.Bytes()
 			continue
 		}
 
@@ -167,8 +176,16 @@ func doMethodBatchCall(ctx context.Context, lggr logger.Logger, client MultipleA
 
 		// HACK: workaround for OffRampLatestConfigDetails: we need to use an input param to filter an array in the return values, not for seeds
 		if batchCall.ReadName == ccipconsts.MethodNameOffRampLatestConfigDetails {
-			params := batchCall.Params.(map[string]any)
-			ocrPluginType := params["ocrPluginType"].(uint8)
+			params, ok := batchCall.Params.(map[string]any)
+			if !ok {
+				results[idx].err = fmt.Errorf("batch call params is unexpected type: %T, expected %T", batchCall.Params, map[string]any{})
+				continue
+			}
+			ocrPluginType, ok := params["ocrPluginType"].(uint8)
+			if !ok {
+				results[idx].err = fmt.Errorf("ocrPluginType is unexpected type: %T, expected uint8", params["ocrPluginType"])
+				continue
+			}
 
 			output := map[string]any{}
 			err := asValueDotValue(
diff --git a/pkg/solana/chainreader/chain_reader.go b/pkg/solana/chainreader/chain_reader.go
index af592cdcf..63714cd40 100644
--- a/pkg/solana/chainreader/chain_reader.go
+++ b/pkg/solana/chainreader/chain_reader.go
@@ -602,7 +602,10 @@ func (s *ContractReaderService) addEventRead(
 		return err
 	}
 
-	pf := setPollingFilterOverrides(common, conf.PollingFilter)
+	pf, err := setPollingFilterOverrides(common, conf.PollingFilter)
+	if err != nil {
+		return fmt.Errorf("failed to set polling filter overrides: %w", err)
+	}
 
 	eventIdl, isOk := idlDef.(codec.IdlEvent)
 	if !isOk {
@@ -918,7 +921,7 @@ func (s *ContractReaderService) getPDAsForGetTokenPrices(params any, values read
 	return pdaAddresses, nil
 }
 
-func setPollingFilterOverrides(common *config.PollingFilter, overrides ...*config.PollingFilter) config.PollingFilter {
+func setPollingFilterOverrides(common *config.PollingFilter, overrides ...*config.PollingFilter) (config.PollingFilter, error) {
 	final := reflect.New(reflect.TypeOf(common).Elem())
 	valOfF := final.Elem()
 	allOverrides := append([]*config.PollingFilter{common}, overrides...)
@@ -946,5 +949,10 @@ func setPollingFilterOverrides(common *config.PollingFilter, overrides ...*confi
 		}
 	}
 
-	return final.Elem().Interface().(config.PollingFilter)
+	filter, ok := final.Elem().Interface().(config.PollingFilter)
+	if !ok {
+		return config.PollingFilter{}, fmt.Errorf("encountered unexpected type: %T, expected: %T", final.Elem().Interface(), config.PollingFilter{})
+	}
+
+	return filter, nil
 }
diff --git a/pkg/solana/client/client.go b/pkg/solana/client/client.go
index 9302e66a2..19103708f 100644
--- a/pkg/solana/client/client.go
+++ b/pkg/solana/client/client.go
@@ -129,7 +129,10 @@ func (c *Client) Balance(ctx context.Context, addr solana.PublicKey) (bal uint64
 	if err != nil {
 		return 0, err
 	}
-	res := v.(*rpc.GetBalanceResult)
+	res, ok := v.(*rpc.GetBalanceResult)
+	if !ok {
+		return 0, fmt.Errorf("result is unexpected type %T, expected %T", v, &rpc.GetBalanceResult{})
+	}
 	return res.Value, err
 }
 
@@ -149,7 +152,11 @@ func (c *Client) SlotHeightWithCommitment(ctx context.Context, commitment rpc.Co
 	v, err, _ := c.requestGroup.Do(key, func() (interface{}, error) {
 		return c.rpc.GetSlot(ctx, commitment)
 	})
-	return v.(uint64), err
+	res, ok := v.(uint64)
+	if !ok {
+		return 0, fmt.Errorf("result is unexpected type %T, expected uint64", v)
+	}
+	return res, err
 }
 
 func (c *Client) GetSignaturesForAddressWithOpts(ctx context.Context, addr solana.PublicKey, opts *rpc.GetSignaturesForAddressOpts) (sigs []*rpc.TransactionSignature, err error) {
@@ -179,7 +186,11 @@ func (c *Client) GetTransaction(ctx context.Context, txHash solana.Signature) (t
 		version := MaxSupportTransactionVersion
 		return c.rpc.GetTransaction(ctx, txHash, &rpc.GetTransactionOpts{Encoding: solana.EncodingBase64, Commitment: c.commitment, MaxSupportedTransactionVersion: &version})
 	})
-	return v.(*rpc.GetTransactionResult), err
+	res, ok := v.(*rpc.GetTransactionResult)
+	if !ok {
+		return nil, fmt.Errorf("result is unexpected type %T, expected %T", v, &rpc.GetTransactionResult{})
+	}
+	return res, err
 }
 
 func (c *Client) GetAccountInfoWithOpts(ctx context.Context, addr solana.PublicKey, opts *rpc.GetAccountInfoOpts) (result *rpc.GetAccountInfoResult, err error) {
@@ -227,7 +238,11 @@ func (c *Client) GetBlocks(ctx context.Context, startSlot uint64, endSlot *uint6
 	v, err, _ := c.requestGroup.Do(key, func() (interface{}, error) {
 		return c.rpc.GetBlocks(ctx, startSlot, endSlot, c.commitment)
 	})
-	return v.(rpc.BlocksResult), err
+	res, ok := v.(rpc.BlocksResult)
+	if !ok {
+		return nil, fmt.Errorf("result is unexpected type %T, expected %T", v, rpc.BlocksResult{})
+	}
+	return res, err
 }
 
 func (c *Client) LatestBlockhash(ctx context.Context) (result *rpc.GetLatestBlockhashResult, err error) {
@@ -240,7 +255,11 @@ func (c *Client) LatestBlockhash(ctx context.Context) (result *rpc.GetLatestBloc
 	v, err, _ := c.requestGroup.Do("GetLatestBlockhash", func() (interface{}, error) {
 		return c.rpc.GetLatestBlockhash(ctx, c.commitment)
 	})
-	return v.(*rpc.GetLatestBlockhashResult), err
+	res, ok := v.(*rpc.GetLatestBlockhashResult)
+	if !ok {
+		return nil, fmt.Errorf("result is unexpected type %T, expected %T", v, &rpc.GetLatestBlockhashResult{})
+	}
+	return res, err
 }
 
 func (c *Client) ChainID(ctx context.Context) (chainID mn.StringID, err error) {
@@ -257,7 +276,11 @@ func (c *Client) ChainID(ctx context.Context) (chainID mn.StringID, err error) {
 		return "", err
 	}
 
-	return mn.StringID(v.(solana.Hash).String()), nil
+	res, ok := v.(solana.Hash)
+	if !ok {
+		return "", fmt.Errorf("result is unexpected type %T, expected %T", v, solana.Hash{})
+	}
+	return mn.StringID(res.String()), nil
 }
 
 func (c *Client) GetFeeForMessage(ctx context.Context, msg string) (fee uint64, err error) {
@@ -366,7 +389,11 @@ func (c *Client) GetLatestBlockHeight(ctx context.Context) (height uint64, err e
 	v, err, _ := c.requestGroup.Do("GetBlockHeight", func() (interface{}, error) {
 		return c.rpc.GetBlockHeight(ctx, c.commitment)
 	})
-	return v.(uint64), err
+	res, ok := v.(uint64)
+	if !ok {
+		return 0, fmt.Errorf("result is unexpected type %T, expected uint64", v)
+	}
+	return res, err
 }
 
 func (c *Client) GetBlockWithOpts(ctx context.Context, slot uint64, opts *rpc.GetBlockOpts) (result *rpc.GetBlockResult, err error) {
@@ -393,7 +420,11 @@ func (c *Client) GetBlock(ctx context.Context, slot uint64) (result *rpc.GetBloc
 			MaxSupportedTransactionVersion: &version,
 		})
 	})
-	return v.(*rpc.GetBlockResult), err
+	res, ok := v.(*rpc.GetBlockResult)
+	if !ok {
+		return nil, fmt.Errorf("result is unexpected type %T, expected %T", v, &rpc.GetBlockResult{})
+	}
+	return res, err
 }
 
 func (c *Client) GetBlocksWithLimit(ctx context.Context, startSlot uint64, limit uint64) (result *rpc.BlocksResult, err error) {
@@ -408,5 +439,9 @@ func (c *Client) GetBlocksWithLimit(ctx context.Context, startSlot uint64, limit
 	v, err, _ := c.requestGroup.Do(key, func() (interface{}, error) {
 		return c.rpc.GetBlocksWithLimit(ctx, startSlot, limit, c.commitment)
 	})
-	return v.(*rpc.BlocksResult), err
+	res, ok := v.(*rpc.BlocksResult)
+	if !ok {
+		return nil, fmt.Errorf("result is unexpected type %T, expected %T", v, &rpc.BlocksResult{})
+	}
+	return res, err
 }
diff --git a/pkg/solana/codec/anchoridl.go b/pkg/solana/codec/anchoridl.go
index 7b4caa424..5ea8a08b1 100644
--- a/pkg/solana/codec/anchoridl.go
+++ b/pkg/solana/codec/anchoridl.go
@@ -313,10 +313,10 @@ func (env *IdlType) UnmarshalJSON(data []byte) error {
 			if typeFound {
 				return fmt.Errorf("multiple types found for IdlType: %s", spew.Sdump(temp))
 			}
-			if _, ok := got.([]interface{}); !ok {
+			arrVal, ok := got.([]interface{})
+			if !ok {
 				return fmt.Errorf("array is not in expected format: %s", spew.Sdump(got))
 			}
-			arrVal := got.([]interface{})
 			if len(arrVal) != 2 {
 				return fmt.Errorf("array is not of expected length: %s", spew.Sdump(got))
 			}
@@ -324,8 +324,11 @@ func (env *IdlType) UnmarshalJSON(data []byte) error {
 			if err := utilz.TranscodeJSON(arrVal[0], &target.Thing); err != nil {
 				return err
 			}
-
-			target.Num = int(arrVal[1].(float64))
+			num, ok := arrVal[1].(float64)
+			if !ok {
+				return fmt.Errorf("value is unexpected type: %T, expected float64", arrVal[1])
+			}
+			target.Num = int(num)
 			env.AsIdlTypeArray = &target
 		}
 	default:
diff --git a/pkg/solana/logpoller/blocks_sorter.go b/pkg/solana/logpoller/blocks_sorter.go
index f71ba7c1e..2fc3c84b3 100644
--- a/pkg/solana/logpoller/blocks_sorter.go
+++ b/pkg/solana/logpoller/blocks_sorter.go
@@ -110,7 +110,10 @@ func (p *blocksSorter) readNextReadyBlock() *types.Block {
 		return nil
 	}
 
-	slotNumber := element.Value.(uint64)
+	slotNumber, ok := element.Value.(uint64)
+	if !ok {
+		return nil
+	}
 	block, ok := p.readyBlocks[slotNumber]
 	if !ok {
 		return nil


Reviews:
user: login: reductionista
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-24T02:31:39Z
user: login: amit-momin
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-27T19:10:51Z
user: login: reductionista
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-27T19:29:47Z

Review comments:
diff_hunk: @@ -146,7 +146,12 @@ func doMethodBatchCall(ctx context.Context, lggr logger.Logger, client MultipleA
 
 		// HACK: workaround for RouterGetWrappedNative
 		if batchCall.ReadName == ccipconsts.MethodNameRouterGetWrappedNative {
-			*(results[idx].returnVal.(*[]byte)) = solana.WrappedSol.Bytes()
+			returnVal, ok := results[idx].returnVal.(*[]byte)
+			if !ok {
+				results[idx].err = fmt.Errorf("returnVal is unexpected type: %T, expected %T", results[idx].returnVal, &[]byte{})
+				continue
+			}
path: pkg/solana/chainreader/batch.go
user: login: reductionista
body: To be safe, should add a `returnVal != nil` check as well?
created_at: 2025-05-24T02:31:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -146,7 +146,12 @@ func doMethodBatchCall(ctx context.Context, lggr logger.Logger, client MultipleA
 
 		// HACK: workaround for RouterGetWrappedNative
 		if batchCall.ReadName == ccipconsts.MethodNameRouterGetWrappedNative {
-			*(results[idx].returnVal.(*[]byte)) = solana.WrappedSol.Bytes()
+			returnVal, ok := results[idx].returnVal.(*[]byte)
+			if !ok {
+				results[idx].err = fmt.Errorf("returnVal is unexpected type: %T, expected %T", results[idx].returnVal, &[]byte{})
+				continue
+			}
path: pkg/solana/chainreader/batch.go
user: login: amit-momin
body: Good call, added it in the latest commit
created_at: 2025-05-27T19:10:51Z
author_association: COLLABORATOR

PR comments:
user: login: cl-sonarqube-production[bot]
created_at: 2025-05-27T19:27:26Z
body: ## [![Quality Gate failed](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/checks/QualityGateBadge/failed-16px.png 'Quality Gate failed')](https://sonarqube.main.prod.cldev.sh/dashboard?id=smartcontractkit_chainlink-solana&pullRequest=1252) **Quality Gate failed**  
Failed conditions  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/failed-16px.png '')](https://sonarqube.main.prod.cldev.sh/component_measures?id=smartcontractkit_chainlink-solana&pullRequest=1252&metric=new_coverage&view=list) [54.1% Coverage on New Code](https://sonarqube.main.prod.cldev.sh/component_measures?id=smartcontractkit_chainlink-solana&pullRequest=1252&metric=new_coverage&view=list) (required ≥ 75%)  
  
[See analysis details on SonarQube](https://sonarqube.main.prod.cldev.sh/dashboard?id=smartcontractkit_chainlink-solana&pullRequest=1252)


author_association: NONE