diff --git a/deployment/ccip/changeset/solana/cs_chain_contracts.go b/deployment/ccip/changeset/solana/cs_chain_contracts.go index 59ec483feb4..a5a4bfae663 100644 --- a/deployment/ccip/changeset/solana/cs_chain_contracts.go +++ b/deployment/ccip/changeset/solana/cs_chain_contracts.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "strconv" "github.com/gagliardetto/solana-go" @@ -95,9 +96,8 @@ func validateRouterConfig(chain deployment.SolChain, chainState cs.SolCCIPChainS return fmt.Errorf("router not found in existing state, deploy the router first chain %d", chain.Selector) } // addressing errcheck in the next PR - routerConfigPDA, _, _ := solState.FindConfigPDA(chainState.Router) var routerConfigAccount solRouter.Config - err := chain.GetAccountDataBorshInto(context.Background(), routerConfigPDA, &routerConfigAccount) + err := chain.GetAccountDataBorshInto(context.Background(), chainState.RouterConfigPDA, &routerConfigAccount) if err != nil { return fmt.Errorf("router config not found in existing state, initialize the router first %d", chain.Selector) } @@ -146,10 +146,9 @@ func (cfg AddRemoteChainToSolanaConfig) Validate(e deployment.Environment) error if err := commoncs.ValidateOwnershipSolana(e.GetContext(), cfg.MCMS != nil, e.SolChains[chainSel].DeployerKey.PublicKey(), chainState.Timelock, chainState.Router); err != nil { return fmt.Errorf("failed to validate ownership: %w", err) } - routerConfigPDA, _, _ := solState.FindConfigPDA(chainState.Router) var routerConfigAccount solRouter.Config // already validated that router config exists - _ = chain.GetAccountDataBorshInto(context.Background(), routerConfigPDA, &routerConfigAccount) + _ = chain.GetAccountDataBorshInto(context.Background(), chainState.RouterConfigPDA, &routerConfigAccount) for remote := range updates { if _, ok := supportedChains[remote]; !ok { @@ -175,17 +174,22 @@ func AddRemoteChainToSolana(e deployment.Environment, cfg AddRemoteChainToSolana return deployment.ChangesetOutput{}, err } + ab := deployment.NewMemoryAddressBook() for chainSel, updates := range cfg.UpdatesByChain { - _, err := doAddRemoteChainToSolana(e, s, chainSel, updates) + err := doAddRemoteChainToSolana(e, s, chainSel, updates, ab) if err != nil { - return deployment.ChangesetOutput{}, err + return deployment.ChangesetOutput{AddressBook: ab}, err } } - - return deployment.ChangesetOutput{}, nil + return deployment.ChangesetOutput{AddressBook: ab}, nil } -func doAddRemoteChainToSolana(e deployment.Environment, s cs.CCIPOnChainState, chainSel uint64, updates map[uint64]RemoteChainConfigSolana) (deployment.ChangesetOutput, error) { +func doAddRemoteChainToSolana( + e deployment.Environment, + s cs.CCIPOnChainState, + chainSel uint64, + updates map[uint64]RemoteChainConfigSolana, + ab deployment.AddressBook) error { chain := e.SolChains[chainSel] ccipRouterID := s.SolChains[chainSel].Router @@ -195,11 +199,11 @@ func doAddRemoteChainToSolana(e deployment.Environment, s cs.CCIPOnChainState, c remoteChainFamily, _ := chainsel.GetSelectorFamily(remoteChainSel) switch remoteChainFamily { case chainsel.FamilySolana: - return deployment.ChangesetOutput{}, fmt.Errorf("support for solana chain as remote chain is not implemented yet %d", remoteChainSel) + return fmt.Errorf("support for solana chain as remote chain is not implemented yet %d", remoteChainSel) case chainsel.FamilyEVM: onRampAddress := s.Chains[remoteChainSel].OnRamp.Address().String() if onRampAddress == "" { - return deployment.ChangesetOutput{}, fmt.Errorf("onramp address not found for chain %d", remoteChainSel) + return fmt.Errorf("onramp address not found for chain %d", remoteChainSel) } addressBytes := []byte(onRampAddress) copy(onRampBytes[:], addressBytes) @@ -210,7 +214,6 @@ func doAddRemoteChainToSolana(e deployment.Environment, s cs.CCIPOnChainState, c IsEnabled: update.EnabledAsSource, } // addressing errcheck in the next PR - routerConfigPDA, _, _ := solState.FindConfigPDA(ccipRouterID) destChainStatePDA, _ := solState.FindDestChainStatePDA(remoteChainSel, ccipRouterID) sourceChainStatePDA, _ := solState.FindSourceChainStatePDA(remoteChainSel, ccipRouterID) @@ -220,24 +223,38 @@ func doAddRemoteChainToSolana(e deployment.Environment, s cs.CCIPOnChainState, c update.DestinationConfig, sourceChainStatePDA, destChainStatePDA, - routerConfigPDA, + s.SolChains[chainSel].RouterConfigPDA, chain.DeployerKey.PublicKey(), solana.SystemProgramID, ).ValidateAndBuild() if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("failed to generate instructions: %w", err) + return fmt.Errorf("failed to generate instructions: %w", err) } err = chain.Confirm([]solana.Instruction{instruction}) - if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("failed to confirm instructions: %w", err) + return fmt.Errorf("failed to confirm instructions: %w", err) } e.Logger.Infow("Confirmed instruction", "instruction", instruction) + + tv := deployment.NewTypeAndVersion(cs.RemoteDest, deployment.Version1_0_0) + remoteChainSelStr := strconv.FormatUint(remoteChainSel, 10) + tv.AddLabel(remoteChainSelStr) + err = ab.Save(chainSel, destChainStatePDA.String(), tv) + if err != nil { + return fmt.Errorf("failed to save dest chain state to address book: %w", err) + } + + tv = deployment.NewTypeAndVersion(cs.RemoteSource, deployment.Version1_0_0) + tv.AddLabel(remoteChainSelStr) + err = ab.Save(chainSel, sourceChainStatePDA.String(), tv) + if err != nil { + return fmt.Errorf("failed to save source chain state to address book: %w", err) + } } - return deployment.ChangesetOutput{}, nil + return nil } // SET OCR3 CONFIG @@ -282,10 +299,8 @@ func SetOCR3ConfigSolana(e deployment.Environment, cfg cs.SetOCR3OffRampConfig) // TODO: check if ocr3 has already been set // set, err := isOCR3ConfigSetSolana(e.Logger, e.Chains[remote], state.Chains[remote].OffRamp, args) var instructions []solana.Instruction - ccipRouterID := solChains[remote].Router - // addressing errcheck in the next PR - routerConfigPDA, _, _ := solState.FindConfigPDA(ccipRouterID) - routerStatePDA, _, _ := solState.FindStatePDA(ccipRouterID) + routerConfigPDA := solChains[remote].RouterConfigPDA + routerStatePDA := solChains[remote].RouterStatePDA for _, arg := range args { instruction, err := solRouter.NewSetOcrConfigInstruction( arg.OCRPluginType, @@ -566,7 +581,6 @@ func AddBillingToken(e deployment.Environment, cfg BillingTokenConfig) (deployme // verified tokenprogramID, _ := GetTokenProgramID(cfg.TokenProgramName) - routerConfigPDA, _, _ := solState.FindConfigPDA(chainState.Router) billingConfigPDA, _, _ := solState.FindFeeBillingTokenConfigPDA(tokenPubKey, chainState.Router) // addressing errcheck in the next PR @@ -575,7 +589,7 @@ func AddBillingToken(e deployment.Environment, cfg BillingTokenConfig) (deployme ixConfig, cerr := solRouter.NewAddBillingTokenConfigInstruction( cfg.Config, - routerConfigPDA, + chainState.RouterConfigPDA, billingConfigPDA, tokenprogramID, tokenPubKey, @@ -639,13 +653,12 @@ func AddBillingTokenForRemoteChain(e deployment.Environment, cfg BillingTokenFor tokenPubKey := solana.MustPublicKeyFromBase58(cfg.TokenPubKey) // verified remoteBillingPDA, _, _ := solState.FindCcipTokenpoolBillingPDA(cfg.RemoteChainSelector, tokenPubKey, chainState.Router) - routerConfigPDA, _, _ := solState.FindConfigPDA(chainState.Router) ix, err := solRouter.NewSetTokenBillingInstruction( cfg.RemoteChainSelector, tokenPubKey, cfg.Config, - routerConfigPDA, + chainState.RouterConfigPDA, remoteBillingPDA, chain.DeployerKey.PublicKey(), solana.SystemProgramID, @@ -716,7 +729,6 @@ func RegisterTokenAdminRegistry(e deployment.Environment, cfg RegisterTokenAdmin tokenPubKey := solana.MustPublicKeyFromBase58(cfg.TokenPubKey) // verified - routerConfigPDA, _, _ := solState.FindConfigPDA(chainState.Router) tokenAdminRegistryPDA, _, _ := solState.FindTokenAdminRegistryPDA(tokenPubKey, chainState.Router) var instruction *solRouter.Instruction @@ -728,7 +740,7 @@ func RegisterTokenAdminRegistry(e deployment.Environment, cfg RegisterTokenAdmin instruction, err = solRouter.NewRegisterTokenAdminRegistryViaGetCcipAdminInstruction( tokenPubKey, tokenAdminRegistryAdmin, // admin of the tokenAdminRegistry PDA - routerConfigPDA, + chainState.RouterConfigPDA, tokenAdminRegistryPDA, // this gets created chain.DeployerKey.PublicKey(), // (ccip admin) solana.SystemProgramID, @@ -739,7 +751,7 @@ func RegisterTokenAdminRegistry(e deployment.Environment, cfg RegisterTokenAdmin case ViaOwnerInstruction: // the token mint authority signs and makes itself the authority of the tokenAdminRegistry PDA instruction, err = solRouter.NewRegisterTokenAdminRegistryViaOwnerInstruction( - routerConfigPDA, + chainState.RouterConfigPDA, tokenAdminRegistryPDA, // this gets created tokenPubKey, chain.DeployerKey.PublicKey(), // (token mint authority) becomes the authority of the tokenAdminRegistry PDA @@ -819,7 +831,6 @@ func TransferAdminRoleTokenAdminRegistry(e deployment.Environment, cfg TransferA // verified tokenAdminRegistryPDA, _, _ := solState.FindTokenAdminRegistryPDA(tokenPubKey, chainState.Router) - routerConfigPDA, _, _ := solState.FindConfigPDA(chainState.Router) currentRegistryAdminPrivateKey := solana.MustPrivateKeyFromBase58(cfg.CurrentRegistryAdminPrivateKey) newRegistryAdminPubKey := solana.MustPublicKeyFromBase58(cfg.NewRegistryAdminPublicKey) @@ -827,7 +838,7 @@ func TransferAdminRoleTokenAdminRegistry(e deployment.Environment, cfg TransferA ix1, err := solRouter.NewTransferAdminRoleTokenAdminRegistryInstruction( tokenPubKey, newRegistryAdminPubKey, - routerConfigPDA, + chainState.RouterConfigPDA, tokenAdminRegistryPDA, currentRegistryAdminPrivateKey.PublicKey(), // as we are assuming this is the default authority for everything in the beginning ).ValidateAndBuild() @@ -892,11 +903,10 @@ func AcceptAdminRoleTokenAdminRegistry(e deployment.Environment, cfg AcceptAdmin // verified tokenAdminRegistryPDA, _, _ := solState.FindTokenAdminRegistryPDA(tokenPubKey, chainState.Router) - routerConfigPDA, _, _ := solState.FindConfigPDA(chainState.Router) ix1, err := solRouter.NewAcceptAdminRoleTokenAdminRegistryInstruction( tokenPubKey, - routerConfigPDA, + chainState.RouterConfigPDA, tokenAdminRegistryPDA, newRegistryAdminPrivateKey.PublicKey(), ).ValidateAndBuild() diff --git a/deployment/ccip/changeset/solana/cs_chain_contracts_test.go b/deployment/ccip/changeset/solana/cs_chain_contracts_test.go index a7b489a5aa4..a8e122d48a7 100644 --- a/deployment/ccip/changeset/solana/cs_chain_contracts_test.go +++ b/deployment/ccip/changeset/solana/cs_chain_contracts_test.go @@ -36,7 +36,7 @@ func TestAddRemoteChain(t *testing.T) { state, err := changeset.LoadOnchainState(tenv.Env) require.NoError(t, err) - _, err = commonchangeset.ApplyChangesets(t, tenv.Env, nil, []commonchangeset.ChangesetApplication{ + tenv.Env, err = commonchangeset.ApplyChangesets(t, tenv.Env, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.UpdateOnRampsDestsChangeset), Config: changeset.UpdateOnRampDestsConfig{ @@ -80,16 +80,15 @@ func TestAddRemoteChain(t *testing.T) { state, err = changeset.LoadOnchainStateSolana(tenv.Env) require.NoError(t, err) - var sourceChainStateAccount solRouter.SourceChain - evmSourceChainStatePDA, _ := solState.FindSourceChainStatePDA(evmChain, state.SolChains[solChain].Router) + evmSourceChainStatePDA := state.SolChains[solChain].SourceChainStatePDAs[evmChain] err = tenv.Env.SolChains[solChain].GetAccountDataBorshInto(ctx, evmSourceChainStatePDA, &sourceChainStateAccount) require.NoError(t, err) require.Equal(t, uint64(1), sourceChainStateAccount.State.MinSeqNr) require.True(t, sourceChainStateAccount.Config.IsEnabled) var destChainStateAccount solRouter.DestChain - evmDestChainStatePDA, _ := solState.FindDestChainStatePDA(evmChain, state.SolChains[solChain].Router) + evmDestChainStatePDA := state.SolChains[solChain].DestChainStatePDAs[evmChain] err = tenv.Env.SolChains[solChain].GetAccountDataBorshInto(ctx, evmDestChainStatePDA, &destChainStateAccount) require.NoError(t, err) } diff --git a/deployment/ccip/changeset/solana_state.go b/deployment/ccip/changeset/solana_state.go index 306115d78f2..1a4e8161a88 100644 --- a/deployment/ccip/changeset/solana_state.go +++ b/deployment/ccip/changeset/solana_state.go @@ -3,9 +3,12 @@ package changeset import ( "errors" "fmt" + "strconv" "github.com/gagliardetto/solana-go" + solState "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/state" + "github.com/smartcontractkit/chainlink/deployment" commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" ) @@ -16,6 +19,9 @@ var ( Receiver deployment.ContractType = "Receiver" SPL2022Tokens deployment.ContractType = "SPL2022Tokens" WSOL deployment.ContractType = "WSOL" + // for PDAs from AddRemoteChainToSolana + RemoteSource deployment.ContractType = "RemoteSource" + RemoteDest deployment.ContractType = "RemoteDest" ) // SolChainState holds a Go binding for all the currently deployed CCIP programs @@ -29,6 +35,11 @@ type SolCCIPChainState struct { SPL2022Tokens []solana.PublicKey TokenPool solana.PublicKey WSOL solana.PublicKey + // PDAs to avoid redundant lookups + RouterStatePDA solana.PublicKey + RouterConfigPDA solana.PublicKey + SourceChainStatePDAs map[uint64]solana.PublicKey + DestChainStatePDAs map[uint64]solana.PublicKey } func LoadOnchainStateSolana(e deployment.Environment) (CCIPOnChainState, error) { @@ -55,28 +66,61 @@ func LoadOnchainStateSolana(e deployment.Environment) (CCIPOnChainState, error) // LoadChainStateSolana Loads all state for a SolChain into state func LoadChainStateSolana(chain deployment.SolChain, addresses map[string]deployment.TypeAndVersion) (SolCCIPChainState, error) { - var state SolCCIPChainState + state := SolCCIPChainState{ + SourceChainStatePDAs: make(map[uint64]solana.PublicKey), + DestChainStatePDAs: make(map[uint64]solana.PublicKey), + } var spl2022Tokens []solana.PublicKey for address, tvStr := range addresses { - switch tvStr.String() { - case deployment.NewTypeAndVersion(commontypes.LinkToken, deployment.Version1_0_0).String(): + switch tvStr.Type { + case commontypes.LinkToken: pub := solana.MustPublicKeyFromBase58(address) state.LinkToken = pub - case deployment.NewTypeAndVersion(Router, deployment.Version1_0_0).String(): + case Router: pub := solana.MustPublicKeyFromBase58(address) state.Router = pub - case deployment.NewTypeAndVersion(AddressLookupTable, deployment.Version1_0_0).String(): + routerStatePDA, _, err := solState.FindStatePDA(state.Router) + if err != nil { + return state, err + } + state.RouterStatePDA = routerStatePDA + routerConfigPDA, _, err := solState.FindConfigPDA(state.Router) + if err != nil { + return state, err + } + state.RouterConfigPDA = routerConfigPDA + case AddressLookupTable: pub := solana.MustPublicKeyFromBase58(address) state.AddressLookupTable = pub - case deployment.NewTypeAndVersion(Receiver, deployment.Version1_0_0).String(): + case Receiver: pub := solana.MustPublicKeyFromBase58(address) state.Receiver = pub - case deployment.NewTypeAndVersion(SPL2022Tokens, deployment.Version1_0_0).String(): + case SPL2022Tokens: pub := solana.MustPublicKeyFromBase58(address) spl2022Tokens = append(spl2022Tokens, pub) - case deployment.NewTypeAndVersion(TokenPool, deployment.Version1_0_0).String(): + case TokenPool: pub := solana.MustPublicKeyFromBase58(address) state.TokenPool = pub + case RemoteSource: + pub := solana.MustPublicKeyFromBase58(address) + // Labels should only have one entry + for selStr := range tvStr.Labels { + selector, err := strconv.ParseUint(selStr, 10, 64) + if err != nil { + return state, err + } + state.SourceChainStatePDAs[selector] = pub + } + case RemoteDest: + pub := solana.MustPublicKeyFromBase58(address) + // Labels should only have one entry + for selStr := range tvStr.Labels { + selector, err := strconv.ParseUint(selStr, 10, 64) + if err != nil { + return state, err + } + state.DestChainStatePDAs[selector] = pub + } default: return state, fmt.Errorf("unknown contract %s", tvStr) } diff --git a/deployment/ccip/changeset/testhelpers/test_helpers.go b/deployment/ccip/changeset/testhelpers/test_helpers.go index f394940b802..b2a5dc26653 100644 --- a/deployment/ccip/changeset/testhelpers/test_helpers.go +++ b/deployment/ccip/changeset/testhelpers/test_helpers.go @@ -1383,14 +1383,15 @@ func ValidateSolanaState(t *testing.T, e deployment.Environment, solChainSelecto // Validate addresses require.False(t, chainState.LinkToken.IsZero(), "Link token address is zero for chain %d", sel) require.False(t, chainState.Router.IsZero(), "Router address is zero for chain %d", sel) + require.False(t, chainState.RouterConfigPDA.IsZero(), "RouterConfigPDA is zero for chain %d", sel) + require.False(t, chainState.RouterStatePDA.IsZero(), "RouterStatePDA is zero for chain %d", sel) require.False(t, chainState.AddressLookupTable.IsZero(), "Address lookup table is zero for chain %d", sel) // Get router config var routerConfigAccount solRouter.Config - configPDA, _, _ := solState.FindConfigPDA(chainState.Router) // Check if account exists first - err = e.SolChains[sel].GetAccountDataBorshInto(testcontext.Get(t), configPDA, &routerConfigAccount) + err = e.SolChains[sel].GetAccountDataBorshInto(testcontext.Get(t), chainState.RouterConfigPDA, &routerConfigAccount) require.NoError(t, err, "Failed to deserialize router config for chain %d", sel) } }