diff --git a/.changeset/smooth-heads-jam.md b/.changeset/smooth-heads-jam.md new file mode 100644 index 000000000..33827ec97 --- /dev/null +++ b/.changeset/smooth-heads-jam.md @@ -0,0 +1,5 @@ +--- +"chainlink-deployments-framework": patch +--- + +chore: use mcms chainwrappers helpers diff --git a/chain/mcms/adapters/chain_access.go b/chain/mcms/adapters/chain_access.go index 16670951e..ac1b3aa58 100644 --- a/chain/mcms/adapters/chain_access.go +++ b/chain/mcms/adapters/chain_access.go @@ -33,6 +33,8 @@ type ChainsFetcher interface { CantonChains() map[uint64]cldfcanton.Chain } +var _ ChainsFetcher = &chain.BlockChains{} + // ChainAccessAdapter adapts CLDF's chain.BlockChains into a selector + lookup style API. // It is used to make it compatible with the mcms lib chain access interface. type ChainAccessAdapter struct { diff --git a/engine/cld/commands/mcms/chain_helpers.go b/engine/cld/commands/mcms/chain_helpers.go index 581a25bd6..a2227f0e6 100644 --- a/engine/cld/commands/mcms/chain_helpers.go +++ b/engine/cld/commands/mcms/chain_helpers.go @@ -4,61 +4,25 @@ import ( "context" "fmt" - chainsel "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/mcms" + "github.com/smartcontractkit/mcms/chainwrappers" "github.com/smartcontractkit/mcms/sdk" - "github.com/smartcontractkit/mcms/sdk/aptos" - "github.com/smartcontractkit/mcms/sdk/evm" - "github.com/smartcontractkit/mcms/sdk/solana" - "github.com/smartcontractkit/mcms/sdk/sui" - "github.com/smartcontractkit/mcms/sdk/ton" "github.com/smartcontractkit/mcms/types" - "github.com/xssnick/tonutils-go/tlb" - suibindings "github.com/smartcontractkit/chainlink-sui/bindings" + cldfmcmsadapters "github.com/smartcontractkit/chainlink-deployments-framework/chain/mcms/adapters" ) // getInspectorFromChainSelector returns an inspector for the given chain selector. func getInspectorFromChainSelector(cfg *forkConfig) (sdk.Inspector, error) { - fam, err := types.GetChainSelectorFamily(types.ChainSelector(cfg.chainSelector)) - if err != nil { - return nil, fmt.Errorf("error getting chain family: %w", err) + chainSelector := types.ChainSelector(cfg.chainSelector) + chainMetadata, ok := cfg.proposal.ChainMetadata[chainSelector] + if !ok { + return nil, fmt.Errorf("failed to get chain metadata from timelock proposal for chain selector %v", cfg.chainSelector) } - var inspector sdk.Inspector - switch fam { - case chainsel.FamilyEVM: - evmChain := cfg.blockchains.EVMChains()[cfg.chainSelector] - inspector = evm.NewInspector(evmChain.Client) - case chainsel.FamilySolana: - solanaChain := cfg.blockchains.SolanaChains()[cfg.chainSelector] - inspector = solana.NewInspector(solanaChain.Client) - case chainsel.FamilyAptos: - role, err := aptosRoleFromProposal(cfg.timelockProposal) - if err != nil { - return nil, fmt.Errorf("error getting aptos role from proposal: %w", err) - } - aptosChain := cfg.blockchains.AptosChains()[cfg.chainSelector] - mcmsType := aptos.MCMSTypeFromOperations(cfg.timelockProposal.Operations, types.ChainSelector(cfg.chainSelector)) - inspector = aptos.NewInspectorWithMCMSType(aptosChain.Client, *role, mcmsType) - case chainsel.FamilySui: - metadata, err := suiMetadataFromProposal(types.ChainSelector(cfg.chainSelector), cfg.timelockProposal) - if err != nil { - return nil, fmt.Errorf("error getting sui metadata from proposal: %w", err) - } - suiChain := cfg.blockchains.SuiChains()[cfg.chainSelector] - inspector, err = sui.NewInspector(suiChain.Client, suiChain.Signer, metadata.McmsPackageID, metadata.Role) - if err != nil { - return nil, fmt.Errorf("error creating sui inspector: %w", err) - } - case chainsel.FamilyTon: - tonChain := cfg.blockchains.TonChains()[cfg.chainSelector] - inspector = ton.NewInspector(tonChain.Client) - default: - return nil, fmt.Errorf("unsupported chain family %s", fam) - } + chainAccessor := cldfmcmsadapters.Wrap(cfg.blockchains) - return inspector, nil + return chainwrappers.BuildInspector(&chainAccessor, chainSelector, cfg.timelockProposal.Action, chainMetadata) } // createExecutable creates an MCMS executable for the proposal. @@ -96,128 +60,32 @@ func createTimelockExecutable(ctx context.Context, cfg *forkConfig) (*mcms.Timel // getExecutorWithChainOverride returns an executor for the given chain selector. func getExecutorWithChainOverride(cfg *forkConfig, chainSelector types.ChainSelector) (sdk.Executor, error) { - family, err := types.GetChainSelectorFamily(chainSelector) - if err != nil { - return nil, fmt.Errorf("error getting chain family: %w", err) - } - encoders, err := cfg.proposal.GetEncoders() if err != nil { return nil, fmt.Errorf("error getting encoders: %w", err) } encoder, ok := encoders[chainSelector] if !ok { - return nil, fmt.Errorf("unable to get encoder from proposal for chain selector %v", chainSelector) + return nil, fmt.Errorf("failed to get encoder from proposal for chain selector %v", chainSelector) + } + chainMetadata, ok := cfg.timelockProposal.ChainMetadata[chainSelector] + if !ok { + return nil, fmt.Errorf("failed to get chain metadata from timelock proposal for chain selector %v", chainSelector) } - switch family { - case chainsel.FamilyEVM: - evmEncoder, ok := encoder.(*evm.Encoder) - if !ok { - return nil, fmt.Errorf("invalid encoder type: %T", encoder) - } - c := cfg.blockchains.EVMChains()[uint64(chainSelector)] - - return evm.NewExecutor(evmEncoder, c.Client, c.DeployerKey), nil - - case chainsel.FamilySolana: - solanaEncoder, ok := encoder.(*solana.Encoder) - if !ok { - return nil, fmt.Errorf("invalid encoder type: %T", encoder) - } - c := cfg.blockchains.SolanaChains()[uint64(chainSelector)] - - return solana.NewExecutor(solanaEncoder, c.Client, *c.DeployerKey), nil - - case chainsel.FamilyAptos: - aptosEncoder, ok := encoder.(*aptos.Encoder) - if !ok { - return nil, fmt.Errorf("error getting encoder for chain %d", cfg.chainSelector) - } - role, err := aptosRoleFromProposal(cfg.timelockProposal) - if err != nil { - return nil, fmt.Errorf("error getting aptos role from proposal: %w", err) - } - c := cfg.blockchains.AptosChains()[uint64(chainSelector)] - mcmsType := aptos.MCMSTypeFromOperations(cfg.timelockProposal.Operations, chainSelector) - - return aptos.NewExecutorWithMCMSType(c.Client, c.DeployerSigner, aptosEncoder, *role, mcmsType), nil - - case chainsel.FamilySui: - suiEncoder, ok := encoder.(*sui.Encoder) - if !ok { - return nil, fmt.Errorf("error getting encoder for chain %d", cfg.chainSelector) - } - metadata, err := suiMetadataFromProposal(chainSelector, cfg.timelockProposal) - if err != nil { - return nil, fmt.Errorf("error getting sui metadata from proposal: %w", err) - } - c := cfg.blockchains.SuiChains()[uint64(chainSelector)] - entrypointEncoder := suibindings.NewCCIPEntrypointArgEncoder(metadata.RegistryObj, metadata.DeployerStateObj) - - return sui.NewExecutor(c.Client, c.Signer, suiEncoder, entrypointEncoder, metadata.McmsPackageID, metadata.Role, cfg.timelockProposal.ChainMetadata[chainSelector].MCMAddress, metadata.AccountObj, metadata.RegistryObj, metadata.TimelockObj) - - case chainsel.FamilyTon: - tonEncoder, ok := encoder.(*ton.Encoder) - if !ok { - return nil, fmt.Errorf("invalid encoder type for TON chain %d: expected *ton.Encoder, got %T", chainSelector, encoder) - } - c := cfg.blockchains.TonChains()[uint64(chainSelector)] - opts := ton.ExecutorOpts{ - Encoder: tonEncoder, - Client: c.Client, - Wallet: c.Wallet, - Amount: tlb.MustFromTON(defaultTONExecutorAmount), - } - - return ton.NewExecutor(opts) + chainAccessor := cldfmcmsadapters.Wrap(cfg.blockchains) - default: - return nil, fmt.Errorf("unsupported chain family %s", family) - } + return chainwrappers.BuildExecutor(&chainAccessor, chainSelector, encoder, cfg.timelockProposal.Action, chainMetadata) } // getTimelockExecutorWithChainOverride returns a timelock executor for the given chain selector. func getTimelockExecutorWithChainOverride(cfg *forkConfig, chainSelector types.ChainSelector) (sdk.TimelockExecutor, error) { - family, err := types.GetChainSelectorFamily(chainSelector) - if err != nil { - return nil, fmt.Errorf("error getting chain family: %w", err) + chainMetadata, ok := cfg.timelockProposal.ChainMetadata[chainSelector] + if !ok { + return nil, fmt.Errorf("failed to get chain metadata from timelock proposal for chain selector %v", chainSelector) } - var executor sdk.TimelockExecutor - switch family { - case chainsel.FamilyEVM: - c := cfg.blockchains.EVMChains()[uint64(chainSelector)] - executor = evm.NewTimelockExecutor(c.Client, c.DeployerKey) - case chainsel.FamilySolana: - c := cfg.blockchains.SolanaChains()[uint64(chainSelector)] - executor = solana.NewTimelockExecutor(c.Client, *c.DeployerKey) - case chainsel.FamilyAptos: - c := cfg.blockchains.AptosChains()[uint64(chainSelector)] - executor = aptos.NewTimelockExecutor(c.Client, c.DeployerSigner) - case chainsel.FamilySui: - c := cfg.blockchains.SuiChains()[uint64(chainSelector)] - metadata, err := suiMetadataFromProposal(chainSelector, cfg.timelockProposal) - if err != nil { - return nil, fmt.Errorf("error getting sui metadata from proposal: %w", err) - } - entrypointEncoder := suibindings.NewCCIPEntrypointArgEncoder(metadata.RegistryObj, metadata.DeployerStateObj) - executor, err = sui.NewTimelockExecutor(c.Client, c.Signer, entrypointEncoder, metadata.McmsPackageID, metadata.RegistryObj, metadata.AccountObj) - if err != nil { - return nil, fmt.Errorf("error creating sui timelock executor: %w", err) - } - case chainsel.FamilyTon: - c := cfg.blockchains.TonChains()[uint64(chainSelector)] - opts := ton.TimelockExecutorOpts{ - Client: c.Client, - Wallet: c.Wallet, - Amount: tlb.MustFromTON(defaultTONExecutorAmount), - } - - return ton.NewTimelockExecutor(opts) - default: - return nil, fmt.Errorf("unsupported chain family %s", family) - } + chainAccessor := cldfmcmsadapters.Wrap(cfg.blockchains) - return executor, nil + return chainwrappers.BuildTimelockExecutor(&chainAccessor, chainSelector, cfg.timelockProposal.Action, chainMetadata) } diff --git a/engine/cld/commands/mcms/chain_helpers_aptos.go b/engine/cld/commands/mcms/chain_helpers_aptos.go deleted file mode 100644 index 811d16c53..000000000 --- a/engine/cld/commands/mcms/chain_helpers_aptos.go +++ /dev/null @@ -1,33 +0,0 @@ -package mcms - -import ( - "errors" - - "github.com/smartcontractkit/mcms" - "github.com/smartcontractkit/mcms/sdk/aptos" - "github.com/smartcontractkit/mcms/types" -) - -// aptosRoleFromProposal extracts the Aptos role from a timelock proposal. -func aptosRoleFromProposal(proposal *mcms.TimelockProposal) (*aptos.TimelockRole, error) { - if proposal == nil { - return nil, errors.New("aptos timelock proposal is needed") - } - - switch proposal.Action { - case types.TimelockActionBypass: - role := aptos.TimelockRoleBypasser - - return &role, nil - case types.TimelockActionSchedule: - role := aptos.TimelockRoleProposer - - return &role, nil - case types.TimelockActionCancel: - role := aptos.TimelockRoleCanceller - - return &role, nil - default: - return nil, errors.New("unknown timelock action") - } -} diff --git a/engine/cld/commands/mcms/chain_helpers_sui.go b/engine/cld/commands/mcms/chain_helpers_sui.go deleted file mode 100644 index 6454d8a58..000000000 --- a/engine/cld/commands/mcms/chain_helpers_sui.go +++ /dev/null @@ -1,30 +0,0 @@ -package mcms - -import ( - "encoding/json" - "errors" - - "github.com/smartcontractkit/mcms" - "github.com/smartcontractkit/mcms/sdk/sui" - "github.com/smartcontractkit/mcms/types" -) - -// suiMetadataFromProposal extracts Sui metadata from a timelock proposal. -func suiMetadataFromProposal(selector types.ChainSelector, proposal *mcms.TimelockProposal) (sui.AdditionalFieldsMetadata, error) { - if proposal == nil { - return sui.AdditionalFieldsMetadata{}, errors.New("sui timelock proposal is needed") - } - - var metadata sui.AdditionalFieldsMetadata - err := json.Unmarshal([]byte(proposal.ChainMetadata[selector].AdditionalFields), &metadata) - if err != nil { - return sui.AdditionalFieldsMetadata{}, err - } - - err = metadata.Validate() - if err != nil { - return sui.AdditionalFieldsMetadata{}, err - } - - return metadata, nil -} diff --git a/engine/test/internal/mcmsutils/aptos.go b/engine/test/internal/mcmsutils/aptos.go deleted file mode 100644 index 7262e7932..000000000 --- a/engine/test/internal/mcmsutils/aptos.go +++ /dev/null @@ -1,125 +0,0 @@ -package mcmsutils - -import ( - "fmt" - "maps" - "slices" - - mcmssdk "github.com/smartcontractkit/mcms/sdk" - mcmsaptossdk "github.com/smartcontractkit/mcms/sdk/aptos" - mcmstypes "github.com/smartcontractkit/mcms/types" - - fchainaptos "github.com/smartcontractkit/chainlink-deployments-framework/chain/aptos" -) - -var ( - // actionToAptosRole maps MCMS timelock actions to their corresponding Aptos timelock roles. - // This mapping is used to determine the appropriate role when creating Aptos inspectors - // and executors for different types of timelock operations. - // - // Note: Only Aptos requires a role to instantiate an inspector. All other chains implement - // their contracts in a sane way so that they can be inspected without a role. - actionToAptosRole = map[mcmstypes.TimelockAction]mcmsaptossdk.TimelockRole{ - mcmstypes.TimelockActionSchedule: mcmsaptossdk.TimelockRoleProposer, - mcmstypes.TimelockActionBypass: mcmsaptossdk.TimelockRoleBypasser, - mcmstypes.TimelockActionCancel: mcmsaptossdk.TimelockRoleCanceller, - } -) - -var _ InspectorFactory = &aptosInspectorFactory{} - -// aptosInspectorFactory is a factory for creating Aptos-specific MCMS inspectors. -// It implements the InspectorFactory interface and is responsible for creating -// inspectors that can examine the state of MCMS and Timelock contracts on the Aptos blockchain. -type aptosInspectorFactory struct { - chain fchainaptos.Chain // The Aptos chain configuration and client - action mcmstypes.TimelockAction // The timelock action that determines the inspector role -} - -// newAptosInspectorFactory creates a new Aptos inspector factory. -func newAptosInspectorFactory( - chain fchainaptos.Chain, action mcmstypes.TimelockAction, -) *aptosInspectorFactory { - return &aptosInspectorFactory{ - chain: chain, - action: action, - } -} - -// Make creates and returns a new Aptos MCMS inspector. -func (f *aptosInspectorFactory) Make() (mcmssdk.Inspector, error) { - role, ok := actionToAptosRole[f.action] - if !ok { - return nil, fmt.Errorf("invalid action [%s]: must be one of %v", - f.action, slices.Collect(maps.Keys(actionToAptosRole)), - ) - } - - return mcmsaptossdk.NewInspector(f.chain.Client, role), nil -} - -var _ ConverterFactory = &aptosConverterFactory{} - -// aptosConverterFactory is a factory for creating Aptos-specific timelock converters. -// It implements the ConverterFactory interface and creates converters that can -// transform MCMS timelock proposals into a standard MCMS proposal. -type aptosConverterFactory struct{} - -// newAptosConverterFactory creates a new Aptos converter factory. -func newAptosConverterFactory() *aptosConverterFactory { - return &aptosConverterFactory{} -} - -// Make creates and returns a new Aptos timelock converter. -func (f *aptosConverterFactory) Make() (mcmssdk.TimelockConverter, error) { - return mcmsaptossdk.NewTimelockConverter(), nil -} - -var _ ExecutorFactory = &aptosExecutorFactory{} - -// aptosExecutorFactory is a factory for creating Aptos-specific MCMS executors. -// It implements the ExecutorFactory interface and creates executors that can -// execute MCMS operations on the Aptos blockchain. -type aptosExecutorFactory struct { - chain fchainaptos.Chain // The Aptos chain configuration and client - encoder *mcmsaptossdk.Encoder // The encoder for creating Aptos-specific transaction data -} - -// newAptosExecutorFactory creates a new Aptos executor factory. -func newAptosExecutorFactory( - chain fchainaptos.Chain, encoder *mcmsaptossdk.Encoder, -) *aptosExecutorFactory { - return &aptosExecutorFactory{ - chain: chain, - encoder: encoder, - } -} - -// Make creates and returns a new Aptos MCMS executor. -func (f *aptosExecutorFactory) Make() (mcmssdk.Executor, error) { - return mcmsaptossdk.NewExecutor( - f.chain.Client, - f.chain.DeployerSigner, - f.encoder, - mcmsaptossdk.TimelockRoleProposer, - ), nil -} - -var _ TimelockExecutorFactory = &aptosTimelockExecutorFactory{} - -// aptosTimelockExecutorFactory is a factory for creating Aptos-specific timelock executors. -// It implements the TimelockExecutorFactory interface and creates executors specifically -// designed for executing Timelock operations on the Aptos blockchain. -type aptosTimelockExecutorFactory struct { - chain fchainaptos.Chain // The Aptos chain configuration and client -} - -// newAptosTimelockExecutorFactory creates a new Aptos timelock executor factory. -func newAptosTimelockExecutorFactory(chain fchainaptos.Chain) *aptosTimelockExecutorFactory { - return &aptosTimelockExecutorFactory{chain: chain} -} - -// Make creates and returns a new Aptos timelock executor. -func (f *aptosTimelockExecutorFactory) Make() (mcmssdk.TimelockExecutor, error) { - return mcmsaptossdk.NewTimelockExecutor(f.chain.Client, f.chain.DeployerSigner), nil -} diff --git a/engine/test/internal/mcmsutils/aptos_test.go b/engine/test/internal/mcmsutils/aptos_test.go deleted file mode 100644 index 854a8afb7..000000000 --- a/engine/test/internal/mcmsutils/aptos_test.go +++ /dev/null @@ -1,147 +0,0 @@ -package mcmsutils - -import ( - "testing" - - mcmsaptossdk "github.com/smartcontractkit/mcms/sdk/aptos" - mcmstypes "github.com/smartcontractkit/mcms/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestNewAptosInspectorFactory(t *testing.T) { - t.Parallel() - - chain := stubAptosChain() - action := mcmstypes.TimelockActionSchedule - - factory := newAptosInspectorFactory(chain, action) - - assert.NotNil(t, factory) - assert.Equal(t, chain.Selector, factory.chain.Selector) - assert.Equal(t, chain.URL, factory.chain.URL) - assert.Equal(t, action, factory.action) -} - -func TestAptosInspectorFactory_Make(t *testing.T) { - t.Parallel() - - t.Run("valid actions", func(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - action mcmstypes.TimelockAction - wantRole mcmsaptossdk.TimelockRole - wantErr string - }{ - { - name: "schedule action", - action: mcmstypes.TimelockActionSchedule, - wantRole: mcmsaptossdk.TimelockRoleProposer, - }, - { - name: "bypass action", - action: mcmstypes.TimelockActionBypass, - wantRole: mcmsaptossdk.TimelockRoleBypasser, - }, - { - name: "cancel action", - action: mcmstypes.TimelockActionCancel, - wantRole: mcmsaptossdk.TimelockRoleCanceller, - }, - { - name: "invalid action", - action: mcmstypes.TimelockAction("invalid"), - wantErr: "invalid action", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - chain := stubAptosChain() - factory := newAptosInspectorFactory(chain, tt.action) - - inspector, err := factory.Make() - - if tt.wantErr != "" { - require.Error(t, err) - assert.ErrorContains(t, err, tt.wantErr) - } else { - require.NoError(t, err) - assert.NotNil(t, inspector) - } - }) - } - }) -} - -func TestNewAptosConverterFactory(t *testing.T) { - t.Parallel() - - factory := newAptosConverterFactory() - - assert.NotNil(t, factory, "Factory should not be nil") -} - -func TestAptosConverterFactory_Make(t *testing.T) { - t.Parallel() - - factory := newAptosConverterFactory() - - converter, err := factory.Make() - require.NoError(t, err) - assert.NotNil(t, converter) -} - -func TestNewAptosExecutorFactory(t *testing.T) { - t.Parallel() - - chain := stubAptosChain() - encoder := &mcmsaptossdk.Encoder{} // Empty encoder for testing - - factory := newAptosExecutorFactory(chain, encoder) - - require.NotNil(t, factory) - assert.Equal(t, chain.Selector, factory.chain.Selector) - assert.Equal(t, chain.URL, factory.chain.URL) - assert.Equal(t, encoder, factory.encoder) -} - -func TestAptosExecutorFactory_Make(t *testing.T) { - t.Parallel() - - chain := stubAptosChain() - encoder := &mcmsaptossdk.Encoder{} // Empty encoder for testing - - factory := newAptosExecutorFactory(chain, encoder) - - executor, err := factory.Make() - require.NoError(t, err) - assert.NotNil(t, executor) -} - -func TestNewAptosTimelockExecutorFactory(t *testing.T) { - t.Parallel() - - chain := stubAptosChain() - - factory := newAptosTimelockExecutorFactory(chain) - - require.NotNil(t, factory) - assert.Equal(t, chain.Selector, factory.chain.Selector) - assert.Equal(t, chain.URL, factory.chain.URL) -} - -func TestAptosTimelockExecutorFactory_Make(t *testing.T) { - t.Parallel() - - chain := stubAptosChain() - factory := newAptosTimelockExecutorFactory(chain) - - executor, err := factory.Make() - require.NoError(t, err) - assert.NotNil(t, executor) -} diff --git a/engine/test/internal/mcmsutils/conversion.go b/engine/test/internal/mcmsutils/conversion.go index f07beab8f..fe7fed35d 100644 --- a/engine/test/internal/mcmsutils/conversion.go +++ b/engine/test/internal/mcmsutils/conversion.go @@ -4,39 +4,16 @@ import ( "context" "fmt" - chainselectors "github.com/smartcontractkit/chain-selectors" mcmslib "github.com/smartcontractkit/mcms" - mcmssdk "github.com/smartcontractkit/mcms/sdk" - mcmstypes "github.com/smartcontractkit/mcms/types" + "github.com/smartcontractkit/mcms/chainwrappers" ) // convertTimelock converts a timelock proposal to an MCMS proposal by creating chain-specific // converters for each chain in the proposal and then performing the conversion. func convertTimelock(ctx context.Context, proposal mcmslib.TimelockProposal) (*mcmslib.Proposal, error) { - converters := make(map[mcmstypes.ChainSelector]mcmssdk.TimelockConverter, 0) - for selector := range proposal.ChainMetadata { - family, err := chainselectors.GetSelectorFamily(uint64(selector)) - if err != nil { - return nil, fmt.Errorf("failed to get selector family for chain (selector: %d): %w", - selector, err, - ) - } - - convFactory, err := GetConverterFactory(family) - if err != nil { - return nil, fmt.Errorf("failed to get converter factory for chain (selector: %d, family: %s): %w", - selector, family, err, - ) - } - - converter, err := convFactory.Make() - if err != nil { - return nil, fmt.Errorf("failed to create converter for chain (selector: %d, family: %s): %w", - selector, family, err, - ) - } - - converters[selector] = converter + converters, err := chainwrappers.BuildConverters(proposal.ChainMetadata) + if err != nil { + return nil, fmt.Errorf("failed to build converters: %w", err) } p, _, err := proposal.Convert(ctx, converters) diff --git a/engine/test/internal/mcmsutils/conversion_test.go b/engine/test/internal/mcmsutils/conversion_test.go index 0b27b6723..03ec67858 100644 --- a/engine/test/internal/mcmsutils/conversion_test.go +++ b/engine/test/internal/mcmsutils/conversion_test.go @@ -89,17 +89,16 @@ func TestConvertTimelock(t *testing.T) { }, } }, - wantErr: "failed to get selector family for chain", + wantErr: "failed to build converters: error getting chain family: chain family not found for selector 999999", }, { - name: "fails to get converter factory", + name: "fails with invalid chain family", setupProposal: func(t *testing.T) mcmslib.TimelockProposal { t.Helper() return mcmslib.TimelockProposal{ BaseProposal: mcmslib.BaseProposal{ ChainMetadata: map[mcmstypes.ChainSelector]mcmstypes.ChainMetadata{ - // We are choosing Tron here because it is not supported by the converter factory. This may change in the future. mcmstypes.ChainSelector(chainselectors.TRON_TESTNET_NILE.Selector): { MCMAddress: "0x0000000000000000000000000000000000000000", }, @@ -107,7 +106,7 @@ func TestConvertTimelock(t *testing.T) { }, } }, - wantErr: "failed to get converter factory for chain", + wantErr: "failed to build converters: error getting chain family: unsupported chain family: tron", }, } diff --git a/engine/test/internal/mcmsutils/errors.go b/engine/test/internal/mcmsutils/errors.go index a26b0f887..4b2c321cb 100644 --- a/engine/test/internal/mcmsutils/errors.go +++ b/engine/test/internal/mcmsutils/errors.go @@ -2,18 +2,7 @@ package mcmsutils import ( "errors" - "fmt" ) -// errFamilyNotSupported is the error returned when a chain family is not implemented. +// ErrFamilyNotSupported is the error returned when a chain family is not implemented. var ErrFamilyNotSupported = errors.New("chain family not supported") - -// errFamilyNotSupported returns a wrapped error with additional information about the chain -// family that is not supported. -func errFamilyNotSupported(family string) error { - return fmt.Errorf("%w: %s", ErrFamilyNotSupported, family) -} - -func errEncoderNotFound(selector uint64) error { - return fmt.Errorf("encoder not found for chain selector %d", selector) -} diff --git a/engine/test/internal/mcmsutils/evm.go b/engine/test/internal/mcmsutils/evm.go deleted file mode 100644 index cc09e93e3..000000000 --- a/engine/test/internal/mcmsutils/evm.go +++ /dev/null @@ -1,94 +0,0 @@ -package mcmsutils - -import ( - mcmssdk "github.com/smartcontractkit/mcms/sdk" - mcmsevmsdk "github.com/smartcontractkit/mcms/sdk/evm" - - fchainevm "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm" -) - -var _ InspectorFactory = &evmInspectorFactory{} - -// evmInspectorFactory is a factory for creating EVM-specific MCMS inspectors. -// It implements the InspectorFactory interface and is responsible for creating -// inspectors that can examine the state of MCMS and Timelock contracts on EVM-compatible blockchains. -type evmInspectorFactory struct { - chain fchainevm.Chain // The EVM chain configuration and client -} - -// newEVMInspectorFactory creates a new EVM inspector factory. -func newEVMInspectorFactory(chain fchainevm.Chain) *evmInspectorFactory { - return &evmInspectorFactory{chain: chain} -} - -// Make creates and returns a new EVM MCMS inspector. -func (f *evmInspectorFactory) Make() (mcmssdk.Inspector, error) { - return mcmsevmsdk.NewInspector(f.chain.Client), nil -} - -//------------------------------------------------------------------------------ - -var _ ConverterFactory = &evmConverterFactory{} - -// evmConverterFactory is a factory for creating EVM-specific timelock converters. -// It implements the ConverterFactory interface and creates converters that can -// transform MCMS timelock proposals into a standard MCMS proposal. -type evmConverterFactory struct{} - -// newEVMConverterFactory creates a new EVM converter factory. -func newEVMConverterFactory() *evmConverterFactory { - return &evmConverterFactory{} -} - -// Make creates and returns a new EVM timelock converter. -func (f *evmConverterFactory) Make() (mcmssdk.TimelockConverter, error) { - return &mcmsevmsdk.TimelockConverter{}, nil -} - -//------------------------------------------------------------------------------ - -var _ ExecutorFactory = &evmExecutorFactory{} - -// evmExecutorFactory is a factory for creating EVM-specific MCMS executors. -// It implements the ExecutorFactory interface and creates executors that can -// execute MCMS operations on EVM-compatible blockchains. -type evmExecutorFactory struct { - chain fchainevm.Chain // The EVM chain configuration and client - encoder *mcmsevmsdk.Encoder // The encoder for creating EVM-specific transaction data -} - -// newEVMExecutorFactory creates a new EVM executor factory. -func newEVMExecutorFactory( - chain fchainevm.Chain, encoder *mcmsevmsdk.Encoder, -) *evmExecutorFactory { - return &evmExecutorFactory{ - chain: chain, - encoder: encoder, - } -} - -// Make creates and returns a new EVM MCMS executor. -func (f *evmExecutorFactory) Make() (mcmssdk.Executor, error) { - return mcmsevmsdk.NewExecutor(f.encoder, f.chain.Client, f.chain.DeployerKey), nil -} - -//------------------------------------------------------------------------------ - -var _ TimelockExecutorFactory = &evmTimelockExecutorFactory{} - -// evmTimelockExecutorFactory is a factory for creating EVM-specific timelock executors. -// It implements the TimelockExecutorFactory interface and creates executors specifically -// designed for executing Timelock operations on EVM-compatible blockchains. -type evmTimelockExecutorFactory struct { - chain fchainevm.Chain // The EVM chain configuration and client -} - -// newEVMTimelockExecutorFactory creates a new EVM timelock executor factory. -func newEVMTimelockExecutorFactory(chain fchainevm.Chain) *evmTimelockExecutorFactory { - return &evmTimelockExecutorFactory{chain: chain} -} - -// Make creates and returns a new EVM timelock executor. -func (f *evmTimelockExecutorFactory) Make() (mcmssdk.TimelockExecutor, error) { - return mcmsevmsdk.NewTimelockExecutor(f.chain.Client, f.chain.DeployerKey), nil -} diff --git a/engine/test/internal/mcmsutils/evm_test.go b/engine/test/internal/mcmsutils/evm_test.go deleted file mode 100644 index 38ec3ece9..000000000 --- a/engine/test/internal/mcmsutils/evm_test.go +++ /dev/null @@ -1,96 +0,0 @@ -package mcmsutils - -import ( - "testing" - - mcmsevmsdk "github.com/smartcontractkit/mcms/sdk/evm" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestNewEVMInspectorFactory(t *testing.T) { - t.Parallel() - - chain := stubEVMChain() - factory := newEVMInspectorFactory(chain) - - require.NotNil(t, factory) - assert.Equal(t, chain.Selector, factory.chain.Selector) -} - -func TestEVMInspectorFactory_Make(t *testing.T) { - t.Parallel() - - chain := stubEVMChain() - factory := newEVMInspectorFactory(chain) - - inspector, err := factory.Make() - require.NoError(t, err) - assert.NotNil(t, inspector) -} - -func TestNewEVMConverterFactory(t *testing.T) { - t.Parallel() - - factory := newEVMConverterFactory() - - require.NotNil(t, factory) -} - -func TestEVMConverterFactory_Make(t *testing.T) { - t.Parallel() - - factory := newEVMConverterFactory() - - converter, err := factory.Make() - require.NoError(t, err) - assert.NotNil(t, converter) -} - -func TestNewEVMExecutorFactory(t *testing.T) { - t.Parallel() - - chain := stubEVMChain() - encoder := &mcmsevmsdk.Encoder{} // Empty encoder for testing - - factory := newEVMExecutorFactory(chain, encoder) - - require.NotNil(t, factory) - assert.Equal(t, chain.Selector, factory.chain.Selector) - assert.Equal(t, encoder, factory.encoder) -} - -func TestEVMExecutorFactory_Make(t *testing.T) { - t.Parallel() - - chain := stubEVMChain() - encoder := &mcmsevmsdk.Encoder{} // Empty encoder for testing - - factory := newEVMExecutorFactory(chain, encoder) - - executor, err := factory.Make() - require.NoError(t, err) - assert.NotNil(t, executor) -} - -func TestNewEVMTimelockExecutorFactory(t *testing.T) { - t.Parallel() - - chain := stubEVMChain() - - factory := newEVMTimelockExecutorFactory(chain) - - require.NotNil(t, factory) - assert.Equal(t, chain.Selector, factory.chain.Selector) -} - -func TestEVMTimelockExecutorFactory_Make(t *testing.T) { - t.Parallel() - - chain := stubEVMChain() - factory := newEVMTimelockExecutorFactory(chain) - - executor, err := factory.Make() - require.NoError(t, err) - assert.NotNil(t, executor) -} diff --git a/engine/test/internal/mcmsutils/executor.go b/engine/test/internal/mcmsutils/executor.go index c7bc2877d..b5626f9d1 100644 --- a/engine/test/internal/mcmsutils/executor.go +++ b/engine/test/internal/mcmsutils/executor.go @@ -14,12 +14,14 @@ import ( chainselectors "github.com/smartcontractkit/chain-selectors" mcmslib "github.com/smartcontractkit/mcms" + "github.com/smartcontractkit/mcms/chainwrappers" mcmssdk "github.com/smartcontractkit/mcms/sdk" mcmstypes "github.com/smartcontractkit/mcms/types" fchain "github.com/smartcontractkit/chainlink-deployments-framework/chain" fchainaptos "github.com/smartcontractkit/chainlink-deployments-framework/chain/aptos" fchainevm "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm" + "github.com/smartcontractkit/chainlink-deployments-framework/chain/mcms/adapters" fchainsolana "github.com/smartcontractkit/chainlink-deployments-framework/chain/solana" fchainton "github.com/smartcontractkit/chainlink-deployments-framework/chain/ton" "github.com/smartcontractkit/chainlink-deployments-framework/datastore" @@ -81,7 +83,12 @@ func NewExecutor(e fdeployment.Environment) *Executor { // 4. Executing each operation sequentially and confirming transactions // // Returns an error if any step fails -func (e *Executor) ExecuteMCMS(ctx context.Context, proposal *mcmslib.Proposal) error { +func (e *Executor) ExecuteMCMS(ctx context.Context, proposal *mcmslib.Proposal, execOpts ...ExecuteOption) error { + opts := &executeOptions{action: mcmstypes.TimelockActionSchedule} + for _, execOpt := range execOpts { + execOpt(opts) + } + // Validate the proposal to ensure it is valid ensuring that all chain metadata is present. if err := proposal.Validate(); err != nil { return fmt.Errorf("failed to validate MCMS proposal: %w", err) @@ -102,21 +109,10 @@ func (e *Executor) ExecuteMCMS(ctx context.Context, proposal *mcmslib.Proposal) return fmt.Errorf("failed to retrieve encoders from MCMS proposal: %w", err) } - // Generate executors for each chain - executors := make(map[mcmstypes.ChainSelector]mcmssdk.Executor, 0) - for selector := range proposal.ChainMetadata { - b := blockchains[selector] - - execFactory, ferr := GetExecutorFactory(b, encoders[selector]) - if ferr != nil { - return fmt.Errorf("failed to create executor factory for chain selector %d (%s): %w", selector, b.Name(), ferr) - } - - executor, merr := execFactory.Make() - if merr != nil { - return fmt.Errorf("failed to create executor for chain selector %d (%s): %w", selector, b.Name(), merr) - } - executors[selector] = executor + mcmsBlockChains := adapters.Wrap(e.env.BlockChains) + executors, err := chainwrappers.BuildExecutors(&mcmsBlockChains, proposal.ChainMetadata, encoders, opts.action) + if err != nil { + return fmt.Errorf("failed to build executors: %w", err) } executable, err := e.newExecutable(proposal, executors) @@ -158,6 +154,18 @@ func (e *Executor) ExecuteMCMS(ctx context.Context, proposal *mcmslib.Proposal) return nil } +type executeOptions struct { + action mcmstypes.TimelockAction +} + +type ExecuteOption = func(o *executeOptions) + +func WithTimelockAction(action mcmstypes.TimelockAction) ExecuteOption { + return func(o *executeOptions) { + o.action = action + } +} + // ExecuteTimelock executes a timelock proposal, which involves both MCMS execution // and timelock-specific operations. The execution process includes: // @@ -190,7 +198,7 @@ func (e *Executor) ExecuteTimelock(ctx context.Context, timelockProposal *mcmsli } // Execute the proposal against the MCMS Contract - if err = e.ExecuteMCMS(ctx, proposal); err != nil { + if err = e.ExecuteMCMS(ctx, proposal, WithTimelockAction(timelockProposal.Action)); err != nil { return fmt.Errorf("failed to execute MCMS proposal: %w", err) } @@ -202,19 +210,10 @@ func (e *Executor) ExecuteTimelock(ctx context.Context, timelockProposal *mcmsli // Now we execute the proposal on the Timelock contract - // Generate executors for each blockchain - executors := make(map[mcmstypes.ChainSelector]mcmssdk.TimelockExecutor, 0) - for selector, b := range blockchains { - execFactory, gerr := GetTimelockExecutorFactory(b) - if gerr != nil { - return fmt.Errorf("failed to create timelock executor factory for chain selector %d (%s): %w", selector, b.Name(), gerr) - } - - executor, merr := execFactory.Make() - if merr != nil { - return fmt.Errorf("failed to create timelock executor for chain selector %d (%s): %w", selector, b.Name(), merr) - } - executors[selector] = executor + mcmsBlockChains := adapters.Wrap(e.env.BlockChains) + executors, err := chainwrappers.BuildTimelockExecutors(&mcmsBlockChains, proposal.ChainMetadata, timelockProposal.Action) + if err != nil { + return fmt.Errorf("failed to build executors: %w", err) } // Generate call proxies for each EVM operation. diff --git a/engine/test/internal/mcmsutils/factory.go b/engine/test/internal/mcmsutils/factory.go deleted file mode 100644 index 7288b5a6c..000000000 --- a/engine/test/internal/mcmsutils/factory.go +++ /dev/null @@ -1,152 +0,0 @@ -package mcmsutils - -import ( - "errors" - - chainselectors "github.com/smartcontractkit/chain-selectors" - mcmssdk "github.com/smartcontractkit/mcms/sdk" - mcmsaptossdk "github.com/smartcontractkit/mcms/sdk/aptos" - mcmsevmsdk "github.com/smartcontractkit/mcms/sdk/evm" - mcmssolanasdk "github.com/smartcontractkit/mcms/sdk/solana" - mcmstypes "github.com/smartcontractkit/mcms/types" - - fchain "github.com/smartcontractkit/chainlink-deployments-framework/chain" - fchainaptos "github.com/smartcontractkit/chainlink-deployments-framework/chain/aptos" - fchainevm "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm" - fchainsolana "github.com/smartcontractkit/chainlink-deployments-framework/chain/solana" -) - -// InspectorFactory creates MCMS Inspector instances for MCMS contract inspection. -type InspectorFactory interface { - Make() (mcmssdk.Inspector, error) -} - -// GetInspectorFactory returns a blockchain-specific InspectorFactory based on the provided -// blockchain's type for use in performing MCMS operations. -// -// Note: Aptos chains only support timelock-specific inspection (use GetTimelockInspectorFactory -// when performing inspection against a Timelock proposal). -// -// Returns an error if the blockchain family is not supported. -func GetInspectorFactory( - blockchain fchain.BlockChain, -) (InspectorFactory, error) { - switch c := blockchain.(type) { - case fchainevm.Chain: - return newEVMInspectorFactory(c), nil - case fchainsolana.Chain: - return newSolanaInspectorFactory(c), nil - case fchainaptos.Chain: - return nil, errors.New("aptos does not support inspection on non-timelock proposals") - default: - return nil, errFamilyNotSupported(c.Family()) - } -} - -// GetTimelockInspectorFactory returns a blockchain-specific InspectorFactory based on the provided -// blockchain's type for use in performing MCMS Timelock operations. -// -// Returns an error if the blockchain family is not supported. -func GetTimelockInspectorFactory( - blockchain fchain.BlockChain, - action mcmstypes.TimelockAction, -) (InspectorFactory, error) { - switch c := blockchain.(type) { - case fchainevm.Chain: - return newEVMInspectorFactory(c), nil - case fchainsolana.Chain: - return newSolanaInspectorFactory(c), nil - case fchainaptos.Chain: - return newAptosInspectorFactory(c, action), nil - default: - return nil, errFamilyNotSupported(c.Family()) - } -} - -// ConverterFactory creates MCMS TimelockConverter instances for converting a Timelock proposal to -// an MCMS proposal. -type ConverterFactory interface { - Make() (mcmssdk.TimelockConverter, error) -} - -// GetConverterFactory returns a blockchain family specific ConverterFactory. -// -// Returns an error if the blockchain family is not supported. -func GetConverterFactory(family string) (ConverterFactory, error) { - switch family { - case chainselectors.FamilyEVM: - return newEVMConverterFactory(), nil - case chainselectors.FamilySolana: - return newSolanaConverterFactory(), nil - case chainselectors.FamilyAptos: - return newAptosConverterFactory(), nil - default: - return nil, errFamilyNotSupported(family) - } -} - -// ExecutorFactory creates MCMS Executor instances for executing MCMS contract operations. -type ExecutorFactory interface { - Make() (mcmssdk.Executor, error) -} - -// GetExecutorFactory returns a blockchain-specific ExecutorFactory with the appropriate encoder. -// -// The provided encoder must match the blockchain type for proper transaction encoding and execution. -// -// Returns an error if the blockchain family is not supported. -func GetExecutorFactory( - blockchain fchain.BlockChain, - encoder mcmssdk.Encoder, -) (ExecutorFactory, error) { - switch c := blockchain.(type) { - case fchainevm.Chain: - encoder, ok := encoder.(*mcmsevmsdk.Encoder) - if !ok { - return nil, errEncoderNotFound(c.Selector) - } - - return newEVMExecutorFactory(c, encoder), nil - case fchainsolana.Chain: - encoder, ok := encoder.(*mcmssolanasdk.Encoder) - if !ok { - return nil, errEncoderNotFound(c.Selector) - } - - return newSolanaExecutorFactory(c, encoder), nil - case fchainaptos.Chain: - encoder, ok := encoder.(*mcmsaptossdk.Encoder) - if !ok { - return nil, errEncoderNotFound(c.Selector) - } - - return newAptosExecutorFactory(c, encoder), nil - default: - return nil, errFamilyNotSupported(c.Family()) - } -} - -// TimelockExecutorFactory creates MCMS TimelockExecutor instances for executing the Timelock -// contract operations. -type TimelockExecutorFactory interface { - Make() (mcmssdk.TimelockExecutor, error) -} - -// GetTimelockExecutorFactory returns a blockchain-specific TimelockExecutorFactory based on the -// provided blockchain type. -// -// Returns an error if the blockchain family is not supported. -func GetTimelockExecutorFactory( - blockchain fchain.BlockChain, -) (TimelockExecutorFactory, error) { - switch c := blockchain.(type) { - case fchainevm.Chain: - return newEVMTimelockExecutorFactory(c), nil - case fchainsolana.Chain: - return newSolanaTimelockExecutorFactory(c), nil - case fchainaptos.Chain: - return newAptosTimelockExecutorFactory(c), nil - default: - return nil, errFamilyNotSupported(c.Family()) - } -} diff --git a/engine/test/internal/mcmsutils/factory_test.go b/engine/test/internal/mcmsutils/factory_test.go deleted file mode 100644 index e886f34ad..000000000 --- a/engine/test/internal/mcmsutils/factory_test.go +++ /dev/null @@ -1,320 +0,0 @@ -package mcmsutils - -import ( - "fmt" - "testing" - - chainselectors "github.com/smartcontractkit/chain-selectors" - mcmssdk "github.com/smartcontractkit/mcms/sdk" - mcmsaptossdk "github.com/smartcontractkit/mcms/sdk/aptos" - mcmsevmsdk "github.com/smartcontractkit/mcms/sdk/evm" - mcmssolanasdk "github.com/smartcontractkit/mcms/sdk/solana" - mcmstypes "github.com/smartcontractkit/mcms/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - fchain "github.com/smartcontractkit/chainlink-deployments-framework/chain" - "github.com/smartcontractkit/chainlink-deployments-framework/engine/test/internal/testutils" -) - -func TestGetInspectorFactory(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - chain fchain.BlockChain - wantType any - wantErr string - }{ - { - name: "EVM chain success", - chain: stubEVMChain(), - wantType: &evmInspectorFactory{}, - }, - { - name: "Solana chain success", - chain: stubSolanaChain(), - wantType: &solanaInspectorFactory{}, - }, - { - name: "Aptos chain should fail", - chain: stubAptosChain(), - wantErr: "aptos does not support inspection on non-timelock proposals", - }, - { - name: "unsupported chain family", - chain: testutils.NewStubChain(999999), - wantErr: "chain family not supported: test", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - factory, err := GetInspectorFactory(tt.chain) - - if tt.wantErr != "" { - require.Error(t, err) - require.ErrorContains(t, err, tt.wantErr) - assert.Nil(t, factory) - } else { - require.NoError(t, err) - require.NotNil(t, factory) - - // Verify we can create an inspector - inspector, err := factory.Make() - require.NoError(t, err) - assert.NotNil(t, inspector) - assert.IsType(t, tt.wantType, factory) - } - }) - } -} - -func TestGetTimelockInspectorFactory(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - chain fchain.BlockChain - action mcmstypes.TimelockAction - wantType any - wantErr string - }{ - { - name: "EVM chain", - chain: stubEVMChain(), - action: mcmstypes.TimelockActionSchedule, - wantType: &evmInspectorFactory{}, - }, - { - name: "Solana chain", - chain: stubSolanaChain(), - action: mcmstypes.TimelockActionCancel, - wantType: &solanaInspectorFactory{}, - }, - { - name: "Aptos chain", - chain: stubAptosChain(), - action: mcmstypes.TimelockActionBypass, - wantType: &aptosInspectorFactory{}, - }, - { - name: "unsupported chain family", - chain: testutils.NewStubChain(999999), - action: mcmstypes.TimelockActionSchedule, - wantErr: "chain family not supported: test", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - factory, err := GetTimelockInspectorFactory(tt.chain, tt.action) - - if tt.wantErr != "" { - require.Error(t, err) - require.ErrorContains(t, err, tt.wantErr) - assert.Nil(t, factory) - } else { - require.NoError(t, err) - require.NotNil(t, factory) - - // Verify we can create an inspector - inspector, err := factory.Make() - require.NoError(t, err) - assert.NotNil(t, inspector) - assert.IsType(t, tt.wantType, factory) - } - }) - } -} - -func TestGetConverterFactory(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - family string - wantType any - wantErr string - }{ - { - name: "EVM chain success", - family: chainselectors.FamilyEVM, - wantType: &evmConverterFactory{}, - }, - { - name: "Solana chain success", - family: chainselectors.FamilySolana, - wantType: &solanaConverterFactory{}, - }, - { - name: "Aptos chain success", - family: chainselectors.FamilyAptos, - wantType: &aptosConverterFactory{}, - }, - { - name: "unsupported chain family", - family: "unsupported", - wantErr: "chain family not supported: unsupported", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - factory, err := GetConverterFactory(tt.family) - - if tt.wantErr != "" { - require.Error(t, err) - require.ErrorContains(t, err, tt.wantErr) - assert.Nil(t, factory) - } else { - require.NoError(t, err) - require.NotNil(t, factory) - - // Verify we can create a converter - converter, err := factory.Make() - require.NoError(t, err) - assert.NotNil(t, converter) - assert.IsType(t, tt.wantType, factory) - } - }) - } -} - -func TestGetExecutorFactory(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - chain fchain.BlockChain - encoder mcmssdk.Encoder - wantType any - wantErr string - }{ - { - name: "EVM chain with correct encoder", - chain: stubEVMChain(), - encoder: &mcmsevmsdk.Encoder{}, - wantType: &evmExecutorFactory{}, - }, - { - name: "EVM chain with wrong encoder", - chain: stubEVMChain(), - encoder: &mcmssolanasdk.Encoder{}, - wantErr: fmt.Sprintf("encoder not found for chain selector %d", stubEVMChain().Selector), - }, - { - name: "Solana chain with correct encoder", - chain: stubSolanaChain(), - encoder: &mcmssolanasdk.Encoder{}, - wantType: &solanaExecutorFactory{}, - }, - { - name: "Solana chain with wrong encoder", - chain: stubSolanaChain(), - encoder: &mcmsevmsdk.Encoder{}, - wantErr: fmt.Sprintf("encoder not found for chain selector %d", stubSolanaChain().Selector), - }, - { - name: "Aptos chain with correct encoder", - chain: stubAptosChain(), - encoder: &mcmsaptossdk.Encoder{}, - wantType: &aptosExecutorFactory{}, - }, - { - name: "Aptos chain with wrong encoder", - chain: stubAptosChain(), - encoder: &mcmsevmsdk.Encoder{}, - wantErr: fmt.Sprintf("encoder not found for chain selector %d", stubAptosChain().Selector), - }, - { - name: "unsupported chain family", - chain: testutils.NewStubChain(999999), - encoder: &mcmsevmsdk.Encoder{}, - wantErr: "chain family not supported: test", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - factory, err := GetExecutorFactory(tt.chain, tt.encoder) - - if tt.wantErr != "" { - require.Error(t, err) - require.ErrorContains(t, err, tt.wantErr) - assert.Nil(t, factory) - } else { - require.NoError(t, err) - require.NotNil(t, factory) - - // Verify we can create an executor - executor, err := factory.Make() - require.NoError(t, err) - assert.NotNil(t, executor) - assert.IsType(t, tt.wantType, factory) - } - }) - } -} - -func TestGetTimelockExecutorFactory(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - chain fchain.BlockChain - wantType any - wantErr string - }{ - { - name: "EVM chain success", - chain: stubEVMChain(), - wantType: &evmTimelockExecutorFactory{}, - }, - { - name: "Solana chain success", - chain: stubSolanaChain(), - wantType: &solanaTimelockExecutorFactory{}, - }, - { - name: "Aptos chain success", - chain: stubAptosChain(), - wantType: &aptosTimelockExecutorFactory{}, - }, - { - name: "unsupported chain family", - chain: testutils.NewStubChain(999999), - wantErr: "chain family not supported: test", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - factory, err := GetTimelockExecutorFactory(tt.chain) - - if tt.wantErr != "" { - require.Error(t, err) - require.ErrorContains(t, err, tt.wantErr) - assert.Nil(t, factory) - } else { - require.NoError(t, err) - require.NotNil(t, factory) - - // Verify we can create a timelock executor - executor, err := factory.Make() - require.NoError(t, err) - assert.NotNil(t, executor) - } - }) - } -} diff --git a/engine/test/internal/mcmsutils/solana.go b/engine/test/internal/mcmsutils/solana.go deleted file mode 100644 index c68283eed..000000000 --- a/engine/test/internal/mcmsutils/solana.go +++ /dev/null @@ -1,96 +0,0 @@ -package mcmsutils - -import ( - mcmssdk "github.com/smartcontractkit/mcms/sdk" - mcmssolanasdk "github.com/smartcontractkit/mcms/sdk/solana" - - fchainsolana "github.com/smartcontractkit/chainlink-deployments-framework/chain/solana" -) - -//------------------------------------------------------------------------------ - -var _ InspectorFactory = &solanaInspectorFactory{} - -// solanaInspectorFactory is a factory for creating Solana-specific MCMS inspectors. -// It implements the InspectorFactory interface and is responsible for creating -// inspectors that can examine the state of MCMS and Timelock contracts on the Solana blockchain. -type solanaInspectorFactory struct { - chain fchainsolana.Chain // The Solana chain configuration and client -} - -// newSolanaInspectorFactory creates a new Solana inspector factory. -func newSolanaInspectorFactory(chain fchainsolana.Chain) *solanaInspectorFactory { - return &solanaInspectorFactory{chain: chain} -} - -// Make creates and returns a new Solana MCMS inspector. -func (f *solanaInspectorFactory) Make() (mcmssdk.Inspector, error) { - return mcmssolanasdk.NewInspector(f.chain.Client), nil -} - -//------------------------------------------------------------------------------ - -var _ ConverterFactory = &solanaConverterFactory{} - -// solanaConverterFactory is a factory for creating Solana-specific timelock converters. -// It implements the ConverterFactory interface and creates converters that can -// transform MCMS timelock proposals into a standard MCMS proposal. -type solanaConverterFactory struct{} - -// newSolanaConverterFactory creates a new Solana converter factory. -func newSolanaConverterFactory() *solanaConverterFactory { - return &solanaConverterFactory{} -} - -// Make creates and returns a new Solana timelock converter. -func (f *solanaConverterFactory) Make() (mcmssdk.TimelockConverter, error) { - return &mcmssolanasdk.TimelockConverter{}, nil -} - -//------------------------------------------------------------------------------ - -var _ ExecutorFactory = &solanaExecutorFactory{} - -// solanaExecutorFactory is a factory for creating Solana-specific MCMS executors. -// It implements the ExecutorFactory interface and creates executors that can -// execute MCMS operations on the Solana blockchain. -type solanaExecutorFactory struct { - chain fchainsolana.Chain // The Solana chain configuration and client - encoder *mcmssolanasdk.Encoder // The encoder for creating Solana-specific transaction data -} - -// newSolanaExecutorFactory creates a new Solana executor factory. -func newSolanaExecutorFactory( - chain fchainsolana.Chain, encoder *mcmssolanasdk.Encoder, -) *solanaExecutorFactory { - return &solanaExecutorFactory{ - chain: chain, - encoder: encoder, - } -} - -// Make creates and returns a new Solana MCMS executor. -func (f *solanaExecutorFactory) Make() (mcmssdk.Executor, error) { - return mcmssolanasdk.NewExecutor(f.encoder, f.chain.Client, *f.chain.DeployerKey), nil -} - -//------------------------------------------------------------------------------ - -var _ TimelockExecutorFactory = &solanaTimelockExecutorFactory{} - -// solanaTimelockExecutorFactory is a factory for creating Solana-specific timelock executors. -// It implements the TimelockExecutorFactory interface and creates executors specifically -// designed for executing Timelock operations on the Solana blockchain. -type solanaTimelockExecutorFactory struct { - chain fchainsolana.Chain // The Solana chain configuration and client -} - -// newSolanaTimelockExecutorFactory creates a new Solana timelock executor factory. -func newSolanaTimelockExecutorFactory(chain fchainsolana.Chain) *solanaTimelockExecutorFactory { - return &solanaTimelockExecutorFactory{chain: chain} -} - -// Make creates and returns a new Solana timelock executor. -func (f *solanaTimelockExecutorFactory) Make() (mcmssdk.TimelockExecutor, error) { - return mcmssolanasdk.NewTimelockExecutor(f.chain.Client, *f.chain.DeployerKey), nil -} diff --git a/engine/test/internal/mcmsutils/solana_test.go b/engine/test/internal/mcmsutils/solana_test.go deleted file mode 100644 index 1aa98e9fb..000000000 --- a/engine/test/internal/mcmsutils/solana_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package mcmsutils - -import ( - "testing" - - mcmssolanasdk "github.com/smartcontractkit/mcms/sdk/solana" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestNewSolanaInspectorFactory(t *testing.T) { - t.Parallel() - - chain := stubSolanaChain() - factory := newSolanaInspectorFactory(chain) - - require.NotNil(t, factory) - assert.Equal(t, chain.Selector, factory.chain.Selector) - assert.Equal(t, chain.URL, factory.chain.URL) - assert.Equal(t, chain.WSURL, factory.chain.WSURL) -} - -func TestSolanaInspectorFactory_Make(t *testing.T) { - t.Parallel() - - chain := stubSolanaChain() - factory := newSolanaInspectorFactory(chain) - - inspector, err := factory.Make() - require.NoError(t, err) - assert.NotNil(t, inspector) -} - -func TestNewSolanaConverterFactory(t *testing.T) { - t.Parallel() - - factory := newSolanaConverterFactory() - - require.NotNil(t, factory) -} - -func TestSolanaConverterFactory_Make(t *testing.T) { - t.Parallel() - - factory := newSolanaConverterFactory() - - converter, err := factory.Make() - require.NoError(t, err) - assert.NotNil(t, converter) -} - -func TestNewSolanaExecutorFactory(t *testing.T) { - t.Parallel() - - chain := stubSolanaChain() - encoder := &mcmssolanasdk.Encoder{} // Empty encoder for testing - - factory := newSolanaExecutorFactory(chain, encoder) - - require.NotNil(t, factory) - assert.Equal(t, chain.Selector, factory.chain.Selector) - assert.Equal(t, chain.URL, factory.chain.URL) - assert.Equal(t, encoder, factory.encoder) -} - -func TestSolanaExecutorFactory_Make(t *testing.T) { - t.Parallel() - - chain := stubSolanaChain() - encoder := &mcmssolanasdk.Encoder{} // Empty encoder for testing - - factory := newSolanaExecutorFactory(chain, encoder) - - executor, err := factory.Make() - require.NoError(t, err) - assert.NotNil(t, executor) -} - -func TestNewSolanaTimelockExecutorFactory(t *testing.T) { - t.Parallel() - - chain := stubSolanaChain() - - factory := newSolanaTimelockExecutorFactory(chain) - - require.NotNil(t, factory) - assert.Equal(t, chain.Selector, factory.chain.Selector) - assert.Equal(t, chain.URL, factory.chain.URL) -} - -func TestSolanaTimelockExecutorFactory_Make(t *testing.T) { - t.Parallel() - - chain := stubSolanaChain() - factory := newSolanaTimelockExecutorFactory(chain) - - executor, err := factory.Make() - require.NoError(t, err) - assert.NotNil(t, executor) -} diff --git a/engine/test/internal/mcmsutils/stub_test.go b/engine/test/internal/mcmsutils/stub_test.go index 3963bef34..3c56271cf 100644 --- a/engine/test/internal/mcmsutils/stub_test.go +++ b/engine/test/internal/mcmsutils/stub_test.go @@ -5,8 +5,8 @@ import ( "time" "github.com/Masterminds/semver/v3" + gethbind "github.com/ethereum/go-ethereum/accounts/abi/bind" gethtypes "github.com/ethereum/go-ethereum/core/types" - sollib "github.com/gagliardetto/solana-go" chainselectors "github.com/smartcontractkit/chain-selectors" mcmslib "github.com/smartcontractkit/mcms" @@ -15,7 +15,6 @@ import ( fchain "github.com/smartcontractkit/chainlink-deployments-framework/chain" fchainaptos "github.com/smartcontractkit/chainlink-deployments-framework/chain/aptos" fchainevm "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm" - fchainsolana "github.com/smartcontractkit/chainlink-deployments-framework/chain/solana" fdatastore "github.com/smartcontractkit/chainlink-deployments-framework/datastore" fdeployment "github.com/smartcontractkit/chainlink-deployments-framework/deployment" ) @@ -30,28 +29,14 @@ func stubAptosChain() fchainaptos.Chain { // stubEVMChain creates a stubbed EVM chain func stubEVMChain() fchainevm.Chain { return fchainevm.Chain{ - Selector: chainselectors.ETHEREUM_TESTNET_SEPOLIA.Selector, + Selector: chainselectors.ETHEREUM_TESTNET_SEPOLIA.Selector, + DeployerKey: &gethbind.TransactOpts{}, Confirm: func(tx *gethtypes.Transaction) (uint64, error) { // This is a stubbed implementation of the Confirm function which always returns success return 0, nil }, } } -// stubSolanaChain creates a stubbed Solana chain -func stubSolanaChain() fchainsolana.Chain { - // Create a dummy private key for testing (32 bytes repeated to make 64 bytes) - privateKeyBytes := make([]byte, 64) - for i := range 64 { - privateKeyBytes[i] = byte(i%32 + 1) - } - dummyKey := sollib.PrivateKey(privateKeyBytes) - - return fchainsolana.Chain{ - Selector: chainselectors.TEST_22222222222222222222222222222222222222222222.Selector, - DeployerKey: &dummyKey, - } -} - const ( // Test fixture addresses for stubbed MCMS environment testTimelockAddress = "0x1111111111111111111111111111111111111111" diff --git a/engine/test/runtime/mock_proposal_executor_test.go b/engine/test/runtime/mock_proposal_executor_test.go index 408e0c9fb..814dd9fc3 100644 --- a/engine/test/runtime/mock_proposal_executor_test.go +++ b/engine/test/runtime/mock_proposal_executor_test.go @@ -7,6 +7,7 @@ package runtime import ( "context" + "github.com/smartcontractkit/chainlink-deployments-framework/engine/test/internal/mcmsutils" "github.com/smartcontractkit/mcms" mock "github.com/stretchr/testify/mock" ) @@ -39,16 +40,22 @@ func (_m *mockProposalExecutor) EXPECT() *mockProposalExecutor_Expecter { } // ExecuteMCMS provides a mock function for the type mockProposalExecutor -func (_mock *mockProposalExecutor) ExecuteMCMS(ctx context.Context, proposal *mcms.Proposal) error { - ret := _mock.Called(ctx, proposal) +func (_mock *mockProposalExecutor) ExecuteMCMS(ctx context.Context, proposal *mcms.Proposal, opts ...mcmsutils.ExecuteOption) error { + var tmpRet mock.Arguments + if len(opts) > 0 { + tmpRet = _mock.Called(ctx, proposal, opts) + } else { + tmpRet = _mock.Called(ctx, proposal) + } + ret := tmpRet if len(ret) == 0 { panic("no return value specified for ExecuteMCMS") } var r0 error - if returnFunc, ok := ret.Get(0).(func(context.Context, *mcms.Proposal) error); ok { - r0 = returnFunc(ctx, proposal) + if returnFunc, ok := ret.Get(0).(func(context.Context, *mcms.Proposal, ...mcmsutils.ExecuteOption) error); ok { + r0 = returnFunc(ctx, proposal, opts...) } else { r0 = ret.Error(0) } @@ -63,11 +70,13 @@ type mockProposalExecutor_ExecuteMCMS_Call struct { // ExecuteMCMS is a helper method to define mock.On call // - ctx context.Context // - proposal *mcms.Proposal -func (_e *mockProposalExecutor_Expecter) ExecuteMCMS(ctx interface{}, proposal interface{}) *mockProposalExecutor_ExecuteMCMS_Call { - return &mockProposalExecutor_ExecuteMCMS_Call{Call: _e.mock.On("ExecuteMCMS", ctx, proposal)} +// - opts ...mcmsutils.ExecuteOption +func (_e *mockProposalExecutor_Expecter) ExecuteMCMS(ctx interface{}, proposal interface{}, opts ...interface{}) *mockProposalExecutor_ExecuteMCMS_Call { + return &mockProposalExecutor_ExecuteMCMS_Call{Call: _e.mock.On("ExecuteMCMS", + append([]interface{}{ctx, proposal}, opts...)...)} } -func (_c *mockProposalExecutor_ExecuteMCMS_Call) Run(run func(ctx context.Context, proposal *mcms.Proposal)) *mockProposalExecutor_ExecuteMCMS_Call { +func (_c *mockProposalExecutor_ExecuteMCMS_Call) Run(run func(ctx context.Context, proposal *mcms.Proposal, opts ...mcmsutils.ExecuteOption)) *mockProposalExecutor_ExecuteMCMS_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -77,9 +86,16 @@ func (_c *mockProposalExecutor_ExecuteMCMS_Call) Run(run func(ctx context.Contex if args[1] != nil { arg1 = args[1].(*mcms.Proposal) } + var arg2 []mcmsutils.ExecuteOption + var variadicArgs []mcmsutils.ExecuteOption + if len(args) > 2 { + variadicArgs = args[2].([]mcmsutils.ExecuteOption) + } + arg2 = variadicArgs run( arg0, arg1, + arg2..., ) }) return _c @@ -90,7 +106,7 @@ func (_c *mockProposalExecutor_ExecuteMCMS_Call) Return(err error) *mockProposal return _c } -func (_c *mockProposalExecutor_ExecuteMCMS_Call) RunAndReturn(run func(ctx context.Context, proposal *mcms.Proposal) error) *mockProposalExecutor_ExecuteMCMS_Call { +func (_c *mockProposalExecutor_ExecuteMCMS_Call) RunAndReturn(run func(ctx context.Context, proposal *mcms.Proposal, opts ...mcmsutils.ExecuteOption) error) *mockProposalExecutor_ExecuteMCMS_Call { _c.Call.Return(run) return _c } diff --git a/engine/test/runtime/task_mcms.go b/engine/test/runtime/task_mcms.go index d2147dbd8..533235e7f 100644 --- a/engine/test/runtime/task_mcms.go +++ b/engine/test/runtime/task_mcms.go @@ -301,7 +301,7 @@ func signTimelockProposal( // proposalExecutor is an interface that defines the methods for executing MCMS and timelock // proposals. type proposalExecutor interface { - ExecuteMCMS(ctx context.Context, proposal *mcmslib.Proposal) error + ExecuteMCMS(ctx context.Context, proposal *mcmslib.Proposal, opts ...mcmsutils.ExecuteOption) error ExecuteTimelock(ctx context.Context, proposal *mcmslib.TimelockProposal) error }