Repository: smartcontractkit/chainlink-ccip
Pull request #850: solana: shared address validation by chain family selector
Author: login: jadepark-dev
Created: 2025-04-16T20:31:42Z | Merged: 2025-04-17T16:14:45Z

This PR refactors address validation for token addresses based on chain family selectors

## Primary Changes
- Moved address validation logic from fee-quoter to `ccip-common` to unify validation across programs
- Added chain family selector constants in `ccip-common` module for cross-program usage
- Implemented shared validation helpers (`validate_evm_address` and `validate_svm_address`) in common library
- Added validation check for destination token addresses in onramp transfers based on chain family

## Others
- Removed duplicate validation code and error types from `fee-quoter` program
- Added test data with valid EVM address formats for token validation testing
- Updated error handling to use common error types for address validation failures


Diff:
diff --git a/chains/solana/contracts/Cargo.lock b/chains/solana/contracts/Cargo.lock
index 36546e0558..2d17b1afac 100644
--- a/chains/solana/contracts/Cargo.lock
+++ b/chains/solana/contracts/Cargo.lock
@@ -681,6 +681,7 @@ version = "0.1.0-dev"
 dependencies = [
  "anchor-lang",
  "anchor-spl",
+ "ethnum",
  "solana-program",
 ]
 
diff --git a/chains/solana/contracts/programs/ccip-common/Cargo.toml b/chains/solana/contracts/programs/ccip-common/Cargo.toml
index 73835fc65d..30c7e092dd 100644
--- a/chains/solana/contracts/programs/ccip-common/Cargo.toml
+++ b/chains/solana/contracts/programs/ccip-common/Cargo.toml
@@ -20,3 +20,4 @@ default = ["no-entrypoint"]
 solana-program = "1.17.25" # pin solana to 1.17
 anchor-lang = { version = "0.29.0", features = ["init-if-needed"] }
 anchor-spl = "0.29.0"
+ethnum = "1.5"
\ No newline at end of file
diff --git a/chains/solana/contracts/programs/ccip-common/src/lib.rs b/chains/solana/contracts/programs/ccip-common/src/lib.rs
index f8d7a49b93..b37975acf9 100644
--- a/chains/solana/contracts/programs/ccip-common/src/lib.rs
+++ b/chains/solana/contracts/programs/ccip-common/src/lib.rs
@@ -24,8 +24,21 @@ pub enum CommonCcipError {
     InvalidInputsLookupTableAccountWritable,
     #[msg("Invalid pool signer account")]
     InvalidInputsPoolSignerAccounts,
+
+    #[msg("Invalid chain family selector")]
+    InvalidChainFamilySelector,
+    #[msg("Invalid encoding")]
+    InvalidEncoding,
+    #[msg("Invalid EVM address")]
+    InvalidEVMAddress,
+    #[msg("Invalid SVM address")]
+    InvalidSVMAddress,
 }
 
+// https://github.com/smartcontractkit/chainlink/blob/ff8a597fd9df653f8967427498eaa5a04b19febb/contracts/src/v0.8/ccip/libraries/Internal.sol#L276
+pub const CHAIN_FAMILY_SELECTOR_EVM: u32 = 0x2812d52c;
+pub const CHAIN_FAMILY_SELECTOR_SVM: u32 = 0x1e10bdc4;
+
 // Duplicates the router ID to declare router accounts that must be visible from the common crate,
 // avoiding a circular dependency. This means this crate may only declare accounts that belong
 // to the router, and no other program.
diff --git a/chains/solana/contracts/programs/ccip-common/src/v1.rs b/chains/solana/contracts/programs/ccip-common/src/v1.rs
index 49295c1484..1abe710709 100644
--- a/chains/solana/contracts/programs/ccip-common/src/v1.rs
+++ b/chains/solana/contracts/programs/ccip-common/src/v1.rs
@@ -1,4 +1,5 @@
 use anchor_lang::prelude::*;
+use ethnum::U256;
 use solana_program::address_lookup_table::state::AddressLookupTable;
 
 use crate::{
@@ -7,6 +8,8 @@ use crate::{
 };
 
 pub const MIN_TOKEN_POOL_ACCOUNTS: usize = 13; // see TokenAccounts struct for all required accounts
+const U160_MAX: U256 = U256::from_words(u32::MAX as u128, u128::MAX);
+const EVM_PRECOMPILE_SPACE: u32 = 1024;
 
 pub struct TokenAccounts<'a> {
     pub user_token_account: &'a AccountInfo<'a>,
@@ -303,3 +306,35 @@ pub mod token_admin_registry_writable {
         }
     }
 }
