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

support mcms in forwarder contract config changeset #15533

Merged
merged 8 commits into from
Dec 6, 2024
42 changes: 41 additions & 1 deletion deployment/keystone/changeset/deploy_forwarder.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ var _ deployment.ChangeSet[uint64] = DeployForwarder

// DeployForwarder deploys the KeystoneForwarder contract to all chains in the environment
// callers must merge the output addressbook with the existing one
func DeployForwarder(env deployment.Environment, registryChainSel uint64) (deployment.ChangesetOutput, error) {
// TODO: add selectors to deploy only to specific chains
func DeployForwarder(env deployment.Environment, _ uint64) (deployment.ChangesetOutput, error) {
lggr := env.Logger
ab := deployment.NewMemoryAddressBook()
for _, chain := range env.Chains {
Expand All @@ -25,3 +26,42 @@ func DeployForwarder(env deployment.Environment, registryChainSel uint64) (deplo

return deployment.ChangesetOutput{AddressBook: ab}, nil
}

var _ deployment.ChangeSet[ConfigureForwardContractsRequest] = ConfigureForwardContracts

type ConfigureForwardContractsRequest struct {
WFDonName string
// workflow don node ids in the offchain client. Used to fetch and derive the signer keys
WFNodeIDs []string
RegistryChainSel uint64

UseMCMS bool
}

func (r ConfigureForwardContractsRequest) Validate() error {
if len(r.WFNodeIDs) == 0 {
return fmt.Errorf("WFNodeIDs must not be empty")
}
return nil
}

func ConfigureForwardContracts(env deployment.Environment, req ConfigureForwardContractsRequest) (deployment.ChangesetOutput, error) {
wfDon, err := kslib.NewRegisteredDon(env, kslib.RegisteredDonConfig{
NodeIDs: req.WFNodeIDs,
Name: req.WFDonName,
RegistryChainSel: req.RegistryChainSel,
})
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to create registered don: %w", err)
}
r, err := kslib.ConfigureForwardContracts(&env, kslib.ConfigureForwarderContractsRequest{
Dons: []kslib.RegisteredDon{*wfDon},
UseMCMS: req.UseMCMS,
})
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to configure forward contracts: %w", err)
}
return deployment.ChangesetOutput{
Proposals: r.Proposals,
}, nil
}
90 changes: 90 additions & 0 deletions deployment/keystone/changeset/deploy_forwarder_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package changeset_test

import (
"fmt"
"testing"

"go.uber.org/zap/zapcore"

"github.com/stretchr/testify/require"

"github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers"
"github.com/smartcontractkit/chainlink-common/pkg/logger"
"github.com/smartcontractkit/chainlink/deployment"
commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset"
"github.com/smartcontractkit/chainlink/deployment/environment/memory"
"github.com/smartcontractkit/chainlink/deployment/keystone/changeset"
)
Expand Down Expand Up @@ -45,3 +48,90 @@ func TestDeployForwarder(t *testing.T) {
require.Len(t, oaddrs, 1)
})
}

func TestConfigureForwarders(t *testing.T) {
t.Parallel()

t.Run("no mcms ", func(t *testing.T) {
for _, nChains := range []int{1, 3} {
name := fmt.Sprintf("nChains=%d", nChains)
t.Run(name, func(t *testing.T) {
te := SetupTestEnv(t, TestConfig{
WFDonConfig: DonConfig{N: 4},
AssetDonConfig: DonConfig{N: 4},
WriterDonConfig: DonConfig{N: 4},
NumChains: nChains,
})

var wfNodes []string
for id, _ := range te.WFNodes {
wfNodes = append(wfNodes, id)
}

cfg := changeset.ConfigureForwardContractsRequest{
WFDonName: "test-wf-don",
WFNodeIDs: wfNodes,
RegistryChainSel: te.RegistrySelector,
}
csOut, err := changeset.ConfigureForwardContracts(te.Env, cfg)
require.NoError(t, err)
require.Nil(t, csOut.AddressBook)
require.Len(t, csOut.Proposals, 0)
// check that forwarder
// TODO set up a listener to check that the forwarder is configured
contractSet := te.ContractSets()
for selector := range te.Env.Chains {
cs, ok := contractSet[selector]
require.True(t, ok)
require.NotNil(t, cs.Forwarder)
}
})
}
})

t.Run("with mcms", func(t *testing.T) {
for _, nChains := range []int{1, 3} {
name := fmt.Sprintf("nChains=%d", nChains)
t.Run(name, func(t *testing.T) {
te := SetupTestEnv(t, TestConfig{
WFDonConfig: DonConfig{N: 4},
AssetDonConfig: DonConfig{N: 4},
WriterDonConfig: DonConfig{N: 4},
NumChains: nChains,
UseMCMS: true,
})

var wfNodes []string
for id, _ := range te.WFNodes {
wfNodes = append(wfNodes, id)
}

cfg := changeset.ConfigureForwardContractsRequest{
WFDonName: "test-wf-don",
WFNodeIDs: wfNodes,
RegistryChainSel: te.RegistrySelector,
UseMCMS: true,
}
csOut, err := changeset.ConfigureForwardContracts(te.Env, cfg)
require.NoError(t, err)
require.Len(t, csOut.Proposals, nChains)
require.Nil(t, csOut.AddressBook)

timelocks := make(map[uint64]*gethwrappers.RBACTimelock)
for selector, contractSet := range te.ContractSets() {
require.NotNil(t, contractSet.Timelock)
timelocks[selector] = contractSet.Timelock
}
_, err = commonchangeset.ApplyChangesets(t, te.Env, timelocks, []commonchangeset.ChangesetApplication{
{
Changeset: commonchangeset.WrapChangeSet(changeset.ConfigureForwardContracts),
Config: cfg,
},
})
require.NoError(t, err)

})
}
})

}
74 changes: 39 additions & 35 deletions deployment/keystone/changeset/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ func (c TestConfig) Validate() error {
}

