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
165 changes: 26 additions & 139 deletions cmd/workflow/activate/activate.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
package activate

import (
"encoding/hex"
"fmt"
"math/big"
"sort"
"sync"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/smartcontractkit/cre-cli/cmd/client"
cmdCommon "github.com/smartcontractkit/cre-cli/cmd/common"
"github.com/smartcontractkit/cre-cli/internal/environments"
"github.com/smartcontractkit/cre-cli/internal/runtime"
"github.com/smartcontractkit/cre-cli/internal/settings"
"github.com/smartcontractkit/cre-cli/internal/types"
"github.com/smartcontractkit/cre-cli/internal/ui"
"github.com/smartcontractkit/cre-cli/internal/validation"
)
Expand All @@ -28,11 +20,9 @@ const (
)

type Inputs struct {
WorkflowName string `validate:"workflow_name"`
WorkflowOwner string `validate:"workflow_owner"`
DonFamily string `validate:"required"`
WorkflowRegistryContractAddress string `validate:"required"`
WorkflowRegistryContractChainName string `validate:"required"`
WorkflowName string `validate:"workflow_name"`
WorkflowOwner string `validate:"workflow_owner"`
DonFamily string `validate:"required"`
}

func New(runtimeContext *runtime.Context) *cobra.Command {
Expand Down Expand Up @@ -70,13 +60,9 @@ type handler struct {
settings *settings.Settings
environmentSet *environments.EnvironmentSet
inputs Inputs
wrc *client.WorkflowRegistryV2Client
runtimeContext *runtime.Context

validated bool

wg sync.WaitGroup
wrcErr error
}

func newHandler(ctx *runtime.Context) *handler {
Expand All @@ -87,34 +73,21 @@ func newHandler(ctx *runtime.Context) *handler {
environmentSet: ctx.EnvironmentSet,
runtimeContext: ctx,
validated: false,
wg: sync.WaitGroup{},
wrcErr: nil,
}
h.wg.Add(1)
go func() {
defer h.wg.Done()
wrc, err := h.clientFactory.NewWorkflowRegistryV2Client()
if err != nil {
h.wrcErr = fmt.Errorf("failed to create workflow registry client: %w", err)
return
}
h.wrc = wrc
}()

return &h
}

func (h *handler) ResolveInputs(v *viper.Viper) (Inputs, error) {
oc, err := settings.AsOnChain(h.runtimeContext.ResolvedRegistry, "activate")
resolvedWorkflowOwner, err := h.resolveWorkflowOwner(h.runtimeContext.ResolvedRegistry.Type())
if err != nil {
return Inputs{}, err
return Inputs{}, fmt.Errorf("failed to resolve workflow owner: %w", err)
}

return Inputs{
WorkflowName: h.settings.Workflow.UserWorkflowSettings.WorkflowName,
WorkflowOwner: h.settings.Workflow.UserWorkflowSettings.WorkflowOwnerAddress,
DonFamily: oc.DonFamily(),
WorkflowRegistryContractAddress: oc.Address(),
WorkflowRegistryContractChainName: oc.ChainName(),
WorkflowName: h.settings.Workflow.UserWorkflowSettings.WorkflowName,
WorkflowOwner: resolvedWorkflowOwner,
DonFamily: h.runtimeContext.ResolvedRegistry.DonFamily(),
}, nil
}

Expand All @@ -137,126 +110,40 @@ func (h *handler) Execute() error {
return fmt.Errorf("handler inputs not validated")
}

workflowName := h.inputs.WorkflowName
workflowOwner := h.inputs.WorkflowOwner

h.displayWorkflowDetails()

h.wg.Wait()
if h.wrcErr != nil {
return h.wrcErr
}

ownerAddr := common.HexToAddress(workflowOwner)

const pageLimit = 200
workflows, err := h.wrc.GetWorkflowListByOwnerAndName(ownerAddr, workflowName, big.NewInt(0), big.NewInt(pageLimit))
strategy, err := newRegistryActivateStrategy(h.runtimeContext.ResolvedRegistry, h)
if err != nil {
return fmt.Errorf("failed to get workflow list: %w", err)
}
if len(workflows) == 0 {
return fmt.Errorf("no workflows found for owner=%s name=%s", workflowOwner, workflowName)
return err
}

// Sort by CreatedAt descending
sort.Slice(workflows, func(i, j int) bool {
return workflows[i].CreatedAt > workflows[j].CreatedAt
})

latest := workflows[0]

h.runtimeContext.Workflow.ID = hex.EncodeToString(latest.WorkflowId[:])
return strategy.Activate()
}