+
+// address validation helpers based on the chain family selector
+pub fn validate_evm_address(address: &[u8]) -> Result<()> {
+    require_eq!(address.len(), 32, CommonCcipError::InvalidEVMAddress);
+
+    let address: U256 = U256::from_be_bytes(
+        address
+            .try_into()
+            .map_err(|_| CommonCcipError::InvalidEncoding)?,
+    );
+    require!(address <= U160_MAX, CommonCcipError::InvalidEVMAddress);
+    if let Ok(small_address) = TryInto::<u32>::try_into(address) {
+        require_gte!(
+            small_address,
+            EVM_PRECOMPILE_SPACE,
+            CommonCcipError::InvalidEVMAddress
+        )
+    };
+    Ok(())
+}
+
+pub fn validate_svm_address(address: &[u8], address_must_be_nonzero: bool) -> Result<()> {
+    require_eq!(address.len(), 32, CommonCcipError::InvalidSVMAddress);
+    require!(
+        !address_must_be_nonzero || address.iter().any(|b| *b != 0),
+        CommonCcipError::InvalidSVMAddress
+    );
+
+    Pubkey::try_from_slice(address)
+        .map(|_| ())
+        .map_err(|_| CommonCcipError::InvalidSVMAddress.into())
+}
diff --git a/chains/solana/contracts/programs/ccip-router/src/instructions/v1/onramp.rs b/chains/solana/contracts/programs/ccip-router/src/instructions/v1/onramp.rs
index 8c365b83f7..8ab05fe40d 100644
--- a/chains/solana/contracts/programs/ccip-router/src/instructions/v1/onramp.rs
+++ b/chains/solana/contracts/programs/ccip-router/src/instructions/v1/onramp.rs
@@ -2,12 +2,14 @@ use anchor_lang::prelude::*;
 use anchor_spl::token::spl_token;
 use anchor_spl::token_interface;
 
+use crate::context::ANCHOR_DISCRIMINATOR;
 use crate::events::on_ramp as events;
 use crate::messages::GetFeeResult;
 
 use ccip_common::seed;
 use ccip_common::v1::{validate_and_parse_token_accounts, TokenAccounts};
 use fee_quoter::messages::TokenTransferAdditionalData;
+use fee_quoter::state::DestChain;
 
 use super::super::interfaces::OnRamp;
 use super::fees::{get_fee_cpi, transfer_and_wrap_native_sol, transfer_fee};
@@ -188,6 +190,12 @@ impl OnRamp for Impl {
                 CcipRouterError::InvalidInputsTokenAccounts,
             );
 
