From 32a0d9579bc61cd872fb4e2cad2baf6f3f594d76 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Fri, 17 Oct 2025 15:43:39 -0300 Subject: [PATCH 1/7] feat(ops): Add MCMS to Ramp Operations --- deployment/ops/ccip_offramp/op_deploy.go | 74 ++++++++++--- deployment/ops/ccip_onramp/op_deploy.go | 134 +++++++++++++++++++---- 2 files changed, 171 insertions(+), 37 deletions(-) diff --git a/deployment/ops/ccip_offramp/op_deploy.go b/deployment/ops/ccip_offramp/op_deploy.go index 235a20e7..3f94d793 100644 --- a/deployment/ops/ccip_offramp/op_deploy.go +++ b/deployment/ops/ccip_offramp/op_deploy.go @@ -126,11 +126,7 @@ var setOCR3ConfigHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input S return sui_ops.OpTxResult[DeployCCIPOffRampObjects]{}, err } - opts := deps.GetCallOpts() - opts.Signer = deps.Signer - tx, err := offRampPackage.SetOcr3Config( - b.GetContext(), - opts, + encodedCall, err := offRampPackage.Encoder().SetOcr3Config( bind.Object{Id: input.CCIPObjectRefId}, bind.Object{Id: input.OffRampStateId}, bind.Object{Id: input.OwnerCapObjectId}, @@ -142,14 +138,41 @@ var setOCR3ConfigHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input S input.Transmitters, ) if err != nil { - return sui_ops.OpTxResult[DeployCCIPOffRampObjects]{}, fmt.Errorf("failed to execute set ocr3 config in offramp: %w", err) + return sui_ops.OpTxResult[DeployCCIPOffRampObjects]{}, fmt.Errorf("failed to encode SetOcr3Config call: %w", err) + } + call, err := sui_ops.ToTransactionCall(encodedCall, input.OffRampStateId) + if err != nil { + return sui_ops.OpTxResult[DeployCCIPOffRampObjects]{}, fmt.Errorf("failed to convert encoded call to TransactionCall: %w", err) + } + if deps.Signer == nil { + b.Logger.Infow("Skipping execution of SetOcr3Config on OffRamp as per no Signer provided") + return sui_ops.OpTxResult[DeployCCIPOffRampObjects]{ + Digest: "", + PackageId: input.OffRampPackageId, + Objects: DeployCCIPOffRampObjects{}, + Call: call, + }, nil } + opts := deps.GetCallOpts() + opts.Signer = deps.Signer + tx, err := offRampPackage.Bound().ExecuteTransaction( + b.GetContext(), + opts, + encodedCall, + ) + if err != nil { + return sui_ops.OpTxResult[DeployCCIPOffRampObjects]{}, fmt.Errorf("failed to execute SetOcr3Config on OffRamp: %w", err) + } + + b.Logger.Infow("OCR3 config set on OffRamp") + return sui_ops.OpTxResult[DeployCCIPOffRampObjects]{ Digest: tx.Digest, PackageId: input.OffRampPackageId, Objects: DeployCCIPOffRampObjects{}, - }, err + Call: call, + }, nil } type ApplySourceChainConfigUpdateInput struct { @@ -169,11 +192,7 @@ var applySourceChainConfigUpdateHandler = func(b cld_ops.Bundle, deps sui_ops.Op return sui_ops.OpTxResult[DeployCCIPOffRampObjects]{}, err } - opts := deps.GetCallOpts() - opts.Signer = deps.Signer - tx, err := offRampPackage.ApplySourceChainConfigUpdates( - b.GetContext(), - opts, + encodedCall, err := offRampPackage.Encoder().ApplySourceChainConfigUpdates( bind.Object{Id: input.CCIPObjectRef}, bind.Object{Id: input.OffRampStateId}, bind.Object{Id: input.OwnerCapObjectId}, @@ -183,14 +202,41 @@ var applySourceChainConfigUpdateHandler = func(b cld_ops.Bundle, deps sui_ops.Op input.SourceChainsOnRamp, ) if err != nil { - return sui_ops.OpTxResult[DeployCCIPOffRampObjects]{}, fmt.Errorf("failed to execute applySourceChainConfigUpdate in offramp: %w", err) + return sui_ops.OpTxResult[DeployCCIPOffRampObjects]{}, fmt.Errorf("failed to encode ApplySourceChainConfigUpdates call: %w", err) + } + call, err := sui_ops.ToTransactionCall(encodedCall, input.OffRampStateId) + if err != nil { + return sui_ops.OpTxResult[DeployCCIPOffRampObjects]{}, fmt.Errorf("failed to convert encoded call to TransactionCall: %w", err) + } + if deps.Signer == nil { + b.Logger.Infow("Skipping execution of ApplySourceChainConfigUpdates on OffRamp as per no Signer provided") + return sui_ops.OpTxResult[DeployCCIPOffRampObjects]{ + Digest: "", + PackageId: input.OffRampPackageId, + Objects: DeployCCIPOffRampObjects{}, + Call: call, + }, nil } + opts := deps.GetCallOpts() + opts.Signer = deps.Signer + tx, err := offRampPackage.Bound().ExecuteTransaction( + b.GetContext(), + opts, + encodedCall, + ) + if err != nil { + return sui_ops.OpTxResult[DeployCCIPOffRampObjects]{}, fmt.Errorf("failed to execute ApplySourceChainConfigUpdates on OffRamp: %w", err) + } + + b.Logger.Infow("Source chain config updates applied on OffRamp") + return sui_ops.OpTxResult[DeployCCIPOffRampObjects]{ Digest: tx.Digest, PackageId: input.OffRampPackageId, Objects: DeployCCIPOffRampObjects{}, - }, err + Call: call, + }, nil } type AddPackageIdOffRampInput struct { diff --git a/deployment/ops/ccip_onramp/op_deploy.go b/deployment/ops/ccip_onramp/op_deploy.go index 1efecbeb..d55c2231 100644 --- a/deployment/ops/ccip_onramp/op_deploy.go +++ b/deployment/ops/ccip_onramp/op_deploy.go @@ -126,11 +126,7 @@ var ApplyDestChainUpdateHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, return sui_ops.OpTxResult[DeployCCIPOnRampObjects]{}, err } - opts := deps.GetCallOpts() - opts.Signer = deps.Signer - tx, err := onRampPackage.ApplyDestChainConfigUpdates( - b.GetContext(), - opts, + encodedCall, err := onRampPackage.Encoder().ApplyDestChainConfigUpdates( bind.Object{Id: input.CCIPObjectRefId}, bind.Object{Id: input.StateObjectId}, bind.Object{Id: input.OwnerCapObjectId}, @@ -139,14 +135,41 @@ var ApplyDestChainUpdateHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input.DestChainRouters, ) if err != nil { - return sui_ops.OpTxResult[DeployCCIPOnRampObjects]{}, fmt.Errorf("failed to execute ApplyDestChainUpdate on onRamp: %w", err) + return sui_ops.OpTxResult[DeployCCIPOnRampObjects]{}, fmt.Errorf("failed to encode ApplyDestChainConfigUpdates call: %w", err) + } + call, err := sui_ops.ToTransactionCall(encodedCall, input.StateObjectId) + if err != nil { + return sui_ops.OpTxResult[DeployCCIPOnRampObjects]{}, fmt.Errorf("failed to convert encoded call to TransactionCall: %w", err) } + if deps.Signer == nil { + b.Logger.Infow("Skipping execution of ApplyDestChainConfigUpdates on OnRamp as per no Signer provided") + return sui_ops.OpTxResult[DeployCCIPOnRampObjects]{ + Digest: "", + PackageId: input.OnRampPackageId, + Objects: DeployCCIPOnRampObjects{}, + Call: call, + }, nil + } + + opts := deps.GetCallOpts() + opts.Signer = deps.Signer + tx, err := onRampPackage.Bound().ExecuteTransaction( + b.GetContext(), + opts, + encodedCall, + ) + if err != nil { + return sui_ops.OpTxResult[DeployCCIPOnRampObjects]{}, fmt.Errorf("failed to execute ApplyDestChainConfigUpdates on OnRamp: %w", err) + } + + b.Logger.Infow("Destination chain config updates applied on OnRamp") return sui_ops.OpTxResult[DeployCCIPOnRampObjects]{ Digest: tx.Digest, PackageId: input.OnRampPackageId, Objects: DeployCCIPOnRampObjects{}, - }, err + Call: call, + }, nil } type ApplyAllowListUpdatesInput struct { @@ -166,11 +189,7 @@ var ApplyAllowListUpdatesHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, return sui_ops.OpTxResult[DeployCCIPOnRampObjects]{}, err } - opts := deps.GetCallOpts() - opts.Signer = deps.Signer - tx, err := onRampPackage.ApplyAllowlistUpdates( - b.GetContext(), - opts, + encodedCall, err := onRampPackage.Encoder().ApplyAllowlistUpdates( bind.Object{Id: input.CCIPObjectRefId}, bind.Object{Id: input.StateObjectId}, bind.Object{Id: input.OwnerCapObjectId}, @@ -180,14 +199,41 @@ var ApplyAllowListUpdatesHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input.DestChainRemoveAllowedSenders, ) if err != nil { - return sui_ops.OpTxResult[DeployCCIPOnRampObjects]{}, fmt.Errorf("failed to execute ApplyAllowListUpdates on onRamp: %w", err) + return sui_ops.OpTxResult[DeployCCIPOnRampObjects]{}, fmt.Errorf("failed to encode ApplyAllowlistUpdates call: %w", err) + } + call, err := sui_ops.ToTransactionCall(encodedCall, input.StateObjectId) + if err != nil { + return sui_ops.OpTxResult[DeployCCIPOnRampObjects]{}, fmt.Errorf("failed to convert encoded call to TransactionCall: %w", err) + } + if deps.Signer == nil { + b.Logger.Infow("Skipping execution of ApplyAllowlistUpdates on OnRamp as per no Signer provided") + return sui_ops.OpTxResult[DeployCCIPOnRampObjects]{ + Digest: "", + PackageId: input.OnRampPackageId, + Objects: DeployCCIPOnRampObjects{}, + Call: call, + }, nil + } + + opts := deps.GetCallOpts() + opts.Signer = deps.Signer + tx, err := onRampPackage.Bound().ExecuteTransaction( + b.GetContext(), + opts, + encodedCall, + ) + if err != nil { + return sui_ops.OpTxResult[DeployCCIPOnRampObjects]{}, fmt.Errorf("failed to execute ApplyAllowlistUpdates on OnRamp: %w", err) } + b.Logger.Infow("Allowlist updates applied on OnRamp") + return sui_ops.OpTxResult[DeployCCIPOnRampObjects]{ Digest: tx.Digest, PackageId: input.OnRampPackageId, Objects: DeployCCIPOnRampObjects{}, - }, err + Call: call, + }, nil } type IsChainSupportedInput struct { @@ -348,17 +394,37 @@ var addPackageIdHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input Ad return sui_ops.OpTxResult[AddPackageIdObjects]{}, err } + encodedCall, err := onRampPackage.Encoder().AddPackageId( + bind.Object{Id: input.StateObjectId}, + bind.Object{Id: input.OwnerCapObjectId}, + input.PackageId, + ) + if err != nil { + return sui_ops.OpTxResult[AddPackageIdObjects]{}, fmt.Errorf("failed to encode AddPackageId call: %w", err) + } + call, err := sui_ops.ToTransactionCall(encodedCall, input.StateObjectId) + if err != nil { + return sui_ops.OpTxResult[AddPackageIdObjects]{}, fmt.Errorf("failed to convert encoded call to TransactionCall: %w", err) + } + if deps.Signer == nil { + b.Logger.Infow("Skipping execution of AddPackageId on OnRamp as per no Signer provided", "packageId", input.PackageId) + return sui_ops.OpTxResult[AddPackageIdObjects]{ + Digest: "", + PackageId: input.OnRampPackageId, + Objects: AddPackageIdObjects{}, + Call: call, + }, nil + } + opts := deps.GetCallOpts() opts.Signer = deps.Signer - tx, err := onRampPackage.AddPackageId( + tx, err := onRampPackage.Bound().ExecuteTransaction( b.GetContext(), opts, - bind.Object{Id: input.StateObjectId}, - bind.Object{Id: input.OwnerCapObjectId}, - input.PackageId, + encodedCall, ) if err != nil { - return sui_ops.OpTxResult[AddPackageIdObjects]{}, fmt.Errorf("failed to execute AddPackageId on onRamp: %w", err) + return sui_ops.OpTxResult[AddPackageIdObjects]{}, fmt.Errorf("failed to execute AddPackageId on OnRamp: %w", err) } b.Logger.Infow("Package ID added to OnRamp", "packageId", input.PackageId) @@ -367,6 +433,7 @@ var addPackageIdHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input Ad Digest: tx.Digest, PackageId: input.OnRampPackageId, Objects: AddPackageIdObjects{}, + Call: call, }, nil } @@ -394,14 +461,34 @@ var removePackageIdOnRampHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, return sui_ops.OpTxResult[RemovePackageIdOnRampObjects]{}, err } + encodedCall, err := onRampPackage.Encoder().RemovePackageId( + bind.Object{Id: input.StateObjectId}, + bind.Object{Id: input.OwnerCapObjectId}, + input.PackageId, + ) + if err != nil { + return sui_ops.OpTxResult[RemovePackageIdOnRampObjects]{}, fmt.Errorf("failed to encode RemovePackageId call: %w", err) + } + call, err := sui_ops.ToTransactionCall(encodedCall, input.StateObjectId) + if err != nil { + return sui_ops.OpTxResult[RemovePackageIdOnRampObjects]{}, fmt.Errorf("failed to convert encoded call to TransactionCall: %w", err) + } + if deps.Signer == nil { + b.Logger.Infow("Skipping execution of RemovePackageId on OnRamp as per no Signer provided", "packageId", input.PackageId) + return sui_ops.OpTxResult[RemovePackageIdOnRampObjects]{ + Digest: "", + PackageId: input.OnRampPackageId, + Objects: RemovePackageIdOnRampObjects{}, + Call: call, + }, nil + } + opts := deps.GetCallOpts() opts.Signer = deps.Signer - tx, err := onRampPackage.RemovePackageId( + tx, err := onRampPackage.Bound().ExecuteTransaction( b.GetContext(), opts, - bind.Object{Id: input.StateObjectId}, - bind.Object{Id: input.OwnerCapObjectId}, - input.PackageId, + encodedCall, ) if err != nil { return sui_ops.OpTxResult[RemovePackageIdOnRampObjects]{}, fmt.Errorf("failed to execute RemovePackageId on OnRamp: %w", err) @@ -413,6 +500,7 @@ var removePackageIdOnRampHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, Digest: tx.Digest, PackageId: input.OnRampPackageId, Objects: RemovePackageIdOnRampObjects{}, + Call: call, }, nil } From bcaebc13697ec90baa8807745a1b02bfc0fd3753 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Tue, 21 Oct 2025 18:57:27 -0300 Subject: [PATCH 2/7] fix(testCCIPOwnership): Finish transfer of onRamp and offRamp --- integration-tests/mcms/mcms_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/integration-tests/mcms/mcms_test.go b/integration-tests/mcms/mcms_test.go index d73994b4..abcc44f9 100644 --- a/integration-tests/mcms/mcms_test.go +++ b/integration-tests/mcms/mcms_test.go @@ -35,6 +35,7 @@ func (s *CCIPMCMSTestSuite) Test_CCIP_MCMS() { s.T().Run("Execute config proposal against CCIP from MCMS", func(t *testing.T) { RunTestCCIPFeeQuoterProposal(s) + // RunCCIPOffRampProposal(s) }) } @@ -150,14 +151,27 @@ func RunTestCCIPOwnershipTransfer(s *CCIPMCMSTestSuite) { // 3. Execute transfer ownership from original owner // 3.1. Execute the proposal s.ExecuteProposalE2e(&timelockProposal, s.bypasserConfig, 0) + // 3.2. Finish the ownership transfer with the original owner signer _, err = ccipContract.ExecuteOwnershipTransferToMcms(s.T().Context(), s.deps.GetCallOpts(), bind.Object{Id: s.ccipObjects.CCIPObjectRefObjectId}, bind.Object{Id: s.ccipObjects.OwnerCapObjectId}, bind.Object{Id: s.registryObj}, s.mcmsPackageID) s.Require().NoError(err, "executing ownership transfer of CCIP to MCMS") + _, err = ccipOnRampContract.ExecuteOwnershipTransferToMcms(s.T().Context(), s.deps.GetCallOpts(), bind.Object{Id: s.ccipObjects.CCIPObjectRefObjectId}, bind.Object{Id: s.ccipOnrampObjects.OwnerCapObjectId}, bind.Object{Id: s.ccipOnrampObjects.StateObjectId}, bind.Object{Id: s.registryObj}, s.mcmsPackageID) + 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") // 4. Verify the new owner is MCMS newOwner, err := ccipContract.DevInspect().Owner(s.T().Context(), s.deps.GetCallOpts(), bind.Object{Id: s.ccipObjects.CCIPObjectRefObjectId}) s.Require().NoError(err, "getting new owner of CCIP state object") s.Require().Equal(s.mcmsPackageID, newOwner, "new owner of CCIP should be MCMS") + + newOwnerOnRamp, err := ccipOnRampContract.DevInspect().Owner(s.T().Context(), s.deps.GetCallOpts(), bind.Object{Id: s.ccipOnrampObjects.StateObjectId}) + s.Require().NoError(err, "getting new owner of OnRamp state object") + s.Require().Equal(s.mcmsPackageID, newOwnerOnRamp, "new owner of OnRamp should be MCMS") + + 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") } func RunTestCCIPFeeQuoterProposal(s *CCIPMCMSTestSuite) { From ba554905f3a1a219040c74d7403d04b8a79eea2a Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Wed, 22 Oct 2025 22:42:51 -0300 Subject: [PATCH 3/7] test(offramp): Add offramp tests --- deployment/ops/ccip_offramp/op_deploy.go | 18 ++-- deployment/ops/ccip_offramp/op_registry.go | 2 + deployment/ops/ccip_onramp/op_registry.go | 2 + integration-tests/mcms/mcms_test.go | 95 +++++++++++++++++++++- 4 files changed, 107 insertions(+), 10 deletions(-) diff --git a/deployment/ops/ccip_offramp/op_deploy.go b/deployment/ops/ccip_offramp/op_deploy.go index 3f94d793..e7f93770 100644 --- a/deployment/ops/ccip_offramp/op_deploy.go +++ b/deployment/ops/ccip_offramp/op_deploy.go @@ -176,14 +176,14 @@ var setOCR3ConfigHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input S } type ApplySourceChainConfigUpdateInput struct { - CCIPObjectRef string - OffRampPackageId string - OffRampStateId string - OwnerCapObjectId string - SourceChainsSelectors []uint64 - SourceChainsIsEnabled []bool - SouceChainsIsRMNVerificationDisabled []bool - SourceChainsOnRamp [][]byte + CCIPObjectRef string + OffRampPackageId string + OffRampStateId string + OwnerCapObjectId string + SourceChainsSelectors []uint64 + SourceChainsIsEnabled []bool + SourceChainsIsRMNVerificationDisabled []bool + SourceChainsOnRamp [][]byte } var applySourceChainConfigUpdateHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input ApplySourceChainConfigUpdateInput) (output sui_ops.OpTxResult[DeployCCIPOffRampObjects], err error) { @@ -198,7 +198,7 @@ var applySourceChainConfigUpdateHandler = func(b cld_ops.Bundle, deps sui_ops.Op bind.Object{Id: input.OwnerCapObjectId}, input.SourceChainsSelectors, input.SourceChainsIsEnabled, - input.SouceChainsIsRMNVerificationDisabled, + input.SourceChainsIsRMNVerificationDisabled, input.SourceChainsOnRamp, ) if err != nil { diff --git a/deployment/ops/ccip_offramp/op_registry.go b/deployment/ops/ccip_offramp/op_registry.go index b7dd9c53..d8d13e80 100644 --- a/deployment/ops/ccip_offramp/op_registry.go +++ b/deployment/ops/ccip_offramp/op_registry.go @@ -6,4 +6,6 @@ var AllOperationsOfframp = []cld_ops.Operation[any, any, any]{ *TransferOwnershipOffRampOp.AsUntyped(), *AcceptOwnershipOffRampOp.AsUntyped(), *ExecuteOwnershipTransferToMcmsOffRampOp.AsUntyped(), + *ApplySourceChainConfigUpdatesOp.AsUntyped(), + *SetOCR3ConfigOp.AsUntyped(), } diff --git a/deployment/ops/ccip_onramp/op_registry.go b/deployment/ops/ccip_onramp/op_registry.go index 3461224e..51dd7437 100644 --- a/deployment/ops/ccip_onramp/op_registry.go +++ b/deployment/ops/ccip_onramp/op_registry.go @@ -6,4 +6,6 @@ var AllOperationsOnramp = []cld_ops.Operation[any, any, any]{ *TransferOwnershipOnRampOp.AsUntyped(), *AcceptOwnershipOnRampOp.AsUntyped(), *ExecuteOwnershipTransferToMcmsOnRampOp.AsUntyped(), + *ApplyAllowListUpdateOp.AsUntyped(), + *ApplyDestChainConfigUpdateOp.AsUntyped(), } diff --git a/integration-tests/mcms/mcms_test.go b/integration-tests/mcms/mcms_test.go index abcc44f9..46ce9d1f 100644 --- a/integration-tests/mcms/mcms_test.go +++ b/integration-tests/mcms/mcms_test.go @@ -35,7 +35,7 @@ func (s *CCIPMCMSTestSuite) Test_CCIP_MCMS() { s.T().Run("Execute config proposal against CCIP from MCMS", func(t *testing.T) { RunTestCCIPFeeQuoterProposal(s) - // RunCCIPOffRampProposal(s) + RunCCIPOffRampProposal(s) }) } @@ -319,3 +319,96 @@ func RunTestCCIPFeeQuoterProposal(s *CCIPMCMSTestSuite) { require.NoError(s.T(), err) require.Equal(s.T(), expectedPremiumMultiplier, actualPremiumMultiplier) } + +func RunCCIPOffRampProposal(s *CCIPMCMSTestSuite) { + // 1. Build configs + // onRampBytes := []byte{ + // 0x33, 0x17, 0xaa, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + // 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + // } + // expectedSCC := module_offramp.SourceChainConfig{ + // IsEnabled: false, + // IsRmnVerificationDisabled: true, + // OnRamp: onRampBytes, + // } + expectedDCC := module_onramp.DestChainConfig{ + AllowlistEnabled: true, + Router: "0x304121906bf93b21f915a04cffea4df21090432e3c2fd60e51ebe68f79c90a41", + AllowedSenders: []string{ + "0x1cf00ee891001df44fc0736e56f469ab85dcf9b78511ac9268f292716fc04447", + "0x1cf00ee891001df44fc0736e56f469ab85dcf9b78511ac9268f292716fc04447", + }, + } + + // 2. Run ops to generate proposal + input := mcmsops.ProposalGenerateInput{ + Defs: []cld_ops.Definition{ + // offrampops.ApplySourceChainConfigUpdatesOp.Def(), + // offrampops.SetOCR3ConfigOp.Def(), + onrampops.ApplyDestChainConfigUpdateOp.Def(), + onrampops.ApplyAllowListUpdateOp.Def(), + }, + Inputs: []any{ + // offrampops.ApplySourceChainConfigUpdateInput{ + // CCIPObjectRef: s.ccipObjects.CCIPObjectRefObjectId, + // OffRampPackageId: s.ccipOfframpPackageId, + // OffRampStateId: s.ccipOfframpObjects.StateObjectId, + // OwnerCapObjectId: s.ccipOfframpObjects.OwnerCapId, + // SourceChainsSelectors: []uint64{cselectors.ETHEREUM_MAINNET.Selector}, + // SourceChainsIsEnabled: []bool{expectedSCC.IsEnabled}, + // SourceChainsIsRMNVerificationDisabled: []bool{expectedSCC.IsRmnVerificationDisabled}, + // SourceChainsOnRamp: [][]byte{expectedSCC.OnRamp}, + // }, + // offrampops.SetOCR3ConfigInput{ + // OffRampPackageId: s.ccipOfframpPackageId, + // OffRampStateId: s.ccipOfframpObjects.StateObjectId, + // CCIPObjectRefId: s.ccipObjects.CCIPObjectRefObjectId, + // OwnerCapObjectId: s.ccipOfframpObjects.OwnerCapId, + // ConfigDigest: []byte{0x01, 0x02, 0x03}, + // OCRPluginType: 1, + // BigF: 0, + // IsSignatureVerificationEnabled: false, + // Signers: [][]byte{onRampBytes}, + // Transmitters: []string{"0x11223344556677889900aabbccddeeff00112233"}, + // }, + onrampops.ApplyDestChainConfigureOnRampInput{ + OnRampPackageId: s.ccipOnrampPackageId, + CCIPObjectRefId: s.ccipObjects.CCIPObjectRefObjectId, + OwnerCapObjectId: s.ccipOnrampObjects.OwnerCapObjectId, + StateObjectId: s.ccipOnrampObjects.StateObjectId, + DestChainSelector: []uint64{cselectors.ETHEREUM_MAINNET.Selector}, + DestChainAllowListEnabled: []bool{false}, + DestChainRouters: []string{expectedDCC.Router}, + }, + onrampops.ApplyAllowListUpdatesInput{ + OnRampPackageId: s.ccipOnrampPackageId, + CCIPObjectRefId: s.ccipObjects.CCIPObjectRefObjectId, + OwnerCapObjectId: s.ccipOnrampObjects.OwnerCapObjectId, + StateObjectId: s.ccipOnrampObjects.StateObjectId, + DestChainSelector: []uint64{cselectors.ETHEREUM_MAINNET.Selector}, + DestChainAllowListEnabled: []bool{expectedDCC.AllowlistEnabled}, + DestChainAddAllowedSenders: [][]string{expectedDCC.AllowedSenders}, + DestChainRemoveAllowedSenders: [][]string{{}}, + }, + }, + // 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, + } + rampsReport, err := cld_ops.ExecuteSequence(s.bundle, mcmsops.MCMSDynamicProposalGenerateSeq, s.deps, input) + s.Require().NoError(err, "executing ramps proposal sequence") + + timelockProposal := rampsReport.Output + + // 3. Execute proposal + s.ExecuteProposalE2e(&timelockProposal, s.bypasserConfig, 0) + + // TODO: 4. Assert changes in contracts +} From 74ac67d18f5baaa1b95a9a9aa41e0f596d1134b2 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Thu, 23 Oct 2025 20:18:04 -0300 Subject: [PATCH 4/7] fix(encoder): hack fix for vector> on encoder --- bindings/bind/bcs_decoder_test.go | 3 ++ bindings/bind/type_converter.go | 56 +++++++++++++++++++++++++++---- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/bindings/bind/bcs_decoder_test.go b/bindings/bind/bcs_decoder_test.go index f4e5bc9f..f630596b 100644 --- a/bindings/bind/bcs_decoder_test.go +++ b/bindings/bind/bcs_decoder_test.go @@ -333,6 +333,9 @@ func TestDeserializer(t *testing.T) { } func TestDeserializer_ShouldFail(t *testing.T) { + // TODO: this test will fail when https://github.com/block-vision/sui-go-sdk/pull/78 + // is released and will have to be removed + // mystenbcs encoder has an issue where fixed-size byte arrays ([n]byte) are treated as regular slices. // so encoding a u128 via their encoder will result in failure to decode it back. // We're using this behavior to test our DeserializeBCS error handling. diff --git a/bindings/bind/type_converter.go b/bindings/bind/type_converter.go index dbfe8ce2..02b21943 100644 --- a/bindings/bind/type_converter.go +++ b/bindings/bind/type_converter.go @@ -585,20 +585,62 @@ func convertVectorToBCS(innerType string, value any) (any, error) { type SuiAddressBytes [32]byte func bcsEncode(value any) ([]byte, error) { - if addrs, ok := value.([][32]byte); ok { - suiAddrs := make([]SuiAddressBytes, len(addrs)) - for i, addr := range addrs { - suiAddrs[i] = SuiAddressBytes(addr) - } - value = suiAddrs + // Normalize value before encoding + normalized, err := normalizeValue(value) + if err != nil { + return nil, err } bcsEncodedMsg := bytes.Buffer{} bcsEncoder := mystenbcs.NewEncoder(&bcsEncodedMsg) - err := bcsEncoder.Encode(value) + err = bcsEncoder.Encode(normalized) if err != nil { return nil, err } return bcsEncodedMsg.Bytes(), nil } + +// normalizeValue converts special Go types into the correct BCS-friendly form. +// TODO: This is a temporary solution until sui-go-sdk addresses [n]bytes bug +// https://github.com/block-vision/sui-go-sdk/issues/75 won't be necessary after release +// fixes vector
and vector> encoding. +func normalizeValue(value any) (any, error) { + switch v := value.(type) { + case [][32]byte: + // Single-level vector
+ return convertToSliceSuiAddressBytes(v), nil + case []interface{}: + // Possible nested address vectors: [][][32]byte (wrapped as []interface{}) (vector>) + if len(v) == 0 { + return v, nil + } + // Check if the first element matches the pattern + if _, ok := v[0].([][32]byte); !ok { + return value, nil + } + result := make([][]SuiAddressBytes, len(v)) + for i, item := range v { + // Make sure all the items are of the expected type + addrs, ok := item.([][32]byte) + if !ok { + return nil, fmt.Errorf("expected [][32]byte at index %d, got %T", i, item) + } + result[i] = convertToSliceSuiAddressBytes(addrs) + } + + return result, nil + } + + return value, nil +} + +// convertToSuiAddressBytes converts a slice of [32]byte into []SuiAddressBytes. +func convertToSliceSuiAddressBytes(addresses [][32]byte) []SuiAddressBytes { + out := make([]SuiAddressBytes, len(addresses)) + for i, addr := range addresses { + out[i] = SuiAddressBytes(addr) + } + + return out +} From 28eb1349ea5aa5d341a3fe93d76e3fe2463e472d Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Thu, 23 Oct 2025 21:40:04 -0300 Subject: [PATCH 5/7] test(offramp): add offramp tests --- .../ownership/seq_accept_ccip_ownership.go | 3 +- integration-tests/mcms/mcms_test.go | 72 ++++++++++--------- 2 files changed, 41 insertions(+), 34 deletions(-) diff --git a/deployment/ops/ownership/seq_accept_ccip_ownership.go b/deployment/ops/ownership/seq_accept_ccip_ownership.go index 3309ab3d..be38ee68 100644 --- a/deployment/ops/ownership/seq_accept_ccip_ownership.go +++ b/deployment/ops/ownership/seq_accept_ccip_ownership.go @@ -7,13 +7,14 @@ import ( cld_ops "github.com/smartcontractkit/chainlink-deployments-framework/operations" "github.com/smartcontractkit/mcms" + suisdk "github.com/smartcontractkit/mcms/sdk/sui" + sui_ops "github.com/smartcontractkit/chainlink-sui/deployment/ops" 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" - suisdk "github.com/smartcontractkit/mcms/sdk/sui" ) type AcceptCCIPOwnershipInput struct { diff --git a/integration-tests/mcms/mcms_test.go b/integration-tests/mcms/mcms_test.go index 46ce9d1f..ea99cc35 100644 --- a/integration-tests/mcms/mcms_test.go +++ b/integration-tests/mcms/mcms_test.go @@ -322,15 +322,21 @@ func RunTestCCIPFeeQuoterProposal(s *CCIPMCMSTestSuite) { func RunCCIPOffRampProposal(s *CCIPMCMSTestSuite) { // 1. Build configs - // onRampBytes := []byte{ - // 0x33, 0x17, 0xaa, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, - // 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - // } - // expectedSCC := module_offramp.SourceChainConfig{ - // IsEnabled: false, - // IsRmnVerificationDisabled: true, - // OnRamp: onRampBytes, - // } + mock32Bytes := []byte{ + 0x33, 0x17, 0xaa, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + } + configDigest := []byte{ + 0x00, 0x0A, 0x2F, 0x1F, 0x37, 0xB0, 0x33, 0xCC, + 0xC4, 0x42, 0x8A, 0xB6, 0x5C, 0x35, 0x39, 0xC9, + 0x31, 0x5D, 0xBF, 0x88, 0x2D, 0x4B, 0xAB, 0x13, + 0xF1, 0xE7, 0xEF, 0xE7, 0xB3, 0xDD, 0xDC, 0x36, + } + expectedSCC := module_offramp.SourceChainConfig{ + IsEnabled: false, + IsRmnVerificationDisabled: true, + OnRamp: mock32Bytes, + } expectedDCC := module_onramp.DestChainConfig{ AllowlistEnabled: true, Router: "0x304121906bf93b21f915a04cffea4df21090432e3c2fd60e51ebe68f79c90a41", @@ -343,34 +349,34 @@ func RunCCIPOffRampProposal(s *CCIPMCMSTestSuite) { // 2. Run ops to generate proposal input := mcmsops.ProposalGenerateInput{ Defs: []cld_ops.Definition{ - // offrampops.ApplySourceChainConfigUpdatesOp.Def(), - // offrampops.SetOCR3ConfigOp.Def(), + offrampops.ApplySourceChainConfigUpdatesOp.Def(), + offrampops.SetOCR3ConfigOp.Def(), onrampops.ApplyDestChainConfigUpdateOp.Def(), onrampops.ApplyAllowListUpdateOp.Def(), }, Inputs: []any{ - // offrampops.ApplySourceChainConfigUpdateInput{ - // CCIPObjectRef: s.ccipObjects.CCIPObjectRefObjectId, - // OffRampPackageId: s.ccipOfframpPackageId, - // OffRampStateId: s.ccipOfframpObjects.StateObjectId, - // OwnerCapObjectId: s.ccipOfframpObjects.OwnerCapId, - // SourceChainsSelectors: []uint64{cselectors.ETHEREUM_MAINNET.Selector}, - // SourceChainsIsEnabled: []bool{expectedSCC.IsEnabled}, - // SourceChainsIsRMNVerificationDisabled: []bool{expectedSCC.IsRmnVerificationDisabled}, - // SourceChainsOnRamp: [][]byte{expectedSCC.OnRamp}, - // }, - // offrampops.SetOCR3ConfigInput{ - // OffRampPackageId: s.ccipOfframpPackageId, - // OffRampStateId: s.ccipOfframpObjects.StateObjectId, - // CCIPObjectRefId: s.ccipObjects.CCIPObjectRefObjectId, - // OwnerCapObjectId: s.ccipOfframpObjects.OwnerCapId, - // ConfigDigest: []byte{0x01, 0x02, 0x03}, - // OCRPluginType: 1, - // BigF: 0, - // IsSignatureVerificationEnabled: false, - // Signers: [][]byte{onRampBytes}, - // Transmitters: []string{"0x11223344556677889900aabbccddeeff00112233"}, - // }, + offrampops.ApplySourceChainConfigUpdateInput{ + CCIPObjectRef: s.ccipObjects.CCIPObjectRefObjectId, + OffRampPackageId: s.ccipOfframpPackageId, + OffRampStateId: s.ccipOfframpObjects.StateObjectId, + OwnerCapObjectId: s.ccipOfframpObjects.OwnerCapId, + SourceChainsSelectors: []uint64{cselectors.ETHEREUM_MAINNET.Selector}, + SourceChainsIsEnabled: []bool{expectedSCC.IsEnabled}, + SourceChainsIsRMNVerificationDisabled: []bool{expectedSCC.IsRmnVerificationDisabled}, + SourceChainsOnRamp: [][]byte{expectedSCC.OnRamp}, + }, + offrampops.SetOCR3ConfigInput{ + OffRampPackageId: s.ccipOfframpPackageId, + OffRampStateId: s.ccipOfframpObjects.StateObjectId, + CCIPObjectRefId: s.ccipObjects.CCIPObjectRefObjectId, + OwnerCapObjectId: s.ccipOfframpObjects.OwnerCapId, + ConfigDigest: configDigest, + OCRPluginType: byte(1), + BigF: byte(1), + IsSignatureVerificationEnabled: false, + Signers: [][]byte{mock32Bytes}, + Transmitters: []string{"0x11223344556677889900aabbccddeeff00112233"}, + }, onrampops.ApplyDestChainConfigureOnRampInput{ OnRampPackageId: s.ccipOnrampPackageId, CCIPObjectRefId: s.ccipObjects.CCIPObjectRefObjectId, From f88687a6ecd3fe5f8e45db490dfd017bb6d97909 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Fri, 24 Oct 2025 17:56:09 -0300 Subject: [PATCH 6/7] add assertions --- integration-tests/mcms/mcms_test.go | 57 +++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/integration-tests/mcms/mcms_test.go b/integration-tests/mcms/mcms_test.go index ea99cc35..5185cfcc 100644 --- a/integration-tests/mcms/mcms_test.go +++ b/integration-tests/mcms/mcms_test.go @@ -35,7 +35,7 @@ func (s *CCIPMCMSTestSuite) Test_CCIP_MCMS() { s.T().Run("Execute config proposal against CCIP from MCMS", func(t *testing.T) { RunTestCCIPFeeQuoterProposal(s) - RunCCIPOffRampProposal(s) + RunCCIPRampsProposal(s) }) } @@ -320,7 +320,7 @@ func RunTestCCIPFeeQuoterProposal(s *CCIPMCMSTestSuite) { require.Equal(s.T(), expectedPremiumMultiplier, actualPremiumMultiplier) } -func RunCCIPOffRampProposal(s *CCIPMCMSTestSuite) { +func RunCCIPRampsProposal(s *CCIPMCMSTestSuite) { // 1. Build configs mock32Bytes := []byte{ 0x33, 0x17, 0xaa, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, @@ -342,7 +342,7 @@ func RunCCIPOffRampProposal(s *CCIPMCMSTestSuite) { Router: "0x304121906bf93b21f915a04cffea4df21090432e3c2fd60e51ebe68f79c90a41", AllowedSenders: []string{ "0x1cf00ee891001df44fc0736e56f469ab85dcf9b78511ac9268f292716fc04447", - "0x1cf00ee891001df44fc0736e56f469ab85dcf9b78511ac9268f292716fc04447", + "0x2d011ff9a2112e0550d1847f67057abc96ed09b78511ac9268f292716fc04447", }, } @@ -416,5 +416,54 @@ func RunCCIPOffRampProposal(s *CCIPMCMSTestSuite) { // 3. Execute proposal s.ExecuteProposalE2e(&timelockProposal, s.bypasserConfig, 0) - // TODO: 4. Assert changes in contracts + // 4. Assert changes in contracts + + // Create contract instances + offrampContract, err := module_offramp.NewOfframp(s.ccipOfframpPackageId, s.client) + require.NoError(s.T(), err, "creating offramp contract") + + onrampContract, err := module_onramp.NewOnramp(s.ccipOnrampPackageId, s.client) + require.NoError(s.T(), err, "creating onramp contract") + + ccipObjRef := bind.Object{Id: s.ccipObjects.CCIPObjectRefObjectId} + offRampStateObj := bind.Object{Id: s.ccipOfframpObjects.StateObjectId} + onRampStateObj := bind.Object{Id: s.ccipOnrampObjects.StateObjectId} + + // Verify SourceChainConfig changes in OffRamp + actualSCC, err := offrampContract.DevInspect().GetSourceChainConfig(s.T().Context(), s.deps.GetCallOpts(), ccipObjRef, offRampStateObj, cselectors.ETHEREUM_MAINNET.Selector) + require.NoError(s.T(), err, "getting source chain config") + require.Equal(s.T(), expectedSCC.IsEnabled, actualSCC.IsEnabled, "source chain config IsEnabled should match") + require.Equal(s.T(), expectedSCC.IsRmnVerificationDisabled, actualSCC.IsRmnVerificationDisabled, "source chain config IsRmnVerificationDisabled should match") + require.Equal(s.T(), expectedSCC.OnRamp, actualSCC.OnRamp, "source chain config OnRamp should match") + + // Verify DestChainConfig changes in OnRamp + actualDCCResults, err := onrampContract.DevInspect().GetDestChainConfig(s.T().Context(), s.deps.GetCallOpts(), onRampStateObj, cselectors.ETHEREUM_MAINNET.Selector) + require.NoError(s.T(), err, "getting dest chain config") + // GetDestChainConfig returns multiple values: [0]: u64, [1]: bool, [2]: address + // Based on the bindings, it seems to return sequence number, allowlist enabled, and router + require.Len(s.T(), actualDCCResults, 3, "dest chain config should return 3 values") + actualAllowlistEnabled, ok := actualDCCResults[1].(bool) + require.True(s.T(), ok, "second value should be bool for allowlist enabled") + actualRouter, ok := actualDCCResults[2].(string) + require.True(s.T(), ok, "third value should be string for router") + + // Note: The allowlist enabled in the ApplyDestChainConfigureOnRampInput was set to false, + // but in ApplyAllowListUpdatesInput it was set to true. The final state should be true. + require.Equal(s.T(), expectedDCC.AllowlistEnabled, actualAllowlistEnabled, "dest chain config AllowlistEnabled should match") + require.Equal(s.T(), expectedDCC.Router, actualRouter, "dest chain config Router should match") + + // Verify AllowList changes in OnRamp + actualAllowListResults, err := onrampContract.DevInspect().GetAllowedSendersList(s.T().Context(), s.deps.GetCallOpts(), onRampStateObj, cselectors.ETHEREUM_MAINNET.Selector) + require.NoError(s.T(), err, "getting allowed senders list") + // GetAllowedSendersList returns [0]: bool, [1]: vector
+ require.Len(s.T(), actualAllowListResults, 2, "allowed senders list should return 2 values") + actualAllowlistEnabledFromList, ok := actualAllowListResults[0].(bool) + require.True(s.T(), ok, "first value should be bool for allowlist enabled") + actualAllowedSenders, ok := actualAllowListResults[1].([]string) + require.True(s.T(), ok, "second value should be []string for allowed senders") + + require.Equal(s.T(), expectedDCC.AllowlistEnabled, actualAllowlistEnabledFromList, "allowlist enabled should match") + for _, expectedSender := range expectedDCC.AllowedSenders { + require.Contains(s.T(), actualAllowedSenders, expectedSender, "allowed senders should contain expected sender") + } } From 26610f40407feb6a57b0eea97ecc3abe68160443 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Mon, 27 Oct 2025 12:41:28 -0300 Subject: [PATCH 7/7] rebase --- integration-tests/mcms/mcms_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/integration-tests/mcms/mcms_test.go b/integration-tests/mcms/mcms_test.go index 5185cfcc..6a39486b 100644 --- a/integration-tests/mcms/mcms_test.go +++ b/integration-tests/mcms/mcms_test.go @@ -5,6 +5,7 @@ package mcms import ( "testing" + cselectors "github.com/smartcontractkit/chain-selectors" cld_ops "github.com/smartcontractkit/chainlink-deployments-framework/operations" suisdk "github.com/smartcontractkit/mcms/sdk/sui" "github.com/stretchr/testify/require" @@ -16,6 +17,8 @@ import ( module_onramp "github.com/smartcontractkit/chainlink-sui/bindings/generated/ccip/ccip_onramp/onramp" module_router "github.com/smartcontractkit/chainlink-sui/bindings/generated/ccip/ccip_router" 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" mcmsops "github.com/smartcontractkit/chainlink-sui/deployment/ops/mcms" ownershipops "github.com/smartcontractkit/chainlink-sui/deployment/ops/ownership" )