Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CCIP-4593 Create rmnproxy setRMN changeset #15674

Merged
merged 12 commits into from
Dec 13, 2024
1 change: 1 addition & 0 deletions deployment/ccip/changeset/cs_add_chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
)

func TestAddChainInbound(t *testing.T) {
t.Skipf("Skipping test as it is running into timeout issues, move the test into integration in-memory tests")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you create a ticket for this? Its weird that its timing out, it takes 90s on my laptop

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

t.Parallel()
// 4 chains where the 4th is added after initial deployment.
e := NewMemoryEnvironment(t,
Expand Down
24 changes: 18 additions & 6 deletions deployment/ccip/changeset/cs_deploy_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ var _ deployment.ChangeSet[DeployChainContractsConfig] = DeployChainContracts
// DeployChainContracts is idempotent. If there is an error, it will return the successfully deployed addresses and the error so that the caller can call the
// changeset again with the same input to retry the failed deployment.
// Caller should update the environment's address book with the returned addresses.
// Points to note :
// In case of migrating from legacy ccip to 1.6, the previous RMN address should be set while deploying RMNRemote.
// if there is no existing RMN address found, RMNRemote will be deployed with 0x0 address for previous RMN address
// which will set RMN to 0x0 address immutably in RMNRemote.
func DeployChainContracts(env deployment.Environment, c DeployChainContractsConfig) (deployment.ChangesetOutput, error) {
if err := c.Validate(); err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("invalid DeployChainContractsConfig: %w", err)
Expand Down Expand Up @@ -192,6 +196,14 @@ func deployChainContracts(
} else {
e.Logger.Infow("receiver already deployed", "addr", chainState.Receiver.Address, "chain", chain.String())
}
var rmnLegacyAddr common.Address
if chainState.MockRMN != nil {
rmnLegacyAddr = chainState.MockRMN.Address()
}
// TODO add legacy RMN here when 1.5 contracts are available
if rmnLegacyAddr == (common.Address{}) {
e.Logger.Warnf("No legacy RMN contract found for chain %s, will not setRMN in RMNRemote", chain.String())
}
rmnRemoteContract := chainState.RMNRemote
if chainState.RMNRemote == nil {
// TODO: Correctly configure RMN remote.
Expand All @@ -201,8 +213,7 @@ func deployChainContracts(
chain.DeployerKey,
chain.Client,
chain.Selector,
// Indicates no legacy RMN contract
common.HexToAddress("0x0"),
rmnLegacyAddr,
)
return deployment.ContractDeploy[*rmn_remote.RMNRemote]{
rmnRemoteAddr, rmnRemote, tx, deployment.NewTypeAndVersion(RMNRemote, deployment.Version1_6_0_dev), err2,
Expand All @@ -216,6 +227,7 @@ func deployChainContracts(
} else {
e.Logger.Infow("rmn remote already deployed", "chain", chain.String(), "addr", chainState.RMNRemote.Address)
}

activeDigest, err := rmnHome.GetActiveDigest(&bind.CallOpts{})
if err != nil {
e.Logger.Errorw("Failed to get active digest", "chain", chain.String(), "err", err)
Expand All @@ -237,8 +249,8 @@ func deployChainContracts(

// we deploy a new RMNProxy so that RMNRemote can be tested first before pointing it to the main Existing RMNProxy
// To differentiate between the two RMNProxies, we will deploy new one with Version1_6_0_dev
rmnProxyContract := chainState.RMNProxyNew
if chainState.RMNProxyNew == nil {
rmnProxyContract := chainState.RMNProxy
if chainState.RMNProxy == nil {
// we deploy a new rmnproxy contract to test RMNRemote
rmnProxy, err := deployment.DeployContract(e.Logger, chain, ab,
func(chain deployment.Chain) deployment.ContractDeploy[*rmn_proxy_contract.RMNProxyContract] {
Expand All @@ -252,12 +264,12 @@ func deployChainContracts(
}
})
if err != nil {
e.Logger.Errorw("Failed to deploy RMNProxyNew", "chain", chain.String(), "err", err)
e.Logger.Errorw("Failed to deploy RMNProxy", "chain", chain.String(), "err", err)
return err
}
rmnProxyContract = rmnProxy.Contract
} else {
e.Logger.Infow("rmn proxy already deployed", "chain", chain.String(), "addr", chainState.RMNProxyNew.Address)
e.Logger.Infow("rmn proxy already deployed", "chain", chain.String(), "addr", chainState.RMNProxy.Address)
}
if chainState.TestRouter == nil {
_, err := deployment.DeployContract(e.Logger, chain, ab,
Expand Down
1 change: 0 additions & 1 deletion deployment/ccip/changeset/cs_initial_add_chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ func TestInitialAddChainAppliedTwice(t *testing.T) {
require.NoError(t, err)
// send requests
chain1, chain2 := allChains[0], allChains[1]

_, err = AddLanes(e.Env, AddLanesConfig{
LaneConfigs: []LaneConfig{
{
Expand Down
8 changes: 2 additions & 6 deletions deployment/ccip/changeset/cs_prerequisites.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,23 +133,19 @@ func deployPrerequisiteContracts(e deployment.Environment, ab deployment.Address
weth9Contract = chainState.Weth9
tokenAdminReg = chainState.TokenAdminRegistry
registryModule = chainState.RegistryModule
rmnProxy = chainState.RMNProxyExisting
rmnProxy = chainState.RMNProxy
r = chainState.Router
mc3 = chainState.Multicall3
}
if rmnProxy == nil {
// we want to replicate the mainnet scenario where RMNProxy is already deployed with some existing RMN
// This will need us to use two different RMNProxy contracts
// 1. RMNProxyNew with RMNRemote - ( deployed later in chain contracts)
// 2. RMNProxyExisting with mockRMN - ( deployed here, replicating the behavior of existing RMNProxy with already set RMN)
rmn, err := deployment.DeployContract(lggr, chain, ab,
func(chain deployment.Chain) deployment.ContractDeploy[*mock_rmn_contract.MockRMNContract] {
rmnAddr, tx2, rmn, err2 := mock_rmn_contract.DeployMockRMNContract(
chain.DeployerKey,
chain.Client,
)
return deployment.ContractDeploy[*mock_rmn_contract.MockRMNContract]{
rmnAddr, rmn, tx2, deployment.NewTypeAndVersion(MockRMN, deployment.Version1_0_0), err2,
Address: rmnAddr, Contract: rmn, Tx: tx2, Tv: deployment.NewTypeAndVersion(MockRMN, deployment.Version1_0_0), Err: err2,
}
})
if err != nil {
Expand Down
100 changes: 100 additions & 0 deletions deployment/ccip/changeset/cs_update_rmn_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,113 @@ import (
"github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock"

"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/common/proposalutils"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_remote"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey"
)

type SetRMNRemoteOnRMNProxyConfig struct {
ChainSelectors []uint64
MCMSConfig *MCMSConfig
}

func (c SetRMNRemoteOnRMNProxyConfig) Validate(state CCIPOnChainState) error {
for _, chain := range c.ChainSelectors {
err := deployment.IsValidChainSelector(chain)
if err != nil {
return err
}
chainState, exists := state.Chains[chain]
if !exists {
return fmt.Errorf("chain %d not found in state", chain)
}
if chainState.RMNRemote == nil {
return fmt.Errorf("RMNRemote not found for chain %d", chain)
}
if chainState.RMNProxy == nil {
return fmt.Errorf("RMNProxy not found for chain %d", chain)
}
}
return nil
}

func SetRMNRemoteOnRMNProxy(e deployment.Environment, cfg SetRMNRemoteOnRMNProxyConfig) (deployment.ChangesetOutput, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming nit (not a big fan of it though) SetRMNRemoteOnRMNProxyChangeset (getting really Java-ey 😢 )

state, err := LoadOnchainState(e)
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to load onchain state: %w", err)
}
if err := cfg.Validate(state); err != nil {
return deployment.ChangesetOutput{}, err
}
var timelockBatch []timelock.BatchChainOperation
multiSigs := make(map[uint64]*gethwrappers.ManyChainMultiSig)
timelocks := make(map[uint64]common.Address)
Comment on lines +56 to +57
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do this so often I feel like we should just have a helper like state.GetAllMCMSes() and state.GetAllTimelocks() 😛

for _, sel := range cfg.ChainSelectors {
chain, exists := e.Chains[sel]
if !exists {
return deployment.ChangesetOutput{}, fmt.Errorf("chain %d not found", sel)
}
txOpts := chain.DeployerKey
if cfg.MCMSConfig != nil {
txOpts = deployment.SimTransactOpts()
}
mcmsOps, err := setRMNRemoteOnRMNProxyOp(txOpts, chain, state.Chains[sel], cfg.MCMSConfig != nil)
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to set RMNRemote on RMNProxy for chain %s: %w", chain.String(), err)
}
if cfg.MCMSConfig != nil {
timelockBatch = append(timelockBatch, timelock.BatchChainOperation{
ChainIdentifier: mcms.ChainIdentifier(sel),
Batch: []mcms.Operation{mcmsOps},
})
multiSigs[sel] = state.Chains[sel].ProposerMcm
timelocks[sel] = state.Chains[sel].Timelock.Address()
}
}
// If we're not using MCMS, we can just return now as we've already confirmed the transactions
if len(timelockBatch) == 0 {
return deployment.ChangesetOutput{}, nil
}
prop, err := proposalutils.BuildProposalFromBatches(
timelocks,
multiSigs,
timelockBatch,
fmt.Sprintf("proposal to set RMNRemote on RMNProxy for chains %v", cfg.ChainSelectors),
cfg.MCMSConfig.MinDelay,
)
if err != nil {
return deployment.ChangesetOutput{}, err
}
return deployment.ChangesetOutput{
Proposals: []timelock.MCMSWithTimelockProposal{
*prop,
},
}, nil
}

func setRMNRemoteOnRMNProxyOp(txOpts *bind.TransactOpts, chain deployment.Chain, chainState CCIPChainState, mcmsEnabled bool) (mcms.Operation, error) {
rmnProxy := chainState.RMNProxy
rmnRemoteAddr := chainState.RMNRemote.Address()
setRMNTx, err := rmnProxy.SetARM(txOpts, rmnRemoteAddr)
if err != nil {
return mcms.Operation{}, fmt.Errorf("failed to build call data/transaction to set RMNRemote on RMNProxy for chain %s: %w", chain.String(), err)
}
if !mcmsEnabled {
_, err = deployment.ConfirmIfNoError(chain, setRMNTx, err)
if err != nil {
return mcms.Operation{}, fmt.Errorf("failed to confirm tx to set RMNRemote on RMNProxy for chain %s: %w", chain.String(), deployment.MaybeDataErr(err))
}
}
return mcms.Operation{
To: rmnProxy.Address(),
Data: setRMNTx.Data(),
Value: big.NewInt(0),
}, nil
}

type RMNNopConfig struct {
NodeIndex uint64
OffchainPublicKey [32]byte
Expand Down
85 changes: 85 additions & 0 deletions deployment/ccip/changeset/cs_update_rmn_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (

"github.com/smartcontractkit/chainlink/deployment"
commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset"
"github.com/smartcontractkit/chainlink/deployment/common/proposalutils"
commontypes "github.com/smartcontractkit/chainlink/deployment/common/types"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_remote"
)
Expand Down Expand Up @@ -206,3 +208,86 @@ func buildRMNRemoteAddressPerChain(e deployment.Environment, state CCIPOnChainSt
}
return rmnRemoteAddressPerChain
}

func TestSetRMNRemoteOnRMNProxy(t *testing.T) {
t.Parallel()
e := NewMemoryEnvironment(t, WithNoJobsAndContracts())
allChains := e.Env.AllChainSelectors()
mcmsCfg := make(map[uint64]commontypes.MCMSWithTimelockConfig)
var err error
for _, c := range e.Env.AllChainSelectors() {
mcmsCfg[c] = proposalutils.SingleGroupTimelockConfig(t)
}
// Need to deploy prerequisites first so that we can form the USDC config
// no proposals to be made, timelock can be passed as nil here
e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{
{
Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployLinkToken),
Config: allChains,
},
{
Changeset: commonchangeset.WrapChangeSet(DeployPrerequisites),
Config: DeployPrerequisiteConfig{
ChainSelectors: allChains,
},
},
{
Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployMCMSWithTimelock),
Config: mcmsCfg,
},
})
require.NoError(t, err)
contractsByChain := make(map[uint64][]common.Address)
state, err := LoadOnchainState(e.Env)
require.NoError(t, err)
for _, chain := range allChains {
rmnProxy := state.Chains[chain].RMNProxy
require.NotNil(t, rmnProxy)
contractsByChain[chain] = []common.Address{rmnProxy.Address()}
}
timelockContractsPerChain := make(map[uint64]*proposalutils.TimelockExecutionContracts)
for _, chain := range allChains {
timelockContractsPerChain[chain] = &proposalutils.TimelockExecutionContracts{
Timelock: state.Chains[chain].Timelock,
CallProxy: state.Chains[chain].CallProxy,
}
}
e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, timelockContractsPerChain, []commonchangeset.ChangesetApplication{
// transfer ownership of RMNProxy to timelock
{
Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock),
Config: commonchangeset.TransferToMCMSWithTimelockConfig{
ContractsByChain: contractsByChain,
MinDelay: 0,
},
},
{
Changeset: commonchangeset.WrapChangeSet(DeployChainContracts),
Config: DeployChainContractsConfig{
ChainSelectors: allChains,
HomeChainSelector: e.HomeChainSel,
},
},
{
Changeset: commonchangeset.WrapChangeSet(SetRMNRemoteOnRMNProxy),
Config: SetRMNRemoteOnRMNProxyConfig{
ChainSelectors: allChains,
MCMSConfig: &MCMSConfig{
MinDelay: 0,
},
},
},
})
require.NoError(t, err)
state, err = LoadOnchainState(e.Env)
require.NoError(t, err)
for _, chain := range allChains {
rmnProxy := state.Chains[chain].RMNProxy
proxyOwner, err := rmnProxy.Owner(nil)
require.NoError(t, err)
require.Equal(t, state.Chains[chain].Timelock.Address(), proxyOwner)
rmnAddr, err := rmnProxy.GetARM(nil)
require.NoError(t, err)
require.Equal(t, rmnAddr, state.Chains[chain].RMNRemote.Address())
}
}
38 changes: 9 additions & 29 deletions deployment/ccip/changeset/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,18 +87,10 @@ type CCIPChainState struct {
commoncs.MCMSWithTimelockState
commoncs.LinkTokenState
commoncs.StaticLinkTokenState
OnRamp *onramp.OnRamp
OffRamp *offramp.OffRamp
FeeQuoter *fee_quoter.FeeQuoter
// We need 2 RMNProxy contracts because we are in the process of migrating to a new version.
// We will switch to the existing one once the migration is complete.
// This is the new RMNProxy contract that will be used for testing RMNRemote before migration.
// Initially RMNProxyNew will point to RMNRemote
RMNProxyNew *rmn_proxy_contract.RMNProxyContract
// Existing RMNProxy contract that is used in production, This already has existing 1.5 RMN set.
// once RMNRemote is tested with RMNProxyNew, as part of migration
// RMNProxyExisting will point to RMNRemote. This will switch over CCIP 1.5 to 1.6
RMNProxyExisting *rmn_proxy_contract.RMNProxyContract
OnRamp *onramp.OnRamp
OffRamp *offramp.OffRamp
FeeQuoter *fee_quoter.FeeQuoter
RMNProxy *rmn_proxy_contract.RMNProxyContract
NonceManager *nonce_manager.NonceManager
TokenAdminRegistry *token_admin_registry.TokenAdminRegistry
RegistryModule *registry_module_owner_custom.RegistryModuleOwnerCustom
Expand Down Expand Up @@ -209,12 +201,12 @@ func (c CCIPChainState) GenerateView() (view.ChainView, error) {
chainView.CommitStore[c.CommitStore.Address().Hex()] = commitStoreView
}

if c.RMNProxyNew != nil {
rmnProxyView, err := v1_0.GenerateRMNProxyView(c.RMNProxyNew)
if c.RMNProxy != nil {
rmnProxyView, err := v1_0.GenerateRMNProxyView(c.RMNProxy)
if err != nil {
return chainView, errors.Wrapf(err, "failed to generate rmn proxy view for rmn proxy %s", c.RMNProxyNew.Address().String())
return chainView, errors.Wrapf(err, "failed to generate rmn proxy view for rmn proxy %s", c.RMNProxy.Address().String())
}
chainView.RMNProxy[c.RMNProxyNew.Address().Hex()] = rmnProxyView
chainView.RMNProxy[c.RMNProxy.Address().Hex()] = rmnProxyView
}
if c.CCIPHome != nil && c.CapabilityRegistry != nil {
chView, err := v1_6.GenerateCCIPHomeView(c.CapabilityRegistry, c.CCIPHome)
Expand Down Expand Up @@ -361,19 +353,7 @@ func LoadChainState(chain deployment.Chain, addresses map[string]deployment.Type
if err != nil {
return state, err
}
state.RMNProxyExisting = armProxy
case deployment.NewTypeAndVersion(ARMProxy, deployment.Version1_6_0_dev).String():
armProxy, err := rmn_proxy_contract.NewRMNProxyContract(common.HexToAddress(address), chain.Client)
if err != nil {
return state, err
}
state.RMNProxyNew = armProxy
case deployment.NewTypeAndVersion(ARMProxy, deployment.Version1_6_0_dev).String():
armProxy, err := rmn_proxy_contract.NewRMNProxyContract(common.HexToAddress(address), chain.Client)
if err != nil {
return state, err
}
state.RMNProxyNew = armProxy
state.RMNProxy = armProxy
case deployment.NewTypeAndVersion(MockRMN, deployment.Version1_0_0).String():
mockRMN, err := mock_rmn_contract.NewMockRMNContract(common.HexToAddress(address), chain.Client)
if err != nil {
Expand Down
6 changes: 6 additions & 0 deletions deployment/ccip/changeset/test_environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,12 @@ func NewEnvironmentWithJobsAndContracts(t *testing.T, tc *TestConfigs, tEnv Test
HomeChainSelector: e.HomeChainSel,
},
},
{
Changeset: commonchangeset.WrapChangeSet(SetRMNRemoteOnRMNProxy),
Config: SetRMNRemoteOnRMNProxyConfig{
ChainSelectors: allChains,
},
},
})
require.NoError(t, err)

Expand Down
Loading
Loading