diff --git a/deployment/ccip/changeset/cs_add_lane_test.go b/deployment/ccip/changeset/cs_add_lane_test.go index 5be37122a51..8f395ea0ec9 100644 --- a/deployment/ccip/changeset/cs_add_lane_test.go +++ b/deployment/ccip/changeset/cs_add_lane_test.go @@ -44,3 +44,23 @@ func TestAddLanesWithTestRouter(t *testing.T) { }] = []uint64{msgSentEvent.SequenceNumber} testhelpers.ConfirmExecWithSeqNrsForAll(t, e.Env, state, expectedSeqNumExec, startBlocks) } + +// dev is on going for sending request between solana and evm chains +// this test is there to ensure addLane works between solana and evm chains +func TestAddLanesWithSolana(t *testing.T) { + t.Parallel() + e, _ := testhelpers.NewMemoryEnvironment(t, testhelpers.WithSolChains(1)) + // Here we have CR + nodes set up, but no CCIP contracts deployed. + state, err := changeset.LoadOnchainState(e.Env) + require.NoError(t, err) + + evmSelectors := e.Env.AllChainSelectors() + chain1, chain2 := evmSelectors[0], evmSelectors[1] + solSelectors := e.Env.AllChainSelectorsSolana() + solChain := solSelectors[0] + testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, chain1, solChain, true) + // AddLaneWithDefaultPricesAndFeeQuoterConfig involves calling AddRemoteChainToSolana + // which adds chain1 to solana + // so we can not call AddRemoteChainToSolana again with chain1 again, hence using chain2 below + testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, solChain, chain2, true) +} diff --git a/deployment/ccip/changeset/cs_ccip_home.go b/deployment/ccip/changeset/cs_ccip_home.go index 0922b526e5a..84d70d6f6f0 100644 --- a/deployment/ccip/changeset/cs_ccip_home.go +++ b/deployment/ccip/changeset/cs_ccip_home.go @@ -307,7 +307,7 @@ func (p PromoteCandidateChangesetConfig) Validate(e deployment.Environment) (map if err := deployment.IsValidChainSelector(chainSelector); err != nil { return nil, fmt.Errorf("don chain selector invalid: %w", err) } - if err := state.ValidateOffRamp(chainSelector); err != nil { + if err := state.ValidateRamp(chainSelector, OffRamp); err != nil { return nil, err } @@ -461,7 +461,7 @@ func (p SetCandidatePluginInfo) Validate(state CCIPOnChainState, homeChain uint6 if err := deployment.IsValidChainSelector(chainSelector); err != nil { return fmt.Errorf("don chain selector invalid: %w", err) } - if err := state.ValidateOffRamp(chainSelector); err != nil { + if err := state.ValidateRamp(chainSelector, OffRamp); err != nil { return err } if p.PluginType == types.PluginTypeCCIPCommit && params.CommitOffChainConfig == nil { diff --git a/deployment/ccip/changeset/cs_chain_contracts.go b/deployment/ccip/changeset/cs_chain_contracts.go index c7f1a47f7f6..e176851088e 100644 --- a/deployment/ccip/changeset/cs_chain_contracts.go +++ b/deployment/ccip/changeset/cs_chain_contracts.go @@ -1057,11 +1057,9 @@ func (cfg UpdateOffRampSourcesConfig) Validate(e deployment.Environment, state C if source == chainSel { return fmt.Errorf("cannot update offramp source to the same chain %d", source) } - sourceChain := state.Chains[source] - // Source chain must have the onramp deployed. - // Note this also validates the specified source selector. - if sourceChain.OnRamp == nil { - return fmt.Errorf("missing onramp for source %d", source) + + if err := state.ValidateRamp(source, OnRamp); err != nil { + return err } } } @@ -1095,13 +1093,22 @@ func UpdateOffRampSourcesChangeset(e deployment.Environment, cfg UpdateOffRampSo } else { router = state.Chains[chainSel].Router.Address() } - onRamp := state.Chains[source].OnRamp + sourceChainFamily, _ := chain_selectors.GetSelectorFamily(source) + + onRampBytes := []byte{} + // can ignore err as validation checks for nil addresses + if sourceChainFamily == chain_selectors.FamilyEVM { + onRampBytes, _ = state.Chains[source].OnRampBytes() + } else if sourceChainFamily == chain_selectors.FamilySolana { + onRampBytes, _ = state.SolChains[source].OnRampBytes() + } + args = append(args, offramp.OffRampSourceChainConfigArgs{ SourceChainSelector: source, Router: router, IsEnabled: update.IsEnabled, // TODO: how would this work when the onRamp is nonEVM? - OnRamp: common.LeftPadBytes(onRamp.Address().Bytes(), 32), + OnRamp: common.LeftPadBytes(onRampBytes, 32), IsRMNVerificationDisabled: update.IsRMNVerificationDisabled, }) } @@ -1198,11 +1205,8 @@ func (cfg UpdateRouterRampsConfig) Validate(e deployment.Environment, state CCIP if source == chainSel { return fmt.Errorf("cannot update offramp source to the same chain %d", source) } - sourceChain := state.Chains[source] - // Source chain must have the onramp deployed. - // Note this also validates the specified source selector. - if sourceChain.OnRamp == nil { - return fmt.Errorf("missing onramp for source %d", source) + if err := state.ValidateRamp(source, OnRamp); err != nil { + return err } } for destination := range update.OnRampUpdates { @@ -1213,7 +1217,7 @@ func (cfg UpdateRouterRampsConfig) Validate(e deployment.Environment, state CCIP if destination == chainSel { return fmt.Errorf("cannot update onRamp dest to the same chain %d", destination) } - if err := state.ValidateOffRamp(destination); err != nil { + if err := state.ValidateRamp(destination, OffRamp); err != nil { return err } } diff --git a/deployment/ccip/changeset/solana/cs_add_remote_chain.go b/deployment/ccip/changeset/solana/cs_add_remote_chain.go index 2053ff32c0d..06b7b849eb0 100644 --- a/deployment/ccip/changeset/solana/cs_add_remote_chain.go +++ b/deployment/ccip/changeset/solana/cs_add_remote_chain.go @@ -6,6 +6,7 @@ import ( "fmt" "strconv" + "github.com/ethereum/go-ethereum/common" "github.com/gagliardetto/solana-go" solOffRamp "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_offramp" @@ -71,8 +72,19 @@ func (cfg AddRemoteChainToSolanaConfig) Validate(e deployment.Environment) error if remote == routerConfigAccount.SvmChainSelector { return fmt.Errorf("cannot add remote chain %d with same chain selector as current chain %d", remote, cfg.ChainSelector) } + if err := state.ValidateRamp(remote, cs.OnRamp); err != nil { + return err + } + routerDestChainPDA, err := solState.FindDestChainStatePDA(remote, chainState.Router) + if err != nil { + return fmt.Errorf("failed to find dest chain state pda for remote chain %d: %w", remote, err) + } + var destChainStateAccount solRouter.DestChain + err = chain.GetAccountDataBorshInto(context.Background(), routerDestChainPDA, &destChainStateAccount) + if err == nil { + return fmt.Errorf("remote %d is already configured on solana chain %d", remote, cfg.ChainSelector) + } } - return nil } @@ -111,17 +123,15 @@ func doAddRemoteChainToSolana( var onRampBytes [64]byte // already verified, skipping errcheck remoteChainFamily, _ := chainsel.GetSelectorFamily(remoteChainSel) + var addressBytes []byte switch remoteChainFamily { case chainsel.FamilySolana: - return fmt.Errorf("support for solana chain as remote chain is not implemented yet %d", remoteChainSel) + addressBytes, _ = s.SolChains[remoteChainSel].OnRampBytes() case chainsel.FamilyEVM: - onRampAddress := s.Chains[remoteChainSel].OnRamp.Address().String() - if onRampAddress == "" { - return fmt.Errorf("onramp address not found for chain %d", remoteChainSel) - } - addressBytes := []byte(onRampAddress) - copy(onRampBytes[:], addressBytes) + addressBytes, _ = s.Chains[remoteChainSel].OnRampBytes() } + addressBytes = common.LeftPadBytes(addressBytes, 64) + copy(onRampBytes[:], addressBytes) // verified while loading state fqDestChainPDA, _, _ := solState.FindFqDestChainPDA(remoteChainSel, feeQuoterID) diff --git a/deployment/ccip/changeset/solana/cs_billing.go b/deployment/ccip/changeset/solana/cs_billing.go index af1518ddd44..c57e04b4bca 100644 --- a/deployment/ccip/changeset/solana/cs_billing.go +++ b/deployment/ccip/changeset/solana/cs_billing.go @@ -68,7 +68,6 @@ func AddBillingToken(e deployment.Environment, cfg BillingTokenConfig) (deployme tokenPubKey := solana.MustPublicKeyFromBase58(cfg.TokenPubKey) // verified tokenprogramID, _ := GetTokenProgramID(cfg.TokenProgramName) - // TODO: add this to offramp address lookup table tokenBillingPDA, _, _ := solState.FindFqBillingTokenConfigPDA(tokenPubKey, chainState.FeeQuoter) // addressing errcheck in the next PR diff --git a/deployment/ccip/changeset/solana/cs_chain_contracts.go b/deployment/ccip/changeset/solana/cs_chain_contracts.go index a5366ff5842..cd54f7d76e0 100644 --- a/deployment/ccip/changeset/solana/cs_chain_contracts.go +++ b/deployment/ccip/changeset/solana/cs_chain_contracts.go @@ -22,6 +22,7 @@ var _ deployment.ChangeSet[BillingTokenForRemoteChainConfig] = AddBillingTokenFo var _ deployment.ChangeSet[RegisterTokenAdminRegistryConfig] = RegisterTokenAdminRegistry var _ deployment.ChangeSet[TransferAdminRoleTokenAdminRegistryConfig] = TransferAdminRoleTokenAdminRegistry var _ deployment.ChangeSet[AcceptAdminRoleTokenAdminRegistryConfig] = AcceptAdminRoleTokenAdminRegistry +var _ deployment.ChangeSet[SetFeeAggregatorConfig] = SetFeeAggregator // HELPER FUNCTIONS // GetTokenProgramID returns the program ID for the given token program name diff --git a/deployment/ccip/changeset/solana/cs_chain_contracts_test.go b/deployment/ccip/changeset/solana/cs_chain_contracts_test.go index b55bb413804..5734604e730 100644 --- a/deployment/ccip/changeset/solana/cs_chain_contracts_test.go +++ b/deployment/ccip/changeset/solana/cs_chain_contracts_test.go @@ -121,65 +121,72 @@ func TestAddTokenPool(t *testing.T) { state, err := ccipChangeset.LoadOnchainStateSolana(e) require.NoError(t, err) - tokenAddress := state.SolChains[solChain].SPL2022Tokens[0] - - // TODO: can test this with solana.SolMint as well (WSOL) - // https://smartcontract-it.atlassian.net/browse/INTAUTO-440 - e, err = commonchangeset.Apply(t, e, nil, - commonchangeset.Configure( - deployment.CreateLegacyChangeSet(changeset_solana.AddTokenPool), - changeset_solana.TokenPoolConfig{ - ChainSelector: solChain, - TokenPubKey: tokenAddress.String(), - TokenProgramName: deployment.SPL2022Tokens, - PoolType: solTestTokenPool.LockAndRelease_PoolType, - // this works for testing, but if we really want some other authority we need to pass in a private key for signing purposes - Authority: e.SolChains[solChain].DeployerKey.PublicKey().String(), - }, - ), - commonchangeset.Configure( - deployment.CreateLegacyChangeSet(changeset_solana.SetupTokenPoolForRemoteChain), - changeset_solana.RemoteChainTokenPoolConfig{ - SolChainSelector: solChain, - RemoteChainSelector: evmChain, - SolTokenPubKey: tokenAddress.String(), - RemoteConfig: solTestTokenPool.RemoteConfig{ - // TODO:this can be potentially read from the state if we are given the token symbol - PoolAddresses: []solTestTokenPool.RemoteAddress{{Address: []byte{1, 2, 3}}}, - TokenAddress: solTestTokenPool.RemoteAddress{Address: []byte{4, 5, 6}}, - Decimals: 9, - }, - InboundRateLimit: solTestTokenPool.RateLimitConfig{ - Enabled: true, - Capacity: uint64(1000), - Rate: 1, + newTokenAddress := state.SolChains[solChain].SPL2022Tokens[0] + + remoteConfig := solTestTokenPool.RemoteConfig{ + PoolAddresses: []solTestTokenPool.RemoteAddress{{Address: []byte{1, 2, 3}}}, + TokenAddress: solTestTokenPool.RemoteAddress{Address: []byte{4, 5, 6}}, + Decimals: 9, + } + inboundConfig := solTestTokenPool.RateLimitConfig{ + Enabled: true, + Capacity: uint64(1000), + Rate: 1, + } + outboundConfig := solTestTokenPool.RateLimitConfig{ + Enabled: false, + Capacity: 0, + Rate: 0, + } + + tokenMap := map[string]solana.PublicKey{ + deployment.SPL2022Tokens: newTokenAddress, + deployment.SPLTokens: state.SolChains[solChain].WSOL, + } + + for tokenProgramName, tokenAddress := range tokenMap { + e, err = commonchangeset.Apply(t, e, nil, + commonchangeset.Configure( + deployment.CreateLegacyChangeSet(changeset_solana.AddTokenPool), + changeset_solana.TokenPoolConfig{ + ChainSelector: solChain, + TokenPubKey: tokenAddress.String(), + TokenProgramName: tokenProgramName, + PoolType: solTestTokenPool.LockAndRelease_PoolType, + // this works for testing, but if we really want some other authority we need to pass in a private key for signing purposes + Authority: e.SolChains[solChain].DeployerKey.PublicKey().String(), }, - OutboundRateLimit: solTestTokenPool.RateLimitConfig{ - Enabled: false, - Capacity: 0, - Rate: 0, + ), + commonchangeset.Configure( + deployment.CreateLegacyChangeSet(changeset_solana.SetupTokenPoolForRemoteChain), + changeset_solana.RemoteChainTokenPoolConfig{ + SolChainSelector: solChain, + RemoteChainSelector: evmChain, + SolTokenPubKey: tokenAddress.String(), + RemoteConfig: remoteConfig, + InboundRateLimit: inboundConfig, + OutboundRateLimit: outboundConfig, }, - }, - ), - ) - require.NoError(t, err) - - // test AddTokenPool results - poolConfigPDA, err := solTokenUtil.TokenPoolConfigAddress(tokenAddress, state.SolChains[solChain].TokenPool) - require.NoError(t, err) - var configAccount solTestTokenPool.State - err = e.SolChains[solChain].GetAccountDataBorshInto(ctx, poolConfigPDA, &configAccount) - require.NoError(t, err) - require.Equal(t, solTestTokenPool.LockAndRelease_PoolType, configAccount.PoolType) - require.Equal(t, tokenAddress, configAccount.Config.Mint) - // try minting after this and see if the pool or the deployer key is the authority - - // test SetupTokenPoolForRemoteChain results - remoteChainConfigPDA, _, _ := solTokenUtil.TokenPoolChainConfigPDA(evmChain, tokenAddress, state.SolChains[solChain].TokenPool) - var remoteChainConfigAccount solTestTokenPool.ChainConfig - err = e.SolChains[solChain].GetAccountDataBorshInto(ctx, remoteChainConfigPDA, &remoteChainConfigAccount) - require.NoError(t, err) - require.Equal(t, uint8(9), remoteChainConfigAccount.Base.Remote.Decimals) + ), + ) + require.NoError(t, err) + + // test AddTokenPool results + poolConfigPDA, err := solTokenUtil.TokenPoolConfigAddress(tokenAddress, state.SolChains[solChain].TokenPool) + require.NoError(t, err) + var configAccount solTestTokenPool.State + err = e.SolChains[solChain].GetAccountDataBorshInto(ctx, poolConfigPDA, &configAccount) + require.NoError(t, err) + require.Equal(t, solTestTokenPool.LockAndRelease_PoolType, configAccount.PoolType) + require.Equal(t, tokenAddress, configAccount.Config.Mint) + + // test SetupTokenPoolForRemoteChain results + remoteChainConfigPDA, _, _ := solTokenUtil.TokenPoolChainConfigPDA(evmChain, tokenAddress, state.SolChains[solChain].TokenPool) + var remoteChainConfigAccount solTestTokenPool.ChainConfig + err = e.SolChains[solChain].GetAccountDataBorshInto(ctx, remoteChainConfigPDA, &remoteChainConfigAccount) + require.NoError(t, err) + require.Equal(t, uint8(9), remoteChainConfigAccount.Base.Remote.Decimals) + } } func TestBilling(t *testing.T) { @@ -345,7 +352,6 @@ func TestTokenAdminRegistry(t *testing.T) { require.Equal(t, tokenAdminRegistryAdminPrivKey.PublicKey(), tokenAdminRegistryAccount.Administrator) require.Equal(t, solana.PublicKey{}, tokenAdminRegistryAccount.PendingAdministrator) - // TODO: transfer and accept is breaking newTokenAdminRegistryAdminPrivKey, _ := solana.NewRandomPrivateKey() e, err = commonchangeset.Apply(t, e, nil, commonchangeset.Configure( diff --git a/deployment/ccip/changeset/solana/cs_deploy_chain.go b/deployment/ccip/changeset/solana/cs_deploy_chain.go index 2623e69a9c5..6badbd6aaba 100644 --- a/deployment/ccip/changeset/solana/cs_deploy_chain.go +++ b/deployment/ccip/changeset/solana/cs_deploy_chain.go @@ -9,6 +9,7 @@ import ( "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" + cs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" solBinary "github.com/gagliardetto/binary" solRpc "github.com/gagliardetto/solana-go/rpc" @@ -102,8 +103,9 @@ func initializeRouter( externalTokenPoolsSignerPDA, _, _ := solState.FindExternalTokenPoolsSignerPDA(ccipRouterProgram) instruction, err := solRouter.NewInitializeInstruction( - chain.Selector, // chain selector - solana.PublicKey{}, // fee aggregator (TODO: changeset to set the fee aggregator) + chain.Selector, // chain selector + // this is where the fee aggregator address would go (but have written a separate changeset to set that) + solana.PublicKey{}, feeQuoterAddress, linkTokenAddress, // link token mint routerConfigPDA, @@ -428,3 +430,73 @@ func deployChainContractsSolana( return nil } + +type SetFeeAggregatorConfig struct { + ChainSelector uint64 + FeeAggregator string +} + +func (cfg SetFeeAggregatorConfig) Validate(e deployment.Environment) error { + state, err := cs.LoadOnchainState(e) + if err != nil { + return fmt.Errorf("failed to load onchain state: %w", err) + } + chainState, chainExists := state.SolChains[cfg.ChainSelector] + if !chainExists { + return fmt.Errorf("chain %d not found in existing state", cfg.ChainSelector) + } + chain := e.SolChains[cfg.ChainSelector] + + if err := validateRouterConfig(chain, chainState); err != nil { + return err + } + + // Validate fee aggregator address is valid + if _, err := solana.PublicKeyFromBase58(cfg.FeeAggregator); err != nil { + return fmt.Errorf("invalid fee aggregator address: %w", err) + } + + if chainState.FeeAggregator.Equals(solana.MustPublicKeyFromBase58(cfg.FeeAggregator)) { + return fmt.Errorf("fee aggregator %s is already set on chain %d", cfg.FeeAggregator, cfg.ChainSelector) + } + + return nil +} + +func SetFeeAggregator(e deployment.Environment, cfg SetFeeAggregatorConfig) (deployment.ChangesetOutput, error) { + if err := cfg.Validate(e); err != nil { + return deployment.ChangesetOutput{}, err + } + + state, _ := cs.LoadOnchainState(e) + chainState := state.SolChains[cfg.ChainSelector] + chain := e.SolChains[cfg.ChainSelector] + + feeAggregatorPubKey := solana.MustPublicKeyFromBase58(cfg.FeeAggregator) + routerConfigPDA, _, _ := solState.FindConfigPDA(chainState.Router) + + solRouter.SetProgramID(chainState.Router) + instruction, err := solRouter.NewUpdateFeeAggregatorInstruction( + feeAggregatorPubKey, + routerConfigPDA, + chain.DeployerKey.PublicKey(), + solana.SystemProgramID, + ).ValidateAndBuild() + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to build instruction: %w", err) + } + + if err := chain.Confirm([]solana.Instruction{instruction}); err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to confirm instructions: %w", err) + } + newAddresses := deployment.NewMemoryAddressBook() + err = newAddresses.Save(cfg.ChainSelector, cfg.FeeAggregator, deployment.NewTypeAndVersion(changeset.FeeAggregator, deployment.Version1_0_0)) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to save address: %w", err) + } + + e.Logger.Infow("Set new fee aggregator", "chain", chain.String(), "fee_aggregator", feeAggregatorPubKey.String()) + return deployment.ChangesetOutput{ + AddressBook: newAddresses, + }, nil +} diff --git a/deployment/ccip/changeset/solana/cs_deploy_chain_test.go b/deployment/ccip/changeset/solana/cs_deploy_chain_test.go index f4d4e591412..5be5006d6b4 100644 --- a/deployment/ccip/changeset/solana/cs_deploy_chain_test.go +++ b/deployment/ccip/changeset/solana/cs_deploy_chain_test.go @@ -3,12 +3,13 @@ package solana_test import ( "testing" + "github.com/gagliardetto/solana-go" "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" - "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/solana" + cs_solana "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/solana" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers" "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" "github.com/smartcontractkit/chainlink/deployment/environment/memory" @@ -48,6 +49,9 @@ func TestDeployChainContractsChangesetSolana(t *testing.T) { }) } + feeAggregatorPrivKey, _ := solana.NewRandomPrivateKey() + feeAggregatorPubKey := feeAggregatorPrivKey.PublicKey() + testhelpers.SavePreloadedSolAddresses(t, e, solChainSelectors[0]) e, err = commonchangeset.Apply(t, e, nil, commonchangeset.Configure( @@ -89,7 +93,7 @@ func TestDeployChainContractsChangesetSolana(t *testing.T) { }, ), commonchangeset.Configure( - deployment.CreateLegacyChangeSet(solana.DeployChainContractsChangesetSolana), + deployment.CreateLegacyChangeSet(cs_solana.DeployChainContractsChangesetSolana), changeset.DeployChainContractsConfig{ HomeChainSelector: homeChainSel, ContractParamsPerChain: map[uint64]changeset.ChainContractParams{ @@ -100,8 +104,16 @@ func TestDeployChainContractsChangesetSolana(t *testing.T) { }, }, ), + commonchangeset.Configure( + deployment.CreateLegacyChangeSet(cs_solana.SetFeeAggregator), + cs_solana.SetFeeAggregatorConfig{ + ChainSelector: solChainSelectors[0], + FeeAggregator: feeAggregatorPubKey.String(), + }, + ), ) require.NoError(t, err) // solana verification testhelpers.ValidateSolanaState(t, e, solChainSelectors) + } diff --git a/deployment/ccip/changeset/solana/cs_set_ocr3.go b/deployment/ccip/changeset/solana/cs_set_ocr3.go index e79c9a1239d..1dd2145b920 100644 --- a/deployment/ccip/changeset/solana/cs_set_ocr3.go +++ b/deployment/ccip/changeset/solana/cs_set_ocr3.go @@ -3,9 +3,8 @@ package solana import ( "fmt" - // "strconv" - "github.com/gagliardetto/solana-go" + chain_selectors "github.com/smartcontractkit/chain-selectors" solOffRamp "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_offramp" @@ -14,6 +13,11 @@ import ( "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" ) +const ( + OcrCommitPlugin uint8 = iota + OcrExecutePlugin +) + // SET OCR3 CONFIG func btoi(b bool) uint8 { if b { @@ -37,10 +41,14 @@ func SetOCR3ConfigSolana(e deployment.Environment, cfg cs.SetOCR3OffRampConfig) if err := cfg.Validate(e, state); err != nil { return deployment.ChangesetOutput{}, err } - solChains := state.SolChains - // cfg.RemoteChainSels will be a bunch of solana chains - // can add this in validate + for _, remote := range cfg.RemoteChainSels { + chainFamily, _ := chain_selectors.GetSelectorFamily(remote) + if chainFamily != chain_selectors.FamilySolana { + return deployment.ChangesetOutput{}, fmt.Errorf("chain %d is not a solana chain", remote) + } + } + for _, remote := range cfg.RemoteChainSels { donID, err := internal.DonIDForChain( state.Chains[cfg.HomeChainSel].CapabilityRegistry, @@ -53,12 +61,19 @@ func SetOCR3ConfigSolana(e deployment.Environment, cfg cs.SetOCR3OffRampConfig) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("failed to build set ocr3 config args: %w", err) } - // TODO: check if ocr3 has already been set - // set, err := isOCR3ConfigSetSolana(e.Logger, e.Chains[remote], state.Chains[remote].OffRamp, args) + set, err := isOCR3ConfigSetOnOffRampSolana(e, e.SolChains[remote], state.SolChains[remote], args) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to check if ocr3 config is set on offramp: %w", err) + } + if set { + e.Logger.Infof("OCR3 config already set on offramp for chain %d", remote) + continue + } + var instructions []solana.Instruction - offRampConfigPDA := solChains[remote].OffRampConfigPDA - offRampStatePDA := solChains[remote].OffRampStatePDA - solOffRamp.SetProgramID(solChains[remote].OffRamp) + offRampConfigPDA := state.SolChains[remote].OffRampConfigPDA + offRampStatePDA := state.SolChains[remote].OffRampStatePDA + solOffRamp.SetProgramID(state.SolChains[remote].OffRamp) for _, arg := range args { instruction, err := solOffRamp.NewSetOcrConfigInstruction( arg.OCRPluginType, @@ -86,3 +101,55 @@ func SetOCR3ConfigSolana(e deployment.Environment, cfg cs.SetOCR3OffRampConfig) } return deployment.ChangesetOutput{}, nil } + +func isOCR3ConfigSetOnOffRampSolana( + e deployment.Environment, + chain deployment.SolChain, + chainState cs.SolCCIPChainState, + args []internal.MultiOCR3BaseOCRConfigArgsSolana, +) (bool, error) { + var configAccount solOffRamp.Config + err := chain.GetAccountDataBorshInto(e.GetContext(), chainState.OffRampConfigPDA, &configAccount) + if err != nil { + return false, fmt.Errorf("failed to get account info: %w", err) + } + for _, newState := range args { + existingState := configAccount.Ocr3[newState.OCRPluginType] + if existingState.ConfigInfo.ConfigDigest != newState.ConfigDigest { + e.Logger.Infof("OCR3 config digest mismatch") + return false, nil + } + if existingState.ConfigInfo.F != newState.F { + e.Logger.Infof("OCR3 config F mismatch") + return false, nil + } + if existingState.ConfigInfo.IsSignatureVerificationEnabled != btoi(newState.IsSignatureVerificationEnabled) { + e.Logger.Infof("OCR3 config signature verification mismatch") + return false, nil + } + if newState.OCRPluginType == OcrCommitPlugin { + // only commit will set signers, exec doesn't need them. + if len(existingState.Signers) != len(newState.Signers) { + e.Logger.Infof("OCR3 config signers length mismatch") + return false, nil + } + for i := 0; i < len(existingState.Signers); i++ { + if existingState.Signers[i] != newState.Signers[i] { + e.Logger.Infof("OCR3 config signers mismatch") + return false, nil + } + } + } + if len(existingState.Transmitters) != len(newState.Transmitters) { + e.Logger.Infof("OCR3 config transmitters length mismatch") + return false, nil + } + for i := 0; i < len(existingState.Transmitters); i++ { + if existingState.Transmitters[i] != newState.Transmitters[i] { + e.Logger.Infof("OCR3 config transmitters mismatch") + return false, nil + } + } + } + return true, nil +} diff --git a/deployment/ccip/changeset/solana/cs_solana_token.go b/deployment/ccip/changeset/solana/cs_solana_token.go index bfce1e70da5..fb16c125944 100644 --- a/deployment/ccip/changeset/solana/cs_solana_token.go +++ b/deployment/ccip/changeset/solana/cs_solana_token.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/rpc" "github.com/smartcontractkit/chainlink/deployment" @@ -59,7 +60,6 @@ func DeploySolanaToken(e deployment.Environment, cfg DeploySolanaTokenConfig) (d if err != nil { return deployment.ChangesetOutput{}, err } - // TODO:does the mint need to be added as a signer here ? err = chain.Confirm(instructions, solCommomUtil.AddSigners(mintPrivKey)) if err != nil { e.Logger.Errorw("Failed to confirm instructions for link token deployment", "chain", chain.String(), "err", err) @@ -81,25 +81,51 @@ func DeploySolanaToken(e deployment.Environment, cfg DeploySolanaTokenConfig) (d }, nil } -// TODO: there is no validation done around if the token is already deployed -// https://smartcontract-it.atlassian.net/browse/INTAUTO-439 type MintSolanaTokenConfig struct { ChainSelector uint64 TokenProgram string - TokenPubkey solana.PublicKey + TokenPubkey string AmountToAddress map[string]uint64 // address -> amount } -func MintSolanaToken(e deployment.Environment, cfg MintSolanaTokenConfig) (deployment.ChangesetOutput, error) { - // get chain +func (cfg MintSolanaTokenConfig) Validate(e deployment.Environment) error { chain := e.SolChains[cfg.ChainSelector] // get addresses - tokenAddress := cfg.TokenPubkey + tokenAddress := solana.MustPublicKeyFromBase58(cfg.TokenPubkey) // get token program id tokenprogramID, err := GetTokenProgramID(cfg.TokenProgram) + if err != nil { + return err + } + + accountInfo, err := chain.Client.GetAccountInfoWithOpts(e.GetContext(), tokenAddress, &rpc.GetAccountInfoOpts{ + Commitment: deployment.SolDefaultCommitment, + }) + if err != nil { + fmt.Println("error getting account info", err) + return err + } + if accountInfo == nil || accountInfo.Value == nil { + return fmt.Errorf("token address %s not found", tokenAddress.String()) + } + if accountInfo.Value.Owner != tokenprogramID { + return fmt.Errorf("token address %s is not owned by the SPL token program", tokenAddress.String()) + } + return nil +} + +func MintSolanaToken(e deployment.Environment, cfg MintSolanaTokenConfig) (deployment.ChangesetOutput, error) { + err := cfg.Validate(e) if err != nil { return deployment.ChangesetOutput{}, err } + // get chain + chain := e.SolChains[cfg.ChainSelector] + // get addresses + tokenAddress := solana.MustPublicKeyFromBase58(cfg.TokenPubkey) + // get token program id + tokenprogramID, _ := GetTokenProgramID(cfg.TokenProgram) + // get mint instructions instructions := []solana.Instruction{} for toAddress, amount := range cfg.AmountToAddress { @@ -119,7 +145,8 @@ func MintSolanaToken(e deployment.Environment, cfg MintSolanaTokenConfig) (deplo e.Logger.Errorw("Failed to confirm instructions for token minting", "chain", chain.String(), "err", err) return deployment.ChangesetOutput{}, err } - e.Logger.Infow("Minted tokens on", "chain", cfg.ChainSelector, "for token", cfg.TokenPubkey.String()) + e.Logger.Infow("Minted tokens on", "chain", cfg.ChainSelector, "for token", tokenAddress.String()) + return deployment.ChangesetOutput{}, nil } diff --git a/deployment/ccip/changeset/solana/cs_solana_token_test.go b/deployment/ccip/changeset/solana/cs_solana_token_test.go index 0437c868b06..c46e7eae7ad 100644 --- a/deployment/ccip/changeset/solana/cs_solana_token_test.go +++ b/deployment/ccip/changeset/solana/cs_solana_token_test.go @@ -63,7 +63,7 @@ func TestSolanaTokenOps(t *testing.T) { deployment.CreateLegacyChangeSet(changeset_solana.MintSolanaToken), changeset_solana.MintSolanaTokenConfig{ ChainSelector: solChain1, - TokenPubkey: tokenAddress, + TokenPubkey: tokenAddress.String(), TokenProgram: deployment.SPL2022Tokens, AmountToAddress: map[string]uint64{ deployerKey.String(): uint64(1000), @@ -86,13 +86,11 @@ func TestSolanaTokenOps(t *testing.T) { // test if minting was done correctly outDec, outVal, err := solTokenUtil.TokenBalance(context.Background(), e.SolChains[solChain1].Client, deployerATA, solRpc.CommitmentConfirmed) require.NoError(t, err) - t.Logf("outDec: %d, outVal: %d", outDec, outVal) require.Equal(t, int(1000), outVal) require.Equal(t, 9, int(outDec)) outDec, outVal, err = solTokenUtil.TokenBalance(context.Background(), e.SolChains[solChain1].Client, testUserATA, solRpc.CommitmentConfirmed) require.NoError(t, err) - t.Logf("outDec: %d, outVal: %d", outDec, outVal) require.Equal(t, int(1000), outVal) require.Equal(t, 9, int(outDec)) } diff --git a/deployment/ccip/changeset/solana/cs_token_pool.go b/deployment/ccip/changeset/solana/cs_token_pool.go index 191398d3ead..5e6315eba18 100644 --- a/deployment/ccip/changeset/solana/cs_token_pool.go +++ b/deployment/ccip/changeset/solana/cs_token_pool.go @@ -93,17 +93,22 @@ func AddTokenPool(e deployment.Environment, cfg TokenPoolConfig) (deployment.Cha if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("failed to generate instructions: %w", err) } - // make pool mint_authority for token (required for burn/mint) - authI, err := solTokenUtil.SetTokenMintAuthority( - tokenprogramID, - poolSigner, - tokenPubKey, - authorityPubKey, - ) - if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("failed to generate instructions: %w", err) + + instructions := []solana.Instruction{createI, poolInitI} + + if cfg.PoolType == solTestTokenPool.BurnAndMint_PoolType && tokenPubKey != solana.SolMint { + // make pool mint_authority for token + authI, err := solTokenUtil.SetTokenMintAuthority( + tokenprogramID, + poolSigner, + tokenPubKey, + authorityPubKey, + ) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to generate instructions: %w", err) + } + instructions = append(instructions, authI) } - instructions := []solana.Instruction{createI, poolInitI, authI} // add signer here if authority is different from deployer key if err := chain.Confirm(instructions); err != nil { @@ -241,7 +246,6 @@ func (cfg TokenPoolLookupTableConfig) Validate(e deployment.Environment) error { return fmt.Errorf("token pool not found in existing state, deploy the token pool first for chain %d", cfg.ChainSelector) } - // TODO: do we need to validate if everything that goes into the lookup table is already created ? return nil } diff --git a/deployment/ccip/changeset/solana_state.go b/deployment/ccip/changeset/solana_state.go index eb524fb3dce..473a65b16b5 100644 --- a/deployment/ccip/changeset/solana_state.go +++ b/deployment/ccip/changeset/solana_state.go @@ -19,6 +19,7 @@ var ( Receiver deployment.ContractType = "Receiver" SPL2022Tokens deployment.ContractType = "SPL2022Tokens" WSOL deployment.ContractType = "WSOL" + FeeAggregator deployment.ContractType = "FeeAggregator" // for PDAs from AddRemoteChainToSolana RemoteSource deployment.ContractType = "RemoteSource" RemoteDest deployment.ContractType = "RemoteDest" @@ -40,6 +41,8 @@ type SolCCIPChainState struct { WSOL solana.PublicKey FeeQuoter solana.PublicKey OffRamp solana.PublicKey + FeeAggregator solana.PublicKey + // PDAs to avoid redundant lookups RouterConfigPDA solana.PublicKey SourceChainStatePDAs map[uint64]solana.PublicKey // deprecated @@ -153,6 +156,9 @@ func LoadChainStateSolana(chain deployment.SolChain, addresses map[string]deploy return state, err } state.OffRampStatePDA = offRampStatePDA + case FeeAggregator: + pub := solana.MustPublicKeyFromBase58(address) + state.FeeAggregator = pub default: return state, fmt.Errorf("unknown contract %s", tvStr) } @@ -160,3 +166,10 @@ func LoadChainStateSolana(chain deployment.SolChain, addresses map[string]deploy state.WSOL = solana.SolMint return state, nil } + +func (c SolCCIPChainState) OnRampBytes() ([]byte, error) { + if !c.Router.IsZero() { + return c.Router.Bytes(), nil + } + return nil, errors.New("no onramp found in the state") +} diff --git a/deployment/ccip/changeset/state.go b/deployment/ccip/changeset/state.go index a38949c320a..a11d068bc75 100644 --- a/deployment/ccip/changeset/state.go +++ b/deployment/ccip/changeset/state.go @@ -173,6 +173,13 @@ func (c CCIPChainState) LinkTokenAddress() (common.Address, error) { return common.Address{}, errors.New("no link token found in the state") } +func (c CCIPChainState) OnRampBytes() ([]byte, error) { + if c.OnRamp != nil { + return c.OnRamp.Address().Bytes(), nil + } + return nil, errors.New("no onramp found in the state") +} + func (c CCIPChainState) GenerateView() (view.ChainView, error) { chainView := view.NewChain() if c.Router != nil { @@ -812,7 +819,7 @@ func LoadChainState(ctx context.Context, chain deployment.Chain, addresses map[s return state, nil } -func (s CCIPOnChainState) ValidateOffRamp(chainSelector uint64) error { +func (s CCIPOnChainState) ValidateRamp(chainSelector uint64, rampType deployment.ContractType) error { family, err := chain_selectors.GetSelectorFamily(chainSelector) if err != nil { return err @@ -823,19 +830,37 @@ func (s CCIPOnChainState) ValidateOffRamp(chainSelector uint64) error { if !exists { return fmt.Errorf("chain %d does not exist", chainSelector) } - if chainState.OffRamp == nil { - // should not be possible, but a defensive check. - return fmt.Errorf("OffRamp contract does not exist on chain %d", chainSelector) + switch rampType { + case OffRamp: + if chainState.OffRamp == nil { + return fmt.Errorf("offramp contract does not exist on evm chain %d", chainSelector) + } + case OnRamp: + if chainState.OnRamp == nil { + return fmt.Errorf("onramp contract does not exist on evm chain %d", chainSelector) + } + default: + return fmt.Errorf("unknown ramp type %s", rampType) } + case chain_selectors.FamilySolana: chainState, exists := s.SolChains[chainSelector] if !exists { return fmt.Errorf("chain %d does not exist", chainSelector) } - if chainState.Router.IsZero() { - // should not be possible, but a defensive check. - return fmt.Errorf("CCIP router contract does not exist on chain %d", chainSelector) + switch rampType { + case OffRamp: + if chainState.OffRamp.IsZero() { + return fmt.Errorf("offramp contract does not exist on solana chain %d", chainSelector) + } + case OnRamp: + if chainState.Router.IsZero() { + return fmt.Errorf("router contract does not exist on solana chain %d", chainSelector) + } + default: + return fmt.Errorf("unknown ramp type %s", rampType) } + default: return fmt.Errorf("unknown chain family %s", family) } diff --git a/deployment/ccip/changeset/testhelpers/test_helpers.go b/deployment/ccip/changeset/testhelpers/test_helpers.go index 17934465b0a..b9e794f7627 100644 --- a/deployment/ccip/changeset/testhelpers/test_helpers.go +++ b/deployment/ccip/changeset/testhelpers/test_helpers.go @@ -423,16 +423,65 @@ func AddLane( fqCfg fee_quoter.FeeQuoterDestChainConfig, ) { var err error - - // from family fromFamily, _ := chainsel.GetSelectorFamily(from) toFamily, _ := chainsel.GetSelectorFamily(to) + changesets := []commoncs.ConfiguredChangeSet{} + if fromFamily == chainsel.FamilyEVM { + evmSrcChangesets := addEVMSrcChangesets(from, to, isTestRouter, gasprice, tokenPrices, fqCfg) + changesets = append(changesets, evmSrcChangesets...) + } + if toFamily == chainsel.FamilyEVM { + evmDstChangesets := addEVMDestChangesets(e, to, from, isTestRouter) + changesets = append(changesets, evmDstChangesets...) + } + if fromFamily == chainsel.FamilySolana { + changesets = append(changesets, addLaneSolanaChangesets(t, from, to, toFamily)...) + } + if toFamily == chainsel.FamilySolana { + changesets = append(changesets, addLaneSolanaChangesets(t, to, from, fromFamily)...) + } + + e.Env, err = commoncs.ApplyChangesets(t, e.Env, e.TimelockContracts(t), changesets) + require.NoError(t, err) +} - if fromFamily != chainsel.FamilyEVM { - t.Fatalf("from family is not evm, %s", fromFamily) +func addLaneSolanaChangesets(t *testing.T, solChainSelector, remoteChainSelector uint64, remoteFamily string) []commoncs.ConfiguredChangeSet { + chainFamilySelector := [4]uint8{} + if remoteFamily == chainsel.FamilyEVM { + // bytes4(keccak256("CCIP ChainFamilySelector EVM")) + chainFamilySelector = [4]uint8{40, 18, 213, 44} + } else if remoteFamily == chainsel.FamilySolana { + // bytes4(keccak256("CCIP ChainFamilySelector SVM")); + chainFamilySelector = [4]uint8{30, 16, 189, 196} + } + solanaChangesets := []commoncs.ConfiguredChangeSet{ + commoncs.Configure( + deployment.CreateLegacyChangeSet(changeset_solana.AddRemoteChainToSolana), + changeset_solana.AddRemoteChainToSolanaConfig{ + ChainSelector: solChainSelector, + UpdatesByChain: map[uint64]changeset_solana.RemoteChainConfigSolana{ + remoteChainSelector: { + EnabledAsSource: true, + RouterDestinationConfig: solRouter.DestChainConfig{}, + FeeQuoterDestinationConfig: solFeeQuoter.DestChainConfig{ + IsEnabled: true, + DefaultTxGasLimit: 200000, + MaxPerMsgGasLimit: 3000000, + MaxDataBytes: 30000, + MaxNumberOfTokensPerMsg: 5, + DefaultTokenDestGasOverhead: 5000, + ChainFamilySelector: chainFamilySelector, + }, + }, + }, + }, + ), } + return solanaChangesets +} - changesets := []commoncs.ConfiguredChangeSet{ +func addEVMSrcChangesets(from, to uint64, isTestRouter bool, gasprice map[uint64]*big.Int, tokenPrices map[common.Address]*big.Int, fqCfg fee_quoter.FeeQuoterDestChainConfig) []commoncs.ConfiguredChangeSet { + evmSrcChangesets := []commoncs.ConfiguredChangeSet{ commoncs.Configure( deployment.CreateLegacyChangeSet(changeset.UpdateOnRampsDestsChangeset), changeset.UpdateOnRampDestsConfig{ @@ -483,77 +532,41 @@ func AddLane( }, ), } + return evmSrcChangesets +} - require.NoError(t, err) - - switch toFamily { - case chainsel.FamilyEVM: - evmChangesets := []commoncs.ConfiguredChangeSet{ - commoncs.Configure( - deployment.CreateLegacyChangeSet(changeset.UpdateOffRampSourcesChangeset), - changeset.UpdateOffRampSourcesConfig{ - UpdatesByChain: map[uint64]map[uint64]changeset.OffRampSourceUpdate{ - to: { - from: { - IsEnabled: true, - TestRouter: isTestRouter, - IsRMNVerificationDisabled: !e.RmnEnabledSourceChains[from], - }, - }, - }, - }, - ), - commoncs.Configure( - deployment.CreateLegacyChangeSet(changeset.UpdateRouterRampsChangeset), - changeset.UpdateRouterRampsConfig{ - TestRouter: isTestRouter, - UpdatesByChain: map[uint64]changeset.RouterUpdates{ - // offramp update on dest chain - to: { - OffRampUpdates: map[uint64]bool{ - from: true, - }, +func addEVMDestChangesets(e *DeployedEnv, to, from uint64, isTestRouter bool) []commoncs.ConfiguredChangeSet { + evmDstChangesets := []commoncs.ConfiguredChangeSet{ + commoncs.Configure( + deployment.CreateLegacyChangeSet(changeset.UpdateOffRampSourcesChangeset), + changeset.UpdateOffRampSourcesConfig{ + UpdatesByChain: map[uint64]map[uint64]changeset.OffRampSourceUpdate{ + to: { + from: { + IsEnabled: true, + TestRouter: isTestRouter, + IsRMNVerificationDisabled: !e.RmnEnabledSourceChains[from], }, }, }, - ), - } - changesets = append(changesets, evmChangesets...) - case chainsel.FamilySolana: - value := [28]uint8{} - bigNum, ok := new(big.Int).SetString("19816680000000000000", 10) - require.True(t, ok) - bigNum.FillBytes(value[:]) - solanaChangesets := []commoncs.ConfiguredChangeSet{ - commoncs.Configure( - deployment.CreateLegacyChangeSet(changeset_solana.AddRemoteChainToSolana), - changeset_solana.AddRemoteChainToSolanaConfig{ - ChainSelector: to, - UpdatesByChain: map[uint64]changeset_solana.RemoteChainConfigSolana{ - from: { - EnabledAsSource: true, - RouterDestinationConfig: solRouter.DestChainConfig{}, - FeeQuoterDestinationConfig: solFeeQuoter.DestChainConfig{ - IsEnabled: true, - DefaultTxGasLimit: 200000, - MaxPerMsgGasLimit: 3000000, - MaxDataBytes: 30000, - MaxNumberOfTokensPerMsg: 5, - DefaultTokenDestGasOverhead: 5000, - // bytes4(keccak256("CCIP ChainFamilySelector EVM")) - // TODO: do a similar test for other chain families - ChainFamilySelector: [4]uint8{40, 18, 213, 44}, - }, + }, + ), + commoncs.Configure( + deployment.CreateLegacyChangeSet(changeset.UpdateRouterRampsChangeset), + changeset.UpdateRouterRampsConfig{ + TestRouter: isTestRouter, + UpdatesByChain: map[uint64]changeset.RouterUpdates{ + // offramp update on dest chain + to: { + OffRampUpdates: map[uint64]bool{ + from: true, }, }, }, - ), - } - changesets = append(changesets, solanaChangesets...) + }, + ), } - - e.Env, err = commoncs.ApplyChangesets(t, e.Env, e.TimelockContracts(t), changesets) - require.NoError(t, err) + return evmDstChangesets } // RemoveLane removes a lane between the source and destination chains in the deployed environment. @@ -603,18 +616,28 @@ func RemoveLane(t *testing.T, e *DeployedEnv, src, dest uint64, isTestRouter boo } func AddLaneWithDefaultPricesAndFeeQuoterConfig(t *testing.T, e *DeployedEnv, state changeset.CCIPOnChainState, from, to uint64, isTestRouter bool) { - stateChainFrom := state.Chains[from] + gasPrices := map[uint64]*big.Int{ + to: DefaultGasPrice, + } + fromFamily, _ := chainsel.GetSelectorFamily(from) + tokenPrices := map[common.Address]*big.Int{} + if fromFamily == chainsel.FamilyEVM { + stateChainFrom := state.Chains[from] + tokenPrices = map[common.Address]*big.Int{ + stateChainFrom.LinkToken.Address(): DefaultLinkPrice, + stateChainFrom.Weth9.Address(): DefaultWethPrice, + } + } + fqCfg := changeset.DefaultFeeQuoterDestChainConfig(true, to) AddLane( t, e, from, to, isTestRouter, - map[uint64]*big.Int{ - to: DefaultGasPrice, - }, map[common.Address]*big.Int{ - stateChainFrom.LinkToken.Address(): DefaultLinkPrice, - stateChainFrom.Weth9.Address(): DefaultWethPrice, - }, changeset.DefaultFeeQuoterDestChainConfig(true, to)) + gasPrices, + tokenPrices, + fqCfg, + ) } // AddLanesForAll adds densely connected lanes for all chains in the environment so that each chain @@ -811,6 +834,20 @@ func DeployTransferableTokenSolana( evmTokenName string, ) (*burn_mint_erc677.BurnMintERC677, *burn_mint_token_pool.BurnMintTokenPool, solana.PublicKey, error) { + selectorFamily, err := chainsel.GetSelectorFamily(evmChainSel) + if err != nil { + return nil, nil, solana.PublicKey{}, err + } + if selectorFamily != chainsel.FamilyEVM { + return nil, nil, solana.PublicKey{}, fmt.Errorf("evmChainSel %d is not an evm chain", evmChainSel) + } + selectorFamily, err = chainsel.GetSelectorFamily(solChainSel) + if err != nil { + return nil, nil, solana.PublicKey{}, err + } + if selectorFamily != chainsel.FamilySolana { + return nil, nil, solana.PublicKey{}, fmt.Errorf("solChainSel %d is not a solana chain", solChainSel) + } state, err := changeset.LoadOnchainState(e) require.NoError(t, err) @@ -858,7 +895,7 @@ func DeployTransferableTokenSolana( deployment.CreateLegacyChangeSet(changeset_solana.MintSolanaToken), changeset_solana.MintSolanaTokenConfig{ ChainSelector: solChainSel, - TokenPubkey: solTokenAddress, + TokenPubkey: solTokenAddress.String(), TokenProgram: deployment.SPL2022Tokens, AmountToAddress: map[string]uint64{ solDeployerKey.String(): uint64(1000),