Skip to content

Commit

Permalink
add more solana mcms support (#16555)
Browse files Browse the repository at this point in the history
* add timelock to remote chain

* lint

* re-add transfer

* move to mcms helper

* fix buffer bug

* mcms validation

* set authority

* refactor

* try to unify ci and local

* lint

* add update dest configs

* add disable

* cleanup

* error message

* v2

* fix test

* add mcms to billing

* refactor into helper

* lint

* add more mcms support

* wip

* wip

* wip

* set token authority

* fix tests with/without mcms

* lint

* bug fix

* lint
  • Loading branch information
tt-cll authored Feb 27, 2025
1 parent f4ad216 commit 1a9a8ca
Show file tree
Hide file tree
Showing 8 changed files with 598 additions and 196 deletions.
394 changes: 252 additions & 142 deletions deployment/ccip/changeset/solana/cs_chain_contracts_test.go

Large diffs are not rendered by default.

47 changes: 42 additions & 5 deletions deployment/ccip/changeset/solana/cs_deploy_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,7 @@ func generateCloseBufferIxn(
type SetFeeAggregatorConfig struct {
ChainSelector uint64
FeeAggregator string
MCMSSolana *MCMSConfigSolana
}

func (cfg SetFeeAggregatorConfig) Validate(e deployment.Environment) error {
Expand All @@ -916,6 +917,14 @@ func (cfg SetFeeAggregatorConfig) Validate(e deployment.Environment) error {
return err
}

if err := ValidateMCMSConfigSolana(e, cfg.ChainSelector, cfg.MCMSSolana); err != nil {
return err
}
routerUsingMCMS := cfg.MCMSSolana != nil && cfg.MCMSSolana.RouterOwnedByTimelock
if err := ccipChangeset.ValidateOwnershipSolana(&e, chain, routerUsingMCMS, chainState.Router, ccipChangeset.Router); err != nil {
return fmt.Errorf("failed to validate ownership: %w", err)
}

// Validate fee aggregator address is valid
if _, err := solana.PublicKeyFromBase58(cfg.FeeAggregator); err != nil {
return fmt.Errorf("invalid fee aggregator address: %w", err)
Expand All @@ -939,28 +948,56 @@ func SetFeeAggregator(e deployment.Environment, cfg SetFeeAggregatorConfig) (dep

feeAggregatorPubKey := solana.MustPublicKeyFromBase58(cfg.FeeAggregator)
routerConfigPDA, _, _ := solState.FindConfigPDA(chainState.Router)
routerUsingMCMS := cfg.MCMSSolana != nil && cfg.MCMSSolana.RouterOwnedByTimelock

solRouter.SetProgramID(chainState.Router)
var authority solana.PublicKey
var err error
if routerUsingMCMS {
authority, err = FetchTimelockSigner(e, cfg.ChainSelector)
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to fetch timelock signer: %w", err)
}
} else {
authority = e.SolChains[cfg.ChainSelector].DeployerKey.PublicKey()
}

instruction, err := solRouter.NewUpdateFeeAggregatorInstruction(
feeAggregatorPubKey,
routerConfigPDA,
chain.DeployerKey.PublicKey(),
authority,
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(ccipChangeset.FeeAggregator, deployment.Version1_0_0))
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to save address: %w", err)
}

if routerUsingMCMS {
tx, err := BuildMCMSTxn(instruction, chainState.Router.String(), ccipChangeset.Router)
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to create transaction: %w", err)
}
proposal, err := BuildProposalsForTxns(
e, cfg.ChainSelector, "proposal to SetFeeAggregator in Solana", cfg.MCMSSolana.MCMS.MinDelay, []mcmsTypes.Transaction{*tx})
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to build proposal: %w", err)
}
return deployment.ChangesetOutput{
MCMSTimelockProposals: []mcms.TimelockProposal{*proposal},
AddressBook: newAddresses,
}, nil
}

if err := chain.Confirm([]solana.Instruction{instruction}); err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to confirm instructions: %w", err)
}
e.Logger.Infow("Set new fee aggregator", "chain", chain.String(), "fee_aggregator", feeAggregatorPubKey.String())

