diff --git a/cmd/root.go b/cmd/root.go index 8c24d6e5..fa76354d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -531,7 +531,6 @@ func isLoadCredentials(cmd *cobra.Command) bool { "cre account": {}, "cre secrets": {}, "cre workflow build": {}, - "cre workflow hash": {}, "cre templates": {}, "cre templates list": {}, "cre templates add": {}, diff --git a/cmd/root_test.go b/cmd/root_test.go index 274c9ee7..2f076066 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -53,3 +53,26 @@ func TestIsRegistryRPCCommand(t *testing.T) { }) } } + +func TestIsLoadCredentials(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + path []string + wantLoad bool + }{ + {"workflow hash", []string{"cre", "workflow", "hash"}, true}, + {"workflow deploy", []string{"cre", "workflow", "deploy"}, true}, + {"login", []string{"cre", "login"}, false}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + cmd := buildCommandPath(tc.path...) + assert.Equal(t, tc.wantLoad, isLoadCredentials(cmd), + "isLoadCredentials(%q)", cmd.CommandPath()) + }) + } +} diff --git a/cmd/workflow/hash/hash.go b/cmd/workflow/hash/hash.go index 99cc311c..fb7e1349 100644 --- a/cmd/workflow/hash/hash.go +++ b/cmd/workflow/hash/hash.go @@ -24,6 +24,8 @@ type Inputs struct { OwnerFromSettings string PrivateKey string SkipTypeChecks bool + RegistryType settings.RegistryType + DerivedOwner string } func New(runtimeContext *runtime.Context) *cobra.Command { @@ -41,6 +43,10 @@ func New(runtimeContext *runtime.Context) *cobra.Command { v := runtimeContext.Viper rawPrivKey := v.GetString(settings.EthPrivateKeyEnvVar) + registryType := settings.RegistryTypeOnChain + if runtimeContext.ResolvedRegistry != nil { + registryType = runtimeContext.ResolvedRegistry.Type() + } inputs := Inputs{ ForUser: forUser, @@ -51,6 +57,8 @@ func New(runtimeContext *runtime.Context) *cobra.Command { OwnerFromSettings: s.Workflow.UserWorkflowSettings.WorkflowOwnerAddress, PrivateKey: settings.NormalizeHexKey(rawPrivKey), SkipTypeChecks: v.GetBool(cmdcommon.SkipTypeChecksCLIFlag), + RegistryType: registryType, + DerivedOwner: runtimeContext.DerivedWorkflowOwner, } return Execute(inputs) @@ -87,7 +95,7 @@ func Execute(inputs Inputs) error { return err } - ownerAddress, err := ResolveOwner(inputs.ForUser, inputs.OwnerFromSettings, inputs.PrivateKey) + ownerAddress, err := ResolveOwner(inputs) if err != nil { return err } @@ -107,17 +115,24 @@ func Execute(inputs Inputs) error { return nil } -func ResolveOwner(forUser, ownerFromSettings, privateKey string) (string, error) { - if forUser != "" { - return forUser, nil +func ResolveOwner(inputs Inputs) (string, error) { + if inputs.RegistryType == settings.RegistryTypeOffChain { + if inputs.DerivedOwner == "" { + return "", fmt.Errorf("derived workflow owner is not available; ensure authentication succeeded") + } + return inputs.DerivedOwner, nil + } + + if inputs.ForUser != "" { + return inputs.ForUser, nil } - if ownerFromSettings != "" { - return ownerFromSettings, nil + if inputs.OwnerFromSettings != "" { + return inputs.OwnerFromSettings, nil } - if privateKey != "" { - addr, err := ethkeys.DeriveEthAddressFromPrivateKey(privateKey) + if inputs.PrivateKey != "" { + addr, err := ethkeys.DeriveEthAddressFromPrivateKey(inputs.PrivateKey) if err != nil { return "", fmt.Errorf("failed to derive owner from private key: %w", err) } diff --git a/cmd/workflow/hash/hash_test.go b/cmd/workflow/hash/hash_test.go index 6587ba2e..6050e78a 100644 --- a/cmd/workflow/hash/hash_test.go +++ b/cmd/workflow/hash/hash_test.go @@ -15,6 +15,7 @@ import ( workflowUtils "github.com/smartcontractkit/chainlink-common/pkg/workflows" cmdcommon "github.com/smartcontractkit/cre-cli/cmd/common" + "github.com/smartcontractkit/cre-cli/internal/settings" ) // Well-known test private key (never use on a real network). @@ -25,14 +26,20 @@ const testDerivedAddress = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" func TestResolveOwner_WithForUser(t *testing.T) { t.Parallel() - addr, err := ResolveOwner("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", "", "") + inputs := resolveInputs() + inputs.ForUser = "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF" + addr, err := ResolveOwner(inputs) require.NoError(t, err) assert.Equal(t, "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", addr) } func TestResolveOwner_WithForUserOverridesAll(t *testing.T) { t.Parallel() - addr, err := ResolveOwner("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", "0xOtherAddress", testPrivateKey) + inputs := resolveInputs() + inputs.ForUser = "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF" + inputs.OwnerFromSettings = "0xOtherAddress" + inputs.PrivateKey = testPrivateKey + addr, err := ResolveOwner(inputs) require.NoError(t, err) assert.Equal(t, "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", addr, "--public_key should take priority over settings and private key") @@ -40,32 +47,58 @@ func TestResolveOwner_WithForUserOverridesAll(t *testing.T) { func TestResolveOwner_FromSettings(t *testing.T) { t.Parallel() - addr, err := ResolveOwner("", "0xSettingsOwner", "") + inputs := resolveInputs() + inputs.OwnerFromSettings = "0xSettingsOwner" + addr, err := ResolveOwner(inputs) require.NoError(t, err) assert.Equal(t, "0xSettingsOwner", addr) } func TestResolveOwner_FromPrivateKey(t *testing.T) { t.Parallel() - addr, err := ResolveOwner("", "", testPrivateKey) + inputs := resolveInputs() + inputs.PrivateKey = testPrivateKey + addr, err := ResolveOwner(inputs) require.NoError(t, err) assert.Equal(t, testDerivedAddress, addr) } func TestResolveOwner_NothingProvided(t *testing.T) { t.Parallel() - _, err := ResolveOwner("", "", "") + _, err := ResolveOwner(resolveInputs()) require.Error(t, err) assert.Contains(t, err.Error(), "--public_key") } func TestResolveOwner_InvalidPrivateKey(t *testing.T) { t.Parallel() - _, err := ResolveOwner("", "", "not-a-valid-key") + inputs := resolveInputs() + inputs.PrivateKey = "not-a-valid-key" + _, err := ResolveOwner(inputs) require.Error(t, err) assert.Contains(t, err.Error(), "failed to derive owner") } +func TestResolveOwner_OffChainUsesDerivedOwner(t *testing.T) { + t.Parallel() + inputs := resolveInputs() + inputs.RegistryType = settings.RegistryTypeOffChain + inputs.DerivedOwner = "0xDerivedOwner" + inputs.ForUser = "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF" + addr, err := ResolveOwner(inputs) + require.NoError(t, err) + assert.Equal(t, "0xDerivedOwner", addr) +} + +func TestResolveOwner_OffChainMissingDerivedOwner(t *testing.T) { + t.Parallel() + inputs := resolveInputs() + inputs.RegistryType = settings.RegistryTypeOffChain + _, err := ResolveOwner(inputs) + require.Error(t, err) + assert.Contains(t, err.Error(), "derived workflow owner is not available") +} + func TestExecute_WithForUser(t *testing.T) { wasmFile, configFile := setupTestArtifacts(t) @@ -230,6 +263,12 @@ func TestHashCommandFlags(t *testing.T) { require.NotNil(t, f, "no-config flag should exist") } +func resolveInputs() Inputs { + return Inputs{ + RegistryType: settings.RegistryTypeOnChain, + } +} + // setupTestArtifacts creates a minimal WASM file and config file in a temp directory. func setupTestArtifacts(t *testing.T) (wasmPath, configPath string) { t.Helper()