Skip to content
Merged
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
114 changes: 114 additions & 0 deletions deployment/adapters/fastcurse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@ func fastCurseTestBundle(t *testing.T) cld_ops.Bundle {
)
}

func uncurseTestBundle(t *testing.T) cld_ops.Bundle {
t.Helper()

registry := cld_ops.NewOperationRegistry(rmnops.UncurseChainOp.AsUntyped())
return cld_ops.NewBundle(
t.Context,
logger.Test(t),
cld_ops.NewMemoryReporter(),
cld_ops.WithOperationRegistry(registry),
)
}

func txAdditionalFields(t *testing.T, tx mcmstypes.Transaction) suisdk.AdditionalFields {
t.Helper()

Expand Down Expand Up @@ -139,3 +151,105 @@ func TestCurseAdapter_Curse_WithUpgradedPackage_PropagatesLatestPackageID(t *tes
require.NoError(t, err)
require.Equal(t, testLatestCCIPPackageID, latestPackageID)
}

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

selector := cselectors.SUI_TESTNET.Selector
adapter := adapters.NewCurseAdapter()
adapter.CCIPAddress = testCCIPPackageID
adapter.CCIPObjectRef = testCCIPObjectRef
adapter.CCIPOwnerCapObjectID = "0x3333333333333333333333333333333333333333333333333333333333333333"

chains := cldf_chain.NewBlockChains(map[uint64]cldf_chain.BlockChain{
selector: cldfsui.Chain{ChainMetadata: cldfsui.ChainMetadata{Selector: selector}},
})

report, err := cld_ops.ExecuteSequence(
uncurseTestBundle(t),
adapter.Uncurse(),
chains,
fastcurse.CurseInput{
ChainSelector: selector,
Subjects: []fastcurse.Subject{fastcurse.GlobalCurseSubject()},
},
)
require.NoError(t, err)
require.Len(t, report.Output.BatchOps, 1)
require.Len(t, report.Output.BatchOps[0].Transactions, 1)

tx := report.Output.BatchOps[0].Transactions[0]
require.Equal(t, testCCIPPackageID, tx.To)

fields := txAdditionalFields(t, tx)
require.Equal(t, "rmn_remote", fields.ModuleName)
require.Equal(t, "uncurse_multiple", fields.Function)
}

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

selector := cselectors.SUI_TESTNET.Selector
adapter := adapters.NewCurseAdapter()
adapter.CCIPAddress = testCCIPPackageID
adapter.LatestCCIPPackageID = testLatestCCIPPackageID
adapter.CCIPObjectRef = testCCIPObjectRef
adapter.CCIPOwnerCapObjectID = "0x3333333333333333333333333333333333333333333333333333333333333333"

chains := cldf_chain.NewBlockChains(map[uint64]cldf_chain.BlockChain{
selector: cldfsui.Chain{ChainMetadata: cldfsui.ChainMetadata{Selector: selector}},
})

report, err := cld_ops.ExecuteSequence(
uncurseTestBundle(t),
adapter.Uncurse(),
chains,
fastcurse.CurseInput{
ChainSelector: selector,
Subjects: []fastcurse.Subject{fastcurse.GenericSelectorToSubject(cselectors.ETHEREUM_MAINNET.Selector)},
},
)
require.NoError(t, err)
require.Len(t, report.Output.BatchOps, 1)
require.Len(t, report.Output.BatchOps[0].Transactions, 1)

tx := report.Output.BatchOps[0].Transactions[0]
require.Equal(t, testCCIPPackageID, tx.To)

latestPackageID, err := suideployutils.TransactionLatestPackageID(tx)
require.NoError(t, err)
require.Equal(t, testLatestCCIPPackageID, latestPackageID)
}

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

selector := cselectors.SUI_TESTNET.Selector
adapter := adapters.NewCurseAdapter()
adapter.CCIPAddress = testCCIPPackageID
adapter.CCIPObjectRef = testCCIPObjectRef
adapter.CCIPOwnerCapObjectID = "0x3333333333333333333333333333333333333333333333333333333333333333"

chains := cldf_chain.NewBlockChains(map[uint64]cldf_chain.BlockChain{
selector: cldfsui.Chain{ChainMetadata: cldfsui.ChainMetadata{Selector: selector}},
})

subjects := []fastcurse.Subject{
fastcurse.GenericSelectorToSubject(cselectors.ETHEREUM_MAINNET.Selector),
fastcurse.GenericSelectorToSubject(cselectors.POLYGON_MAINNET.Selector),
}

