diff --git a/.github/integration-in-memory-tests.yml b/.github/integration-in-memory-tests.yml index a23ec5432ee..f96328953ec 100644 --- a/.github/integration-in-memory-tests.yml +++ b/.github/integration-in-memory-tests.yml @@ -95,4 +95,21 @@ runner-test-matrix: - PR Integration CCIP Tests test_cmd: cd integration-tests/ && go test smoke/ccip/ccip_token_transfer_test.go -timeout 16m -test.parallel=1 -count=1 -json + - id: smoke/ccip/ccip_cs_update_rmn_config_test.go:* + path: integration-tests/smoke/ccip/ccip_cs_update_rmn_config_test.go + test_env_type: in-memory + runs_on: ubuntu-latest + triggers: + - PR Integration CCIP Tests + test_cmd: cd integration-tests/ && go test smoke/ccip/ccip_cs_update_rmn_config_test.go -timeout 20m -test.parallel=1 -count=1 -json + + - id: smoke/ccip/ccip_cs_rmn_curse_uncurse_test.go:* + path: integration-tests/smoke/ccip/ccip_cs_rmn_curse_uncurse_test.go + test_env_type: in-memory + runs_on: ubuntu-latest + triggers: + - PR Integration CCIP Tests + test_cmd: cd integration-tests/ && go test smoke/ccip/ccip_cs_rmn_curse_uncurse_test.go -timeout 10m -test.parallel=1 -count=1 -json + + # END: CCIP tests diff --git a/deployment/ccip/changeset/cs_update_rmn_config.go b/deployment/ccip/changeset/cs_update_rmn_config.go index e220ca50998..9d2023d60c1 100644 --- a/deployment/ccip/changeset/cs_update_rmn_config.go +++ b/deployment/ccip/changeset/cs_update_rmn_config.go @@ -24,6 +24,8 @@ var ( _ deployment.ChangeSet[SetRMNHomeCandidateConfig] = SetRMNHomeCandidateConfigChangeset _ deployment.ChangeSet[PromoteRMNHomeCandidateConfig] = PromoteRMNHomeCandidateConfigChangeset _ deployment.ChangeSet[SetRMNRemoteConfig] = SetRMNRemoteConfigChangeset + _ deployment.ChangeSet[SetRMNHomeDynamicConfigConfig] = SetRMNHomeDynamicConfigChangeset + _ deployment.ChangeSet[RevokeCandidateConfig] = RevokeRMNHomeCandidateConfigChangeset ) type SetRMNRemoteOnRMNProxyConfig struct { @@ -211,8 +213,13 @@ func (c SetRMNHomeCandidateConfig) Validate(state CCIPOnChainState) error { } offchainPublicKeys[node.OffchainPublicKey] = struct{}{} } - rmnHome := state.Chains[c.HomeChainSelector].RMNHome + homeChain, ok := state.Chains[c.HomeChainSelector] + if !ok { + return fmt.Errorf("chain %d not found", c.HomeChainSelector) + } + + rmnHome := homeChain.RMNHome if rmnHome == nil { return fmt.Errorf("RMNHome not found for chain %d", c.HomeChainSelector) } @@ -241,7 +248,13 @@ func (c PromoteRMNHomeCandidateConfig) Validate(state CCIPOnChainState) error { return err } - rmnHome := state.Chains[c.HomeChainSelector].RMNHome + homeChain, ok := state.Chains[c.HomeChainSelector] + + if !ok { + return fmt.Errorf("chain %d not found", c.HomeChainSelector) + } + + rmnHome := homeChain.RMNHome if rmnHome == nil { return fmt.Errorf("RMNHome not found for chain %d", c.HomeChainSelector) } @@ -326,6 +339,10 @@ func SetRMNHomeCandidateConfigChangeset(e deployment.Environment, config SetRMNH config.MCMSConfig.MinDelay, ) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to build proposal for chain %s: %w", homeChain.String(), err) + } + return deployment.ChangesetOutput{ Proposals: []timelock.MCMSWithTimelockProposal{*prop}, }, nil @@ -459,6 +476,150 @@ func (c SetRMNRemoteConfig) Validate() error { return nil } +type SetRMNHomeDynamicConfigConfig struct { + HomeChainSelector uint64 + RMNDynamicConfig rmn_home.RMNHomeDynamicConfig + ActiveDigest [32]byte + MCMS *MCMSConfig +} + +func (c SetRMNHomeDynamicConfigConfig) Validate(e deployment.Environment) error { + err := deployment.IsValidChainSelector(c.HomeChainSelector) + if err != nil { + return err + } + + state, err := LoadOnchainState(e) + if err != nil { + return fmt.Errorf("failed to load onchain state: %w", err) + } + + rmnHome := state.Chains[c.HomeChainSelector].RMNHome + if rmnHome == nil { + return fmt.Errorf("RMNHome not found for chain %s", e.Chains[c.HomeChainSelector].String()) + } + + currentDigest, err := rmnHome.GetActiveDigest(nil) + if err != nil { + return fmt.Errorf("failed to get RMNHome candidate digest for chain %s: %w", e.Chains[c.HomeChainSelector].String(), err) + } + + if currentDigest != c.ActiveDigest { + return fmt.Errorf("onchain active digest (%x) does not match provided digest (%x)", currentDigest[:], c.ActiveDigest[:]) + } + + if len(c.RMNDynamicConfig.OffchainConfig) != 0 { + return errors.New("RMNDynamicConfig.OffchainConfig must be empty") + } + + return nil +} + +func SetRMNHomeDynamicConfigChangeset(e deployment.Environment, cfg SetRMNHomeDynamicConfigConfig) (deployment.ChangesetOutput, error) { + err := cfg.Validate(e) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + state, err := LoadOnchainState(e) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to load onchain state: %w", err) + } + deployerGroup := NewDeployerGroup(e, state, cfg.MCMS) + + chain, exists := e.Chains[cfg.HomeChainSelector] + if !exists { + return deployment.ChangesetOutput{}, fmt.Errorf("chain %d not found", cfg.HomeChainSelector) + } + + rmnHome := state.Chains[cfg.HomeChainSelector].RMNHome + if rmnHome == nil { + return deployment.ChangesetOutput{}, fmt.Errorf("RMNHome not found for chain %s", chain.String()) + } + + deployer, err := deployerGroup.GetDeployer(cfg.HomeChainSelector) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + _, err = rmnHome.SetDynamicConfig(deployer, cfg.RMNDynamicConfig, cfg.ActiveDigest) + + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to set RMNHome dynamic config for chain %s: %w", chain.String(), err) + } + + return deployerGroup.Enact("Set RMNHome dynamic config") +} + +type RevokeCandidateConfig struct { + HomeChainSelector uint64 + CandidateDigest [32]byte + MCMS *MCMSConfig +} + +func (c RevokeCandidateConfig) Validate(e deployment.Environment) error { + err := deployment.IsValidChainSelector(c.HomeChainSelector) + if err != nil { + return err + } + + state, err := LoadOnchainState(e) + if err != nil { + return fmt.Errorf("failed to load onchain state: %w", err) + } + + rmnHome := state.Chains[c.HomeChainSelector].RMNHome + if rmnHome == nil { + return fmt.Errorf("RMNHome not found for chain %s", e.Chains[c.HomeChainSelector].String()) + } + + currentDigest, err := rmnHome.GetCandidateDigest(nil) + if err != nil { + return fmt.Errorf("failed to get RMNHome candidate digest for chain %s: %w", e.Chains[c.HomeChainSelector].String(), err) + } + + if currentDigest != c.CandidateDigest { + return fmt.Errorf("onchain candidate digest (%x) does not match provided digest (%x)", currentDigest[:], c.CandidateDigest[:]) + } + + return nil +} + +func RevokeRMNHomeCandidateConfigChangeset(e deployment.Environment, cfg RevokeCandidateConfig) (deployment.ChangesetOutput, error) { + err := cfg.Validate(e) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + state, err := LoadOnchainState(e) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to load onchain state: %w", err) + } + deployerGroup := NewDeployerGroup(e, state, cfg.MCMS) + + chain, exists := e.Chains[cfg.HomeChainSelector] + if !exists { + return deployment.ChangesetOutput{}, fmt.Errorf("chain %d not found", cfg.HomeChainSelector) + } + + rmnHome := state.Chains[cfg.HomeChainSelector].RMNHome + if rmnHome == nil { + return deployment.ChangesetOutput{}, fmt.Errorf("RMNHome not found for chain %s", chain.String()) + } + + deployer, err := deployerGroup.GetDeployer(cfg.HomeChainSelector) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + _, err = rmnHome.RevokeCandidate(deployer, cfg.CandidateDigest) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to revoke candidate config for chain %s: %w", chain.String(), err) + } + + return deployerGroup.Enact("Revoke candidate config") +} + func SetRMNRemoteConfigChangeset(e deployment.Environment, config SetRMNRemoteConfig) (deployment.ChangesetOutput, error) { state, err := LoadOnchainState(e) if err != nil { diff --git a/deployment/ccip/changeset/cs_rmn_curse_uncurse_test.go b/integration-tests/smoke/ccip/ccip_cs_rmn_curse_uncurse_test.go similarity index 97% rename from deployment/ccip/changeset/cs_rmn_curse_uncurse_test.go rename to integration-tests/smoke/ccip/ccip_cs_rmn_curse_uncurse_test.go index 7a043b36645..2fb9333a14a 100644 --- a/deployment/ccip/changeset/cs_rmn_curse_uncurse_test.go +++ b/integration-tests/smoke/ccip/ccip_cs_rmn_curse_uncurse_test.go @@ -1,4 +1,4 @@ -package changeset_test +package ccip import ( "testing" @@ -120,7 +120,7 @@ func TestRMNCurseIdempotent(t *testing.T) { func TestRMNUncurseIdempotent(t *testing.T) { for _, tc := range testCases { - t.Run(tc.name+"_UNCURESE_IDEMPOTENT_NO_MCMS", func(t *testing.T) { + t.Run(tc.name+"_UNCURSE_IDEMPOTENT_NO_MCMS", func(t *testing.T) { runRmnUncurseIdempotentTest(t, tc) }) } @@ -172,7 +172,14 @@ func runRmnUncurseTest(t *testing.T, tc CurseTestCase) { func transferRMNContractToMCMS(t *testing.T, e *testhelpers.DeployedEnv, state changeset.CCIPOnChainState, timelocksPerChain map[uint64]*proposalutils.TimelockExecutionContracts) { contractsByChain := make(map[uint64][]common.Address) - rmnRemoteAddressesByChain := buildRMNRemoteAddressPerChain(e.Env, state) + rmnRemotePerChain := changeset.BuildRMNRemotePerChain(e.Env, state) + rmnRemoteAddressesByChain := make(map[uint64]common.Address) + for chain, remote := range rmnRemotePerChain { + if remote == nil { + continue + } + rmnRemoteAddressesByChain[chain] = remote.Address() + } for chainSelector, rmnRemoteAddress := range rmnRemoteAddressesByChain { contractsByChain[chainSelector] = []common.Address{rmnRemoteAddress} } diff --git a/deployment/ccip/changeset/cs_update_rmn_config_test.go b/integration-tests/smoke/ccip/ccip_cs_update_rmn_config_test.go similarity index 76% rename from deployment/ccip/changeset/cs_update_rmn_config_test.go rename to integration-tests/smoke/ccip/ccip_cs_update_rmn_config_test.go index 1db80197b15..7a69e3137fa 100644 --- a/deployment/ccip/changeset/cs_update_rmn_config_test.go +++ b/integration-tests/smoke/ccip/ccip_cs_update_rmn_config_test.go @@ -1,8 +1,10 @@ -package changeset_test +package ccip import ( "testing" + "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" @@ -66,6 +68,114 @@ func TestUpdateRMNConfig(t *testing.T) { } } +func TestSetDynamicConfig(t *testing.T) { + e, _ := testhelpers.NewMemoryEnvironment(t) + state, err := changeset.LoadOnchainState(e.Env) + require.NoError(t, err) + rmnHome := state.Chains[e.HomeChainSel].RMNHome + + nops := []changeset.RMNNopConfig{rmnStaging1, rmnStaging2, rmnStaging3} + nodes := make([]rmn_home.RMNHomeNode, 0, len(nops)) + for _, nop := range nops { + nodes = append(nodes, nop.ToRMNHomeNode()) + } + + setRMNHomeCandidateConfig := changeset.SetRMNHomeCandidateConfig{ + HomeChainSelector: e.HomeChainSel, + RMNStaticConfig: rmn_home.RMNHomeStaticConfig{ + Nodes: nodes, + OffchainConfig: []byte(""), + }, + RMNDynamicConfig: rmn_home.RMNHomeDynamicConfig{ + SourceChains: []rmn_home.RMNHomeSourceChain{}, + OffchainConfig: []byte(""), + }, + } + + _, err = changeset.SetRMNHomeCandidateConfigChangeset(e.Env, setRMNHomeCandidateConfig) + require.NoError(t, err) + + candidate, err := rmnHome.GetCandidateDigest(nil) + require.NoError(t, err) + + promoteCandidateConfig := changeset.PromoteRMNHomeCandidateConfig{ + HomeChainSelector: e.HomeChainSel, + DigestToPromote: candidate, + } + + _, err = changeset.PromoteRMNHomeCandidateConfigChangeset(e.Env, promoteCandidateConfig) + require.NoError(t, err) + + active, err := rmnHome.GetActiveDigest(nil) + require.NoError(t, err) + + setDynamicConfig := changeset.SetRMNHomeDynamicConfigConfig{ + HomeChainSelector: e.HomeChainSel, + RMNDynamicConfig: rmn_home.RMNHomeDynamicConfig{ + SourceChains: []rmn_home.RMNHomeSourceChain{ + { + ChainSelector: e.HomeChainSel, + ObserverNodesBitmap: big.NewInt(1), + }, + }, + OffchainConfig: []byte(""), + }, + ActiveDigest: active, + } + + _, err = changeset.SetRMNHomeDynamicConfigChangeset(e.Env, setDynamicConfig) + require.NoError(t, err) + + dynamicConfig, err := rmnHome.GetConfig(nil, active) + require.NoError(t, err) + + require.True(t, dynamicConfig.Ok) + require.Equal(t, setDynamicConfig.RMNDynamicConfig, dynamicConfig.VersionedConfig.DynamicConfig) +} + +func TestRevokeConfig(t *testing.T) { + e, _ := testhelpers.NewMemoryEnvironment(t) + state, err := changeset.LoadOnchainState(e.Env) + require.NoError(t, err) + rmnHome := state.Chains[e.HomeChainSel].RMNHome + + nops := []changeset.RMNNopConfig{rmnStaging1, rmnStaging2, rmnStaging3} + nodes := make([]rmn_home.RMNHomeNode, 0, len(nops)) + for _, nop := range nops { + nodes = append(nodes, nop.ToRMNHomeNode()) + } + + setRMNHomeCandidateConfig := changeset.SetRMNHomeCandidateConfig{ + HomeChainSelector: e.HomeChainSel, + RMNStaticConfig: rmn_home.RMNHomeStaticConfig{ + Nodes: nodes, + OffchainConfig: []byte(""), + }, + RMNDynamicConfig: rmn_home.RMNHomeDynamicConfig{ + SourceChains: []rmn_home.RMNHomeSourceChain{}, + OffchainConfig: []byte(""), + }, + } + + _, err = changeset.SetRMNHomeCandidateConfigChangeset(e.Env, setRMNHomeCandidateConfig) + require.NoError(t, err) + + candidate, err := rmnHome.GetCandidateDigest(nil) + require.NoError(t, err) + + revokeCandidateConfig := changeset.RevokeCandidateConfig{ + HomeChainSelector: e.HomeChainSel, + CandidateDigest: candidate, + } + + _, err = changeset.RevokeRMNHomeCandidateConfigChangeset(e.Env, revokeCandidateConfig) + require.NoError(t, err) + + newCandidate, err := rmnHome.GetCandidateDigest(nil) + require.NoError(t, err) + require.NotEqual(t, candidate, newCandidate) +} + func updateRMNConfig(t *testing.T, tc updateRMNConfigTestCase) { e, _ := testhelpers.NewMemoryEnvironment(t) @@ -92,6 +202,7 @@ func updateRMNConfig(t *testing.T, tc updateRMNConfigTestCase) { }, }, }) + require.NoError(t, err) } rmnHome := state.Chains[e.HomeChainSel].RMNHome