Skip to content

Commit

Permalink
add remote chain (#16540)
Browse files Browse the repository at this point in the history
* add remote chain

* add remote chain

* wip

* wip

* wip

* lint

---------

Co-authored-by: Terry Tata <[email protected]>
  • Loading branch information
yashnevatia and tt-cll authored Feb 24, 2025
1 parent 29f27b8 commit 8196f5b
Show file tree
Hide file tree
Showing 12 changed files with 685 additions and 499 deletions.
173 changes: 142 additions & 31 deletions deployment/ccip/changeset/solana/cs_add_remote_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@ package solana

import (
"context"
// "errors"

"fmt"
"strconv"

"github.com/ethereum/go-ethereum/common"
"github.com/gagliardetto/solana-go"

"github.com/smartcontractkit/mcms"
"github.com/smartcontractkit/mcms/sdk"
mcmsSolana "github.com/smartcontractkit/mcms/sdk/solana"
mcmsTypes "github.com/smartcontractkit/mcms/types"

solOffRamp "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_offramp"
solRouter "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_router"
solFeeQuoter "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/fee_quoter"
Expand All @@ -17,8 +22,9 @@ import (

"github.com/smartcontractkit/chainlink/deployment"
ccipChangeset "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
commoncs "github.com/smartcontractkit/chainlink/deployment/common/changeset"
commonState "github.com/smartcontractkit/chainlink/deployment/common/changeset/state"

"github.com/smartcontractkit/chainlink/deployment/common/changeset/state"
"github.com/smartcontractkit/chainlink/deployment/common/proposalutils"
)

// ADD REMOTE CHAIN
Expand All @@ -29,6 +35,11 @@ type AddRemoteChainToSolanaConfig struct {
// Disallow mixing MCMS/non-MCMS per chain for simplicity.
// (can still be achieved by calling this function multiple times)
MCMS *ccipChangeset.MCMSConfig
// Public key of program authorities. Depending on when this changeset is called, some may be under
// the control of the deployer, and some may be under the control of the timelock. (e.g. during new offramp deploy)
RouterAuthority solana.PublicKey
FeeQuoterAuthority solana.PublicKey
OffRampAuthority solana.PublicKey
}

type RemoteChainConfigSolana struct {
Expand All @@ -55,19 +66,23 @@ func (cfg AddRemoteChainToSolanaConfig) Validate(e deployment.Environment) error
if err := validateOffRampConfig(chain, chainState); err != nil {
return err
}
if err := ValidateMCMSConfig(e, cfg.ChainSelector, cfg.MCMS); err != nil {
return err
}
routerUsingMCMS := cfg.MCMS != nil && !cfg.RouterAuthority.IsZero()
feeQuoterUsingMCMS := cfg.MCMS != nil && !cfg.FeeQuoterAuthority.IsZero()
offRampUsingMCMS := cfg.MCMS != nil && !cfg.OffRampAuthority.IsZero()
chain, ok := e.SolChains[cfg.ChainSelector]
if !ok {
return fmt.Errorf("chain %d not found in environment", cfg.ChainSelector)
}
addresses, err := e.ExistingAddresses.AddressesForChain(cfg.ChainSelector)
if err != nil {
return err
if err := ccipChangeset.ValidateOwnershipSolana(&e, chain, routerUsingMCMS, e.SolChains[cfg.ChainSelector].DeployerKey.PublicKey(), chainState.Router, ccipChangeset.Router); err != nil {
return fmt.Errorf("failed to validate ownership: %w", err)
}
mcmState, err := commonState.MaybeLoadMCMSWithTimelockChainStateSolana(chain, addresses)
if err != nil {
return fmt.Errorf("error loading MCMS state for chain %d: %w", cfg.ChainSelector, err)
if err := ccipChangeset.ValidateOwnershipSolana(&e, chain, feeQuoterUsingMCMS, e.SolChains[cfg.ChainSelector].DeployerKey.PublicKey(), chainState.FeeQuoter, ccipChangeset.FeeQuoter); err != nil {
return fmt.Errorf("failed to validate ownership: %w", err)
}
if err := commoncs.ValidateOwnershipSolana(e.GetContext(), cfg.MCMS != nil, e.SolChains[cfg.ChainSelector].DeployerKey.PublicKey(), mcmState.TimelockProgram, mcmState.TimelockSeed, chainState.Router); err != nil {
if err := ccipChangeset.ValidateOwnershipSolana(&e, chain, offRampUsingMCMS, e.SolChains[cfg.ChainSelector].DeployerKey.PublicKey(), chainState.OffRamp, ccipChangeset.OffRamp); err != nil {
return fmt.Errorf("failed to validate ownership: %w", err)
}
var routerConfigAccount solRouter.Config
Expand Down Expand Up @@ -110,23 +125,66 @@ func AddRemoteChainToSolana(e deployment.Environment, cfg AddRemoteChainToSolana
}

ab := deployment.NewMemoryAddressBook()
err = doAddRemoteChainToSolana(e, s, cfg.ChainSelector, cfg.UpdatesByChain, ab)
txns, err := doAddRemoteChainToSolana(e, s, cfg, ab)
if err != nil {
return deployment.ChangesetOutput{AddressBook: ab}, err
}

// create proposals for ixns
if len(txns) > 0 {
timelocks := map[uint64]string{}
proposers := map[uint64]string{}
inspectors := map[uint64]sdk.Inspector{}
batches := make([]mcmsTypes.BatchOperation, 0)
chain := e.SolChains[cfg.ChainSelector]
addresses, _ := e.ExistingAddresses.AddressesForChain(cfg.ChainSelector)
mcmState, _ := state.MaybeLoadMCMSWithTimelockChainStateSolana(chain, addresses)

timelocks[cfg.ChainSelector] = mcmsSolana.ContractAddress(
mcmState.TimelockProgram,
mcmsSolana.PDASeed(mcmState.TimelockSeed),
)
proposers[cfg.ChainSelector] = mcmsSolana.ContractAddress(mcmState.McmProgram, mcmsSolana.PDASeed(mcmState.ProposerMcmSeed))
inspectors[cfg.ChainSelector] = mcmsSolana.NewInspector(chain.Client)
batches = append(batches, mcmsTypes.BatchOperation{
ChainSelector: mcmsTypes.ChainSelector(cfg.ChainSelector),
Transactions: txns,
})
proposal, err := proposalutils.BuildProposalFromBatchesV2(
e.GetContext(),
timelocks,
proposers,
inspectors,
batches,
"proposal to add remote chains to Solana",
cfg.MCMS.MinDelay)
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to build proposal: %w", err)
}
return deployment.ChangesetOutput{
MCMSTimelockProposals: []mcms.TimelockProposal{*proposal},
AddressBook: ab,
}, nil
}
return deployment.ChangesetOutput{AddressBook: ab}, nil
}

func doAddRemoteChainToSolana(
e deployment.Environment,
s ccipChangeset.CCIPOnChainState,
chainSel uint64,
updates map[uint64]RemoteChainConfigSolana,
ab deployment.AddressBook) error {
cfg AddRemoteChainToSolanaConfig,
ab deployment.AddressBook) ([]mcmsTypes.Transaction, error) {
txns := make([]mcmsTypes.Transaction, 0)
ixns := make([]solana.Instruction, 0)
chainSel := cfg.ChainSelector
updates := cfg.UpdatesByChain
chain := e.SolChains[chainSel]
ccipRouterID := s.SolChains[chainSel].Router
feeQuoterID := s.SolChains[chainSel].FeeQuoter
offRampID := s.SolChains[chainSel].OffRamp
routerUsingMCMS := cfg.MCMS != nil && !cfg.RouterAuthority.IsZero()
feeQuoterUsingMCMS := cfg.MCMS != nil && !cfg.FeeQuoterAuthority.IsZero()
offRampUsingMCMS := cfg.MCMS != nil && !cfg.OffRampAuthority.IsZero()
lookUpTableEntries := make([]solana.PublicKey, 0)

for remoteChainSel, update := range updates {
Expand All @@ -149,85 +207,138 @@ func doAddRemoteChainToSolana(
)

solRouter.SetProgramID(ccipRouterID)
var authority solana.PublicKey
if routerUsingMCMS {
authority = cfg.RouterAuthority
} else {
authority = chain.DeployerKey.PublicKey()
}
routerIx, err := solRouter.NewAddChainSelectorInstruction(
remoteChainSel,
update.RouterDestinationConfig,
routerRemoteStatePDA,
s.SolChains[chainSel].RouterConfigPDA,
chain.DeployerKey.PublicKey(),
authority,
solana.SystemProgramID,
).ValidateAndBuild()
if err != nil {
return fmt.Errorf("failed to generate instructions: %w", err)
return txns, fmt.Errorf("failed to generate instructions: %w", err)
}
if routerUsingMCMS {
tx, err := BuildMCMSTxn(routerIx, ccipRouterID.String(), ccipChangeset.Router)
if err != nil {
return txns, fmt.Errorf("failed to create transaction: %w", err)
}
txns = append(txns, *tx)
} else {
ixns = append(ixns, routerIx)
}

routerOfframpIx, err := solRouter.NewAddOfframpInstruction(
remoteChainSel,
offRampID,
allowedOffRampRemotePDA,
s.SolChains[chainSel].RouterConfigPDA,
chain.DeployerKey.PublicKey(),
authority,
solana.SystemProgramID,
).ValidateAndBuild()
if err != nil {
return fmt.Errorf("failed to generate instructions: %w", err)
return txns, fmt.Errorf("failed to generate instructions: %w", err)
}
if routerUsingMCMS {
tx, err := BuildMCMSTxn(routerOfframpIx, ccipRouterID.String(), ccipChangeset.Router)
if err != nil {
return txns, fmt.Errorf("failed to create transaction: %w", err)
}
txns = append(txns, *tx)
} else {
ixns = append(ixns, routerOfframpIx)
}

solFeeQuoter.SetProgramID(feeQuoterID)
if feeQuoterUsingMCMS {
authority = cfg.RouterAuthority
} else {
authority = chain.DeployerKey.PublicKey()
}
feeQuoterIx, err := solFeeQuoter.NewAddDestChainInstruction(
remoteChainSel,
update.FeeQuoterDestinationConfig,
s.SolChains[chainSel].FeeQuoterConfigPDA,
fqRemoteChainPDA,
chain.DeployerKey.PublicKey(),
authority,
solana.SystemProgramID,
).ValidateAndBuild()
if err != nil {
return fmt.Errorf("failed to generate instructions: %w", err)
return txns, fmt.Errorf("failed to generate instructions: %w", err)
}
if feeQuoterUsingMCMS {
tx, err := BuildMCMSTxn(feeQuoterIx, feeQuoterID.String(), ccipChangeset.FeeQuoter)
if err != nil {
return txns, fmt.Errorf("failed to create transaction: %w", err)
}
txns = append(txns, *tx)
} else {
ixns = append(ixns, feeQuoterIx)
}

solOffRamp.SetProgramID(offRampID)
validSourceChainConfig := solOffRamp.SourceChainConfig{
OnRamp: [2][64]byte{onRampBytes, [64]byte{}},
IsEnabled: update.EnabledAsSource,
}
if offRampUsingMCMS {
authority = cfg.RouterAuthority
} else {
authority = chain.DeployerKey.PublicKey()
}
offRampIx, err := solOffRamp.NewAddSourceChainInstruction(
remoteChainSel,
validSourceChainConfig,
offRampRemoteStatePDA,
s.SolChains[chainSel].OffRampConfigPDA,
chain.DeployerKey.PublicKey(),
authority,
solana.SystemProgramID,
).ValidateAndBuild()

if err != nil {
return fmt.Errorf("failed to generate instructions: %w", err)
return txns, fmt.Errorf("failed to generate instructions: %w", err)
}

err = chain.Confirm([]solana.Instruction{routerIx, routerOfframpIx, feeQuoterIx, offRampIx})
if err != nil {
return fmt.Errorf("failed to confirm instructions: %w", err)
if offRampUsingMCMS {
tx, err := BuildMCMSTxn(offRampIx, offRampID.String(), ccipChangeset.OffRamp)
if err != nil {
return txns, fmt.Errorf("failed to create transaction: %w", err)
}
txns = append(txns, *tx)
} else {
ixns = append(ixns, offRampIx)
}
if len(ixns) > 0 {
err = chain.Confirm(ixns)
if err != nil {
return txns, fmt.Errorf("failed to confirm instructions: %w", err)
}
}

tv := deployment.NewTypeAndVersion(ccipChangeset.RemoteDest, deployment.Version1_0_0)
remoteChainSelStr := strconv.FormatUint(remoteChainSel, 10)
tv.AddLabel(remoteChainSelStr)
err = ab.Save(chainSel, routerRemoteStatePDA.String(), tv)
if err != nil {
return fmt.Errorf("failed to save dest chain state to address book: %w", err)
return txns, fmt.Errorf("failed to save dest chain state to address book: %w", err)
}

tv = deployment.NewTypeAndVersion(ccipChangeset.RemoteSource, deployment.Version1_0_0)
tv.AddLabel(remoteChainSelStr)
err = ab.Save(chainSel, allowedOffRampRemotePDA.String(), tv)
if err != nil {
return fmt.Errorf("failed to save source chain state to address book: %w", err)
return txns, fmt.Errorf("failed to save source chain state to address book: %w", err)
}
}

addressLookupTable, err := ccipChangeset.FetchOfframpLookupTable(e.GetContext(), chain, offRampID)
if err != nil {
return fmt.Errorf("failed to get offramp reference addresses: %w", err)
return txns, fmt.Errorf("failed to get offramp reference addresses: %w", err)
}

if err := solCommonUtil.ExtendLookupTable(
Expand All @@ -237,8 +348,8 @@ func doAddRemoteChainToSolana(
*chain.DeployerKey,
lookUpTableEntries,
); err != nil {
return fmt.Errorf("failed to extend lookup table: %w", err)
return txns, fmt.Errorf("failed to extend lookup table: %w", err)
}

return nil
return txns, nil
}
Loading

0 comments on commit 8196f5b

Please sign in to comment.