Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bindings/generated/latest/ccip/factory/factory.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 2 additions & 5 deletions contracts/ccip/factory/daml/CCIP/Factory.daml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ import CCIP.LockReleaseTokenPoolTypes (TransferTimeout(..))
-- Security/flow constraints:
-- 1. Contract may be created by an arbitrary bootstrap party.
-- 2. No deployment is possible until ownership is handed over to `mcmsParty`
-- (owner == mcmsParty).
-- (owner == mcmsParty) via `SetOwnerToMCMS` (controller `mcmsParty`).
-- 3. Every deployed component instanceId is tracked in `usedInstanceIds` and
-- cannot be reused.
-- 4. All deploy choices are consuming; each recreates CCIPFactory with
Expand Down Expand Up @@ -205,10 +205,7 @@ template CCIPFactory

_ -> abort $ "E_UNKNOWN_FUNCTION: " <> functionName

-- | One-time ownership handover from bootstrap owner to MCMS party.
-- This is consuming because signatory changes.
-- Both owner and mcmsParty must authorize: owner gives up ownership,
-- mcmsParty consents to become the new signatory.
-- | One-time ownership transfer: recreates the factory with owner = mcmsParty.
choice SetOwnerToMCMS : ContractId CCIPFactory
controller mcmsParty
do
Expand Down
13 changes: 4 additions & 9 deletions contracts/ccip/test/daml/CCIP/FactoryTest/FactoryMCMSTest.daml
Original file line number Diff line number Diff line change
Expand Up @@ -74,25 +74,20 @@ testFactoryCreate = script do

pure ()

-- | Test successful ownership transfer to MCMS
-- Note: SetOwnerToMCMS requires the mcmsParty to authorize the new contract creation.
-- In Daml, when a choice creates a contract with a new signatory, that party must authorize.
-- For test purposes, we use the same party as both bootstrap and mcms to demonstrate the flow.
-- | Test SetOwnerToMCMS rejection when owner == mcmsParty.
-- Cross-party happy path is covered in integration tests (ccip_factory_mcms_test.go).
testSetOwnerToMCMS : Script ()
testSetOwnerToMCMS = script do
-- Use the same party for both roles to test the ownership transfer logic
-- The key assertion (owner /= mcmsParty) will fail, so we test the happy path differently
bootstrapOwner <- allocateParty "bootstrap_owner"

let instanceId = "factory-mcms-transfer"

-- Create factory with bootstrapOwner as owner and also as mcmsParty
-- This allows us to test the SetOwnerToMCMS choice without cross-party auth issues
-- owner == mcmsParty exercises only the assertion guard (owner /= mcmsParty)
factoryCid <- submit bootstrapOwner do
createCmd CCIPFactory with
instanceId
owner = bootstrapOwner
mcmsParty = bootstrapOwner -- Same party to avoid authorization issues
mcmsParty = bootstrapOwner
usedInstanceIds = Map.empty
deployedContracts = Map.empty
perPartyRouterFactoryDeployed = False
Expand Down
Binary file modified contracts/dars/current/ccip-factory-current.dar
Binary file not shown.
Binary file modified contracts/dars/current/ccip-test-current.dar
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import (
opcontract "github.com/smartcontractkit/chainlink-canton/deployment/utils/operations/contract"
)