report, err := cld_ops.ExecuteSequence(
uncurseTestBundle(t),
adapter.Uncurse(),
chains,
fastcurse.CurseInput{
ChainSelector: selector,
Subjects: subjects,
},
)
require.NoError(t, err)
require.Len(t, report.Output.BatchOps, 1)
require.Equal(t, mcmstypes.ChainSelector(selector), report.Output.BatchOps[0].ChainSelector)
require.Len(t, report.Output.BatchOps[0].Transactions, 1)
}
66 changes: 66 additions & 0 deletions deployment/adapters/mcmsreader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ const (
testFastMcmsOwnerCap = "0x5656565656565656565656565656565656565656565656565656565656565656"
testFastMcmsTimelock = "0x6767676767676767676767676767676767676767676767676767676767676767"
testFastMcmsDeployer = "0x7878787878787878787878787878787878787878787878787878787878787878"

testSlowMcmsPackageID = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
testSlowMcmsState = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
testSlowMcmsRegistry = "0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
testSlowMcmsAccount = "0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
testSlowMcmsOwnerCap = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
testSlowMcmsTimelock = "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
testSlowMcmsDeployer = "0x0909090909090909090909090909090909090909090909090909090909090909"
)

func TestMCMSReader_RMNMCMSQualifier_SelectsFastMCMS(t *testing.T) {
Expand Down Expand Up @@ -71,3 +79,61 @@ func TestMCMSReader_RMNMCMSQualifier_SelectsFastMCMS(t *testing.T) {
require.Equal(t, testFastMcmsState, mcmsRef.Address)
require.Equal(t, selector, mcmsRef.ChainSelector)
}

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

selector := cselectors.SUI_TESTNET.Selector
ab := cldf.NewMemoryAddressBook()
require.NoError(t, ab.Save(selector, testMcmsReaderCCIPPackageID, cldf.NewTypeAndVersion(deployment.SuiCCIPType, deployment.Version1_0_0)))
require.NoError(t, ab.Save(selector, testMcmsReaderCCIPObjectRef, cldf.NewTypeAndVersion(deployment.SuiCCIPObjectRefType, deployment.Version1_0_0)))
require.NoError(t, deployment.StoreMCMSInAddressBook(ab, selector, mcmsops.DeployMCMSSeqOutput{
PackageId: testSlowMcmsPackageID,
Objects: mcmsops.DeployMCMSObjects{
McmsMultisigStateObjectId: testSlowMcmsState,
McmsRegistryObjectId: testSlowMcmsRegistry,
McmsAccountStateObjectId: testSlowMcmsAccount,
McmsAccountOwnerCapObjectId: testSlowMcmsOwnerCap,
TimelockObjectId: testSlowMcmsTimelock,
McmsDeployerStateObjectId: testSlowMcmsDeployer,
},
}, deployment.MCMSInstanceSlow))

env := cldf.Environment{
ExistingAddresses: ab,
BlockChains: cldf_chain.NewBlockChains(map[uint64]cldf_chain.BlockChain{
selector: cldfsui.Chain{ChainMetadata: cldfsui.ChainMetadata{Selector: selector}},
}),
}

reader := &adapters.MCMSReader{}

tests := []struct {
name string
qualifier string
}{
{"empty qualifier", ""},
{"CLLCCIP qualifier", "CLLCCIP"},
{"any non-RMNMCMS qualifier", "SomeOtherQualifier"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
input := ccipmcms.Input{
Qualifier: tt.qualifier,
TimelockAction: mcmstypes.TimelockActionBypass,
}

timelockRef, err := reader.GetTimelockRef(env, selector, input)
require.NoError(t, err)
require.Equal(t, testSlowMcmsTimelock, timelockRef.Address)
require.Equal(t, selector, timelockRef.ChainSelector)

mcmsRef, err := reader.GetMCMSRef(env, selector, input)
require.NoError(t, err)
require.Equal(t, testSlowMcmsState, mcmsRef.Address)
require.Equal(t, selector, mcmsRef.ChainSelector)
})
}
}
99 changes: 99 additions & 0 deletions deployment/changesets/cs_mcms_init_ownership_transfer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package changesets

import (
"fmt"

cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
cld_ops "github.com/smartcontractkit/chainlink-deployments-framework/operations"

"github.com/smartcontractkit/chainlink-sui/bindings/bind"
"github.com/smartcontractkit/chainlink-sui/deployment"
sui_ops "github.com/smartcontractkit/chainlink-sui/deployment/ops"
mcmsops "github.com/smartcontractkit/chainlink-sui/deployment/ops/mcms"
)