return deployment.ChangesetOutput{
AddressBook: newAddresses,
}, nil
Expand Down
15 changes: 15 additions & 0 deletions deployment/ccip/changeset/solana/cs_deploy_chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ func TestDeployChainContractsChangesetSolana(t *testing.T) {

feeAggregatorPrivKey, _ := solana.NewRandomPrivateKey()
feeAggregatorPubKey := feeAggregatorPrivKey.PublicKey()
feeAggregatorPrivKey2, _ := solana.NewRandomPrivateKey()
feeAggregatorPubKey2 := feeAggregatorPrivKey2.PublicKey()
ci := os.Getenv("CI") == "true"
// we can't upgrade in place locally if we preload addresses so we have to change where we build
// we also don't want to incur two builds in CI, so only do it locally
Expand Down Expand Up @@ -207,6 +209,19 @@ func TestDeployChainContractsChangesetSolana(t *testing.T) {
},
},
),
commonchangeset.Configure(
deployment.CreateLegacyChangeSet(ccipChangesetSolana.SetFeeAggregator),
ccipChangesetSolana.SetFeeAggregatorConfig{
ChainSelector: solChainSelectors[0],
FeeAggregator: feeAggregatorPubKey2.String(),
MCMSSolana: &ccipChangesetSolana.MCMSConfigSolana{
MCMS: &ccipChangeset.MCMSConfig{
MinDelay: 1 * time.Second,
},
RouterOwnedByTimelock: true,
},
},
),
})
require.NoError(t, err)
testhelpers.ValidateSolanaState(t, e, solChainSelectors)
Expand Down
68 changes: 66 additions & 2 deletions deployment/ccip/changeset/solana/cs_set_ocr3.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import (

"github.com/gagliardetto/solana-go"
chain_selectors "github.com/smartcontractkit/chain-selectors"
"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"

Expand All @@ -13,6 +17,8 @@ import (
ccipChangeset "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal"
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/v1_6"
csState "github.com/smartcontractkit/chainlink/deployment/common/changeset/state"
"github.com/smartcontractkit/chainlink/deployment/common/proposalutils"
)

const (
Expand Down Expand Up @@ -49,8 +55,16 @@ func SetOCR3ConfigSolana(e deployment.Environment, cfg v1_6.SetOCR3OffRampConfig
if chainFamily != chain_selectors.FamilySolana {
return deployment.ChangesetOutput{}, fmt.Errorf("chain %d is not a solana chain", remote)
}
chain := e.SolChains[remote]
if err := ccipChangeset.ValidateOwnershipSolana(&e, chain, cfg.MCMS != nil, state.SolChains[remote].OffRamp, ccipChangeset.OffRamp); err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to validate ownership: %w", err)
}
}

timelocks := map[uint64]string{}
proposers := map[uint64]string{}
inspectors := map[uint64]sdk.Inspector{}
var batches []mcmsTypes.BatchOperation
for _, remote := range cfg.RemoteChainSels {
donID, err := internal.DonIDForChain(
state.Chains[cfg.HomeChainSel].CapabilityRegistry,
Expand All @@ -71,11 +85,31 @@ func SetOCR3ConfigSolana(e deployment.Environment, cfg v1_6.SetOCR3OffRampConfig
e.Logger.Infof("OCR3 config already set on offramp for chain %d", remote)
continue
}
chain := e.SolChains[remote]
addresses, _ := e.ExistingAddresses.AddressesForChain(remote)
mcmState, _ := csState.MaybeLoadMCMSWithTimelockChainStateSolana(chain, addresses)

timelocks[remote] = mcmsSolana.ContractAddress(
mcmState.TimelockProgram,
mcmsSolana.PDASeed(mcmState.TimelockSeed),
)
proposers[remote] = mcmsSolana.ContractAddress(mcmState.McmProgram, mcmsSolana.PDASeed(mcmState.ProposerMcmSeed))
inspectors[remote] = mcmsSolana.NewInspector(chain.Client)

var instructions []solana.Instruction
var txns []mcmsTypes.Transaction
offRampConfigPDA := state.SolChains[remote].OffRampConfigPDA
offRampStatePDA := state.SolChains[remote].OffRampStatePDA
solOffRamp.SetProgramID(state.SolChains[remote].OffRamp)
var authority solana.PublicKey
if cfg.MCMS != nil {
authority, err = FetchTimelockSigner(e, remote)
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to fetch timelock signer: %w", err)
}
} else {
authority = e.SolChains[remote].DeployerKey.PublicKey()
}
for _, arg := range args {
instruction, err := solOffRamp.NewSetOcrConfigInstruction(
arg.OCRPluginType,
Expand All @@ -88,18 +122,48 @@ func SetOCR3ConfigSolana(e deployment.Environment, cfg v1_6.SetOCR3OffRampConfig
arg.Transmitters,
offRampConfigPDA,
offRampStatePDA,
e.SolChains[remote].DeployerKey.PublicKey(),
authority,
).ValidateAndBuild()
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to generate instructions: %w", err)
}
instructions = append(instructions, instruction)
if cfg.MCMS == nil {
instructions = append(instructions, instruction)
} else {
tx, err := BuildMCMSTxn(instruction, state.SolChains[remote].OffRamp.String(), ccipChangeset.OffRamp)
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to create transaction: %w", err)
}
txns = append(txns, *tx)
}
}
if cfg.MCMS == nil {
if err := e.SolChains[remote].Confirm(instructions); err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to confirm instructions: %w", err)
}
} else {
batches = append(batches, mcmsTypes.BatchOperation{
ChainSelector: mcmsTypes.ChainSelector(remote),
Transactions: txns,
})
}
}
if cfg.MCMS != nil {
proposal, err := proposalutils.BuildProposalFromBatchesV2(
e,
timelocks,
proposers,
inspectors,
batches,
"set ocr3 config for 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},
}, nil
}
return deployment.ChangesetOutput{}, nil
}
Expand Down
41 changes: 40 additions & 1 deletion deployment/ccip/changeset/solana/cs_solana_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
var _ deployment.ChangeSet[DeploySolanaTokenConfig] = DeploySolanaToken
var _ deployment.ChangeSet[MintSolanaTokenConfig] = MintSolanaToken
var _ deployment.ChangeSet[CreateSolanaTokenATAConfig] = CreateSolanaTokenATA
var _ deployment.ChangeSet[SetTokenMintAuthorityConfig] = SetTokenMintAuthority