+            require_gt!(
+                token_amount.amount,
+                0,
+                CcipRouterError::InvalidInputsTokenAmount
+            );
+
             let seeds = &[
                 seed::EXTERNAL_TOKEN_POOLS_SIGNER,
                 current_token_accounts.pool_program.key.as_ref(),
@@ -245,6 +253,21 @@ impl OnRamp for Impl {
                 )?;
 
                 let lock_or_burn_out_data = LockOrBurnOutV1::try_from_slice(&return_data)?;
+
+                {
+                    // validate the token address based on the destination chain family selector
+                    let dest_chain_info = ctx.accounts.fee_quoter_dest_chain.to_account_info();
+                    let dest_chain = DestChain::try_from_slice(
+                        &dest_chain_info.data.borrow()[ANCHOR_DISCRIMINATOR..],
+                    )?;
+                    let dest_chain_family_selector = dest_chain.config.chain_family_selector;
+
+                    helpers::validate_transfer_dest_address(
+                        dest_chain_family_selector,
+                        &lock_or_burn_out_data.dest_token_address,
+                    )?;
+                }
+
                 new_message.token_amounts[i] = token_transfer(
                     lock_or_burn_out_data,
                     current_token_accounts.pool_config.key(),
@@ -295,6 +318,10 @@ impl OnRamp for Impl {
 }
 
 mod helpers {
+    use ccip_common::{
+        v1::{validate_evm_address, validate_svm_address},
+        CommonCcipError, CHAIN_FAMILY_SELECTOR_EVM, CHAIN_FAMILY_SELECTOR_SVM,
+    };
     use rmn_remote::state::CurseSubject;
 
     use super::*;
@@ -411,6 +438,18 @@ mod helpers {
         result.to_bytes()
     }
 
+    pub(super) fn validate_transfer_dest_address(
+        chain_family_selector: [u8; 4],
+        dest_token_address: &[u8],
+    ) -> Result<()> {
+        let selector = u32::from_be_bytes(chain_family_selector);
+        match selector {
+            CHAIN_FAMILY_SELECTOR_EVM => validate_evm_address(dest_token_address),
+            CHAIN_FAMILY_SELECTOR_SVM => validate_svm_address(dest_token_address, true),
+            _ => Err(CommonCcipError::InvalidChainFamilySelector.into()),
+        }
+    }
+
     #[cfg(test)]
     mod tests {
         use ethnum::U256;
diff --git a/chains/solana/contracts/programs/fee-quoter/src/instructions/v1/admin.rs b/chains/solana/contracts/programs/fee-quoter/src/instructions/v1/admin.rs
index 9093ced5a8..4e1f65e939 100644
--- a/chains/solana/contracts/programs/fee-quoter/src/instructions/v1/admin.rs
+++ b/chains/solana/contracts/programs/fee-quoter/src/instructions/v1/admin.rs
@@ -1,5 +1,7 @@
 use anchor_lang::prelude::*;
 
+use ccip_common::{CommonCcipError, CHAIN_FAMILY_SELECTOR_EVM, CHAIN_FAMILY_SELECTOR_SVM};
+
 use crate::context::{
     AcceptOwnership, AddBillingTokenConfig, AddDestChain, AddPriceUpdater, RemovePriceUpdater,
     SetTokenTransferFeeConfig, UpdateBillingTokenConfig, UpdateConfig, UpdateDestChainConfig,
@@ -12,7 +14,6 @@ use crate::event::{
 };
 use crate::instructions::interfaces::Admin;
 use crate::instructions::v1::public::CCIP_LOCK_OR_BURN_V1_RET_BYTES;
-use crate::messages::{CHAIN_FAMILY_SELECTOR_EVM, CHAIN_FAMILY_SELECTOR_SVM};
 use crate::state::{
     BillingTokenConfig, CodeVersion, DestChain, DestChainConfig, DestChainState,
     PerChainPerTokenConfig, TimestampedPackedU224, TokenTransferFeeConfig,
@@ -263,7 +264,7 @@ fn validate_dest_chain_config(dest_chain_selector: u64, config: &DestChainConfig
             u32::from_be_bytes(config.chain_family_selector),
             CHAIN_FAMILY_SELECTOR_EVM | CHAIN_FAMILY_SELECTOR_SVM
         ),
-        FeeQuoterError::InvalidChainFamilySelector
+        CommonCcipError::InvalidChainFamilySelector
     );
 
     Ok(())
diff --git a/chains/solana/contracts/programs/fee-quoter/src/instructions/v1/messages.rs b/chains/solana/contracts/programs/fee-quoter/src/instructions/v1/messages.rs
index 8b13560470..75d1686e02 100644
--- a/chains/solana/contracts/programs/fee-quoter/src/instructions/v1/messages.rs
+++ b/chains/solana/contracts/programs/fee-quoter/src/instructions/v1/messages.rs
@@ -1,18 +1,16 @@
 use anchor_lang::prelude::*;
-use ethnum::U256;
+
+use ccip_common::v1::{validate_evm_address, validate_svm_address};
+use ccip_common::{CommonCcipError, CHAIN_FAMILY_SELECTOR_EVM, CHAIN_FAMILY_SELECTOR_SVM};
 
 use crate::extra_args::{
     GenericExtraArgsV2, SVMExtraArgsV1, GENERIC_EXTRA_ARGS_V2_TAG, SVM_EXTRA_ARGS_MAX_ACCOUNTS,
     SVM_EXTRA_ARGS_V1_TAG,
 };
-use crate::messages::{
-    ProcessedExtraArgs, SVM2AnyMessage, CHAIN_FAMILY_SELECTOR_EVM, CHAIN_FAMILY_SELECTOR_SVM,
-};
+use crate::messages::{ProcessedExtraArgs, SVM2AnyMessage};
 use crate::state::{BillingTokenConfig, DestChain, DestChainConfig};
 use crate::FeeQuoterError;
 
-const U160_MAX: U256 = U256::from_words(u32::MAX as u128, u128::MAX);
-
 pub fn validate_svm2any(
     msg: &SVM2AnyMessage,
     dest_chain: &DestChain,
@@ -71,47 +69,14 @@ fn validate_dest_family_address(
 ) -> Result<()> {
     let selector = u32::from_be_bytes(chain_family_selector);
     match selector {
-        CHAIN_FAMILY_SELECTOR_EVM => validate_evm_dest_address(&msg.receiver),
+        CHAIN_FAMILY_SELECTOR_EVM => validate_evm_address(&msg.receiver),
         CHAIN_FAMILY_SELECTOR_SVM => {
-            validate_svm_dest_address(&msg.receiver, msg_extra_args.gas_limit > 0)
+            validate_svm_address(&msg.receiver, msg_extra_args.gas_limit > 0)
         }
-        _ => Err(FeeQuoterError::InvalidChainFamilySelector.into()),
+        _ => Err(CommonCcipError::InvalidChainFamilySelector.into()),
     }
 }
 
-fn validate_evm_dest_address(address: &[u8]) -> Result<()> {
-    const PRECOMPILE_SPACE: u32 = 1024;
-
-    require_eq!(address.len(), 32, FeeQuoterError::InvalidEVMAddress);
-
-    let address: U256 = U256::from_be_bytes(
-        address
-            .try_into()
-            .map_err(|_| FeeQuoterError::InvalidEncoding)?,
-    );
-    require!(address <= U160_MAX, FeeQuoterError::InvalidEVMAddress);
-    if let Ok(small_address) = TryInto::<u32>::try_into(address) {
-        require_gte!(
-            small_address,
-            PRECOMPILE_SPACE,
-            FeeQuoterError::InvalidEVMAddress
-        )
-    };
-    Ok(())
-}
-
-fn validate_svm_dest_address(address: &[u8], address_must_be_nonzero: bool) -> Result<()> {
-    require_eq!(address.len(), 32, FeeQuoterError::InvalidSVMAddress);
-    require!(
-        !address_must_be_nonzero || address.iter().any(|b| *b != 0),
-        FeeQuoterError::InvalidSVMAddress
-    );
-
-    Pubkey::try_from_slice(address)
-        .map(|_| ())
-        .map_err(|_| FeeQuoterError::InvalidSVMAddress.into())
-}
-
 // process_extra_args returns serialized extraArgs, gas_limit, allow_out_of_order_execution
 // it calls the chain-specific extra args validation logic
 pub fn process_extra_args(
@@ -222,6 +187,7 @@ pub mod tests {
     use crate::{SVMTokenAmount, TimestampedPackedU224};
     use anchor_lang::solana_program::pubkey::Pubkey;
     use anchor_spl::token::spl_token::native_mint;
+    use ethnum::U256;
 
     #[test]
     fn message_not_validated_for_disabled_destination_chain() {
@@ -276,7 +242,7 @@ pub mod tests {
             assert_eq!(
                 validate_svm2any(&message, &sample_dest_chain(), &sample_billing_config())
                     .unwrap_err(),
-                FeeQuoterError::InvalidEVMAddress.into()
+                CommonCcipError::InvalidEVMAddress.into()
             );
         }
     }
diff --git a/chains/solana/contracts/programs/fee-quoter/src/lib.rs b/chains/solana/contracts/programs/fee-quoter/src/lib.rs
index 8f56cd5e01..c6497341e9 100644
--- a/chains/solana/contracts/programs/fee-quoter/src/lib.rs
+++ b/chains/solana/contracts/programs/fee-quoter/src/lib.rs
@@ -377,10 +377,6 @@ pub enum FeeQuoterError {
     MessageTooLarge,
     #[msg("Message contains an unsupported number of tokens")]
     UnsupportedNumberOfTokens,
-    #[msg("Invalid EVM address")]
-    InvalidEVMAddress,
-    #[msg("Invalid encoding")]
-    InvalidEncoding,
     #[msg("Invalid token price")]
     InvalidTokenPrice,
     #[msg("Stale gas price")]
@@ -399,12 +395,8 @@ pub enum FeeQuoterError {
     InvalidExtraArgsAccounts,
     #[msg("Invalid writability bitmap in extra args")]
     InvalidExtraArgsWritabilityBitmap,
-    #[msg("Invalid chain family selector")]
-    InvalidChainFamilySelector,
     #[msg("Invalid token receiver")]
     InvalidTokenReceiver,
-    #[msg("Invalid SVM address")]
-    InvalidSVMAddress,
     #[msg("The caller is not an authorized price updater")]
     UnauthorizedPriceUpdater,
     #[msg("Minimum token transfer fee exceeds maximum")]
diff --git a/chains/solana/contracts/programs/fee-quoter/src/messages.rs b/chains/solana/contracts/programs/fee-quoter/src/messages.rs
index 2c8c22ddf5..d10121f6c2 100644
--- a/chains/solana/contracts/programs/fee-quoter/src/messages.rs
+++ b/chains/solana/contracts/programs/fee-quoter/src/messages.rs
@@ -2,10 +2,6 @@ use anchor_lang::prelude::*;
 
 use crate::{extra_args::GenericExtraArgsV2, DestChainConfig};
 
-// https://github.com/smartcontractkit/chainlink/blob/ff8a597fd9df653f8967427498eaa5a04b19febb/contracts/src/v0.8/ccip/libraries/Internal.sol#L276
-pub const CHAIN_FAMILY_SELECTOR_EVM: u32 = 0x2812d52c;
-pub const CHAIN_FAMILY_SELECTOR_SVM: u32 = 0x1e10bdc4;
-
 #[derive(Clone, AnchorSerialize, AnchorDeserialize)]
 pub struct SVM2AnyMessage {
     pub receiver: Vec<u8>,
diff --git a/chains/solana/contracts/target/idl/ccip_common.json b/chains/solana/contracts/target/idl/ccip_common.json
index df2ae69f69..b390ecdab6 100644
--- a/chains/solana/contracts/target/idl/ccip_common.json
+++ b/chains/solana/contracts/target/idl/ccip_common.json
@@ -76,6 +76,26 @@
       "code": 10006,
       "name": "InvalidInputsPoolSignerAccounts",
       "msg": "Invalid pool signer account"
+    },
+    {
+      "code": 10007,
+      "name": "InvalidChainFamilySelector",
+      "msg": "Invalid chain family selector"
+    },
+    {
+      "code": 10008,
+      "name": "InvalidEncoding",
+      "msg": "Invalid encoding"
+    },
+    {
+      "code": 10009,
+      "name": "InvalidEVMAddress",
+      "msg": "Invalid EVM address"
+    },
+    {
+      "code": 10010,
+      "name": "InvalidSVMAddress",
+      "msg": "Invalid SVM address"
     }
   ]
 }
\ No newline at end of file
diff --git a/chains/solana/contracts/target/idl/fee_quoter.json b/chains/solana/contracts/target/idl/fee_quoter.json
index 55255256d4..c28cbb52e7 100644
--- a/chains/solana/contracts/target/idl/fee_quoter.json
+++ b/chains/solana/contracts/target/idl/fee_quoter.json
@@ -1588,91 +1588,71 @@
     },
     {
       "code": 8023,
-      "name": "InvalidEVMAddress",
-      "msg": "Invalid EVM address"
-    },
-    {
-      "code": 8024,
-      "name": "InvalidEncoding",
-      "msg": "Invalid encoding"
-    },
-    {
-      "code": 8025,
       "name": "InvalidTokenPrice",
       "msg": "Invalid token price"
     },
     {
-      "code": 8026,
+      "code": 8024,
       "name": "StaleGasPrice",
       "msg": "Stale gas price"
     },
     {
-      "code": 8027,
+      "code": 8025,
       "name": "InvalidInputsMissingTokenConfig",
       "msg": "Inputs are missing token configuration"
     },
     {
-      "code": 8028,
+      "code": 8026,
       "name": "MessageFeeTooHigh",
       "msg": "Message fee is too high"
     },
     {
-      "code": 8029,
+      "code": 8027,
       "name": "MessageGasLimitTooHigh",
       "msg": "Message gas limit too high"
     },
     {
-      "code": 8030,
+      "code": 8028,
       "name": "ExtraArgOutOfOrderExecutionMustBeTrue",
       "msg": "Extra arg out of order execution must be true"
     },
     {
-      "code": 8031,
+      "code": 8029,
       "name": "InvalidExtraArgsTag",
       "msg": "Invalid extra args tag"
     },
     {
-      "code": 8032,
+      "code": 8030,
       "name": "InvalidExtraArgsAccounts",
       "msg": "Invalid amount of accounts in extra args"
     },
     {
-      "code": 8033,
+      "code": 8031,
       "name": "InvalidExtraArgsWritabilityBitmap",
       "msg": "Invalid writability bitmap in extra args"
     },
     {
-      "code": 8034,
-      "name": "InvalidChainFamilySelector",
-      "msg": "Invalid chain family selector"
-    },
-    {
-      "code": 8035,
+      "code": 8032,
       "name": "InvalidTokenReceiver",
       "msg": "Invalid token receiver"
     },
     {
-      "code": 8036,
-      "name": "InvalidSVMAddress",
-      "msg": "Invalid SVM address"
-    },
-    {
-      "code": 8037,
+      "code": 8033,
       "name": "UnauthorizedPriceUpdater",
       "msg": "The caller is not an authorized price updater"
     },
     {
-      "code": 8038,
+      "code": 8034,
       "name": "InvalidTokenTransferFeeMaxMin",
       "msg": "Minimum token transfer fee exceeds maximum"
     },
     {
-      "code": 8039,
+      "code": 8035,
       "name": "InvalidTokenTransferFeeDestBytesOverhead",
       "msg": "Insufficient dest bytes overhead on transfer fee config"
     },
     {
-      "code": 8040,
+      "code": 8036,
       "name": "InvalidCodeVersion",
       "msg": "Invalid code version"
     }
diff --git a/chains/solana/contracts/target/types/ccip_common.ts b/chains/solana/contracts/target/types/ccip_common.ts
index 66d0ca2b18..8fecea220e 100644
--- a/chains/solana/contracts/target/types/ccip_common.ts
+++ b/chains/solana/contracts/target/types/ccip_common.ts
@@ -76,6 +76,26 @@ export type CcipCommon = {
       "code": 10006,
       "name": "InvalidInputsPoolSignerAccounts",
       "msg": "Invalid pool signer account"
+    },
+    {
+      "code": 10007,
+      "name": "InvalidChainFamilySelector",
+      "msg": "Invalid chain family selector"
+    },
+    {
+      "code": 10008,
+      "name": "InvalidEncoding",
+      "msg": "Invalid encoding"
+    },
+    {
+      "code": 10009,
+      "name": "InvalidEVMAddress",
+      "msg": "Invalid EVM address"
+    },
+    {
+      "code": 10010,
+      "name": "InvalidSVMAddress",
+      "msg": "Invalid SVM address"
     }
   ]
 };
@@ -158,6 +178,26 @@ export const IDL: CcipCommon = {
       "code": 10006,
       "name": "InvalidInputsPoolSignerAccounts",
       "msg": "Invalid pool signer account"
+    },
+    {
+      "code": 10007,
+      "name": "InvalidChainFamilySelector",
+      "msg": "Invalid chain family selector"
+    },
+    {
+      "code": 10008,
+      "name": "InvalidEncoding",
+      "msg": "Invalid encoding"
+    },
+    {
+      "code": 10009,
+      "name": "InvalidEVMAddress",
+      "msg": "Invalid EVM address"
+    },
+    {
+      "code": 10010,
+      "name": "InvalidSVMAddress",
+      "msg": "Invalid SVM address"
     }
   ]
 };
diff --git a/chains/solana/contracts/target/types/fee_quoter.ts b/chains/solana/contracts/target/types/fee_quoter.ts
index 769a7e5504..51d328a067 100644
--- a/chains/solana/contracts/target/types/fee_quoter.ts
+++ b/chains/solana/contracts/target/types/fee_quoter.ts
@@ -1588,91 +1588,71 @@ export type FeeQuoter = {
     },
     {
       "code": 8023,
-      "name": "InvalidEVMAddress",
-      "msg": "Invalid EVM address"
-    },
-    {
-      "code": 8024,
-      "name": "InvalidEncoding",
-      "msg": "Invalid encoding"
-    },
-    {
-      "code": 8025,
       "name": "InvalidTokenPrice",
       "msg": "Invalid token price"
     },
     {
-      "code": 8026,
+      "code": 8024,
       "name": "StaleGasPrice",
       "msg": "Stale gas price"
     },
     {
-      "code": 8027,
+      "code": 8025,
       "name": "InvalidInputsMissingTokenConfig",
       "msg": "Inputs are missing token configuration"
     },
     {
-      "code": 8028,
+      "code": 8026,
       "name": "MessageFeeTooHigh",
       "msg": "Message fee is too high"
     },
     {
-      "code": 8029,
+      "code": 8027,
       "name": "MessageGasLimitTooHigh",
       "msg": "Message gas limit too high"
     },
     {
-      "code": 8030,
+      "code": 8028,
       "name": "ExtraArgOutOfOrderExecutionMustBeTrue",
       "msg": "Extra arg out of order execution must be true"
     },
     {
-      "code": 8031,
+      "code": 8029,
       "name": "InvalidExtraArgsTag",
       "msg": "Invalid extra args tag"
     },
     {
-      "code": 8032,
+      "code": 8030,
       "name": "InvalidExtraArgsAccounts",
       "msg": "Invalid amount of accounts in extra args"
     },
     {
-      "code": 8033,
+      "code": 8031,
       "name": "InvalidExtraArgsWritabilityBitmap",
       "msg": "Invalid writability bitmap in extra args"
     },
     {
-      "code": 8034,
-      "name": "InvalidChainFamilySelector",
-      "msg": "Invalid chain family selector"
-    },
-    {
-      "code": 8035,
+      "code": 8032,
       "name": "InvalidTokenReceiver",
       "msg": "Invalid token receiver"
     },
     {
-      "code": 8036,
-      "name": "InvalidSVMAddress",
-      "msg": "Invalid SVM address"
-    },
-    {
-      "code": 8037,
+      "code": 8033,
       "name": "UnauthorizedPriceUpdater",
       "msg": "The caller is not an authorized price updater"
     },
     {
-      "code": 8038,
+      "code": 8034,
       "name": "InvalidTokenTransferFeeMaxMin",
       "msg": "Minimum token transfer fee exceeds maximum"
     },
     {
-      "code": 8039,
+      "code": 8035,
       "name": "InvalidTokenTransferFeeDestBytesOverhead",
       "msg": "Insufficient dest bytes overhead on transfer fee config"
     },
     {
-      "code": 8040,
+      "code": 8036,
       "name": "InvalidCodeVersion",
       "msg": "Invalid code version"
     }
@@ -3269,91 +3249,71 @@ export const IDL: FeeQuoter = {
     },
     {
       "code": 8023,
-      "name": "InvalidEVMAddress",
-      "msg": "Invalid EVM address"
-    },
-    {
-      "code": 8024,
-      "name": "InvalidEncoding",
-      "msg": "Invalid encoding"
-    },
-    {
-      "code": 8025,
       "name": "InvalidTokenPrice",
       "msg": "Invalid token price"
     },
     {
-      "code": 8026,
+      "code": 8024,
       "name": "StaleGasPrice",
       "msg": "Stale gas price"
     },
     {
-      "code": 8027,
+      "code": 8025,
       "name": "InvalidInputsMissingTokenConfig",
       "msg": "Inputs are missing token configuration"
     },
     {
-      "code": 8028,
+      "code": 8026,
       "name": "MessageFeeTooHigh",
       "msg": "Message fee is too high"
     },
     {
-      "code": 8029,
+      "code": 8027,
       "name": "MessageGasLimitTooHigh",
       "msg": "Message gas limit too high"
     },
     {
-      "code": 8030,
+      "code": 8028,
       "name": "ExtraArgOutOfOrderExecutionMustBeTrue",
       "msg": "Extra arg out of order execution must be true"
     },
     {
-      "code": 8031,
+      "code": 8029,
       "name": "InvalidExtraArgsTag",
       "msg": "Invalid extra args tag"
     },
     {
-      "code": 8032,
+      "code": 8030,
       "name": "InvalidExtraArgsAccounts",
       "msg": "Invalid amount of accounts in extra args"
     },
     {
-      "code": 8033,
+      "code": 8031,
       "name": "InvalidExtraArgsWritabilityBitmap",
       "msg": "Invalid writability bitmap in extra args"
     },
     {
-      "code": 8034,
-      "name": "InvalidChainFamilySelector",
-      "msg": "Invalid chain family selector"
-    },
-    {
-      "code": 8035,
+      "code": 8032,
       "name": "InvalidTokenReceiver",
       "msg": "Invalid token receiver"
     },
     {
-      "code": 8036,
-      "name": "InvalidSVMAddress",
-      "msg": "Invalid SVM address"
-    },
-    {
-      "code": 8037,
+      "code": 8033,
       "name": "UnauthorizedPriceUpdater",
       "msg": "The caller is not an authorized price updater"
     },
     {
-      "code": 8038,
+      "code": 8034,
       "name": "InvalidTokenTransferFeeMaxMin",
       "msg": "Minimum token transfer fee exceeds maximum"
     },
     {
-      "code": 8039,
+      "code": 8035,
       "name": "InvalidTokenTransferFeeDestBytesOverhead",
       "msg": "Insufficient dest bytes overhead on transfer fee config"
     },
     {
-      "code": 8040,
+      "code": 8036,
       "name": "InvalidCodeVersion",
       "msg": "Invalid code version"
     }
diff --git a/chains/solana/contracts/tests/ccip/ccip_router_test.go b/chains/solana/contracts/tests/ccip/ccip_router_test.go
index b5d20217d3..ee62d46e2c 100644
--- a/chains/solana/contracts/tests/ccip/ccip_router_test.go
+++ b/chains/solana/contracts/tests/ccip/ccip_router_test.go
@@ -2933,20 +2933,20 @@ func TestCCIPRouter(t *testing.T) {
 		t.Run("RemoteConfig", func(t *testing.T) {
 			for _, selector := range []uint64{config.EvmChainSelector, config.SvmChainSelector} {
 				ix0, err := test_token_pool.NewInitChainRemoteConfigInstruction(selector, token0.Mint, base_token_pool.RemoteConfig{
-					TokenAddress: base_token_pool.RemoteAddress{Address: []byte{1, 2, 3}},
+					TokenAddress: base_token_pool.RemoteAddress{Address: config.EVMToken0AddressBytes},
 					Decimals:     evmToken0Decimals,
 				}, token0.PoolConfig, token0.Chain[selector], token0PoolAdmin.PublicKey(), solana.SystemProgramID).ValidateAndBuild()
 				require.NoError(t, err)
 				PoolAddresses := []base_token_pool.RemoteAddress{{Address: []byte{4, 5, 6}}}
 				ix1, err := test_token_pool.NewInitChainRemoteConfigInstruction(selector, token1.Mint, base_token_pool.RemoteConfig{
 					PoolAddresses: []base_token_pool.RemoteAddress{},
-					TokenAddress:  base_token_pool.RemoteAddress{Address: []byte{4, 5, 6}},
+					TokenAddress:  base_token_pool.RemoteAddress{Address: config.EVMToken1AddressBytes},
 					Decimals:      evmToken1Decimals,
 				}, token1.PoolConfig, token1.Chain[selector], token1PoolAdmin.PublicKey(), solana.SystemProgramID).ValidateAndBuild()
 				require.NoError(t, err)
 				ix2, err := test_token_pool.NewInitChainRemoteConfigInstruction(selector, token2.Mint, base_token_pool.RemoteConfig{
 					PoolAddresses: []base_token_pool.RemoteAddress{},
-					TokenAddress:  base_token_pool.RemoteAddress{Address: []byte{4, 5, 6}},
+					TokenAddress:  base_token_pool.RemoteAddress{Address: config.EVMToken2AddressBytes},
 					Decimals:      evmToken2Decimals,
 				}, token2.PoolConfig, token2.Chain[selector], token2PoolAdmin.PublicKey(), solana.SystemProgramID).ValidateAndBuild()
 				require.NoError(t, err)
@@ -4349,7 +4349,7 @@ func TestCCIPRouter(t *testing.T) {
 				// decimals.
 				require.Equal(t, tokens.ToLittleEndianU256(3633302000000000), ccipMessageSentEvent.Message.FeeValueJuels.LeBytes)
 				require.Equal(t, token0.PoolConfig, ta.SourcePoolAddress)
-				require.Equal(t, []byte{1, 2, 3}, ta.DestTokenAddress)
+				require.Equal(t, config.EVMToken0AddressBytes, ta.DestTokenAddress)
 				// Local decimals are encoded in the extra data. By default, 9 decimals in Solana.
 				expectedExtraData := make([]byte, 32)
 				expectedExtraData[31] = token0Decimals
diff --git a/chains/solana/contracts/tests/config/ccip_config.go b/chains/solana/contracts/tests/config/ccip_config.go
index 7faa93eca8..acf599710d 100644
--- a/chains/solana/contracts/tests/config/ccip_config.go
+++ b/chains/solana/contracts/tests/config/ccip_config.go
@@ -35,6 +35,11 @@ var (
 	Empty24Byte                     = [24]byte{}
 	MaxSignersAndTransmitters       = 16
 
+	// valid evm addresses(over precompiled space) - for remote token addresses
+	EVMToken0AddressBytes = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0} // 1024
+	EVMToken1AddressBytes = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
+	EVMToken2AddressBytes = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1}
+
 	// chain selectors
 	SvmChainSelector uint64 = 15
 	EvmChainSelector uint64 = 21


Reviews:
user: login: matYang
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T15:27:48Z
user: login: matYang
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T15:41:44Z
user: login: matYang
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T15:44:39Z
user: login: matYang
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-17T16:14:09Z

Review comments:
diff_hunk: @@ -303,3 +305,37 @@ pub mod token_admin_registry_writable {
         }
     }
 }
+
+// address validation helpers based on the chain family selector
+pub fn validate_evm_address(address: &[u8]) -> Result<()> {
+    const PRECOMPILE_SPACE: u32 = 1024;
path: chains/solana/contracts/programs/ccip-common/src/v1.rs
user: login: matYang
body: seems like this constant should be brought out of function scope as `EVM_PRECOMPILE_SPACE`
created_at: 2025-04-17T15:27:48Z
author_association: COLLABORATOR
diff_hunk: @@ -35,6 +35,11 @@ var (
 	Empty24Byte                     = [24]byte{}
 	MaxSignersAndTransmitters       = 16
 
+	// valid evm addresses(over precompiled space) - for remote token addresses
+	EVMToken0AddressBytes = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0} // 1024
path: chains/solana/contracts/tests/config/ccip_config.go
user: login: matYang
body: nit: can we hard code hex addr instead and convert them to bytes in code
created_at: 2025-04-17T15:41:44Z
author_association: COLLABORATOR
diff_hunk: @@ -222,6 +187,8 @@ pub mod tests {
     use crate::{SVMTokenAmount, TimestampedPackedU224};
     use anchor_lang::solana_program::pubkey::Pubkey;
     use anchor_spl::token::spl_token::native_mint;
+    use ccip_common::{CommonCcipError, CHAIN_FAMILY_SELECTOR_EVM, CHAIN_FAMILY_SELECTOR_SVM};
+    use ethnum::U256;
path: chains/solana/contracts/programs/fee-quoter/src/instructions/v1/messages.rs
user: login: matYang
body: nit: do we need this here?
created_at: 2025-04-17T15:44:39Z
author_association: COLLABORATOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-17T16:05:07Z
body: | Metric | `solana/dest-token-addr-validation` | `main` |
|--|--|--|
| **Coverage** | 72.3% | 72.2% |
author_association: NONE