var _ cldf.ChangeSetV2[InitMCMSOwnershipTransferConfig] = InitMCMSOwnershipTransfer{}

// InitMCMSOwnershipTransferConfig starts the MCMS self-ownership transfer flow by calling
// mcms_account::transfer_ownership_to_self. This is step 1 of 3; accept and execute follow
// via sui_accept_mcms_ownership_to_self and sui_execute_ownership_transfer.
type InitMCMSOwnershipTransferConfig struct {
ChainSelector uint64 `json:"chainSelector" yaml:"chainSelector"`
// IsFastCurse selects the fastcurse MCMS instance; otherwise the normal instance is used.
IsFastCurse bool `json:"isFastCurse,omitempty" yaml:"isFastCurse,omitempty"`
}

type InitMCMSOwnershipTransfer struct{}

// VerifyPreconditions implements deployment.ChangeSetV2.
func (i InitMCMSOwnershipTransfer) VerifyPreconditions(e cldf.Environment, config InitMCMSOwnershipTransferConfig) error {
if config.ChainSelector == 0 {
return fmt.Errorf("chainSelector is required")
}
return nil
}

// Apply implements deployment.ChangeSetV2.
func (i InitMCMSOwnershipTransfer) Apply(e cldf.Environment, config InitMCMSOwnershipTransferConfig) (cldf.ChangesetOutput, error) {
suiState, err := deployment.LoadOnchainStatesui(e)
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to load sui onchain state: %w", err)
}

chainState, ok := suiState[config.ChainSelector]
if !ok {
return cldf.ChangesetOutput{}, fmt.Errorf("no Sui chain state for chain selector %d", config.ChainSelector)
}

mcmsFields := chainState.MCMSState(config.IsFastCurse)
if mcmsFields.PackageID == "" {
return cldf.ChangesetOutput{}, fmt.Errorf("MCMS package ID not found for chain selector %d (isFastCurse=%v)", config.ChainSelector, config.IsFastCurse)
}
if mcmsFields.AccountOwnerCapObjectID == "" {
return cldf.ChangesetOutput{}, fmt.Errorf("MCMS account owner cap not found for chain selector %d (isFastCurse=%v)", config.ChainSelector, config.IsFastCurse)
}
if mcmsFields.AccountStateObjectID == "" {
return cldf.ChangesetOutput{}, fmt.Errorf("MCMS account state object not found for chain selector %d (isFastCurse=%v)", config.ChainSelector, config.IsFastCurse)
}

suiChains := e.BlockChains.SuiChains()
suiChain, ok := suiChains[config.ChainSelector]
if !ok {
return cldf.ChangesetOutput{}, fmt.Errorf("no Sui chain found for chain selector %d", config.ChainSelector)
}

deps := sui_ops.OpTxDeps{
Client: suiChain.Client,
Signer: suiChain.Signer,
GetCallOpts: func() *bind.CallOpts {
b := uint64(400_000_000)
return &bind.CallOpts{
WaitForExecution: true,
GasBudget: &b,
}
},
SuiRPC: suiChain.URL,
}

opInput := mcmsops.MCMSTransferOwnershipInput{
McmsPackageID: mcmsFields.PackageID,
OwnerCap: mcmsFields.AccountOwnerCapObjectID,
AccountObjectID: mcmsFields.AccountStateObjectID,
}

report, err := cld_ops.ExecuteOperation(e.OperationsBundle, mcmsops.MCMSTransferOwnershipOp, deps, opInput)
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to init MCMS ownership transfer for chain %d: %w", config.ChainSelector, err)
}

e.Logger.Infow("Initiated MCMS ownership transfer to self",
"chainSelector", config.ChainSelector,
"isFastCurse", config.IsFastCurse,
"mcmsPackageID", mcmsFields.PackageID,
"digest", report.Output.Digest,
)

return cldf.ChangesetOutput{
Reports: []cld_ops.Report[any, any]{report.ToGenericReport()},
}, nil
}
23 changes: 23 additions & 0 deletions deployment/changesets/cs_mcms_init_ownership_transfer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package changesets

import (
"testing"

cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
"github.com/stretchr/testify/require"
)

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

cs := InitMCMSOwnershipTransfer{}

err := cs.VerifyPreconditions(cldf.Environment{}, InitMCMSOwnershipTransferConfig{
ChainSelector: 17529533435026248318,
})
require.NoError(t, err)

err = cs.VerifyPreconditions(cldf.Environment{}, InitMCMSOwnershipTransferConfig{})
require.Error(t, err)
require.Contains(t, err.Error(), "chainSelector is required")
}
Loading