type TestEnv struct {
t *testing.T
Env deployment.Environment
RegistrySelector uint64

Expand All @@ -104,6 +105,15 @@ type TestEnv struct {
AssetNodes map[string]memory.Node
}

func (te TestEnv) ContractSets() map[uint64]kslib.ContractSet {
r, err := kslib.GetContractSets(te.Env.Logger, &kslib.GetContractSetsRequest{
Chains: te.Env.Chains,
AddressBook: te.Env.ExistingAddresses,
})
require.NoError(te.t, err)
return r.ContractSets
}

// SetupTestEnv sets up a keystone test environment with the given configuration
func SetupTestEnv(t *testing.T, c TestConfig) TestEnv {
require.NoError(t, c.Validate())
Expand Down Expand Up @@ -250,57 +260,51 @@ func SetupTestEnv(t *testing.T, c TestConfig) TestEnv {

if c.UseMCMS {
// TODO: mcms on all the chains, currently only on the registry chain. need to fix this for forwarders
t.Logf("Enabling MCMS registry chain %d", registryChainSel)
// deploy, configure and xfer ownership of MCMS
timelockCfgs := make(map[uint64]commontypes.MCMSWithTimelockConfig)
for sel := range env.Chains {
t.Logf("Enabling MCMS on chain %d", sel)
timelockCfgs[sel] = commontypes.MCMSWithTimelockConfig{
Canceller: commonchangeset.SingleGroupMCMS(t),
Bypasser: commonchangeset.SingleGroupMCMS(t),
Proposer: commonchangeset.SingleGroupMCMS(t),
TimelockExecutors: env.AllDeployerKeys(),
TimelockMinDelay: big.NewInt(0),
}
}
env, err = commonchangeset.ApplyChangesets(t, env, nil, []commonchangeset.ChangesetApplication{
{
Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployMCMSWithTimelock),
Config: map[uint64]commontypes.MCMSWithTimelockConfig{
registryChainSel: {
Canceller: commonchangeset.SingleGroupMCMS(t),
Bypasser: commonchangeset.SingleGroupMCMS(t),
Proposer: commonchangeset.SingleGroupMCMS(t),
TimelockExecutors: env.AllDeployerKeys(),
TimelockMinDelay: big.NewInt(0),
},
},
Config: timelockCfgs,
},
})
require.NoError(t, err)
// extract the MCMS address
r, err := kslib.GetContractSets(lggr, &kslib.GetContractSetsRequest{
Chains: map[uint64]deployment.Chain{
registryChainSel: env.Chains[registryChainSel],
},
Chains: env.Chains,
AddressBook: env.ExistingAddresses,
})
require.NoError(t, err)
mcms := r.ContractSets[registryChainSel].MCMSWithTimelockState
require.NotNil(t, mcms)
// transfer ownership of all contracts to the MCMS
env, err = commonchangeset.ApplyChangesets(t, env, map[uint64]*gethwrappers.RBACTimelock{registryChainSel: mcms.Timelock}, []commonchangeset.ChangesetApplication{
{
Changeset: commonchangeset.WrapChangeSet(kschangeset.AcceptAllOwnershipsProposal),
Config: &kschangeset.AcceptAllOwnershipRequest{
ChainSelector: registryChainSel,
MinDelay: 0,
for sel := range env.Chains {
mcms := r.ContractSets[sel].MCMSWithTimelockState
require.NotNil(t, mcms, "MCMS not found on chain %d", sel)
require.NoError(t, mcms.Validate())

// transfer ownership of all contracts to the MCMS
env, err = commonchangeset.ApplyChangesets(t, env, map[uint64]*gethwrappers.RBACTimelock{sel: mcms.Timelock}, []commonchangeset.ChangesetApplication{
{
Changeset: commonchangeset.WrapChangeSet(kschangeset.AcceptAllOwnershipsProposal),
Config: &kschangeset.AcceptAllOwnershipRequest{
ChainSelector: sel,
MinDelay: 0,
},
},
},
})
require.NoError(t, err)
// ensure the MCMS is deployed
req = &keystone.GetContractSetsRequest{
Chains: env.Chains,
AddressBook: env.ExistingAddresses,
})
require.NoError(t, err)
}
contractSetsResp, err = keystone.GetContractSets(lggr, req)
require.NoError(t, err)
require.Len(t, contractSetsResp.ContractSets, len(env.Chains))
// check the mcms contract on registry chain
gotMCMS := contractSetsResp.ContractSets[registryChainSel].MCMSWithTimelockState
require.NoError(t, gotMCMS.Validate())
}
return TestEnv{
t: t,
Env: env,
RegistrySelector: registryChainSel,
WFNodes: wfNodes,
Expand Down
Loading
Loading