// TODO: add option to set token mint authority by taking in its public key
// might need to take authority private key if it needs to sign that
Expand Down Expand Up @@ -166,8 +167,10 @@ type CreateSolanaTokenATAConfig struct {

func CreateSolanaTokenATA(e deployment.Environment, cfg CreateSolanaTokenATAConfig) (deployment.ChangesetOutput, error) {
chain := e.SolChains[cfg.ChainSelector]
state, _ := ccipChangeset.LoadOnchainState(e)
chainState := state.SolChains[cfg.ChainSelector]

tokenprogramID, err := GetTokenProgramID(cfg.TokenProgram)
tokenprogramID, err := chainState.TokenToTokenProgram(cfg.TokenPubkey)
if err != nil {
return deployment.ChangesetOutput{}, err
}
Expand Down Expand Up @@ -197,3 +200,39 @@ func CreateSolanaTokenATA(e deployment.Environment, cfg CreateSolanaTokenATAConf

return deployment.ChangesetOutput{}, nil
}

type SetTokenMintAuthorityConfig struct {
ChainSelector uint64
TokenPubkey solana.PublicKey
NewAuthority solana.PublicKey
}

func SetTokenMintAuthority(e deployment.Environment, cfg SetTokenMintAuthorityConfig) (deployment.ChangesetOutput, error) {
chain := e.SolChains[cfg.ChainSelector]
state, _ := ccipChangeset.LoadOnchainState(e)
chainState := state.SolChains[cfg.ChainSelector]

tokenprogramID, err := chainState.TokenToTokenProgram(cfg.TokenPubkey)
if err != nil {
return deployment.ChangesetOutput{}, err
}

ix, err := solTokenUtil.SetTokenMintAuthority(
tokenprogramID,
cfg.NewAuthority,
cfg.TokenPubkey,
chain.DeployerKey.PublicKey(),
)
if err != nil {
return deployment.ChangesetOutput{}, err
}

// confirm instructions
if err = chain.Confirm([]solana.Instruction{ix}); err != nil {
e.Logger.Errorw("Failed to confirm instructions for ATA creation", "chain", chain.String(), "err", err)
return deployment.ChangesetOutput{}, err
}
e.Logger.Infow("Set token mint authority on", "chain", cfg.ChainSelector, "for token", cfg.TokenPubkey.String(), "newAuthority", cfg.NewAuthority.String())

return deployment.ChangesetOutput{}, nil
}
1 change: 0 additions & 1 deletion deployment/ccip/changeset/solana/cs_solana_token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ func TestSolanaTokenOps(t *testing.T) {
changeset_solana.CreateSolanaTokenATAConfig{
ChainSelector: solChain1,
TokenPubkey: tokenAddress,
TokenProgram: ccipChangeset.SPL2022Tokens,
ATAList: []string{deployerKey.String(), testUserPubKey.String()},
},
),
Expand Down
Loading

0 comments on commit 1a9a8ca

Please sign in to comment.