// DeployFactoryAndSetOwnerToMCMS deploys a CCIPFactory and returns an MCMS proposal to transfer ownership.
// DeployFactoryAndSetOwnerToMCMS deploys a CCIPFactory and returns an MCMS proposal to transfer
// ownership via SetOwnerToMCMS (controller = mcmsParty).
type DeployFactoryAndSetOwnerToMCMSConfig struct {
OwnerParty string `json:"ownerParty" yaml:"ownerParty"`
MCMSParty string `json:"mcmsParty" yaml:"mcmsParty"`
Expand Down
1 change: 1 addition & 0 deletions deployment/changesets/deploy_from_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ func (d DeployCCVFromFactory) Apply(e cldf.Environment, config CantonCSDeps[Depl
}

// --- SetFactoryOwnerToMCMS ---
// Encodes an MCMS proposal for SetOwnerToMCMS (controller = mcmsParty only).

type SetFactoryOwnerToMCMSConfig struct {
FactoryQualifier string `json:"factoryQualifier" yaml:"factoryQualifier"`
Expand Down
2 changes: 1 addition & 1 deletion deployment/operations/ccip/factory/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ var DeployPerPartyRouterFactory = contract.NewExercise(contract.ExerciseParams[f
var SetOwnerToMCMS = contract.NewExercise(contract.ExerciseParams[factorybindings.SetOwnerToMCMS]{
Name: "canton/ccip/factory/set_owner_to_mcms",
Version: Version,
Description: "Transfers CCIPFactory ownership from bootstrap owner to MCMS party",
Description: "Transfers CCIPFactory ownership to mcmsParty (SetOwnerToMCMS)",
ContractType: ContractType,
Template: factorybindings.CCIPFactory{},
Method: factorybindings.CCIPFactory{}.SetOwnerToMCMS,
Expand Down
17 changes: 6 additions & 11 deletions integration-tests/mcms/ccip_factory_mcms_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,16 +293,16 @@ func TestCCIP_MCMSFactoryDeploy(t *testing.T) {

// TestCCIP_MCMSFactoryDeploy_FullGovernance validates the complete MCMS governance lifecycle:
// 1. Bootstrap party deploys CCIPFactory (owner != mcmsParty)
// 2. Bootstrap party calls SetOwnerToMCMS to transfer ownership to MCMS party
// 2. mcmsParty exercises SetOwnerToMCMS to transfer factory ownership
// 3. All CCIP components deployed through MCMS Bypasser operations targeting the factory
// 4. Factory state verified to contain all deployed contracts
//
// This tests the realistic scenario where an arbitrary party bootstraps the infrastructure
// and then hands over governance to a multi-sig MCMS system.
// and MCMS takes over governance via the mcmsParty-controlled handover choice.
func TestCCIP_MCMSFactoryDeploy_FullGovernance(t *testing.T) {
t.Parallel()

// Use environment with two parties on the same participant for multi-party submissions
// Use environment with two parties on the same participant (bootstrap + MCMS).
env := GetSharedCCIPMCMSTwoParticipantEnvironment(t)
participant := env.Participant
mcmsEncoder := env.McmsEncoder
Expand Down Expand Up @@ -337,9 +337,7 @@ func TestCCIP_MCMSFactoryDeploy_FullGovernance(t *testing.T) {
require.Equal(t, mcmsParty, initialFields["mcmsParty"], "initial factory mcmsParty should be mcmsParty")
t.Logf("Verified initial state: owner=%s, mcmsParty=%s (different parties)", initialFields["owner"], initialFields["mcmsParty"])

// --- Step 3: Bootstrap party calls SetOwnerToMCMS to transfer ownership ---
// This requires both parties to authorize: owner (controller) and mcmsParty (new signatory).
// We submit from participant with ActAs containing both parties.
// --- Step 3: mcmsParty exercises SetOwnerToMCMS (controller = mcmsParty only) ---
factoryCid = setFactoryOwnerToMCMS(t, participant, mcmsParty, factoryCid)
t.Logf("SetOwnerToMCMS executed: new factory CID=%s", factoryCid)

Expand Down Expand Up @@ -638,7 +636,7 @@ func createCCIPFactory(

// createCCIPFactoryWithMCMS creates a CCIPFactory contract with separate owner and mcmsParty.
// This allows testing the full governance flow where a bootstrap party deploys the factory
// and then hands over ownership to MCMS via SetOwnerToMCMS.
// and mcmsParty takes ownership via SetOwnerToMCMS.
func createCCIPFactoryWithMCMS(
t *testing.T,
participant canton.Participant,
Expand Down Expand Up @@ -680,10 +678,7 @@ func createCCIPFactoryWithMCMS(
// transferring ownership from the current owner to mcmsParty.
// Returns the new factory contract ID.
//
// NOTE: This requires both owner and mcmsParty to authorize because:
// - owner is the controller of the SetOwnerToMCMS choice
// - mcmsParty becomes the signatory of the new factory contract
// The submission must include both parties in ActAs.
// SetOwnerToMCMS is controller mcmsParty; submit with ActAs = mcmsParty.
func setFactoryOwnerToMCMS(
t *testing.T,
participant canton.Participant,
Expand Down
8 changes: 4 additions & 4 deletions integration-tests/mcms/shared_setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ type SharedCCIPMCMSEnvironment struct {

// SharedCCIPMCMSTwoParticipantEnvironment extends SharedCCIPMCMSEnvironment with a second party.
// Used for tests that validate full MCMS governance flow where a bootstrap party deploys the factory
// and then hands over ownership to MCMS party.
// Both parties are on the same participant to enable multi-party submissions.
// and mcmsParty takes ownership via SetOwnerToMCMS.
// Both parties are on the same participant for factory creation and handover submissions.
type SharedCCIPMCMSTwoParticipantEnvironment struct {
SharedCCIPMCMSEnvironment
BootstrapParty string // Second party on the same participant
Expand Down Expand Up @@ -263,8 +263,8 @@ func GetSharedCCIPMCMSEnvironment(t *testing.T) *SharedCCIPMCMSEnvironment {

// GetSharedCCIPMCMSTwoParticipantEnvironment initializes a shared environment with two parties
// on the same participant and all CCIP and MCMS packages. Used for tests that validate full MCMS
// governance flow where a bootstrap party deploys the factory and then hands over ownership to MCMS party.
// Both parties are on the same participant to enable multi-party submissions (ActAs with both parties).
// governance flow where a bootstrap party deploys the factory and mcmsParty exercises SetOwnerToMCMS.
// Both parties share one participant so each can submit with its own ActAs.
func GetSharedCCIPMCMSTwoParticipantEnvironment(t *testing.T) *SharedCCIPMCMSTwoParticipantEnvironment {
t.Helper()

Expand Down
Loading