From 015cf7f9c14aad940f993ae6a8019d96ed485e4b Mon Sep 17 00:00:00 2001 From: Pablo Date: Mon, 17 Feb 2025 13:02:26 -0600 Subject: [PATCH 01/11] feat: add funding changesets for mcms singer accounts --- .../common/changeset/solana/fund_mcm_pdas.go | 87 ++++++ .../changeset/solana/fund_mcm_pdas_test.go | 251 ++++++++++++++++++ deployment/common/changeset/solana/helpers.go | 33 +++ 3 files changed, 371 insertions(+) create mode 100644 deployment/common/changeset/solana/fund_mcm_pdas.go create mode 100644 deployment/common/changeset/solana/fund_mcm_pdas_test.go create mode 100644 deployment/common/changeset/solana/helpers.go diff --git a/deployment/common/changeset/solana/fund_mcm_pdas.go b/deployment/common/changeset/solana/fund_mcm_pdas.go new file mode 100644 index 00000000000..b98fe1a82a2 --- /dev/null +++ b/deployment/common/changeset/solana/fund_mcm_pdas.go @@ -0,0 +1,87 @@ +package solana + +import ( + "fmt" + + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/rpc" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/changeset/state" +) + +var _ deployment.ChangeSetV2[FundMCMSignerConfig] = FundMCMSignersChangeset{} + +type FundMCMSignerConfig struct { + AmountsPerChain map[uint64]uint64 +} + +// FundMCMSignersChangeset is a changeset that funds the MCMS signers on each chain. It will find the +// singer PDA for the proposer, canceller and bypasser MCM as well as the timelock signer PDA and send the amount of +// SOL specified in the config to each of them. +type FundMCMSignersChangeset struct{} + +// VerifyPreconditions checks if the deployer has enough SOL to fund the MCMS signers on each chain. +func (f FundMCMSignersChangeset) VerifyPreconditions(e deployment.Environment, config FundMCMSignerConfig) error { + // the number of accounts to fund per chain (bypasser, canceller, proposer, timelock) + numOfAccountsToFund := 4 + for chainSelector, amount := range config.AmountsPerChain { + solChain, ok := e.SolChains[chainSelector] + if !ok { + return fmt.Errorf("solana chain not found for selector %d", chainSelector) + } + addreses, err := e.ExistingAddresses.AddressesForChain(chainSelector) + if err != nil { + return fmt.Errorf("failed to get existing addresses: %w", err) + } + mcmState, err := state.MaybeLoadMCMSWithTimelockChainStateSolana(solChain, addreses) + if err != nil { + return fmt.Errorf("failed to load MCMS state: %w", err) + } + // Check if seeds are empty + if mcmState.ProposerMcmSeed == [32]byte{} || mcmState.TimelockSeed == [32]byte{} || mcmState.CancellerMcmSeed == [32]byte{} || mcmState.BypasserMcmSeed == [32]byte{} { + return fmt.Errorf("mcm/timelock seeds are empty, please deploy MCMS contracts first") + } + // Check if program IDs exists + if mcmState.McmProgram.IsZero() || mcmState.TimelockProgram.IsZero() { + return fmt.Errorf("mcm/timelock program IDs are empty, please deploy MCMS contracts first") + } + result, err := solChain.Client.GetBalance(e.GetContext(), solChain.DeployerKey.PublicKey(), rpc.CommitmentConfirmed) + if err != nil { + return fmt.Errorf("failed to get deployer balance: %w", err) + } + requiredAmount := uint64(numOfAccountsToFund) * amount + if result.Value < requiredAmount { + return fmt.Errorf("deployer balance is insufficient, required: %d, actual: %d", requiredAmount, result.Value) + } + } + return nil + +} + +// Apply funds the MCMS signers on each chain. +func (f FundMCMSignersChangeset) Apply(e deployment.Environment, config FundMCMSignerConfig) (deployment.ChangesetOutput, error) { + for chainSelector, amount := range config.AmountsPerChain { + solChain := e.SolChains[chainSelector] + addreses, err := e.ExistingAddresses.AddressesForChain(chainSelector) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to get existing addresses: %w", err) + } + mcmState, err := state.MaybeLoadMCMSWithTimelockChainStateSolana(solChain, addreses) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to load MCMS state: %w", err) + } + + accounts := []solana.PublicKey{ + state.GetTimelockSignerPDA(mcmState.TimelockProgram, mcmState.TimelockSeed), + state.GetMCMSignerPDA(mcmState.McmProgram, mcmState.ProposerMcmSeed), + state.GetMCMSignerPDA(mcmState.McmProgram, mcmState.CancellerMcmSeed), + state.GetMCMSignerPDA(mcmState.McmProgram, mcmState.BypasserMcmSeed), + } + err = FundFromDeployerKey(solChain, accounts, amount) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to fund MCMS signers on chain %d: %w", chainSelector, err) + } + } + return deployment.ChangesetOutput{}, nil +} diff --git a/deployment/common/changeset/solana/fund_mcm_pdas_test.go b/deployment/common/changeset/solana/fund_mcm_pdas_test.go new file mode 100644 index 00000000000..5818e258662 --- /dev/null +++ b/deployment/common/changeset/solana/fund_mcm_pdas_test.go @@ -0,0 +1,251 @@ +package solana_test + +import ( + "fmt" + "testing" + + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/rpc" + chainselectors "github.com/smartcontractkit/chain-selectors" + mcmsSolana "github.com/smartcontractkit/mcms/sdk/solana" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/changeset" + commonSolana "github.com/smartcontractkit/chainlink/deployment/common/changeset/solana" + "github.com/smartcontractkit/chainlink/deployment/common/changeset/state" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" + "github.com/smartcontractkit/chainlink/deployment/common/types" + "github.com/smartcontractkit/chainlink/deployment/environment/memory" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +// setupFundingTestEnv deploys all required contracts for the funding test +func setupFundingTestEnv(t *testing.T) deployment.Environment { + lggr := logger.TestLogger(t) + cfg := memory.MemoryEnvironmentConfig{ + Nodes: 1, + SolChains: 1, + } + env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg) + chainSelector := env.AllChainSelectorsSolana()[0] + + config := proposalutils.SingleGroupTimelockConfigV2(t) + // Initialize the address book with a dummy address to avoid deploy precondition errors. + env.ExistingAddresses.Save(chainSelector, "dummyAddress", deployment.TypeAndVersion{Type: "dummy", Version: deployment.Version1_0_0}) + // Deploy MCMS and Timelock + env, err := changeset.Apply(t, env, nil, + changeset.Configure( + deployment.CreateLegacyChangeSet(changeset.DeployMCMSWithTimelockV2), + map[uint64]types.MCMSWithTimelockConfigV2{ + chainSelector: config, + }, + ), + ) + require.NoError(t, err) + return env +} + +func TestFundMCMSignersChangeset_VerifyPreconditions(t *testing.T) { + // Create a logger instance. + lggr := logger.TestLogger(t) + + // Create a valid in–memory environment with one Solana chain. + validEnv := memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{ + SolChains: 1, + }) + validEnv.SolChains[chainselectors.SOLANA_DEVNET.Selector] = deployment.SolChain{} + // Get the sole Solana chain selector. + validSolChainSelector := validEnv.AllChainSelectorsSolana()[0] + + // Save MCMS contract addresses into the environment to simulate that MCMS contracts + // have been deployed. The addresses here are generated using dummy seeds. + timelockID := mcmsSolana.ContractAddress( + solana.NewWallet().PublicKey(), + [32]byte{'t', 'e', 's', 't'}, + ) + mcmDummyProgram := solana.NewWallet().PublicKey() + mcmsProposerID := mcmsSolana.ContractAddress( + mcmDummyProgram, + [32]byte{'t', 'e', 's', 't', '1'}, + ) + + mcmsCancellerID := mcmsSolana.ContractAddress( + mcmDummyProgram, + [32]byte{'t', 'e', 's', 't', '2'}, + ) + + mcmsBypasserID := mcmsSolana.ContractAddress( + mcmDummyProgram, + [32]byte{'t', 'e', 's', 't', '3'}, + ) + validEnv.ExistingAddresses.Save(validSolChainSelector, timelockID, deployment.TypeAndVersion{ + Type: types.RBACTimelock, + Version: deployment.Version1_0_0, + }) + validEnv.ExistingAddresses.Save(validSolChainSelector, mcmsProposerID, deployment.TypeAndVersion{ + Type: types.ProposerManyChainMultisig, + Version: deployment.Version1_0_0, + }) + validEnv.ExistingAddresses.Save(validSolChainSelector, mcmsCancellerID, deployment.TypeAndVersion{ + Type: types.CancellerManyChainMultisig, + Version: deployment.Version1_0_0, + }) + + validEnv.ExistingAddresses.Save(validSolChainSelector, mcmsBypasserID, deployment.TypeAndVersion{ + Type: types.BypasserManyChainMultisig, + Version: deployment.Version1_0_0, + }) + mcmsProposerIDEmpty := mcmsSolana.ContractAddress( + mcmDummyProgram, + [32]byte{}, + ) + + // Create an environment that simulates a chain where the MCMS contracts have not been deployed, + // e.g. missing the required addresses so that the state loader returns empty seeds. + noMCMSEnv := memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{ + Chains: 0, + SolChains: 1, + Nodes: 1, + }) + noMCMSEnv.SolChains[chainselectors.SOLANA_DEVNET.Selector] = deployment.SolChain{} + noMCMSEnv.ExistingAddresses.Save(chainselectors.SOLANA_DEVNET.Selector, mcmsProposerIDEmpty, deployment.TypeAndVersion{ + Type: types.BypasserManyChainMultisig, + Version: deployment.Version1_0_0, + }) + + // Note: We do not call ExistingAddresses.Save on noMCMSEnv. + + // Create an environment with a Solana chain that has an invalid (zero) underlying chain. + invalidSolChainEnv := memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{ + Chains: 0, + SolChains: 0, + Nodes: 1, + }) + // Overwrite the solana chain with an empty one. + invalidSolChainEnv.SolChains[validSolChainSelector] = deployment.SolChain{} + + tests := []struct { + name string + env deployment.Environment + config commonSolana.FundMCMSignerConfig + expectedError string + }{ + { + name: "All preconditions satisfied", + env: validEnv, + config: commonSolana.FundMCMSignerConfig{ + AmountsPerChain: map[uint64]uint64{validSolChainSelector: 100}, + }, + expectedError: "", + }, + { + name: "No Solana chains found in environment", + // Create an environment with zero Solana chains. + env: memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{ + Bootstraps: 1, + Chains: 1, + SolChains: 0, + Nodes: 1, + }), + config: commonSolana.FundMCMSignerConfig{AmountsPerChain: map[uint64]uint64{validSolChainSelector: 100}}, + expectedError: fmt.Sprintf("solana chain not found for selector %d", validSolChainSelector), + }, + { + name: "Chain selector not found in environment", + env: validEnv, + // Use a chain selector that is not present in validEnv. + config: commonSolana.FundMCMSignerConfig{AmountsPerChain: map[uint64]uint64{99999: 100}}, + expectedError: "solana chain not found for selector 99999", + }, + { + name: "MCMS contracts not deployed (empty seeds)", + env: noMCMSEnv, + config: commonSolana.FundMCMSignerConfig{ + AmountsPerChain: map[uint64]uint64{chainselectors.SOLANA_DEVNET.Selector: 100}, + }, + expectedError: "mcm/timelock seeds are empty, please deploy MCMS contracts first", + }, + { + name: "Insufficient deployer balance", + env: validEnv, + config: commonSolana.FundMCMSignerConfig{ + AmountsPerChain: map[uint64]uint64{validSolChainSelector: 9999999999999999999}, + }, + expectedError: "deployer balance is insufficient", + }, + { + name: "Invalid Solana chain in environment", + env: invalidSolChainEnv, + config: commonSolana.FundMCMSignerConfig{ + AmountsPerChain: map[uint64]uint64{validSolChainSelector: 100}, + }, + expectedError: "failed to get existing addresses: chain selector 12463857294658392847: chain not found", + }, + } + + cs := commonSolana.FundMCMSignersChangeset{} + + for _, tt := range tests { + tt := tt // capture range variable + t.Run(tt.name, func(t *testing.T) { + err := cs.VerifyPreconditions(tt.env, tt.config) + if tt.expectedError == "" { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Contains(t, err.Error(), tt.expectedError) + } + }) + } +} + +func TestFundMCMSignersChangeset_Apply(t *testing.T) { + + // Set up the test environment + env := setupFundingTestEnv(t) + + // Build a funding configuration. + // Here, we assume that we want to fund each chain with an amount equal to 1000 SOL per MCMS signer. + // There are 4 signers (bypasser, canceller, proposer, timelock). + amountPerSigner := 589 * solana.LAMPORTS_PER_SOL + amountsPerChain := make(map[uint64]uint64) + for chainSelector := range env.SolChains { + amountsPerChain[chainSelector] = amountPerSigner + } + config := commonSolana.FundMCMSignerConfig{ + AmountsPerChain: amountsPerChain, + } + + // Create the changeset instance. + changesetInstance := commonSolana.FundMCMSignersChangeset{} + + env, err := changeset.ApplyChangesetsV2(t, env, []changeset.ConfiguredChangeSet{ + changeset.Configure(changesetInstance, config), + }) + require.NoError(t, err) + + chainSelector := env.AllChainSelectorsSolana()[0] + solChain := env.SolChains[chainSelector] + addreses, err := env.ExistingAddresses.AddressesForChain(chainSelector) + require.NoError(t, err) + + // Check balances of MCM Signer PDAS + mcmState, err := state.MaybeLoadMCMSWithTimelockChainStateSolana(solChain, addreses) + require.NoError(t, err) + + accounts := []solana.PublicKey{ + state.GetTimelockSignerPDA(mcmState.TimelockProgram, mcmState.TimelockSeed), + state.GetMCMSignerPDA(mcmState.McmProgram, mcmState.ProposerMcmSeed), + state.GetMCMSignerPDA(mcmState.McmProgram, mcmState.CancellerMcmSeed), + state.GetMCMSignerPDA(mcmState.McmProgram, mcmState.BypasserMcmSeed), + } + // Check if the accounts are funded + for _, account := range accounts { + balance, err := solChain.Client.GetBalance(env.GetContext(), account, rpc.CommitmentConfirmed) + t.Logf("Account: %s, Balance: %d", account, balance.Value) + require.NoError(t, err) + require.GreaterOrEqual(t, amountPerSigner, balance.Value) + } +} diff --git a/deployment/common/changeset/solana/helpers.go b/deployment/common/changeset/solana/helpers.go new file mode 100644 index 00000000000..e6dc93c6590 --- /dev/null +++ b/deployment/common/changeset/solana/helpers.go @@ -0,0 +1,33 @@ +package solana + +import ( + "fmt" + + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/programs/system" + + "github.com/smartcontractkit/chainlink/deployment" +) + +// FundFromDeployerKey transfers SOL from the deployer to each provided account and waits for confirmations. +func FundFromDeployerKey(solChain deployment.SolChain, accounts []solana.PublicKey, amount uint64) error { + var ixs []solana.Instruction + for _, account := range accounts { + // Create a transfer instruction using the provided builder. + ix, err := system.NewTransferInstruction( + amount, + solChain.DeployerKey.PublicKey(), // funding account (sender) + account, // recipient account + ).ValidateAndBuild() + if err != nil { + return fmt.Errorf("failed to create transfer instruction: %w", err) + } + ixs = append(ixs, ix) + } + + err := solChain.Confirm(ixs) + if err != nil { + return fmt.Errorf("failed to create transaction: %w", err) + } + return nil +} From 12d2bf57321b65189803ea972e0009fcf1f20821 Mon Sep 17 00:00:00 2001 From: Pablo Date: Mon, 17 Feb 2025 14:32:03 -0600 Subject: [PATCH 02/11] feat: add preloaded programs step to test --- deployment/common/changeset/solana/fund_mcm_pdas_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/deployment/common/changeset/solana/fund_mcm_pdas_test.go b/deployment/common/changeset/solana/fund_mcm_pdas_test.go index 5818e258662..a7d1efbf9ec 100644 --- a/deployment/common/changeset/solana/fund_mcm_pdas_test.go +++ b/deployment/common/changeset/solana/fund_mcm_pdas_test.go @@ -12,6 +12,7 @@ import ( "go.uber.org/zap/zapcore" "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers" "github.com/smartcontractkit/chainlink/deployment/common/changeset" commonSolana "github.com/smartcontractkit/chainlink/deployment/common/changeset/solana" "github.com/smartcontractkit/chainlink/deployment/common/changeset/state" @@ -205,7 +206,8 @@ func TestFundMCMSignersChangeset_Apply(t *testing.T) { // Set up the test environment env := setupFundingTestEnv(t) - + solChainSelectors := env.AllChainSelectorsSolana() + testhelpers.SavePreloadedSolAddresses(t, env, solChainSelectors[0]) // Build a funding configuration. // Here, we assume that we want to fund each chain with an amount equal to 1000 SOL per MCMS signer. // There are 4 signers (bypasser, canceller, proposer, timelock). From 85a0e49d1e0cfa6e3f18db4baed897b84da5bf96 Mon Sep 17 00:00:00 2001 From: Pablo Date: Mon, 17 Feb 2025 14:55:38 -0600 Subject: [PATCH 03/11] fix: load preloaded programs before applying --- deployment/common/changeset/solana/fund_mcm_pdas_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/deployment/common/changeset/solana/fund_mcm_pdas_test.go b/deployment/common/changeset/solana/fund_mcm_pdas_test.go index a7d1efbf9ec..46a81afe88c 100644 --- a/deployment/common/changeset/solana/fund_mcm_pdas_test.go +++ b/deployment/common/changeset/solana/fund_mcm_pdas_test.go @@ -33,6 +33,7 @@ func setupFundingTestEnv(t *testing.T) deployment.Environment { chainSelector := env.AllChainSelectorsSolana()[0] config := proposalutils.SingleGroupTimelockConfigV2(t) + testhelpers.SavePreloadedSolAddresses(t, env, chainSelector) // Initialize the address book with a dummy address to avoid deploy precondition errors. env.ExistingAddresses.Save(chainSelector, "dummyAddress", deployment.TypeAndVersion{Type: "dummy", Version: deployment.Version1_0_0}) // Deploy MCMS and Timelock @@ -45,6 +46,7 @@ func setupFundingTestEnv(t *testing.T) deployment.Environment { ), ) require.NoError(t, err) + return env } @@ -206,8 +208,7 @@ func TestFundMCMSignersChangeset_Apply(t *testing.T) { // Set up the test environment env := setupFundingTestEnv(t) - solChainSelectors := env.AllChainSelectorsSolana() - testhelpers.SavePreloadedSolAddresses(t, env, solChainSelectors[0]) + // Build a funding configuration. // Here, we assume that we want to fund each chain with an amount equal to 1000 SOL per MCMS signer. // There are 4 signers (bypasser, canceller, proposer, timelock). From b907bfbf28fcfec5c5a627aa4eeaa90027613bde Mon Sep 17 00:00:00 2001 From: Pablo Date: Mon, 17 Feb 2025 15:10:40 -0600 Subject: [PATCH 04/11] fix: linter --- .../common/changeset/solana/fund_mcm_pdas.go | 7 ++++--- .../changeset/solana/fund_mcm_pdas_test.go | 16 ++++++++++------ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/deployment/common/changeset/solana/fund_mcm_pdas.go b/deployment/common/changeset/solana/fund_mcm_pdas.go index b98fe1a82a2..616fbd3b108 100644 --- a/deployment/common/changeset/solana/fund_mcm_pdas.go +++ b/deployment/common/changeset/solana/fund_mcm_pdas.go @@ -1,6 +1,7 @@ package solana import ( + "errors" "fmt" "github.com/gagliardetto/solana-go" @@ -24,7 +25,7 @@ type FundMCMSignersChangeset struct{} // VerifyPreconditions checks if the deployer has enough SOL to fund the MCMS signers on each chain. func (f FundMCMSignersChangeset) VerifyPreconditions(e deployment.Environment, config FundMCMSignerConfig) error { // the number of accounts to fund per chain (bypasser, canceller, proposer, timelock) - numOfAccountsToFund := 4 + numOfAccountsToFund := uint64(4) for chainSelector, amount := range config.AmountsPerChain { solChain, ok := e.SolChains[chainSelector] if !ok { @@ -40,11 +41,11 @@ func (f FundMCMSignersChangeset) VerifyPreconditions(e deployment.Environment, c } // Check if seeds are empty if mcmState.ProposerMcmSeed == [32]byte{} || mcmState.TimelockSeed == [32]byte{} || mcmState.CancellerMcmSeed == [32]byte{} || mcmState.BypasserMcmSeed == [32]byte{} { - return fmt.Errorf("mcm/timelock seeds are empty, please deploy MCMS contracts first") + return errors.New("mcm/timelock seeds are empty, please deploy MCMS contracts first") } // Check if program IDs exists if mcmState.McmProgram.IsZero() || mcmState.TimelockProgram.IsZero() { - return fmt.Errorf("mcm/timelock program IDs are empty, please deploy MCMS contracts first") + return errors.New("mcm/timelock program IDs are empty, please deploy MCMS contracts first") } result, err := solChain.Client.GetBalance(e.GetContext(), solChain.DeployerKey.PublicKey(), rpc.CommitmentConfirmed) if err != nil { diff --git a/deployment/common/changeset/solana/fund_mcm_pdas_test.go b/deployment/common/changeset/solana/fund_mcm_pdas_test.go index 46a81afe88c..a0b470e4e59 100644 --- a/deployment/common/changeset/solana/fund_mcm_pdas_test.go +++ b/deployment/common/changeset/solana/fund_mcm_pdas_test.go @@ -83,23 +83,26 @@ func TestFundMCMSignersChangeset_VerifyPreconditions(t *testing.T) { mcmDummyProgram, [32]byte{'t', 'e', 's', 't', '3'}, ) - validEnv.ExistingAddresses.Save(validSolChainSelector, timelockID, deployment.TypeAndVersion{ + err := validEnv.ExistingAddresses.Save(validSolChainSelector, timelockID, deployment.TypeAndVersion{ Type: types.RBACTimelock, Version: deployment.Version1_0_0, }) - validEnv.ExistingAddresses.Save(validSolChainSelector, mcmsProposerID, deployment.TypeAndVersion{ + require.NoError(t, err) + err = validEnv.ExistingAddresses.Save(validSolChainSelector, mcmsProposerID, deployment.TypeAndVersion{ Type: types.ProposerManyChainMultisig, Version: deployment.Version1_0_0, }) - validEnv.ExistingAddresses.Save(validSolChainSelector, mcmsCancellerID, deployment.TypeAndVersion{ + require.NoError(t, err) + err = validEnv.ExistingAddresses.Save(validSolChainSelector, mcmsCancellerID, deployment.TypeAndVersion{ Type: types.CancellerManyChainMultisig, Version: deployment.Version1_0_0, }) - - validEnv.ExistingAddresses.Save(validSolChainSelector, mcmsBypasserID, deployment.TypeAndVersion{ + require.NoError(t, err) + err = validEnv.ExistingAddresses.Save(validSolChainSelector, mcmsBypasserID, deployment.TypeAndVersion{ Type: types.BypasserManyChainMultisig, Version: deployment.Version1_0_0, }) + require.NoError(t, err) mcmsProposerIDEmpty := mcmsSolana.ContractAddress( mcmDummyProgram, [32]byte{}, @@ -113,10 +116,11 @@ func TestFundMCMSignersChangeset_VerifyPreconditions(t *testing.T) { Nodes: 1, }) noMCMSEnv.SolChains[chainselectors.SOLANA_DEVNET.Selector] = deployment.SolChain{} - noMCMSEnv.ExistingAddresses.Save(chainselectors.SOLANA_DEVNET.Selector, mcmsProposerIDEmpty, deployment.TypeAndVersion{ + err = noMCMSEnv.ExistingAddresses.Save(chainselectors.SOLANA_DEVNET.Selector, mcmsProposerIDEmpty, deployment.TypeAndVersion{ Type: types.BypasserManyChainMultisig, Version: deployment.Version1_0_0, }) + require.NoError(t, err) // Note: We do not call ExistingAddresses.Save on noMCMSEnv. From 00f8be62fae59c841664de1ac71b56575ec9fd4f Mon Sep 17 00:00:00 2001 From: Pablo Date: Mon, 17 Feb 2025 15:19:49 -0600 Subject: [PATCH 05/11] fix: linter --- deployment/common/changeset/solana/fund_mcm_pdas.go | 2 +- .../common/changeset/solana/fund_mcm_pdas_test.go | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/deployment/common/changeset/solana/fund_mcm_pdas.go b/deployment/common/changeset/solana/fund_mcm_pdas.go index 616fbd3b108..2a30af3668d 100644 --- a/deployment/common/changeset/solana/fund_mcm_pdas.go +++ b/deployment/common/changeset/solana/fund_mcm_pdas.go @@ -51,7 +51,7 @@ func (f FundMCMSignersChangeset) VerifyPreconditions(e deployment.Environment, c if err != nil { return fmt.Errorf("failed to get deployer balance: %w", err) } - requiredAmount := uint64(numOfAccountsToFund) * amount + requiredAmount := numOfAccountsToFund * amount if result.Value < requiredAmount { return fmt.Errorf("deployer balance is insufficient, required: %d, actual: %d", requiredAmount, result.Value) } diff --git a/deployment/common/changeset/solana/fund_mcm_pdas_test.go b/deployment/common/changeset/solana/fund_mcm_pdas_test.go index a0b470e4e59..f5f494434d4 100644 --- a/deployment/common/changeset/solana/fund_mcm_pdas_test.go +++ b/deployment/common/changeset/solana/fund_mcm_pdas_test.go @@ -35,9 +35,11 @@ func setupFundingTestEnv(t *testing.T) deployment.Environment { config := proposalutils.SingleGroupTimelockConfigV2(t) testhelpers.SavePreloadedSolAddresses(t, env, chainSelector) // Initialize the address book with a dummy address to avoid deploy precondition errors. - env.ExistingAddresses.Save(chainSelector, "dummyAddress", deployment.TypeAndVersion{Type: "dummy", Version: deployment.Version1_0_0}) + err := env.ExistingAddresses.Save(chainSelector, "dummyAddress", deployment.TypeAndVersion{Type: "dummy", Version: deployment.Version1_0_0}) + require.NoError(t, err) + // Deploy MCMS and Timelock - env, err := changeset.Apply(t, env, nil, + env, err = changeset.Apply(t, env, nil, changeset.Configure( deployment.CreateLegacyChangeSet(changeset.DeployMCMSWithTimelockV2), map[uint64]types.MCMSWithTimelockConfigV2{ @@ -53,11 +55,8 @@ func setupFundingTestEnv(t *testing.T) deployment.Environment { func TestFundMCMSignersChangeset_VerifyPreconditions(t *testing.T) { // Create a logger instance. lggr := logger.TestLogger(t) - // Create a valid in–memory environment with one Solana chain. - validEnv := memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{ - SolChains: 1, - }) + validEnv := memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{SolChains: 1}) validEnv.SolChains[chainselectors.SOLANA_DEVNET.Selector] = deployment.SolChain{} // Get the sole Solana chain selector. validSolChainSelector := validEnv.AllChainSelectorsSolana()[0] @@ -209,7 +208,6 @@ func TestFundMCMSignersChangeset_VerifyPreconditions(t *testing.T) { } func TestFundMCMSignersChangeset_Apply(t *testing.T) { - // Set up the test environment env := setupFundingTestEnv(t) From 5926d60dbfc8edba992b40270353da36002c217a Mon Sep 17 00:00:00 2001 From: Pablo Date: Mon, 17 Feb 2025 15:31:00 -0600 Subject: [PATCH 06/11] fix: linter --- deployment/common/changeset/solana/fund_mcm_pdas.go | 1 - 1 file changed, 1 deletion(-) diff --git a/deployment/common/changeset/solana/fund_mcm_pdas.go b/deployment/common/changeset/solana/fund_mcm_pdas.go index 2a30af3668d..dee761ff560 100644 --- a/deployment/common/changeset/solana/fund_mcm_pdas.go +++ b/deployment/common/changeset/solana/fund_mcm_pdas.go @@ -57,7 +57,6 @@ func (f FundMCMSignersChangeset) VerifyPreconditions(e deployment.Environment, c } } return nil - } // Apply funds the MCMS signers on each chain. From b2a9e376e380187b0dd189b252325e08e6c2a25b Mon Sep 17 00:00:00 2001 From: Pablo Date: Mon, 17 Feb 2025 16:24:56 -0600 Subject: [PATCH 07/11] fix: typo --- deployment/common/changeset/solana/fund_mcm_pdas_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deployment/common/changeset/solana/fund_mcm_pdas_test.go b/deployment/common/changeset/solana/fund_mcm_pdas_test.go index f5f494434d4..02d876d5469 100644 --- a/deployment/common/changeset/solana/fund_mcm_pdas_test.go +++ b/deployment/common/changeset/solana/fund_mcm_pdas_test.go @@ -233,11 +233,11 @@ func TestFundMCMSignersChangeset_Apply(t *testing.T) { chainSelector := env.AllChainSelectorsSolana()[0] solChain := env.SolChains[chainSelector] - addreses, err := env.ExistingAddresses.AddressesForChain(chainSelector) + addresses, err := env.ExistingAddresses.AddressesForChain(chainSelector) require.NoError(t, err) // Check balances of MCM Signer PDAS - mcmState, err := state.MaybeLoadMCMSWithTimelockChainStateSolana(solChain, addreses) + mcmState, err := state.MaybeLoadMCMSWithTimelockChainStateSolana(solChain, addresses) require.NoError(t, err) accounts := []solana.PublicKey{ From d347adbe855824a6cef9492e23c2361c552a5dec Mon Sep 17 00:00:00 2001 From: Pablo Date: Mon, 17 Feb 2025 16:36:14 -0600 Subject: [PATCH 08/11] feat: allow granular amounts per signer --- .../common/changeset/solana/fund_mcm_pdas.go | 47 +++++++++++----- .../changeset/solana/fund_mcm_pdas_test.go | 53 ++++++++++++++++--- 2 files changed, 80 insertions(+), 20 deletions(-) diff --git a/deployment/common/changeset/solana/fund_mcm_pdas.go b/deployment/common/changeset/solana/fund_mcm_pdas.go index dee761ff560..2f1f92c5a61 100644 --- a/deployment/common/changeset/solana/fund_mcm_pdas.go +++ b/deployment/common/changeset/solana/fund_mcm_pdas.go @@ -13,8 +13,14 @@ import ( var _ deployment.ChangeSetV2[FundMCMSignerConfig] = FundMCMSignersChangeset{} +type AmountsToTransfer struct { + ProposeMCM uint64 + CancellerMCM uint64 + BypasserMCM uint64 + Timelock uint64 +} type FundMCMSignerConfig struct { - AmountsPerChain map[uint64]uint64 + AmountsPerChain map[uint64]AmountsToTransfer } // FundMCMSignersChangeset is a changeset that funds the MCMS signers on each chain. It will find the @@ -25,8 +31,7 @@ type FundMCMSignersChangeset struct{} // VerifyPreconditions checks if the deployer has enough SOL to fund the MCMS signers on each chain. func (f FundMCMSignersChangeset) VerifyPreconditions(e deployment.Environment, config FundMCMSignerConfig) error { // the number of accounts to fund per chain (bypasser, canceller, proposer, timelock) - numOfAccountsToFund := uint64(4) - for chainSelector, amount := range config.AmountsPerChain { + for chainSelector, chainCfg := range config.AmountsPerChain { solChain, ok := e.SolChains[chainSelector] if !ok { return fmt.Errorf("solana chain not found for selector %d", chainSelector) @@ -51,7 +56,7 @@ func (f FundMCMSignersChangeset) VerifyPreconditions(e deployment.Environment, c if err != nil { return fmt.Errorf("failed to get deployer balance: %w", err) } - requiredAmount := numOfAccountsToFund * amount + requiredAmount := chainCfg.ProposeMCM + chainCfg.CancellerMCM + chainCfg.BypasserMCM + chainCfg.Timelock if result.Value < requiredAmount { return fmt.Errorf("deployer balance is insufficient, required: %d, actual: %d", requiredAmount, result.Value) } @@ -61,7 +66,7 @@ func (f FundMCMSignersChangeset) VerifyPreconditions(e deployment.Environment, c // Apply funds the MCMS signers on each chain. func (f FundMCMSignersChangeset) Apply(e deployment.Environment, config FundMCMSignerConfig) (deployment.ChangesetOutput, error) { - for chainSelector, amount := range config.AmountsPerChain { + for chainSelector, cfgAmounts := range config.AmountsPerChain { solChain := e.SolChains[chainSelector] addreses, err := e.ExistingAddresses.AddressesForChain(chainSelector) if err != nil { @@ -72,15 +77,33 @@ func (f FundMCMSignersChangeset) Apply(e deployment.Environment, config FundMCMS return deployment.ChangesetOutput{}, fmt.Errorf("failed to load MCMS state: %w", err) } - accounts := []solana.PublicKey{ - state.GetTimelockSignerPDA(mcmState.TimelockProgram, mcmState.TimelockSeed), - state.GetMCMSignerPDA(mcmState.McmProgram, mcmState.ProposerMcmSeed), - state.GetMCMSignerPDA(mcmState.McmProgram, mcmState.CancellerMcmSeed), - state.GetMCMSignerPDA(mcmState.McmProgram, mcmState.BypasserMcmSeed), + err = FundFromDeployerKey( + solChain, + []solana.PublicKey{state.GetTimelockSignerPDA(mcmState.TimelockProgram, mcmState.TimelockSeed)}, + cfgAmounts.Timelock) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to fund timelock signer on chain %d: %w", chainSelector) + } + err = FundFromDeployerKey( + solChain, + []solana.PublicKey{state.GetMCMSignerPDA(mcmState.McmProgram, mcmState.ProposerMcmSeed)}, + cfgAmounts.ProposeMCM) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to fund MCMS proposer on chain %d: %w", chainSelector) + } + err = FundFromDeployerKey( + solChain, + []solana.PublicKey{state.GetMCMSignerPDA(mcmState.McmProgram, mcmState.CancellerMcmSeed)}, + cfgAmounts.CancellerMCM) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to fund MCMS canceller on chain %d: %w", chainSelector, err) } - err = FundFromDeployerKey(solChain, accounts, amount) + err = FundFromDeployerKey( + solChain, + []solana.PublicKey{state.GetMCMSignerPDA(mcmState.McmProgram, mcmState.BypasserMcmSeed)}, + cfgAmounts.BypasserMCM) if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("failed to fund MCMS signers on chain %d: %w", chainSelector, err) + return deployment.ChangesetOutput{}, fmt.Errorf("failed to fund mcm bypasser on chain %d: %w", chainSelector) } } return deployment.ChangesetOutput{}, nil diff --git a/deployment/common/changeset/solana/fund_mcm_pdas_test.go b/deployment/common/changeset/solana/fund_mcm_pdas_test.go index 02d876d5469..8197788edd1 100644 --- a/deployment/common/changeset/solana/fund_mcm_pdas_test.go +++ b/deployment/common/changeset/solana/fund_mcm_pdas_test.go @@ -142,7 +142,12 @@ func TestFundMCMSignersChangeset_VerifyPreconditions(t *testing.T) { name: "All preconditions satisfied", env: validEnv, config: commonSolana.FundMCMSignerConfig{ - AmountsPerChain: map[uint64]uint64{validSolChainSelector: 100}, + AmountsPerChain: map[uint64]commonSolana.AmountsToTransfer{validSolChainSelector: { + ProposeMCM: 100, + CancellerMCM: 100, + BypasserMCM: 100, + Timelock: 100, + }}, }, expectedError: "", }, @@ -155,21 +160,38 @@ func TestFundMCMSignersChangeset_VerifyPreconditions(t *testing.T) { SolChains: 0, Nodes: 1, }), - config: commonSolana.FundMCMSignerConfig{AmountsPerChain: map[uint64]uint64{validSolChainSelector: 100}}, + config: commonSolana.FundMCMSignerConfig{ + AmountsPerChain: map[uint64]commonSolana.AmountsToTransfer{validSolChainSelector: { + ProposeMCM: 100, + CancellerMCM: 100, + BypasserMCM: 100, + Timelock: 100, + }}, + }, expectedError: fmt.Sprintf("solana chain not found for selector %d", validSolChainSelector), }, { name: "Chain selector not found in environment", env: validEnv, // Use a chain selector that is not present in validEnv. - config: commonSolana.FundMCMSignerConfig{AmountsPerChain: map[uint64]uint64{99999: 100}}, + config: commonSolana.FundMCMSignerConfig{AmountsPerChain: map[uint64]commonSolana.AmountsToTransfer{99999: { + ProposeMCM: 100, + CancellerMCM: 100, + BypasserMCM: 100, + Timelock: 100, + }}}, expectedError: "solana chain not found for selector 99999", }, { name: "MCMS contracts not deployed (empty seeds)", env: noMCMSEnv, config: commonSolana.FundMCMSignerConfig{ - AmountsPerChain: map[uint64]uint64{chainselectors.SOLANA_DEVNET.Selector: 100}, + AmountsPerChain: map[uint64]commonSolana.AmountsToTransfer{chainselectors.SOLANA_DEVNET.Selector: { + ProposeMCM: 100, + CancellerMCM: 100, + BypasserMCM: 100, + Timelock: 100, + }}, }, expectedError: "mcm/timelock seeds are empty, please deploy MCMS contracts first", }, @@ -177,7 +199,12 @@ func TestFundMCMSignersChangeset_VerifyPreconditions(t *testing.T) { name: "Insufficient deployer balance", env: validEnv, config: commonSolana.FundMCMSignerConfig{ - AmountsPerChain: map[uint64]uint64{validSolChainSelector: 9999999999999999999}, + AmountsPerChain: map[uint64]commonSolana.AmountsToTransfer{validSolChainSelector: { + ProposeMCM: 9999999999999999999, + CancellerMCM: 9999999999999999999, + BypasserMCM: 9999999999999999999, + Timelock: 9999999999999999999, + }}, }, expectedError: "deployer balance is insufficient", }, @@ -185,7 +212,12 @@ func TestFundMCMSignersChangeset_VerifyPreconditions(t *testing.T) { name: "Invalid Solana chain in environment", env: invalidSolChainEnv, config: commonSolana.FundMCMSignerConfig{ - AmountsPerChain: map[uint64]uint64{validSolChainSelector: 100}, + AmountsPerChain: map[uint64]commonSolana.AmountsToTransfer{validSolChainSelector: { + ProposeMCM: 100, + CancellerMCM: 100, + BypasserMCM: 100, + Timelock: 100, + }}, }, expectedError: "failed to get existing addresses: chain selector 12463857294658392847: chain not found", }, @@ -215,9 +247,14 @@ func TestFundMCMSignersChangeset_Apply(t *testing.T) { // Here, we assume that we want to fund each chain with an amount equal to 1000 SOL per MCMS signer. // There are 4 signers (bypasser, canceller, proposer, timelock). amountPerSigner := 589 * solana.LAMPORTS_PER_SOL - amountsPerChain := make(map[uint64]uint64) + amountsPerChain := make(map[uint64]commonSolana.AmountsToTransfer) for chainSelector := range env.SolChains { - amountsPerChain[chainSelector] = amountPerSigner + amountsPerChain[chainSelector] = commonSolana.AmountsToTransfer{ + ProposeMCM: amountPerSigner, + CancellerMCM: amountPerSigner, + BypasserMCM: amountPerSigner, + Timelock: amountPerSigner, + } } config := commonSolana.FundMCMSignerConfig{ AmountsPerChain: amountsPerChain, From ec3d65aadd59e36d24277030fac659ba2ee12d34 Mon Sep 17 00:00:00 2001 From: Pablo Date: Mon, 17 Feb 2025 16:38:38 -0600 Subject: [PATCH 09/11] fix: comment --- deployment/common/changeset/solana/fund_mcm_pdas_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/common/changeset/solana/fund_mcm_pdas_test.go b/deployment/common/changeset/solana/fund_mcm_pdas_test.go index 8197788edd1..5a64b2c0425 100644 --- a/deployment/common/changeset/solana/fund_mcm_pdas_test.go +++ b/deployment/common/changeset/solana/fund_mcm_pdas_test.go @@ -244,7 +244,7 @@ func TestFundMCMSignersChangeset_Apply(t *testing.T) { env := setupFundingTestEnv(t) // Build a funding configuration. - // Here, we assume that we want to fund each chain with an amount equal to 1000 SOL per MCMS signer. + // Here, we assume that we want to fund each chain with an amount equal to 589 SOL per MCMS signer. // There are 4 signers (bypasser, canceller, proposer, timelock). amountPerSigner := 589 * solana.LAMPORTS_PER_SOL amountsPerChain := make(map[uint64]commonSolana.AmountsToTransfer) From cbc2c61d640ec14aa63c62b99384d019ba79db0d Mon Sep 17 00:00:00 2001 From: Pablo Date: Tue, 18 Feb 2025 06:51:50 -0600 Subject: [PATCH 10/11] fix: use different balance values on unit tests and fix linting errors --- .../common/changeset/solana/fund_mcm_pdas.go | 8 ++-- .../changeset/solana/fund_mcm_pdas_test.go | 41 +++++++------------ .../common/proposalutils/mcms_helpers.go | 41 +++++++++++++++++++ 3 files changed, 60 insertions(+), 30 deletions(-) diff --git a/deployment/common/changeset/solana/fund_mcm_pdas.go b/deployment/common/changeset/solana/fund_mcm_pdas.go index 2f1f92c5a61..1151d358e51 100644 --- a/deployment/common/changeset/solana/fund_mcm_pdas.go +++ b/deployment/common/changeset/solana/fund_mcm_pdas.go @@ -24,7 +24,7 @@ type FundMCMSignerConfig struct { } // FundMCMSignersChangeset is a changeset that funds the MCMS signers on each chain. It will find the -// singer PDA for the proposer, canceller and bypasser MCM as well as the timelock signer PDA and send the amount of +// signer PDA for the proposer, canceller and bypasser MCM as well as the timelock signer PDA and send the amount of // SOL specified in the config to each of them. type FundMCMSignersChangeset struct{} @@ -82,14 +82,14 @@ func (f FundMCMSignersChangeset) Apply(e deployment.Environment, config FundMCMS []solana.PublicKey{state.GetTimelockSignerPDA(mcmState.TimelockProgram, mcmState.TimelockSeed)}, cfgAmounts.Timelock) if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("failed to fund timelock signer on chain %d: %w", chainSelector) + return deployment.ChangesetOutput{}, fmt.Errorf("failed to fund timelock signer on chain %d: %w", chainSelector, err) } err = FundFromDeployerKey( solChain, []solana.PublicKey{state.GetMCMSignerPDA(mcmState.McmProgram, mcmState.ProposerMcmSeed)}, cfgAmounts.ProposeMCM) if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("failed to fund MCMS proposer on chain %d: %w", chainSelector) + return deployment.ChangesetOutput{}, fmt.Errorf("failed to fund MCMS proposer on chain %d: %w", chainSelector, err) } err = FundFromDeployerKey( solChain, @@ -103,7 +103,7 @@ func (f FundMCMSignersChangeset) Apply(e deployment.Environment, config FundMCMS []solana.PublicKey{state.GetMCMSignerPDA(mcmState.McmProgram, mcmState.BypasserMcmSeed)}, cfgAmounts.BypasserMCM) if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("failed to fund mcm bypasser on chain %d: %w", chainSelector) + return deployment.ChangesetOutput{}, fmt.Errorf("failed to fund mcm bypasser on chain %d: %w", chainSelector, err) } } return deployment.ChangesetOutput{}, nil diff --git a/deployment/common/changeset/solana/fund_mcm_pdas_test.go b/deployment/common/changeset/solana/fund_mcm_pdas_test.go index 5a64b2c0425..8ebaa557b6f 100644 --- a/deployment/common/changeset/solana/fund_mcm_pdas_test.go +++ b/deployment/common/changeset/solana/fund_mcm_pdas_test.go @@ -53,16 +53,11 @@ func setupFundingTestEnv(t *testing.T) deployment.Environment { } func TestFundMCMSignersChangeset_VerifyPreconditions(t *testing.T) { - // Create a logger instance. lggr := logger.TestLogger(t) - // Create a valid in–memory environment with one Solana chain. validEnv := memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{SolChains: 1}) validEnv.SolChains[chainselectors.SOLANA_DEVNET.Selector] = deployment.SolChain{} - // Get the sole Solana chain selector. validSolChainSelector := validEnv.AllChainSelectorsSolana()[0] - // Save MCMS contract addresses into the environment to simulate that MCMS contracts - // have been deployed. The addresses here are generated using dummy seeds. timelockID := mcmsSolana.ContractAddress( solana.NewWallet().PublicKey(), [32]byte{'t', 'e', 's', 't'}, @@ -121,15 +116,12 @@ func TestFundMCMSignersChangeset_VerifyPreconditions(t *testing.T) { }) require.NoError(t, err) - // Note: We do not call ExistingAddresses.Save on noMCMSEnv. - // Create an environment with a Solana chain that has an invalid (zero) underlying chain. invalidSolChainEnv := memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{ Chains: 0, SolChains: 0, Nodes: 1, }) - // Overwrite the solana chain with an empty one. invalidSolChainEnv.SolChains[validSolChainSelector] = deployment.SolChain{} tests := []struct { @@ -153,7 +145,6 @@ func TestFundMCMSignersChangeset_VerifyPreconditions(t *testing.T) { }, { name: "No Solana chains found in environment", - // Create an environment with zero Solana chains. env: memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{ Bootstraps: 1, Chains: 1, @@ -173,7 +164,6 @@ func TestFundMCMSignersChangeset_VerifyPreconditions(t *testing.T) { { name: "Chain selector not found in environment", env: validEnv, - // Use a chain selector that is not present in validEnv. config: commonSolana.FundMCMSignerConfig{AmountsPerChain: map[uint64]commonSolana.AmountsToTransfer{99999: { ProposeMCM: 100, CancellerMCM: 100, @@ -240,27 +230,21 @@ func TestFundMCMSignersChangeset_VerifyPreconditions(t *testing.T) { } func TestFundMCMSignersChangeset_Apply(t *testing.T) { - // Set up the test environment env := setupFundingTestEnv(t) - - // Build a funding configuration. - // Here, we assume that we want to fund each chain with an amount equal to 589 SOL per MCMS signer. - // There are 4 signers (bypasser, canceller, proposer, timelock). - amountPerSigner := 589 * solana.LAMPORTS_PER_SOL + cfgAmounts := commonSolana.AmountsToTransfer{ + ProposeMCM: 100 * solana.LAMPORTS_PER_SOL, + CancellerMCM: 350 * solana.LAMPORTS_PER_SOL, + BypasserMCM: 75 * solana.LAMPORTS_PER_SOL, + Timelock: 83 * solana.LAMPORTS_PER_SOL, + } amountsPerChain := make(map[uint64]commonSolana.AmountsToTransfer) for chainSelector := range env.SolChains { - amountsPerChain[chainSelector] = commonSolana.AmountsToTransfer{ - ProposeMCM: amountPerSigner, - CancellerMCM: amountPerSigner, - BypasserMCM: amountPerSigner, - Timelock: amountPerSigner, - } + amountsPerChain[chainSelector] = cfgAmounts } config := commonSolana.FundMCMSignerConfig{ AmountsPerChain: amountsPerChain, } - // Create the changeset instance. changesetInstance := commonSolana.FundMCMSignersChangeset{} env, err := changeset.ApplyChangesetsV2(t, env, []changeset.ConfiguredChangeSet{ @@ -283,11 +267,16 @@ func TestFundMCMSignersChangeset_Apply(t *testing.T) { state.GetMCMSignerPDA(mcmState.McmProgram, mcmState.CancellerMcmSeed), state.GetMCMSignerPDA(mcmState.McmProgram, mcmState.BypasserMcmSeed), } - // Check if the accounts are funded + balances := make([]uint64, 4) for _, account := range accounts { balance, err := solChain.Client.GetBalance(env.GetContext(), account, rpc.CommitmentConfirmed) - t.Logf("Account: %s, Balance: %d", account, balance.Value) require.NoError(t, err) - require.GreaterOrEqual(t, amountPerSigner, balance.Value) + t.Logf("Account: %s, Balance: %d", account, balance.Value) + balances = append(balances, balance.Value) } + + require.Equal(t, cfgAmounts.Timelock, balances[0]) + require.Equal(t, cfgAmounts.ProposeMCM, balances[1]) + require.Equal(t, cfgAmounts.CancellerMCM, balances[2]) + require.Equal(t, cfgAmounts.BypasserMCM, balances[3]) } diff --git a/deployment/common/proposalutils/mcms_helpers.go b/deployment/common/proposalutils/mcms_helpers.go index 3cf8ecf8795..5ee4a1aa554 100644 --- a/deployment/common/proposalutils/mcms_helpers.go +++ b/deployment/common/proposalutils/mcms_helpers.go @@ -31,6 +31,47 @@ type TimelockExecutionContracts struct { CallProxy *owner_helpers.CallProxy } +// NewTimelockExecutionContracts creates a new TimelockExecutionContracts struct. +// If there are multiple timelocks or call proxy on the chain, an error is returned. +// Used by CLD'S cli +func NewTimelockExecutionContracts(env deployment.Environment, chainSelector uint64) (*TimelockExecutionContracts, error) { + addrTypeVer, err := env.ExistingAddresses.AddressesForChain(chainSelector) + if err != nil { + return nil, fmt.Errorf("error getting addresses for chain: %w", err) + } + var timelock *owner_helpers.RBACTimelock + var callProxy *owner_helpers.CallProxy + for addr, tv := range addrTypeVer { + if tv.Type == types.RBACTimelock { + if timelock != nil { + return nil, fmt.Errorf("multiple timelocks found on chain %d", chainSelector) + } + var err error + timelock, err = owner_helpers.NewRBACTimelock(common.HexToAddress(addr), env.Chains[chainSelector].Client) + if err != nil { + return nil, fmt.Errorf("error creating timelock: %w", err) + } + } + if tv.Type == types.CallProxy { + if callProxy != nil { + return nil, fmt.Errorf("multiple call proxies found on chain %d", chainSelector) + } + var err error + callProxy, err = owner_helpers.NewCallProxy(common.HexToAddress(addr), env.Chains[chainSelector].Client) + if err != nil { + return nil, fmt.Errorf("error creating call proxy: %w", err) + } + } + } + if timelock == nil || callProxy == nil { + return nil, fmt.Errorf("missing timelock (%T) or call proxy(%T) on chain %d", timelock == nil, callProxy == nil, chainSelector) + } + return &TimelockExecutionContracts{ + Timelock: timelock, + CallProxy: callProxy, + }, nil +} + type RunTimelockExecutorConfig struct { Executor *mcms.Executor TimelockContracts *TimelockExecutionContracts From 84dd6e633a4440c4520d9210c622c4338d2555e3 Mon Sep 17 00:00:00 2001 From: Pablo Date: Tue, 18 Feb 2025 07:22:16 -0600 Subject: [PATCH 11/11] fix: unit tests --- deployment/common/changeset/solana/fund_mcm_pdas_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/common/changeset/solana/fund_mcm_pdas_test.go b/deployment/common/changeset/solana/fund_mcm_pdas_test.go index 8ebaa557b6f..2d810590ce3 100644 --- a/deployment/common/changeset/solana/fund_mcm_pdas_test.go +++ b/deployment/common/changeset/solana/fund_mcm_pdas_test.go @@ -267,7 +267,7 @@ func TestFundMCMSignersChangeset_Apply(t *testing.T) { state.GetMCMSignerPDA(mcmState.McmProgram, mcmState.CancellerMcmSeed), state.GetMCMSignerPDA(mcmState.McmProgram, mcmState.BypasserMcmSeed), } - balances := make([]uint64, 4) + var balances []uint64 for _, account := range accounts { balance, err := solChain.Client.GetBalance(env.GetContext(), account, rpc.CommitmentConfirmed) require.NoError(t, err)