// Validate precondition: workflow must be in paused state
if latest.Status != WorkflowStatusPaused {
return fmt.Errorf("workflow is already active, cancelling transaction")
// resolveWorkflowOwner returns the effective owner address for workflow ID computation.
// For private registry deploys, the derived workflow owner from the runtime context is used.
// For onchain deploys, the configured WorkflowOwner address is used directly.
func (h *handler) resolveWorkflowOwner(registryType settings.RegistryType) (string, error) {
if registryType != settings.RegistryTypeOffChain {
return h.settings.Workflow.UserWorkflowSettings.WorkflowOwnerAddress, nil
}

if err := h.wrc.CheckUserDonLimit(ownerAddr, h.inputs.DonFamily, 1); err != nil {
return err
owner := h.runtimeContext.DerivedWorkflowOwner
if owner == "" {
return "", fmt.Errorf("derived workflow owner is not available; ensure authentication succeeded")
}

ui.Dim(fmt.Sprintf("Activating workflow: Name=%s, Owner=%s, WorkflowID=%s", workflowName, workflowOwner, hex.EncodeToString(latest.WorkflowId[:])))

txOut, err := h.wrc.ActivateWorkflow(latest.WorkflowId, h.inputs.DonFamily)
if err != nil {
return fmt.Errorf("failed to activate workflow: %w", err)
if len(owner) >= 2 && owner[:2] != "0x" {
owner = "0x" + owner
}

oc, _ := h.runtimeContext.ResolvedRegistry.(*settings.OnChainRegistry)

switch txOut.Type {
case client.Regular:
ui.Success(fmt.Sprintf("Transaction confirmed: %s", txOut.Hash))
ui.URL(fmt.Sprintf("%s/tx/%s", oc.ExplorerURL(), txOut.Hash))
ui.Line()
ui.Success("Workflow activated successfully")
ui.Dim(fmt.Sprintf(" Contract address: %s", oc.Address()))
ui.Dim(fmt.Sprintf(" Transaction hash: %s", txOut.Hash))
ui.Dim(fmt.Sprintf(" Workflow Name: %s", workflowName))
ui.Dim(fmt.Sprintf(" Workflow ID: %s", hex.EncodeToString(latest.WorkflowId[:])))

case client.Raw:
ui.Line()
ui.Success("MSIG workflow activation transaction prepared!")
ui.Dim(fmt.Sprintf("To Activate %s with workflowID: %s", workflowName, hex.EncodeToString(latest.WorkflowId[:])))
ui.Line()
ui.Bold("Next steps:")
ui.Line()
ui.Print(" 1. Submit the following transaction on the target chain:")
ui.Dim(fmt.Sprintf(" Chain: %s", h.inputs.WorkflowRegistryContractChainName))
ui.Dim(fmt.Sprintf(" Contract Address: %s", txOut.RawTx.To))
ui.Line()
ui.Print(" 2. Use the following transaction data:")
ui.Line()
ui.Code(fmt.Sprintf(" %x", txOut.RawTx.Data))
ui.Line()

case client.Changeset:
chainSelector, err := settings.GetChainSelectorByChainName(oc.ChainName())
if err != nil {
return fmt.Errorf("failed to get chain selector for chain %q: %w", oc.ChainName(), err)
}
mcmsConfig, err := settings.GetMCMSConfig(h.settings, chainSelector)
if err != nil {
ui.Warning("MCMS config not found or is incorrect, skipping MCMS config in changeset")
}
cldSettings := h.settings.CLDSettings
changesets := []types.Changeset{
{
ActivateWorkflow: &types.ActivateWorkflow{
Payload: types.UserWorkflowActivateInput{
WorkflowID: h.runtimeContext.Workflow.ID,
DonFamily: h.inputs.DonFamily,

ChainSelector: chainSelector,
MCMSConfig: mcmsConfig,
WorkflowRegistryQualifier: cldSettings.WorkflowRegistryQualifier,
},
},
},
}
csFile := types.NewChangesetFile(cldSettings.Environment, cldSettings.Domain, cldSettings.MergeProposals, changesets)

var fileName string
if cldSettings.ChangesetFile != "" {
fileName = cldSettings.ChangesetFile
} else {
fileName = fmt.Sprintf("ActivateWorkflow_%s_%s.yaml", workflowName, time.Now().Format("20060102_150405"))
}

return cmdCommon.WriteChangesetFile(fileName, csFile, h.settings)

default:
h.log.Warn().Msgf("Unsupported transaction type: %s", txOut.Type)
}
return nil
return owner, nil
}

func (h *handler) displayWorkflowDetails() {
ui.Line()
ui.Title(fmt.Sprintf("Activating Workflow: %s", h.inputs.WorkflowName))
ui.Dim(fmt.Sprintf("Target: %s", h.settings.User.TargetName))
ui.Dim(fmt.Sprintf("Owner Address: %s", h.settings.Workflow.UserWorkflowSettings.WorkflowOwnerAddress))
ui.Dim(fmt.Sprintf("Owner Address: %s", h.inputs.WorkflowOwner))
ui.Line()
}
6 changes: 0 additions & 6 deletions cmd/workflow/activate/activate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,6 @@ func TestWorkflowActivateCommand(t *testing.T) {
t.Parallel()

fillRequired := func(in Inputs) Inputs {
if in.WorkflowRegistryContractAddress == "" {
in.WorkflowRegistryContractAddress = "0x0000000000000000000000000000000000000000"
}
if in.WorkflowRegistryContractChainName == "" {
in.WorkflowRegistryContractChainName = "ethereum-testnet-sepolia"
}
return in
}

Expand Down
18 changes: 18 additions & 0 deletions cmd/workflow/activate/registry_activate_strategy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package activate

import (
"github.com/smartcontractkit/cre-cli/internal/settings"
)

// registryActivateStrategy encapsulates target-specific activate logic.
type registryActivateStrategy interface {
Activate() error
}

// newRegistryActivateStrategy returns the appropriate strategy for the given target.
func newRegistryActivateStrategy(resolvedRegistry settings.ResolvedRegistry, h *handler) (registryActivateStrategy, error) {
if resolvedRegistry.Type() == settings.RegistryTypeOffChain {
return newPrivateRegistryActivateStrategy(h), nil
}
return newOnchainRegistryActivateStrategy(h)
}
Loading
Loading