From 5f7577640d80753958ef788bbf41247dc098b5a9 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Fri, 17 Oct 2025 15:49:04 -0300 Subject: [PATCH 1/2] feat(ops): Add MCMS to Router Ops --- deployment/ops/ccip_router/op_deploy.go | 35 ++++++++++++++++++++----- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/deployment/ops/ccip_router/op_deploy.go b/deployment/ops/ccip_router/op_deploy.go index f5cdb166..6ad31047 100644 --- a/deployment/ops/ccip_router/op_deploy.go +++ b/deployment/ops/ccip_router/op_deploy.go @@ -177,18 +177,40 @@ var setOnRampsHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input SetO return sui_ops.OpTxResult[SetOnRampsObjects]{}, err } - opts := deps.GetCallOpts() - opts.Signer = deps.Signer - tx, err := routerPackage.SetOnRamps( - b.GetContext(), - opts, + encodedCall, err := routerPackage.Encoder().SetOnRamps( bind.Object{Id: input.OwnerCapObjectId}, bind.Object{Id: input.RouterStateObjectId}, input.DestChainSelectors, input.OnRampAddresses, ) if err != nil { - return sui_ops.OpTxResult[SetOnRampsObjects]{}, fmt.Errorf("failed to execute set_on_ramps: %w", err) + return sui_ops.OpTxResult[SetOnRampsObjects]{}, fmt.Errorf("failed to encode SetOnRamps call: %w", err) + } + call, err := sui_ops.ToTransactionCall(encodedCall, input.RouterStateObjectId) + if err != nil { + return sui_ops.OpTxResult[SetOnRampsObjects]{}, fmt.Errorf("failed to convert encoded call to TransactionCall: %w", err) + } + if deps.Signer == nil { + b.Logger.Infow("Skipping execution of SetOnRamps on Router as per no Signer provided", + "destChainSelectors", input.DestChainSelectors, + "onRampAddresses", input.OnRampAddresses) + return sui_ops.OpTxResult[SetOnRampsObjects]{ + Digest: "", + PackageId: input.RouterPackageId, + Objects: SetOnRampsObjects{}, + Call: call, + }, nil + } + + opts := deps.GetCallOpts() + opts.Signer = deps.Signer + tx, err := routerPackage.Bound().ExecuteTransaction( + b.GetContext(), + opts, + encodedCall, + ) + if err != nil { + return sui_ops.OpTxResult[SetOnRampsObjects]{}, fmt.Errorf("failed to execute SetOnRamps on Router: %w", err) } b.Logger.Infow("On-ramps set successfully", @@ -199,6 +221,7 @@ var setOnRampsHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input SetO Digest: tx.Digest, PackageId: input.RouterPackageId, Objects: SetOnRampsObjects{}, + Call: call, }, nil } From 4c969ec02e6dfaa11e4df383da1c7e18098b30e4 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Mon, 27 Oct 2025 18:24:25 -0300 Subject: [PATCH 2/2] feat(tests): add tests for router --- deployment/ops/ccip_router/op_registry.go | 1 + integration-tests/mcms/mcms_test.go | 76 +++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/deployment/ops/ccip_router/op_registry.go b/deployment/ops/ccip_router/op_registry.go index d35f8435..1ec832b4 100644 --- a/deployment/ops/ccip_router/op_registry.go +++ b/deployment/ops/ccip_router/op_registry.go @@ -6,4 +6,5 @@ var AllOperationsRouter = []cld_ops.Operation[any, any, any]{ *TransferOwnershipOp.AsUntyped(), *AcceptOwnershipOp.AsUntyped(), *ExecuteOwnershipTransferOp.AsUntyped(), + *SetOnRampsOp.AsUntyped(), } diff --git a/integration-tests/mcms/mcms_test.go b/integration-tests/mcms/mcms_test.go index 6a39486b..fc565be6 100644 --- a/integration-tests/mcms/mcms_test.go +++ b/integration-tests/mcms/mcms_test.go @@ -19,6 +19,7 @@ import ( ccipops "github.com/smartcontractkit/chainlink-sui/deployment/ops/ccip" offrampops "github.com/smartcontractkit/chainlink-sui/deployment/ops/ccip_offramp" onrampops "github.com/smartcontractkit/chainlink-sui/deployment/ops/ccip_onramp" + routerops "github.com/smartcontractkit/chainlink-sui/deployment/ops/ccip_router" mcmsops "github.com/smartcontractkit/chainlink-sui/deployment/ops/mcms" ownershipops "github.com/smartcontractkit/chainlink-sui/deployment/ops/ownership" ) @@ -39,6 +40,7 @@ func (s *CCIPMCMSTestSuite) Test_CCIP_MCMS() { s.T().Run("Execute config proposal against CCIP from MCMS", func(t *testing.T) { RunTestCCIPFeeQuoterProposal(s) RunCCIPRampsProposal(s) + RunTestRouterProposal(s) }) } @@ -120,6 +122,8 @@ func RunTestCCIPOwnershipTransfer(s *CCIPMCMSTestSuite) { require.NoError(s.T(), err, "transferring ownership of CCIP OffRamp to MCMS") require.NotEmpty(s.T(), tx, "Transaction should not be empty") + s.T().Logf("✅ Transferred ownership of CCIP OffRamp to MCMS in tx: %s", tx.Digest) + // 2. Proposal execution with acceptance from MCMS (through bypasser) input := ownershipops.AcceptCCIPOwnershipInput{ // MCMS related @@ -162,6 +166,8 @@ func RunTestCCIPOwnershipTransfer(s *CCIPMCMSTestSuite) { s.Require().NoError(err, "executing ownership transfer of OnRamp to MCMS") _, err = ccipOffRampContract.ExecuteOwnershipTransferToMcms(s.T().Context(), s.deps.GetCallOpts(), bind.Object{Id: s.ccipObjects.CCIPObjectRefObjectId}, bind.Object{Id: s.ccipOfframpObjects.OwnerCapId}, bind.Object{Id: s.ccipOfframpObjects.StateObjectId}, bind.Object{Id: s.registryObj}, s.mcmsPackageID) s.Require().NoError(err, "executing ownership transfer of OffRamp to MCMS") + _, err = ccipRouterContract.ExecuteOwnershipTransferToMcms(s.T().Context(), s.deps.GetCallOpts(), bind.Object{Id: s.ccipRouterObjects.OwnerCapObjectId}, bind.Object{Id: s.ccipRouterObjects.RouterStateObjectId}, bind.Object{Id: s.registryObj}, s.mcmsPackageID) + s.Require().NoError(err, "executing ownership transfer of Router to MCMS") // 4. Verify the new owner is MCMS newOwner, err := ccipContract.DevInspect().Owner(s.T().Context(), s.deps.GetCallOpts(), bind.Object{Id: s.ccipObjects.CCIPObjectRefObjectId}) @@ -175,6 +181,10 @@ func RunTestCCIPOwnershipTransfer(s *CCIPMCMSTestSuite) { newOwnerOffRamp, err := ccipOffRampContract.DevInspect().Owner(s.T().Context(), s.deps.GetCallOpts(), bind.Object{Id: s.ccipOfframpObjects.StateObjectId}) s.Require().NoError(err, "getting new owner of OffRamp state object") s.Require().Equal(s.mcmsPackageID, newOwnerOffRamp, "new owner of OffRamp should be MCMS") + + newOwnerRouter, err := ccipRouterContract.DevInspect().Owner(s.T().Context(), s.deps.GetCallOpts(), bind.Object{Id: s.ccipRouterObjects.RouterStateObjectId}) + s.Require().NoError(err, "getting new owner of Router state object") + s.Require().Equal(s.mcmsPackageID, newOwnerRouter, "new owner of Router should be MCMS") } func RunTestCCIPFeeQuoterProposal(s *CCIPMCMSTestSuite) { @@ -470,3 +480,69 @@ func RunCCIPRampsProposal(s *CCIPMCMSTestSuite) { require.Contains(s.T(), actualAllowedSenders, expectedSender, "allowed senders should contain expected sender") } } + +func RunTestRouterProposal(s *CCIPMCMSTestSuite) { + // 1. Build configs + expectedDestChainSelectors := []uint64{ + cselectors.ETHEREUM_TESTNET_SEPOLIA.Selector, + cselectors.ETHEREUM_TESTNET_SEPOLIA_ARBITRUM_1.Selector, + } + expectedOnRampAddresses := []string{ + "0x1111111111111111111111111111111111111111111111111111111111111111", + "0x2222222222222222222222222222222222222222222222222222222222222222", + } + + // 2. Run ops to generate proposal + input := mcmsops.ProposalGenerateInput{ + Defs: []cld_ops.Definition{ + routerops.SetOnRampsOp.Def(), + }, + Inputs: []any{ + routerops.SetOnRampsInput{ + RouterPackageId: s.ccipRouterPackageId, + RouterStateObjectId: s.ccipRouterObjects.RouterStateObjectId, + OwnerCapObjectId: s.ccipRouterObjects.OwnerCapObjectId, + DestChainSelectors: expectedDestChainSelectors, + OnRampAddresses: expectedOnRampAddresses, + }, + }, + // MCMS related + MmcsPackageID: s.mcmsPackageID, + McmsStateObjID: s.mcmsObj, + TimelockObjID: s.timelockObj, + AccountObjID: s.accountObj, + RegistryObjID: s.registryObj, + // Proposal + Role: suisdk.TimelockRoleBypasser, + ChainSelector: uint64(s.chainSelector), + Delay: 0, + } + routerReport, err := cld_ops.ExecuteSequence(s.bundle, mcmsops.MCMSDynamicProposalGenerateSeq, s.deps, input) + s.Require().NoError(err, "executing router proposal sequence") + + timelockProposal := routerReport.Output + + // 3. Execute proposal + s.ExecuteProposalE2e(&timelockProposal, s.bypasserConfig, 0) + + // 4. Assert changes in contracts + + // Create router contract instance + routerContract, err := module_router.NewRouter(s.ccipRouterPackageId, s.client) + require.NoError(s.T(), err, "creating router contract") + + routerStateObj := bind.Object{Id: s.ccipRouterObjects.RouterStateObjectId} + + // Verify OnRamp addresses are set correctly + for i, destChainSelector := range expectedDestChainSelectors { + // Verify chain is supported + isSupported, err := routerContract.DevInspect().IsChainSupported(s.T().Context(), s.deps.GetCallOpts(), routerStateObj, destChainSelector) + require.NoError(s.T(), err, "checking if chain is supported") + require.True(s.T(), isSupported, "chain %d should be supported", destChainSelector) + + // Verify OnRamp address matches expected + actualOnRampAddress, err := routerContract.DevInspect().GetOnRamp(s.T().Context(), s.deps.GetCallOpts(), routerStateObj, destChainSelector) + require.NoError(s.T(), err, "getting on-ramp address for chain %d", destChainSelector) + require.Equal(s.T(), expectedOnRampAddresses[i], actualOnRampAddress, "on-ramp address for chain %d should match", destChainSelector) + } +}