diff --git a/bindings/generated/latest/ccip/factory/factory.go b/bindings/generated/latest/ccip/factory/factory.go index 12595d657..9c8a15393 100644 --- a/bindings/generated/latest/ccip/factory/factory.go +++ b/bindings/generated/latest/ccip/factory/factory.go @@ -34,7 +34,7 @@ var ( const ( PackageName = "ccip-factory" - PackageID = "9fe505c991e91a2e1b1cdaf3bb666a4354415bdd72e906b2fce2cfd4e8bd6edd" + PackageID = "bfb521c6eec55049b548a701b794a177a0ce964dc0761a34fe98e6d6b8e947df" SDKVersion = "3.4.11" ) diff --git a/contracts/ccip/factory/daml/CCIP/Factory.daml b/contracts/ccip/factory/daml/CCIP/Factory.daml index 06f62159a..7951c198a 100644 --- a/contracts/ccip/factory/daml/CCIP/Factory.daml +++ b/contracts/ccip/factory/daml/CCIP/Factory.daml @@ -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 @@ -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 diff --git a/contracts/ccip/test/daml/CCIP/FactoryTest/FactoryMCMSTest.daml b/contracts/ccip/test/daml/CCIP/FactoryTest/FactoryMCMSTest.daml index 42a39d2a9..6befbe19b 100644 --- a/contracts/ccip/test/daml/CCIP/FactoryTest/FactoryMCMSTest.daml +++ b/contracts/ccip/test/daml/CCIP/FactoryTest/FactoryMCMSTest.daml @@ -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 diff --git a/contracts/dars/current/ccip-factory-current.dar b/contracts/dars/current/ccip-factory-current.dar index b7108985f..67f34a841 100644 Binary files a/contracts/dars/current/ccip-factory-current.dar and b/contracts/dars/current/ccip-factory-current.dar differ diff --git a/contracts/dars/current/ccip-test-current.dar b/contracts/dars/current/ccip-test-current.dar index 3c46ac418..22c31b3eb 100644 Binary files a/contracts/dars/current/ccip-test-current.dar and b/contracts/dars/current/ccip-test-current.dar differ diff --git a/deployment/changesets/deploy_factory_and_set_owner_to_mcms.go b/deployment/changesets/deploy_factory_and_set_owner_to_mcms.go index d4a3b35ee..315e1d173 100644 --- a/deployment/changesets/deploy_factory_and_set_owner_to_mcms.go +++ b/deployment/changesets/deploy_factory_and_set_owner_to_mcms.go @@ -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"` diff --git a/deployment/changesets/deploy_from_factory.go b/deployment/changesets/deploy_from_factory.go index 7831810cf..2cc2f833f 100644 --- a/deployment/changesets/deploy_from_factory.go +++ b/deployment/changesets/deploy_from_factory.go @@ -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"` diff --git a/deployment/operations/ccip/factory/factory.go b/deployment/operations/ccip/factory/factory.go index 83a19da32..2ed9937ac 100644 --- a/deployment/operations/ccip/factory/factory.go +++ b/deployment/operations/ccip/factory/factory.go @@ -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, diff --git a/integration-tests/mcms/ccip_factory_mcms_test.go b/integration-tests/mcms/ccip_factory_mcms_test.go index 77f82128d..94f0865d0 100644 --- a/integration-tests/mcms/ccip_factory_mcms_test.go +++ b/integration-tests/mcms/ccip_factory_mcms_test.go @@ -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 @@ -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) @@ -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, @@ -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, diff --git a/integration-tests/mcms/shared_setup_test.go b/integration-tests/mcms/shared_setup_test.go index 7d597745c..804cd1e8e 100644 --- a/integration-tests/mcms/shared_setup_test.go +++ b/integration-tests/mcms/shared_setup_test.go @@ -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 @@ -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()